From bd0fcc1a07ff513ffa5da44f6583099f6e72aee5 Mon Sep 17 00:00:00 2001 From: Evan Ackmann Date: Sun, 24 May 2020 01:31:27 -0400 Subject: [PATCH] =?UTF-8?q?Took=20into=20account=20DST=20when=20computing?= =?UTF-8?q?=20localtime=20zones.=20=20This=20wasn't=20ac=E2=80=A6=20(#1258?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Took into account DST when computing localtime zones. This wasn't accounted for when using non-UTC datetimes with queue.enqueue_at() * Updates tests with mocked timezones to test both scenarios --- rq/registry.py | 2 +- tests/test_scheduler.py | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/rq/registry.py b/rq/registry.py index 3a0dd95..c9237ca 100644 --- a/rq/registry.py +++ b/rq/registry.py @@ -265,7 +265,7 @@ class ScheduledJobRegistry(BaseRegistry): from datetime import timezone except ImportError: raise ValueError('datetime object with no timezone') - tz = timezone(timedelta(seconds=-time.timezone)) + tz = timezone(timedelta(seconds=-(time.timezone if time.daylight == 0 else time.altzone))) scheduled_datetime = scheduled_datetime.replace(tzinfo=tz) timestamp = calendar.timegm(scheduled_datetime.utctimetuple()) diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index 88c7fa5..712a6ed 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -70,9 +70,38 @@ class TestScheduledJobRegistry(RQTestCase): # If we pass in a datetime with no timezone, `schedule()` # assumes local timezone so depending on your local timezone, # the timestamp maybe different - registry.schedule(job, datetime(2019, 1, 1)) - self.assertEqual(self.testconn.zscore(registry.key, job.id), - 1546300800 + time.timezone) # 2019-01-01 UTC in Unix timestamp + + # we need to account for the difference between a timezone + # with DST active and without DST active. The time.timezone + # property isn't accurate when time.daylight is non-zero, + # we'll test both. + + # first, time.daylight == 0 (not in DST). + # mock the sitatuoin for American/New_York not in DST (UTC - 5) + # time.timezone = 18000 + # time.daylight = 0 + # time.altzone = 14400 + mock_day = mock.patch('time.daylight', 0) + mock_tz = mock.patch('time.timezone', 18000) + mock_atz = mock.patch('time.altzone', 14400) + with mock_tz, mock_day, mock_atz: + registry.schedule(job, datetime(2019, 1, 1)) + self.assertEqual(self.testconn.zscore(registry.key, job.id), + 1546300800 + 18000) # 2019-01-01 UTC in Unix timestamp + + # second, time.daylight != 0 (in DST) + # mock the sitatuoin for American/New_York not in DST (UTC - 4) + # time.timezone = 18000 + # time.daylight = 1 + # time.altzone = 14400 + mock_day = mock.patch('time.daylight', 1) + mock_tz = mock.patch('time.timezone', 18000) + mock_atz = mock.patch('time.altzone', 14400) + with mock_tz, mock_day, mock_atz: + registry.schedule(job, datetime(2019, 1, 1)) + self.assertEqual(self.testconn.zscore(registry.key, job.id), + 1546300800 + 14400) # 2019-01-01 UTC in Unix timestamp + # Score is always stored in UTC even if datetime is in a different tz tz = timezone(timedelta(hours=7)) @@ -99,8 +128,8 @@ class TestScheduler(RQTestCase): self.assertTrue(scheduler.should_reacquire_locks) scheduler.acquire_locks() self.assertIsNotNone(scheduler.lock_acquisition_time) - - # scheduler.should_reacquire_locks always returns False if + + # scheduler.should_reacquire_locks always returns False if # scheduler.acquired_locks and scheduler._queue_names are the same self.assertFalse(scheduler.should_reacquire_locks) scheduler.lock_acquisition_time = datetime.now() - timedelta(minutes=16) @@ -251,7 +280,7 @@ class TestWorker(RQTestCase): p.start() queue.enqueue_at(datetime(2019, 1, 1, tzinfo=utc), say_hello) - worker.work(burst=False, with_scheduler=True) + worker.work(burst=False, with_scheduler=True) p.join(1) self.assertIsNotNone(worker.scheduler) registry = FinishedJobRegistry(queue=queue)