Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement OffsetDateTime.with_offset #233

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions pyoda_time/_offset_date_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions tests/test_offset_date_time.py
Original file line number Diff line number Diff line change
@@ -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
Loading