diff --git a/docs/docs/fluent_helpers.md b/docs/docs/fluent_helpers.md index 2ace4910a..854946114 100644 --- a/docs/docs/fluent_helpers.md +++ b/docs/docs/fluent_helpers.md @@ -65,3 +65,17 @@ converts the time in the appropriate timezone. >>> print(dt) '2013-03-31T02:30:00+00:00' ``` + +## Replacing time attributes + +`Time.replace()` preserves `fold` unless you specify a new value, matching +`datetime.time.replace()`. This keeps the selected occurrence of an ambiguous +time when changing other attributes. + +```python +>>> clock = pendulum.Time(1, 30, tzinfo=pendulum.timezone('America/New_York'), fold=1) +>>> clock.replace(minute=45).fold +1 +>>> clock.replace(fold=0).fold +0 +``` diff --git a/src/pendulum/time.py b/src/pendulum/time.py index 58ca7e819..d555c5083 100644 --- a/src/pendulum/time.py +++ b/src/pendulum/time.py @@ -269,10 +269,12 @@ def replace( second: SupportsIndex | None = None, microsecond: SupportsIndex | None = None, tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, - fold: int = 0, + fold: int | None = None, ) -> Self: if tzinfo is True: tzinfo = self.tzinfo + if fold is None: + fold = self.fold hour = hour if hour is not None else self.hour minute = minute if minute is not None else self.minute @@ -288,7 +290,7 @@ def replace( fold=fold, ) return self.__class__( - t.hour, t.minute, t.second, t.microsecond, tzinfo=t.tzinfo + t.hour, t.minute, t.second, t.microsecond, tzinfo=t.tzinfo, fold=t.fold ) def __getnewargs__(self) -> tuple[Time]: diff --git a/tests/time/test_fluent_setters.py b/tests/time/test_fluent_setters.py index a678e56e3..19e30e98d 100644 --- a/tests/time/test_fluent_setters.py +++ b/tests/time/test_fluent_setters.py @@ -1,5 +1,11 @@ from __future__ import annotations +import datetime + +from zoneinfo import ZoneInfo + +import pytest + from pendulum import Time from tests.conftest import assert_time @@ -10,3 +16,28 @@ def test_replace(): assert isinstance(t, Time) assert_time(t, 1, 2, 3, 654321) + + +@pytest.mark.parametrize("original_fold", [0, 1]) +@pytest.mark.parametrize("replacement_fold", [None, 0, 1]) +def test_replace_fold_matches_native_time( + original_fold: int, replacement_fold: int | None +) -> None: + native = datetime.time( + 1, 30, tzinfo=ZoneInfo("America/New_York"), fold=original_fold + ) + t = Time.instance(native) + if replacement_fold is None: + expected = native.replace(minute=45) + actual = t.replace(minute=45) + else: + expected = native.replace(minute=45, fold=replacement_fold) + actual = t.replace(minute=45, fold=replacement_fold) + + assert isinstance(actual, Time) + assert actual.fold == expected.fold + assert actual.tzinfo is t.tzinfo + day = datetime.date(2026, 11, 1) + assert datetime.datetime.combine(day, actual).astimezone(datetime.timezone.utc) == ( + datetime.datetime.combine(day, expected).astimezone(datetime.timezone.utc) + )