Skip to content

Commit

Permalink
Merge pull request #41 from octoenergy/localtime/add-period-exceeds-o…
Browse files Browse the repository at this point in the history
…ne-year

Add localtime function period_exceeds_one_year
  • Loading branch information
Peter554 authored Feb 28, 2023
2 parents dad4f9b + b26f4cc commit fb36bb9
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
58 changes: 58 additions & 0 deletions tests/test_localtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest
import pytz
import time_machine
from dateutil import relativedelta
from django.conf import settings
from django.test import override_settings
from django.utils import timezone
Expand Down Expand Up @@ -1029,3 +1030,60 @@ def test_timestamp_british_summer_time_after_clocks_move_backward(self):
assert dt.day == 25
assert dt.hour == 1
assert dt.minute == 30


@pytest.mark.parametrize(
("period_start_at", "first_dt_exceeding_one_year"),
[
(
# Basic case.
localtime.datetime(2021, 1, 1),
localtime.datetime(2022, 1, 1, microsecond=1),
),
(
# A leap year.
localtime.datetime(2020, 1, 1),
localtime.datetime(2021, 1, 1, microsecond=1),
),
(
# Start on a leap year, Feb 28th.
localtime.datetime(2020, 2, 28),
localtime.datetime(2021, 2, 28, microsecond=1),
),
(
# Start on a leap year, Feb 29th.
localtime.datetime(2020, 2, 29),
localtime.datetime(2021, 3, 1, microsecond=1), # !important
),
(
# Start on a leap year, March 1st.
localtime.datetime(2020, 3, 1),
localtime.datetime(2021, 3, 1, microsecond=1),
),
(
# End on a leap year, Feb 28th.
localtime.datetime(2019, 2, 28),
localtime.datetime(2020, 2, 28, microsecond=1),
),
(
# End on a leap year, March 1st.
localtime.datetime(2019, 3, 1),
localtime.datetime(2020, 3, 1, microsecond=1),
),
(
# Clock moves backward twice.
localtime.datetime(2021, 10, 31),
localtime.datetime(2022, 10, 31, microsecond=1),
),
(
# Clock moves forward twice.
localtime.datetime(2021, 3, 28),
localtime.datetime(2022, 3, 28, microsecond=1),
),
],
)
def test_period_exceeds_one_year(period_start_at, first_dt_exceeding_one_year):
assert localtime.period_exceeds_one_year(period_start_at, first_dt_exceeding_one_year)
assert not localtime.period_exceeds_one_year(
period_start_at, first_dt_exceeding_one_year - relativedelta.relativedelta(microseconds=1)
)
28 changes: 28 additions & 0 deletions xocto/localtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,31 @@ def translate_english_month_to_spanish(month: int) -> str:
"December": "deciembre",
}
return month_name_lookup[month_name]


def period_exceeds_one_year(start_at: DateTime, end_at: DateTime) -> bool:
"""
Returns true if the passed period exceeds one year.
Edge cases such as leap years and daylight savings time are handled, where a simple approach
using only relativedelta would not be sufficient.
"""

# We take everything as localtime, and then remove the timezone information. This is to
# avoid false results when the period starts or ends on a leap day, or when an uneven
# number of DST changes happen in the year covered by the invoice.

start_at = as_localtime(start_at)
end_at = as_localtime(end_at)

tz_unaware_start_at = start_at.replace(tzinfo=None)
tz_unaware_end_at = end_at.replace(tzinfo=None)

one_year_after_start_at = tz_unaware_start_at + relativedelta(years=1)

if tz_unaware_start_at.month == 2 and tz_unaware_start_at.day == 29:
# Leap year case, on 29th Feb.
# One year after 29th Feb is 1st March (not 28th Feb).
one_year_after_start_at = one_year_after_start_at.replace(month=3, day=1)

return tz_unaware_end_at > one_year_after_start_at

0 comments on commit fb36bb9

Please sign in to comment.