Source code for hebrewcal.calendars.gregorian
"""The proleptic Gregorian calendar.
Valid for all years, including years <= 0 (proleptic). The RD value matches the
Python standard-library proleptic Gregorian ordinal, which makes cross-checking
straightforward.
"""
from __future__ import annotations
from dataclasses import dataclass
from hebrewcal.core.rata_die import RD_EPOCH
_MONTH_LENGTHS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
[docs]
def is_leap_year(year: int) -> bool:
"""Return whether ``year`` is a Gregorian leap year."""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
[docs]
def last_day_of_month(year: int, month: int) -> int:
"""Return the number of days in ``month`` of ``year``."""
if month == 2 and is_leap_year(year):
return 29
return _MONTH_LENGTHS[month - 1]
[docs]
@dataclass(frozen=True, order=True)
class GregorianDate:
"""A date in the proleptic Gregorian calendar."""
year: int
month: int
day: int
def __post_init__(self) -> None:
if not 1 <= self.month <= 12:
raise ValueError(f"month out of range: {self.month}")
if not 1 <= self.day <= last_day_of_month(self.year, self.month):
raise ValueError(f"day out of range: {self.day}")
[docs]
def to_rd(self) -> int:
"""Return the Rata Die day count for this date."""
y = self.year
if self.month <= 2:
correction = 0
elif is_leap_year(y):
correction = -1
else:
correction = -2
return (
RD_EPOCH
- 1
+ 365 * (y - 1)
+ (y - 1) // 4
- (y - 1) // 100
+ (y - 1) // 400
+ (367 * self.month - 362) // 12
+ correction
+ self.day
)
[docs]
@classmethod
def from_rd(cls, rd: int) -> GregorianDate:
"""Reconstruct a Gregorian date from an RD value."""
year = _year_from_rd(rd)
prior_days = rd - cls(year, 1, 1).to_rd()
if rd < cls(year, 3, 1).to_rd():
correction = 0
elif is_leap_year(year):
correction = 1
else:
correction = 2
month = (12 * (prior_days + correction) + 373) // 367
day = rd - cls(year, month, 1).to_rd() + 1
return cls(year, month, day)
def _year_from_rd(rd: int) -> int:
"""Return the Gregorian year containing the given RD value."""
d0 = rd - RD_EPOCH
n400, d1 = divmod(d0, 146097)
n100, d2 = divmod(d1, 36524)
n4, d3 = divmod(d2, 1461)
n1 = d3 // 365
year = 400 * n400 + 100 * n100 + 4 * n4 + n1
if n100 == 4 or n1 == 4:
return year
return year + 1