diff --git a/django_celery_beat/utils.py b/django_celery_beat/utils.py index f22b65b6..3c627095 100644 --- a/django_celery_beat/utils.py +++ b/django_celery_beat/utils.py @@ -1,6 +1,7 @@ """Utilities.""" # -- XXX This module must not use translation as that causes # -- a recursive loader import! +import time from datetime import timezone as datetime_timezone from django.conf import settings @@ -26,7 +27,17 @@ def make_aware(value): else: # naive datetimes are assumed to be in local timezone. if timezone.is_naive(value): - value = timezone.make_aware(value, timezone.get_default_timezone()) + tm_isdst = time.localtime().tm_isdst + + # tm_isdst may return -1 if it cannot be determined + if tm_isdst == -1: + is_dst = None + else: + is_dst = bool(tm_isdst) + + value = timezone.make_aware(value, + timezone.get_default_timezone(), + is_dst=is_dst) return value diff --git a/t/unit/test_utils.py b/t/unit/test_utils.py new file mode 100644 index 00000000..b711a961 --- /dev/null +++ b/t/unit/test_utils.py @@ -0,0 +1,186 @@ +import time +from datetime import datetime +from unittest import mock + +from django.test import TestCase +from django.utils import timezone + +from django_celery_beat import utils + + +class UtilsTest(TestCase): + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_use_tz_naive(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = True + mock_is_naive.return_value = True + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, 0] + mock_localtime_2.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), mock_localtime_2.return_value) + + mock_localtime_1.assert_not_called() + mock_make_aware.assert_called_with(dt, timezone.utc) + mock_get_default_timezone.assert_called() + mock_localtime_2.assert_called_with(dt, + "America/Los_Angeles") + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_use_tz_not_naive(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = True + mock_is_naive.return_value = False + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, 0] + mock_localtime_2.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), mock_localtime_2.return_value) + + mock_localtime_1.assert_not_called() + mock_make_aware.assert_not_called() + mock_get_default_timezone.assert_called() + mock_localtime_2.assert_called_with(dt, + "America/Los_Angeles") + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_not_use_tz_naive_dst(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = False + mock_is_naive.return_value = True + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, 1] + mock_localtime_1.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), dt) + + mock_localtime_1.assert_called_with() + mock_make_aware.assert_called_with(dt, + "America/Los_Angeles", + is_dst=True) + mock_get_default_timezone.assert_called() + mock_localtime_2.assert_not_called() + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_not_use_tz_naive_not_dst(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = False + mock_is_naive.return_value = True + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, 0] + mock_localtime_1.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), dt) + + mock_localtime_1.assert_called_with() + mock_make_aware.assert_called_with(dt, + "America/Los_Angeles", + is_dst=False) + mock_get_default_timezone.assert_called() + mock_localtime_2.assert_not_called() + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_not_use_tz_naive_neg_dst(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = False + mock_is_naive.return_value = True + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, -1] + mock_localtime_1.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), dt) + + mock_localtime_1.assert_called_with() + mock_make_aware.assert_called_with(dt, + "America/Los_Angeles", + is_dst=None) + mock_get_default_timezone.assert_called() + + @mock.patch('django_celery_beat.utils.timezone.localtime') + @mock.patch('django_celery_beat.utils.timezone.get_default_timezone') + @mock.patch('django_celery_beat.utils.timezone.make_aware') + @mock.patch('django_celery_beat.utils.time.localtime') + @mock.patch('django_celery_beat.utils.timezone.is_naive') + @mock.patch('django_celery_beat.utils.getattr') + def test_make_aware_not_use_tz_not_naive_dst(self, + mock_getattr, + mock_is_naive, + mock_localtime_1, + mock_make_aware, + mock_get_default_timezone, + mock_localtime_2): + dt = datetime(2022, 11, 6, 1, 15, 0) + mock_getattr.return_value = False + mock_is_naive.return_value = False + mock_get_default_timezone.return_value = "America/Los_Angeles" + test_time = [2022, 11, 6, 1, 15, 0, 0, 310, 0] + mock_localtime_1.return_value = time.struct_time(test_time) + mock_make_aware.return_value = dt + + self.assertEqual(utils.make_aware(dt), dt) + + mock_localtime_1.assert_not_called() + mock_make_aware.assert_not_called() + mock_get_default_timezone.assert_not_called() + mock_localtime_2.assert_not_called()