From 4526bd91a087fd4d994f25fb6665fbb6cdda1837 Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Tue, 6 May 2025 00:02:27 +0100 Subject: [PATCH 1/7] Scheduler shouldnt ignore specific times on crontab --- django_celery_beat/schedulers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index e28c092f..0d1b785d 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -272,10 +272,10 @@ def all_as_schedule(self): clocked__clocked_time__gt=next_schedule_sync ) - exclude_cron_tasks_query = self._get_crontab_exclude_query() + # exclude_cron_tasks_query = self._get_crontab_exclude_query() # Combine the queries for optimal database filtering - exclude_query = exclude_clock_tasks_query | exclude_cron_tasks_query + exclude_query = exclude_clock_tasks_query # Fetch only the tasks we need to consider for model in self.Model.objects.enabled().exclude(exclude_query): From b6e39f63fecb8ca4d3d07327ad3a0346b1a5b376 Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Tue, 6 May 2025 00:59:03 +0100 Subject: [PATCH 2/7] Use aware_now to get server's timezone rather than using django's --- django_celery_beat/schedulers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index 0d1b785d..9b17b52a 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -364,7 +364,9 @@ def _get_timezone_offset(self, timezone_name): int: The hour offset """ # Get server timezone - server_tz = timezone.get_current_timezone() + # this gets django settings and not server's + # server_tz = timezone.get_current_timezone() + server_tz = ZoneInfo(str(server_time.tzinfo)) if isinstance(timezone_name, ZoneInfo): timezone_name = timezone_name.key From 040df6b8958084aa89ddf7dfc7c7f74f5dcb9c3f Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Tue, 6 May 2025 01:01:15 +0100 Subject: [PATCH 3/7] Update schedulers.py --- django_celery_beat/schedulers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index 9b17b52a..a5ab1478 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -272,10 +272,10 @@ def all_as_schedule(self): clocked__clocked_time__gt=next_schedule_sync ) - # exclude_cron_tasks_query = self._get_crontab_exclude_query() + exclude_cron_tasks_query = self._get_crontab_exclude_query() # Combine the queries for optimal database filtering - exclude_query = exclude_clock_tasks_query + exclude_query = exclude_clock_tasks_query | exclude_cron_tasks_query # Fetch only the tasks we need to consider for model in self.Model.objects.enabled().exclude(exclude_query): From b257a3c72263feb2185617e7549dd8058e058b7b Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Tue, 6 May 2025 01:02:43 +0100 Subject: [PATCH 4/7] Update schedulers.py --- django_celery_beat/schedulers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index a5ab1478..64ed7576 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -366,6 +366,7 @@ def _get_timezone_offset(self, timezone_name): # Get server timezone # this gets django settings and not server's # server_tz = timezone.get_current_timezone() + server_time = aware_now() server_tz = ZoneInfo(str(server_time.tzinfo)) if isinstance(timezone_name, ZoneInfo): From fca14a26889f8e6398703fa87683c31a9eca4d5f Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Wed, 7 May 2025 12:04:33 +0100 Subject: [PATCH 5/7] Update schedulers.py --- django_celery_beat/schedulers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index 64ed7576..9300ca55 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -19,7 +19,6 @@ from django.db.models import Case, F, IntegerField, Q, When from django.db.models.functions import Cast from django.db.utils import DatabaseError, InterfaceError -from django.utils import timezone from kombu.utils.encoding import safe_repr, safe_str from kombu.utils.json import dumps, loads @@ -364,8 +363,6 @@ def _get_timezone_offset(self, timezone_name): int: The hour offset """ # Get server timezone - # this gets django settings and not server's - # server_tz = timezone.get_current_timezone() server_time = aware_now() server_tz = ZoneInfo(str(server_time.tzinfo)) From 068e88e049bc52bc1c04bcfdf249c74b1ffda8e9 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Wed, 7 May 2025 11:20:52 +0000 Subject: [PATCH 6/7] Update django_celery_beat/schedulers.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- django_celery_beat/schedulers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_celery_beat/schedulers.py b/django_celery_beat/schedulers.py index 9300ca55..25d07873 100644 --- a/django_celery_beat/schedulers.py +++ b/django_celery_beat/schedulers.py @@ -364,7 +364,8 @@ def _get_timezone_offset(self, timezone_name): """ # Get server timezone server_time = aware_now() - server_tz = ZoneInfo(str(server_time.tzinfo)) + # Use server_time.tzinfo directly if it is already a ZoneInfo instance + server_tz = server_time.tzinfo if isinstance(server_time.tzinfo, ZoneInfo) else ZoneInfo(str(server_time.tzinfo)) if isinstance(timezone_name, ZoneInfo): timezone_name = timezone_name.key From 70d34acb946a0bb58d5fef690331e4e0cf89d620 Mon Sep 17 00:00:00 2001 From: Lucas Costa Date: Thu, 8 May 2025 09:38:23 +0100 Subject: [PATCH 7/7] adding tests --- t/unit/test_schedulers.py | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/t/unit/test_schedulers.py b/t/unit/test_schedulers.py index b32d257d..8bd2270d 100644 --- a/t/unit/test_schedulers.py +++ b/t/unit/test_schedulers.py @@ -1453,3 +1453,55 @@ def mock_apply_async(*args, **kwargs): ma.run_tasks(self.request, PeriodicTask.objects.filter(id=self.m1.id)) assert 'periodic_task_name' in self.captured_headers assert self.captured_headers['periodic_task_name'] == self.m1.name + + + +@pytest.mark.django_db +class test_timezone_offset_handling: + def setup_method(self): + self.app = patch("django_celery_beat.schedulers.current_app").start() + + def teardown_method(self): + patch.stopall() + + @patch("django_celery_beat.schedulers.aware_now") + def test_server_timezone_handling_with_zoneinfo(self, mock_aware_now): + """Test handling when server timezone is already a ZoneInfo instance.""" + + # Create a mock scheduler with only the methods we need to test + class MockScheduler: + _get_timezone_offset = schedulers.DatabaseScheduler._get_timezone_offset + + s = MockScheduler() + + tokyo_tz = ZoneInfo("Asia/Tokyo") + mock_now = datetime(2023, 1, 1, 12, 0, 0, tzinfo=tokyo_tz) + mock_aware_now.return_value = mock_now + + # Test with a different timezone + new_york_tz = "America/New_York" + offset = s._get_timezone_offset(new_york_tz) # Pass self explicitly + + # Tokyo is UTC+9, New York is UTC-5, so difference should be 14 hours + assert offset == 14 + assert mock_aware_now.called + + @patch("django_celery_beat.schedulers.aware_now") + def test_timezone_offset_with_zoneinfo_object_param(self, mock_aware_now): + """Test handling when timezone_name parameter is a ZoneInfo object.""" + + class MockScheduler: + _get_timezone_offset = schedulers.DatabaseScheduler._get_timezone_offset + + s = MockScheduler() + + tokyo_tz = ZoneInfo("Asia/Tokyo") + mock_now = datetime(2023, 1, 1, 12, 0, 0, tzinfo=tokyo_tz) + mock_aware_now.return_value = mock_now + + # Test with a ZoneInfo object as parameter + new_york_tz = ZoneInfo("America/New_York") + offset = s._get_timezone_offset(new_york_tz) # Pass self explicitly + + # Tokyo is UTC+9, New York is UTC-5, so difference should be 14 hours + assert offset == 14