beginning of leap support
This commit is contained in:
parent
12d83cdf37
commit
3f225c5d6e
@ -1,5 +1,5 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from math import floor
|
from fixedcal.services.leap_days import is_leap_year, gregorian_leap_days_between, fixed_leap_days_between
|
||||||
|
|
||||||
class FixedDate:
|
class FixedDate:
|
||||||
def __init__(self, date = None, day_of_year = None, year = None):
|
def __init__(self, date = None, day_of_year = None, year = None):
|
||||||
@ -40,6 +40,18 @@ class FixedDate:
|
|||||||
def today(self) -> "FixedDate":
|
def today(self) -> "FixedDate":
|
||||||
return FixedDate(date=datetime.today())
|
return FixedDate(date=datetime.today())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_leap_year(self) -> bool:
|
||||||
|
"""Whether the year of this date is leap year.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: Is this leap year
|
||||||
|
"""
|
||||||
|
# if self._year % 100 == 0:
|
||||||
|
# return self._year % 4 == 0 and self._year % 400 == 0
|
||||||
|
# return self._year % 4 == 0
|
||||||
|
return is_leap_year(self._year)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def datetime(self) -> datetime:
|
def datetime(self) -> datetime:
|
||||||
"""Construct a native datetime object from fixed date.
|
"""Construct a native datetime object from fixed date.
|
||||||
@ -149,7 +161,10 @@ class FixedDate:
|
|||||||
With timedelta as argument, new FixedDate will be returned.
|
With timedelta as argument, new FixedDate will be returned.
|
||||||
"""
|
"""
|
||||||
if isinstance(o, FixedDate):
|
if isinstance(o, FixedDate):
|
||||||
return self.datetime - o.datetime
|
difference = self.datetime - o.datetime
|
||||||
|
greg_leap_days = gregorian_leap_days_between(self.datetime, o.datetime)
|
||||||
|
fixed_leap_days = fixed_leap_days_between(self.datetime, o.datetime)
|
||||||
|
return difference - timedelta(greg_leap_days) + timedelta(fixed_leap_days)
|
||||||
elif isinstance(o, timedelta):
|
elif isinstance(o, timedelta):
|
||||||
new_date = self.datetime - o
|
new_date = self.datetime - o
|
||||||
return FixedDate(date=new_date)
|
return FixedDate(date=new_date)
|
||||||
|
38
fixedcal/services/leap_days.py
Normal file
38
fixedcal/services/leap_days.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
def is_leap_year(year: int) -> bool:
|
||||||
|
if year % 100 == 0:
|
||||||
|
return year % 4 == 0 and year % 400 == 0
|
||||||
|
return year % 4 == 0
|
||||||
|
|
||||||
|
def gregorian_leap_days_between(date1: datetime, date2: datetime) -> int:
|
||||||
|
"""Counts the gregorian leap days (29th Feb) between given dates.
|
||||||
|
Count includes both ends (date1 and date2 themselves).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date1 (datetime): The beginning of the count
|
||||||
|
date2 (datetime): The end of the count
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Count of the leap days.
|
||||||
|
"""
|
||||||
|
count = 0
|
||||||
|
if date1 > date2:
|
||||||
|
date1, date2 = date2, date1
|
||||||
|
days_between = (date2 - date1).days
|
||||||
|
for plusday in range(0, days_between):
|
||||||
|
date = date1 + timedelta(plusday)
|
||||||
|
if is_leap_year(date.year) and date.month == 2 and date.day == 29:
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
def fixed_leap_days_between(date1: datetime, date2: datetime) -> int:
|
||||||
|
count = 0
|
||||||
|
if date1 > date2:
|
||||||
|
date1, date2 = date2, date1
|
||||||
|
days_between = (date2 - date1).days
|
||||||
|
for plusday in range(0, days_between):
|
||||||
|
date = date1 + timedelta(plusday)
|
||||||
|
if is_leap_year(date.year) and date.month == 6 and date.day == 27:
|
||||||
|
count += 1
|
||||||
|
return count
|
61
tests/leap_year_test.py
Normal file
61
tests/leap_year_test.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from fixedcal import FixedDate
|
||||||
|
|
||||||
|
class TestLeapYear(unittest.TestCase):
|
||||||
|
def test_leap_year_detection_with_simple_noleap(self):
|
||||||
|
# 2022 is not divisible of four -> not a leap year
|
||||||
|
fixed_date = FixedDate(datetime(2022, 5, 4))
|
||||||
|
self.assertFalse(fixed_date.is_leap_year)
|
||||||
|
|
||||||
|
def test_leap_year_detection_with_complex_noleap(self):
|
||||||
|
# 1900 is divisible of four but also by 100 and not by 400 -> not a leap year
|
||||||
|
fixed_date = FixedDate(datetime(1900, 5, 4))
|
||||||
|
self.assertFalse(fixed_date.is_leap_year)
|
||||||
|
|
||||||
|
def test_leap_year_detection_with_common_leap_year(self):
|
||||||
|
# 2024 is divisible of four but not by 100 -> leap year
|
||||||
|
fixed_date = FixedDate(datetime(2024, 5, 4))
|
||||||
|
self.assertTrue(fixed_date.is_leap_year)
|
||||||
|
|
||||||
|
def test_leap_year_detection_with_centurial_leap_year(self):
|
||||||
|
# 2000 is divisible of all four, 100 and 400 -> leap year
|
||||||
|
fixed_date = FixedDate(datetime(2000, 5, 4))
|
||||||
|
self.assertTrue(fixed_date.is_leap_year)
|
||||||
|
|
||||||
|
def test_fixed_date_difference_over_gregorian_leap_day(self):
|
||||||
|
# in Gregorian system there are 7 days between,
|
||||||
|
# but in IFC the leap day is at the end of June
|
||||||
|
# and thus the difference should be just 6 days
|
||||||
|
date1 = FixedDate(datetime(2024, 2, 25))
|
||||||
|
date2 = FixedDate(datetime(2024, 3, 3))
|
||||||
|
self.assertEqual(date2-date1, timedelta(6))
|
||||||
|
|
||||||
|
def test_fixed_date_difference_over_fixed_leap_day(self):
|
||||||
|
# in Gregorian system there are 7 days between,
|
||||||
|
# but in IFC the leap day is also in between
|
||||||
|
# and therefore the difference should be 8 days
|
||||||
|
date1 = FixedDate(datetime(2024, 6, 27))
|
||||||
|
date2 = FixedDate(datetime(2024, 7, 4))
|
||||||
|
self.assertEqual(date2-date1, timedelta(8))
|
||||||
|
|
||||||
|
def test_fixed_date_difference_with_itself_gregorian_leap_day(self):
|
||||||
|
date = FixedDate(datetime(2024, 2, 29))
|
||||||
|
self.assertEqual(date-date, timedelta(0))
|
||||||
|
|
||||||
|
def test_fixed_date_difference_with_itself_fixed_leap_day(self):
|
||||||
|
date = FixedDate(datetime(2024, 6, 27))
|
||||||
|
self.assertEqual(date-date, timedelta(0))
|
||||||
|
|
||||||
|
def test_fixed_date_difference_over_both_leap_days(self):
|
||||||
|
# day count should be the same in both systems
|
||||||
|
date1 = FixedDate(datetime(2024, 2, 15))
|
||||||
|
date2 = FixedDate(datetime(2024, 8, 3))
|
||||||
|
self.assertEqual(date2-date1, timedelta(170))
|
||||||
|
|
||||||
|
def test_fixed_date_difference_over_multiple_leap_days(self):
|
||||||
|
# there are 3 Gregorian and 2 fixed leap days between
|
||||||
|
# difference is 2987 days in Gregorian including Greg leap days
|
||||||
|
date1 = FixedDate(datetime(2020, 2, 15))
|
||||||
|
date2 = FixedDate(datetime(2028, 4, 20))
|
||||||
|
self.assertEqual(date2-date1, timedelta(2986))
|
Loading…
x
Reference in New Issue
Block a user