diff --git a/pyoda_time/_offset_date_time.py b/pyoda_time/_offset_date_time.py index 7cf563b..9e7c55d 100644 --- a/pyoda_time/_offset_date_time.py +++ b/pyoda_time/_offset_date_time.py @@ -114,6 +114,40 @@ def to_instant(self) -> Instant: return Instant._from_untrusted_duration(self.__to_elapsed_time_since_epoch()) + def with_offset(self, offset: Offset) -> OffsetDateTime: + """Creates a new OffsetDateTime representing the instant in time in the same calendar, but with a different + offset. The local date and time is adjusted accordingly. + + :param offset: The new offset to use. + :return: The converted OffsetDateTime. + """ + from ._offset_time import OffsetTime + + # TODO: unchecked + # Slight change to the normal operation, as it's *just* about plausible that we change day + # twice in one direction or the other. + days = 0 + nanos = self.__offset_time.nanosecond_of_day + offset.nanoseconds - self.__offset_time._offset_nanoseconds + if nanos >= PyodaConstants.NANOSECONDS_PER_DAY: + days += 1 + nanos -= PyodaConstants.NANOSECONDS_PER_DAY + if nanos >= PyodaConstants.NANOSECONDS_PER_DAY: + days += 1 + nanos -= PyodaConstants.NANOSECONDS_PER_DAY + elif nanos < 0: + days -= 1 + nanos += PyodaConstants.NANOSECONDS_PER_DAY + if nanos < 0: + days -= 1 + nanos += PyodaConstants.NANOSECONDS_PER_DAY + return OffsetDateTime._ctor( + local_date=self.__local_date if days == 0 else self.__local_date.plus_days(days), + offset_time=OffsetTime._ctor( + nanosecond_of_day=nanos, + offset_seconds=offset.seconds, + ), + ) + def __to_elapsed_time_since_epoch(self) -> Duration: # Equivalent to LocalDateTime.ToLocalInstant().Minus(offset) days: int = self.__local_date._days_since_epoch diff --git a/tests/test_offset_date_time.py b/tests/test_offset_date_time.py new file mode 100644 index 0000000..ed342c1 --- /dev/null +++ b/tests/test_offset_date_time.py @@ -0,0 +1,30 @@ +# Copyright 2024 The Pyoda Time Authors. All rights reserved. +# Use of this source code is governed by the Apache License 2.0, +# as found in the LICENSE.txt file. + +from pyoda_time import LocalDateTime, Offset, OffsetDateTime + + +class TestOffsetDateTime: + def test_with_offset(self) -> None: + morning = LocalDateTime(2014, 1, 31, 9, 30) + original = OffsetDateTime(morning, Offset.from_hours(-8)) + evening = LocalDateTime(2014, 1, 31, 19, 30) + new_offset = Offset.from_hours(2) + expected = OffsetDateTime(evening, new_offset) + assert original.with_offset(new_offset) == expected + + def test_with_offset_cross_dates(self) -> None: + noon = OffsetDateTime(LocalDateTime(2017, 8, 22, 12, 0, 0), Offset.from_hours(0)) + previous_night = noon.with_offset(Offset.from_hours(-14)) + next_morning = noon.with_offset(Offset.from_hours(14)) + assert previous_night.local_date_time == LocalDateTime(2017, 8, 21, 22, 0, 0) + assert next_morning.local_date_time == LocalDateTime(2017, 8, 23, 2, 0, 0) + + def test_with_offset_two_days_forward_and_back(self) -> None: + # Go from UTC-18 to UTC+18 + night = OffsetDateTime(LocalDateTime(2017, 8, 21, 18, 0, 0), Offset.from_hours(-18)) + morning = night.with_offset(Offset.from_hours(18)) + assert morning.local_date_time == LocalDateTime(2017, 8, 23, 6, 0, 0) + back_again = morning.with_offset(Offset.from_hours(-18)) + assert back_again == night