Skip to content

Commit

Permalink
Handle timezone-changes when next_run is in a different timezone as l…
Browse files Browse the repository at this point in the history
…ast_run
  • Loading branch information
SijmenHuizenga committed Oct 23, 2023
1 parent 5537cb5 commit e3c6b57
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 1 deletion.
12 changes: 11 additions & 1 deletion schedule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,6 @@ def _schedule_next_run(self) -> None:

# Do all computation in the context of the requested timezone
if self.at_time_zone is not None:
# get a naive datetime representation of the current time in the local timezone
now = datetime.datetime.now(self.at_time_zone)
else:
now = datetime.datetime.now()
Expand Down Expand Up @@ -754,8 +753,18 @@ def _schedule_next_run(self) -> None:
kwargs["hour"] = self.at_time.hour
if self.unit in ["days", "hours"] or self.start_day is not None:
kwargs["minute"] = self.at_time.minute

self.next_run = self.next_run.replace(**kwargs) # type: ignore

if self.next_run.tzinfo:
# Sometimes when changing time we move into a different timezone (e.g. DST)
# To correct the timezone-element, we can 'normalize' the time.
self.next_run = self.at_time_zone.normalize(self.next_run)
# But normalization keeps the hour/minute/second elements at the same moment in time,
# For example 23:00 might become 22:00. But the .at() promises a specific hour/minute/second
# so we re-apply those elements here.
self.next_run = self.next_run.replace(**kwargs) # type: ignore

# Make sure we run at the specified time *today* (or *this hour*)
# as well. This accounts for when a job takes so long it finished
# in the next period.
Expand Down Expand Up @@ -784,6 +793,7 @@ def _schedule_next_run(self) -> None:
# Calculations happen in the configured timezone, but to execute the schedule we
# need to know the next_run time in the system time. So we convert back to naive local
if self.next_run.tzinfo:
self.next_run = self.at_time_zone.normalize(self.next_run)
self.next_run = self.next_run.astimezone().replace(tzinfo=None)

def _is_overdue(self, when: datetime.datetime):
Expand Down
11 changes: 11 additions & 0 deletions test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,17 @@ def test_at_timezone(self):
assert next.minute == 00
assert next.second == 20

with mock_datetime(2023, 10, 22, 23, 0, 0, TZ_UTC):
# Current UTC: sunday 23:00
# Current Amsterdam: monday 01:00 (daylight saving active)
# Expected run Amsterdam: sunday 29 oktober 23:00 (daylight saving NOT active)
# Next run UTC time: oktober-29 22:00
schedule.clear()
next = every().sunday.at("23:00", "Europe/Amsterdam").do(mock_job).next_run
assert next.day == 29
assert next.hour == 22
assert next.minute == 00

with self.assertRaises(pytz.exceptions.UnknownTimeZoneError):
every().day.at("10:30", "FakeZone").do(mock_job)

Expand Down

0 comments on commit e3c6b57

Please sign in to comment.