beam: zeller_dow validate gap dates and month lengths; add tests
This commit is contained in:
1 parent
36fe95c7ae
commit
c382a33458
2 files changed
+59
-4
No files matched your search
@@ -0,0 +1,31 @@
|
|||||||
|
import unittest
|
||||||
|
from zeller_dow import dow
|
||||||
|
|
||||||
|
class TestZellerDow(unittest.TestCase):
|
||||||
|
def test_gap_dates_raise(self):
|
||||||
|
for day in range(5, 15):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
dow(1582, 10, day)
|
||||||
|
|
||||||
|
def test_silent_month_length_aliases_raise(self):
|
||||||
|
cases = [(2026, 2, 31), (2026, 2, 30), (2026, 4, 31),
|
||||||
|
(1900, 2, 29), (2000, 2, 30), (2024, 13, 1),
|
||||||
|
(2024, 0, 1), (2024, 1, 0), (2024, 1, 32)]
|
||||||
|
for y, m, d in cases:
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
dow(y, m, d)
|
||||||
|
|
||||||
|
def test_valid_anchors(self):
|
||||||
|
# 1582-10-15 was a Friday (Gregorian cutover)
|
||||||
|
self.assertEqual(dow(1582, 10, 15), "Friday")
|
||||||
|
# 1582-10-04 Julian Thursday
|
||||||
|
self.assertEqual(dow(1582, 10, 4), "Thursday")
|
||||||
|
self.assertEqual(dow(2024, 2, 29), "Thursday") # leap day exists
|
||||||
|
self.assertEqual(dow(2026, 10, 27), "Tuesday")
|
||||||
|
|
||||||
|
def test_leap_day_invalid_non_leap(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
dow(2023, 2, 29)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -1,8 +1,26 @@
|
|||||||
"""Day-of-week via Julian Day Number, handling Julian/Gregorian transition.
|
"""Day-of-week via Julian Day Number, handling Julian/Gregorian transition.
|
||||||
|
|
||||||
Dates before 1582-10-15 use the Julian calendar; on/after use Gregorian.
|
Dates before 1582-10-15 use the Julian calendar; on/after use Gregorian.
|
||||||
The gap 1582-10-05..14 did not exist.
|
The gap 1582-10-05..14 did not exist, so those dates are invalid.
|
||||||
|
Month lengths (including leap-year February) are validated per calendar.
|
||||||
"""
|
"""
|
||||||
|
def _julian_leap(y):
|
||||||
|
return y % 4 == 0
|
||||||
|
|
||||||
|
def _gregorian_leap(y):
|
||||||
|
return (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)
|
||||||
|
|
||||||
|
def _is_julian(y, m, d):
|
||||||
|
return (y, m, d) < (1582, 10, 15)
|
||||||
|
|
||||||
|
def _days_in_month(y, m, julian):
|
||||||
|
if m in (1, 3, 5, 7, 8, 10, 12):
|
||||||
|
return 31
|
||||||
|
if m in (4, 6, 9, 11):
|
||||||
|
return 30
|
||||||
|
leap = _julian_leap(y) if julian else _gregorian_leap(y)
|
||||||
|
return 29 if leap else 28
|
||||||
|
|
||||||
def _jdn_julian(y, m, d):
|
def _jdn_julian(y, m, d):
|
||||||
a = (14 - m) // 12
|
a = (14 - m) // 12
|
||||||
yy = y + 4800 - a
|
yy = y + 4800 - a
|
||||||
@@ -18,9 +36,15 @@ def _jdn_gregorian(y, m, d):
|
|||||||
NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||||
|
|
||||||
def dow(y, m, d):
|
def dow(y, m, d):
|
||||||
if not (1 <= m <= 12 and 1 <= d <= 31):
|
if not (1 <= m <= 12):
|
||||||
raise ValueError("invalid month/day")
|
raise ValueError("invalid month")
|
||||||
if (y, m, d) < (1582, 10, 15):
|
julian = _is_julian(y, m, d)
|
||||||
|
# gap dates did not exist
|
||||||
|
if (1582, 10, 5) <= (y, m, d) <= (1582, 10, 14):
|
||||||
|
raise ValueError("date did not exist (Julian/Gregorian gap)")
|
||||||
|
if not (1 <= d <= _days_in_month(y, m, julian)):
|
||||||
|
raise ValueError("invalid day for month")
|
||||||
|
if julian:
|
||||||
jdn = _jdn_julian(y, m, d)
|
jdn = _jdn_julian(y, m, d)
|
||||||
else:
|
else:
|
||||||
jdn = _jdn_gregorian(y, m, d)
|
jdn = _jdn_gregorian(y, m, d)
|
||||||
|
|||||||
Reference in new issue
Block a user