From 6a9dfa8feb4d8524f952704583a8c342e87c201a Mon Sep 17 00:00:00 2001 From: c-1995-o1 Date: Tue, 28 Oct 2025 23:51:30 +0100 Subject: [PATCH 1/4] fix: bug when DST ends --- schedule/__init__.py | 11 ++++++++++- test_schedule.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index 8e12eeb7..badeacae 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -689,7 +689,7 @@ def run(self): logger.debug("Running job %s", self) ret = self.job_func() - self.last_run = datetime.datetime.now() + self.last_run = datetime.datetime.now(self.at_time_zone) self._schedule_next_run() if self._is_overdue(self.next_run): @@ -789,6 +789,15 @@ def _correct_utc_offset( moment = self.at_time_zone.normalize(moment) offset_after_normalize = moment.utcoffset() + # Check fall back for DST + if self.last_run is not None: + last_execution_dst = self.last_run.dst() + moment_dst = moment.dst() + if last_execution_dst > moment_dst: + if self.unit in ['minutes', 'hours']: + moment -= last_execution_dst - moment_dst + return moment + if offset_before_normalize == offset_after_normalize: # There was no change in the utc-offset, datetime didn't change. return moment diff --git a/test_schedule.py b/test_schedule.py index f497826d..9c644bef 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1239,6 +1239,43 @@ def test_align_utc_offset_after_fold_fixate(self): assert aligned_time.minute == 30 assert aligned_time.day == 27 + def test_fall_back_for_daylight_saving_time(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 2, 58, second=40, fold=0): + job_object = every().minute.at(":30", tz='Europe/Madrid').do(mock_job) + with mock_datetime(2025, 10, 26, 2, 59, second=40, fold=0): + schedule.run_pending() + assert job_object.next_run.hour == 2 + + def test_fall_back_for_daylight_saving_time_2(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 2, 59, second=30, fold=1): + assert every().minute.at(":30").do(mock_job).next_run.hour == 3 + + def test_fall_back_for_daylight_saving_time_3(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 1, 59, second=30, fold=0): + assert every().minute.at(":30").do(mock_job).next_run.hour == 2 + + def test_fall_back_for_daylight_saving_time_4(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 1, 30, second=0, fold=0): + job_object = every().hour.at(":30", tz='Europe/Madrid').do(mock_job) + assert job_object.next_run.hour == 2 + assert job_object.next_run.minute == 30 + with mock_datetime(2025, 10, 26, 2, 59, second=40, fold=0): + schedule.run_pending() + assert job_object.next_run.hour == 2 + assert job_object.next_run.minute == 30 + with mock_datetime(2025, 10, 26, 2, 59, second=40, fold=1): + schedule.run_pending() + assert job_object.next_run.hour == 3 + assert job_object.next_run.minute == 30 + def test_daylight_saving_time(self): mock_job = make_mock_job() # 27 March 2022, 02:00:00 clocks were turned forward 1 hour From f0ee553459b4f69c43e03eb54151f531ffd5fe7c Mon Sep 17 00:00:00 2001 From: c-1995-o1 Date: Wed, 29 Oct 2025 10:16:15 +0100 Subject: [PATCH 2/4] fix: bug when DST ends --- schedule/__init__.py | 2 +- test_schedule.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index badeacae..295089a6 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -794,7 +794,7 @@ def _correct_utc_offset( last_execution_dst = self.last_run.dst() moment_dst = moment.dst() if last_execution_dst > moment_dst: - if self.unit in ['minutes', 'hours']: + if self.unit in ["minutes", "hours"]: moment -= last_execution_dst - moment_dst return moment diff --git a/test_schedule.py b/test_schedule.py index 9c644bef..01b0ebb1 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1243,7 +1243,7 @@ def test_fall_back_for_daylight_saving_time(self): mock_job = make_mock_job() # 26 October 2025, 03:00:00 clocks were turned back 1 hour with mock_datetime(2025, 10, 26, 2, 58, second=40, fold=0): - job_object = every().minute.at(":30", tz='Europe/Madrid').do(mock_job) + job_object = every().minute.at(":30", tz="Europe/Madrid").do(mock_job) with mock_datetime(2025, 10, 26, 2, 59, second=40, fold=0): schedule.run_pending() assert job_object.next_run.hour == 2 @@ -1264,7 +1264,7 @@ def test_fall_back_for_daylight_saving_time_4(self): mock_job = make_mock_job() # 26 October 2025, 03:00:00 clocks were turned back 1 hour with mock_datetime(2025, 10, 26, 1, 30, second=0, fold=0): - job_object = every().hour.at(":30", tz='Europe/Madrid').do(mock_job) + job_object = every().hour.at(":30", tz="Europe/Madrid").do(mock_job) assert job_object.next_run.hour == 2 assert job_object.next_run.minute == 30 with mock_datetime(2025, 10, 26, 2, 59, second=40, fold=0): From c6bce63d909fb75ece90097764be83119da6849a Mon Sep 17 00:00:00 2001 From: c-1995-o1 Date: Fri, 31 Oct 2025 01:30:43 +0100 Subject: [PATCH 3/4] fix: every().seconds bug when DST ends --- schedule/__init__.py | 18 ++++++++++++++++++ test_schedule.py | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/schedule/__init__.py b/schedule/__init__.py index 295089a6..b48e7819 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -468,6 +468,24 @@ def tag(self, *tags: Hashable): self.tags.update(tags) return self + def with_time_zone(self, tz: str): + import pytz + + if self.unit in ("days", "hours", "minutes"): + raise ScheduleValueError( + "Timezone should be defined in at()" + ) + + if isinstance(tz, str): + self.at_time_zone = pytz.timezone(tz) + elif isinstance(tz, pytz.BaseTzInfo): + self.at_time_zone = tz + else: + raise ScheduleValueError( + "Timezone must be string or pytz.timezone object" + ) + return self + def at(self, time_str: str, tz: Optional[str] = None): """ Specify a particular time that the job should be run at. diff --git a/test_schedule.py b/test_schedule.py index 01b0ebb1..404da043 100644 --- a/test_schedule.py +++ b/test_schedule.py @@ -1276,6 +1276,24 @@ def test_fall_back_for_daylight_saving_time_4(self): assert job_object.next_run.hour == 3 assert job_object.next_run.minute == 30 + def test_fall_back_for_daylight_saving_time_5(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 2, 59, second=52, fold=0): + job_object = every(5).seconds.with_time_zone(tz="Europe/Madrid").do(mock_job) + with mock_datetime(2025, 10, 26, 2, 59, second=59, fold=0): + schedule.run_pending() + assert job_object.next_run.hour == 2 + + def test_fall_back_for_daylight_saving_time_6(self): + mock_job = make_mock_job() + # 26 October 2025, 03:00:00 clocks were turned back 1 hour + with mock_datetime(2025, 10, 26, 2, 59, second=52, fold=1): + job_object = every(5).seconds.with_time_zone(tz="Europe/Madrid").do(mock_job) + with mock_datetime(2025, 10, 26, 2, 59, second=59, fold=1): + schedule.run_pending() + assert job_object.next_run.hour == 3 + def test_daylight_saving_time(self): mock_job = make_mock_job() # 27 March 2022, 02:00:00 clocks were turned forward 1 hour From a5a8344a42b511e4e7945d457a2f710f9f07cbd6 Mon Sep 17 00:00:00 2001 From: c-1995-o1 Date: Fri, 31 Oct 2025 01:33:27 +0100 Subject: [PATCH 4/4] fix: every().seconds bug when DST ends --- schedule/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schedule/__init__.py b/schedule/__init__.py index b48e7819..8fd7d8f2 100644 --- a/schedule/__init__.py +++ b/schedule/__init__.py @@ -471,7 +471,7 @@ def tag(self, *tags: Hashable): def with_time_zone(self, tz: str): import pytz - if self.unit in ("days", "hours", "minutes"): + if self.unit != "seconds": raise ScheduleValueError( "Timezone should be defined in at()" )