Skip to content

Commit e0bac94

Browse files
committed
Implement representative years filtering method for Gregorian calendar
1 parent e6b96df commit e0bac94

4 files changed

Lines changed: 47 additions & 14 deletions

File tree

src/undate/converters/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ def days_in_year(self, year: int) -> int:
181181
# add 1 because the difference doesn't include the end point
182182
return (year_end - year_start).days + 1
183183

184+
def representative_years(self, years: None | list[int] = None) -> list[int]:
185+
"""Returns a list of representative years within the specified list.
186+
Result should include one for each type of variant year for this
187+
calendar (e.g., leap year and non-leap year). If no years are specified,
188+
returns a list of representative years for the current calendar.
189+
"""
190+
raise NotImplementedError
191+
184192
def to_gregorian(self, year, month, day) -> tuple[int, int, int]:
185193
"""Convert a date for this calendar specified by numeric year, month, and day,
186194
into the Gregorian equivalent date. Should return a tuple of year, month, day.

src/undate/converters/calendars/gregorian.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from calendar import monthrange
1+
from calendar import monthrange, isleap
22

33
from undate.converters.base import BaseCalendarConverter
44

@@ -45,6 +45,33 @@ def max_day(self, year: int, month: int) -> int:
4545

4646
return max_day
4747

48+
def representative_years(self, years: None | list[int] = None) -> list[int]:
49+
"""Takes a list of years and returns a subset with one leap year and one non-leap year.
50+
If no years are specified, returns a known leap year and non-leap year.
51+
"""
52+
53+
# if years is unset or list is empty
54+
if not years:
55+
return [self.LEAP_YEAR, self.NON_LEAP_YEAR]
56+
57+
found_leap = False
58+
found_non_leap = False
59+
rep_years = []
60+
for year in years:
61+
if isleap(year):
62+
if not found_leap:
63+
found_leap = True
64+
rep_years.append(year)
65+
else:
66+
if not found_non_leap:
67+
found_non_leap = True
68+
rep_years.append(year)
69+
# stop as soon as we've found one example of each type of year
70+
if found_leap and found_non_leap:
71+
break
72+
73+
return rep_years
74+
4875
def to_gregorian(self, year, month, day) -> tuple[int, int, int]:
4976
"""Convert to Gregorian date. This returns the specified by year, month,
5077
and day unchanged, but is provided for consistency since all calendar

src/undate/undate.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,13 @@ def representative_years(self) -> list[int]:
500500
"""A list of representative years for this date."""
501501
try:
502502
# todo: filter by calendar to minimum needed
503-
return list(self.possible_years)
503+
try:
504+
return self.calendar_converter.representative_years(
505+
list(self.possible_years)
506+
)
507+
except NotImplementedError:
508+
# if calendar converter does not support representative years, return all years
509+
return list(self.possible_years)
504510
except ValueError:
505511
return [
506512
self.calendar_converter.LEAP_YEAR,

tests/test_undate.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,19 +408,11 @@ def test_possible_years(self):
408408
Undate("XXXX").possible_years
409409

410410
def test_representative_years(self):
411+
# single year is returned as is
411412
assert Undate("1991").representative_years == [1991]
412-
assert Undate("190X").representative_years == [
413-
1900,
414-
1901,
415-
1902,
416-
1903,
417-
1904,
418-
1905,
419-
1906,
420-
1907,
421-
1908,
422-
1909,
423-
]
413+
# for an uncertain year, returns first leap year and non-leap year in range
414+
assert Undate("190X").representative_years == [1900, 1904]
415+
assert Undate("19XX").representative_years == [1900, 1904]
424416

425417
def test_duration(self):
426418
day_duration = Undate(2022, 11, 7).duration()

0 commit comments

Comments
 (0)