Skip to content

Consider server timezone on _get_timezone_offset instead of django's settings #886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions django_celery_beat/schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -364,7 +363,9 @@ def _get_timezone_offset(self, timezone_name):
int: The hour offset
"""
# Get server timezone
server_tz = timezone.get_current_timezone()
server_time = aware_now()
# 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))
Copy link

@jennifer-richards jennifer-richards May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tzinfo class does not define __str__. It would be better to use server_time.tzinfo.tzname() which is guaranteed to exist.

There's no guarantee ZoneInfo will know what to do with the tzname(), though, so this won't work with obscure timezone implementations. (Edit: or with obscure zones not known to ZoneInfo. Either of these cases will probably break Django, so I don't think that this is a problem, just pointing it out.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would request to come with a better solution for this in a separate PR, as going to merge this for a hhotfix


if isinstance(timezone_name, ZoneInfo):
timezone_name = timezone_name.key
Expand Down
52 changes: 52 additions & 0 deletions t/unit/test_schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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