4.3 KiB
Timezone Fix Summary
Issue
The clock-in time was showing 13:05 when the actual time in Riyadh was 16:05 (3-hour difference). This was because the system was using UTC time instead of properly converting to Asia/Riyadh timezone.
Root Cause
- Django was correctly configured with
TIME_ZONE = "Asia/Riyadh"andUSE_TZ = True - However, some code was using
datetime.now()instead of Django's timezone-awaretimezone.now() - The attendance clock-in/out was using
timezone.now().time()which extracts the UTC time component instead of the local time
Changes Made
1. Fixed finance/models.py
Lines affected: 4 methods in the CSID model
- Changed
from datetime import datetimetofrom django.utils import timezone - Replaced
datetime.now()withtimezone.now()in:is_validpropertydays_until_expirypropertyrevoke()methodincrement_usage()method
2. Fixed finance/zatca_service.py
Lines affected: Import statement
- Removed unused
from datetime import datetime, timezoneimport - Kept
from django.utils import timezone as django_timezonewhich is the correct one to use
3. Fixed finance/csid_manager.py
Lines affected: Import and get_active_csid method
- Changed
from datetime import datetime, timedeltatofrom datetime import timedelta - Added
from django.utils import timezone - Replaced
datetime.now()withtimezone.now()in theget_active_csid()method
4. Fixed hr/views.py (CRITICAL FIX)
Lines affected: AttendanceKioskView.post() method
- Changed
now = timezone.now().time()tonow = timezone.localtime(timezone.now()).time() - This ensures the time is converted to Riyadh timezone before extracting the time component
How It Works Now
-
Django Settings:
TIME_ZONE = "Asia/Riyadh"- Sets the default timezoneUSE_TZ = True- Enables timezone-aware datetimesCELERY_TIMEZONE = "Asia/Riyadh"- Ensures Celery tasks use correct timezone
-
Time Storage:
- Django stores all datetimes in UTC internally (in the database)
- When displaying, Django automatically converts to the configured timezone
-
Clock In/Out:
timezone.now()returns current time in UTCtimezone.localtime(timezone.now())converts it to Riyadh time.time()extracts just the time component (now in Riyadh timezone)
Testing
A test script was created (test_timezone.py) to verify the configuration:
python3 test_timezone.py
Expected output:
- TIME_ZONE: Asia/Riyadh
- USE_TZ: True
- CELERY_TIMEZONE: Asia/Riyadh
- Current timezone offset: +0300 (UTC+3)
Additional Tools Created
1. test_timezone.py
A diagnostic script to verify timezone configuration and test datetime handling.
2. core/management/commands/fix_timezone_data.py
A management command to convert any existing naive datetime fields to timezone-aware datetimes. Usage:
python3 manage.py fix_timezone_data --dry-run # Preview changes
python3 manage.py fix_timezone_data # Apply changes
Verification
To verify the fix is working:
- Try clocking in now - the time should show the correct Riyadh time (16:05 instead of 13:05)
- Check any new attendance records - they should display the correct local time
- All datetime operations throughout the system now use timezone-aware datetimes
Best Practices Going Forward
- Always use
timezone.now()instead ofdatetime.now() - For local time, use
timezone.localtime(timezone.now()) - For time-only fields, use
timezone.localtime(timezone.now()).time() - Never import
from datetime import timezone- use Django'sfrom django.utils import timezone - All DateTimeField models automatically handle timezone conversion when
USE_TZ = True
Files Modified
finance/models.py- Fixed 4 datetime.now() usagesfinance/zatca_service.py- Removed problematic importfinance/csid_manager.py- Fixed datetime.now() usagehr/views.py- Fixed clock-in/out to use local timetest_timezone.py- Created (new file)core/management/commands/fix_timezone_data.py- Created (new file)TIMEZONE_FIX_SUMMARY.md- Created (this file)
Status
✅ All timezone issues have been fixed ✅ Clock-in/out now uses correct Riyadh time ✅ All datetime operations are timezone-aware ✅ System is configured correctly for Asia/Riyadh timezone