diff --git a/src/pendulum/datetime.py b/src/pendulum/datetime.py index da89b13d..cd15f5b7 100644 --- a/src/pendulum/datetime.py +++ b/src/pendulum/datetime.py @@ -826,7 +826,19 @@ def _start_of_day(self) -> Self: """ Reset the time to 00:00:00. """ - return self.at(0, 0, 0, 0) + dt = self.at(0, 0, 0, 0) + + # In zones that spring forward exactly at midnight, 00:00:00 does not + # exist. When ``fold`` is 0, resolving that nonexistent time shifts it + # backward into the previous day, which is not the start of this day. + # Detect that the day changed and rebuild the first valid instant of + # the day by resolving forward (fold=1) instead. + if dt.day != self.day: + dt = self.__class__.create( + self.year, self.month, self.day, 0, 0, 0, 0, tz=self.tz, fold=1 + ) + + return dt def _end_of_day(self) -> Self: """ diff --git a/tests/datetime/test_start_end_of.py b/tests/datetime/test_start_end_of.py index 1937e74d..5455d797 100644 --- a/tests/datetime/test_start_end_of.py +++ b/tests/datetime/test_start_end_of.py @@ -323,3 +323,18 @@ def test_end_of_on_date_after_transition(): assert d.end_of("day").offset == 3600 assert d.end_of("month").offset == 3600 assert d.end_of("year").offset == 3600 + + +def test_start_of_day_when_midnight_does_not_exist(): + # Chile/Continental springs forward exactly at midnight on 2025-09-07, + # so 00:00:00 does not exist that day. start_of("day") must resolve to the + # first valid instant of the day (01:00:00-03:00), not fall back into the + # previous day. See issue #915. + d = pendulum.datetime(2025, 9, 6, 0, 0, tz="Chile/Continental").add(days=1) + new = d.start_of("day") + + assert new.day == 7 + assert_datetime(new, 2025, 9, 7, 1, 0, 0, 0) + assert new.offset == -3 * 3600 + # start_of("day") must be idempotent even across the midnight gap. + assert new == new.start_of("day")