From 5ba009e861397bc0f308254327abce3a8ecf6bcd Mon Sep 17 00:00:00 2001 From: Blake-Madden Date: Sun, 26 Nov 2023 19:22:16 -0500 Subject: [PATCH] Add Catholic Feasts holiday authority class (US observances) This includes a static function to calculate Easter that can be used for other authorities. Document the wxDateTimeWorkDays and wxDateTimeHolidayAuthority classes. Closes #24094. --- include/wx/datetime.h | 91 +++++- interface/wx/datetime.h | 142 +++++++- src/common/datetime.cpp | 128 ++++++++ tests/datetime/datetimetest.cpp | 564 ++++++++++++++++++++++++++++++++ 4 files changed, 912 insertions(+), 13 deletions(-) diff --git a/include/wx/datetime.h b/include/wx/datetime.h index 1dba298b12..6e39ce424b 100644 --- a/include/wx/datetime.h +++ b/include/wx/datetime.h @@ -17,6 +17,8 @@ #include +#include + #include // for INT_MIN #include "wx/longlong.h" @@ -44,7 +46,7 @@ struct _SYSTEMTIME; * ? 2. getdate() function like under Solaris * + 3. text conversion for wxDateSpan * + 4. pluggable modules for the workdays calculations - * 5. wxDateTimeHolidayAuthority for Easter and other christian feasts + * 5. wxDateTimeHolidayAuthority Christian feasts outside of US */ /* @@ -1599,13 +1601,7 @@ protected: virtual bool DoIsHoliday(const wxDateTime& dt) const = 0; // this function should fill the array with all holidays between the two - // given dates - it is implemented in the base class, but in a very - // inefficient way (it just iterates over all days and uses IsHoliday() for - // each of them), so it must be overridden in the derived class where the - // base class version may be explicitly used if needed - // - // returns the number of holidays in the given range and fills holidays - // array + // given dates virtual size_t DoGetHolidaysInRange(const wxDateTime& dtStart, const wxDateTime& dtEnd, wxDateTimeArray& holidays) const = 0; @@ -1625,6 +1621,85 @@ protected: wxDateTimeArray& holidays) const override; }; +// https://marian.org/mary/feast-days +// https://www.omvusa.org/blog/catholic-holy-days-of-obligation/ +class WXDLLIMPEXP_BASE wxDateTimeUSCatholicFeasts : public wxDateTimeHolidayAuthority +{ +public: + // Easter for a given year. + // Based on https://www.geeksforgeeks.org/how-to-calculate-the-easter-date-for-a-given-year-using-gauss-algorithm/ + // Validated against 1600 to 2099, using data from: + // https://www.assa.org.au/edm (Astronomical Society of South Australia) + // https://www.census.gov/data/software/x13as/genhol/easter-dates.html (US Census Bureau) + static wxDateTime GetEaster(int year); + + // Ascension for a given year. + // Celebrated on the 40th day of Easter/ + // the sixth Thursday after Easter Sunday. + static wxDateTime GetThursdayAscension(int year) + { + const wxDateTime ascension = GetEaster(year) + wxDateSpan::Days(39); + wxASSERT_MSG( + ascension.GetWeekDay() == wxDateTime::WeekDay::Thu, + "Error in Ascension calculation!"); + return ascension; + } + + // Ascension for a given year. + // Same as traditional Ascension, but moved to the following Sunday. + static wxDateTime GetSundayAscension(int year) + { + const wxDateTime ascension = GetEaster(year) + wxDateSpan::Weeks(6); + wxASSERT_MSG( + ascension.GetWeekDay() == wxDateTime::WeekDay::Sun, + "Error in Ascension calculation!"); + return ascension; + } +protected: + bool DoIsHoliday(const wxDateTime& dt) const override + { + if (dt.IsSameDate(GetEaster(dt.GetYear())) || + dt.IsSameDate(GetThursdayAscension(dt.GetYear())) ) + { + return true; + } + for (const auto& feast : m_holyDaysOfObligation) + { + if (feast.GetMonth() == dt.GetMonth() && + feast.GetDay() == dt.GetDay()) + { + return true; + } + } + return false; + } + + size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const override; +private: + static std::vector m_holyDaysOfObligation; +}; + +// Christmas and Easter +class WXDLLIMPEXP_BASE wxDateTimeChristianHolidays : public wxDateTimeUSCatholicFeasts +{ +protected: + bool DoIsHoliday(const wxDateTime& dt) const override + { + if (dt.IsSameDate(GetEaster(dt.GetYear())) || + (dt.GetMonth() == 12 && dt.GetDay() == 25)) + { + return true; + } + return false; + } + + size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const override; +}; + // ============================================================================ // inline functions implementation // ============================================================================ diff --git a/interface/wx/datetime.h b/interface/wx/datetime.h index 5e6aec720c..2987a863a8 100644 --- a/interface/wx/datetime.h +++ b/interface/wx/datetime.h @@ -1679,18 +1679,111 @@ const wxDateTime wxDefaultDateTime; /** @class wxDateTimeWorkDays - @todo Write wxDateTimeWorkDays documentation. + Holiday authority that classifies all Saturdays and Sundays + as holidays. @library{wxbase} @category{data} */ -class wxDateTimeWorkDays +class wxDateTimeWorkDays : public wxDateTimeHolidayAuthority { -public: - +protected: + /** + Override which returns @true if provided date is a Saturday and Sunday. + */ + virtual bool DoIsHoliday(const wxDateTime& dt) const override; + /** + Override which returns all Saturdays and Sundays from a provided range. + */ + virtual size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const override; }; +/** + @class wxDateTimeUSCatholicFeasts + Holiday authority that returns Catholic holy days of obligation, + as observed in the United States. This includes: + + - Solemnity of Mary, Mother of God + - Easter (moveable feast) + - Ascension (moveable feast) + - Assumption of the Blessed Virgin Mary + - All Saints Day + - Immaculate Conception of the Blessed Virgin Mary + - Christmas + + @library{wxbase} + @category{data} + + @since 3.3.0 +*/ +class wxDateTimeUSCatholicFeasts : public wxDateTimeHolidayAuthority +{ +public: + /** + Returns the date for Easter for a given year. + */ + static wxDateTime GetEaster(int year); + + /** + Returns the date for Ascension for a given year. + Celebrated on the 40th day of Easter/ + sixth Thursday after Easter Sunday. + */ + static wxDateTime GetThursdayAscension(int year); + + /** + Returns the date for Ascension for a given year. + This is the same as GetThursdayAscension(), + but moved to the Sunday following the traditional Ascension + that falls on a Thursday. + */ + static wxDateTime GetSundayAscension(int year); + +protected: + /** + Override which returns @true if provided date is a holy day of obligation. + */ + bool DoIsHoliday(const wxDateTime& dt) const override; + + /** + Override to determine the holy days of obligation within a date range. + */ + size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const override; +}; + +/** + @class wxDateTimeChristianHolidays + + Holiday authority that returns holidays common to all Christian religions. + This includes: + + - Easter (moveable feast) + - Christmas + + @library{wxbase} + @category{data} + + @since 3.3.0 +*/ +class WXDLLIMPEXP_BASE wxDateTimeChristianHolidays : public wxDateTimeUSCatholicFeasts +{ +protected: + /** + Override which returns @true if provided date is Easter or Christmas. + */ + bool DoIsHoliday(const wxDateTime& dt) const override; + /** + Override to determine the holidays within a date range. + */ + size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const override; +}; /** @class wxDateSpan @@ -2231,7 +2324,12 @@ public: /** @class wxDateTimeHolidayAuthority - @todo Write wxDateTimeHolidayAuthority documentation. + Class which decides whether a given + date is a holiday and is used by all functions working with "work days". + + New classes can be derived from this to determine specific holidays. + These classes should override DoIsHoliday() and DoGetHolidaysInRange(), + and be passed to wxDateTimeHolidayAuthority::AddAuthority() to be used. @library{wxbase} @category{data} @@ -2239,6 +2337,40 @@ public: class wxDateTimeHolidayAuthority { public: + /// Returns @true if the given date is a holiday. + static bool IsHoliday(const wxDateTime& dt); + /** + Fills the provided array with all holidays in the given range, returns + the number of them. + */ + static size_t GetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays); + + /// Clears the list of holiday authorities. + static void ClearAllAuthorities(); + + /** + Adds a new holiday authority. + + The pointer will be deleted by wxDateTimeHolidayAuthority. + */ + static void AddAuthority(wxDateTimeHolidayAuthority* auth); + +protected: + /** + This function should be overridden to determine whether + a given day is a holiday. + */ + virtual bool DoIsHoliday(const wxDateTime& dt) const = 0; + + /** + This function should be overridden to fill an array with + all holidays between the two given dates. + */ + virtual size_t DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const = 0; }; diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp index 01ee68969d..62dcf9d308 100644 --- a/src/common/datetime.cpp +++ b/src/common/datetime.cpp @@ -74,6 +74,7 @@ #include "wx/tokenzr.h" #include +#include #ifdef __WINDOWS__ #include @@ -2271,6 +2272,133 @@ size_t wxDateTimeWorkDays::DoGetHolidaysInRange(const wxDateTime& dtStart, return holidays.GetCount(); } +// ---------------------------------------------------------------------------- +// wxDateTimeUSCatholicFeasts +// ---------------------------------------------------------------------------- + +std::vector wxDateTimeUSCatholicFeasts::m_holyDaysOfObligation = +{ + // Feasts with fixed dates + { wxDateTime(1, wxDateTime::Month::Jan, 0) }, // Solemnity of Mary, Mother of God + { wxDateTime(15, wxDateTime::Month::Aug, 0) }, // Assumption of the Blessed Virgin Mary + { wxDateTime(1, wxDateTime::Month::Nov, 0) }, // All Saints Day + { wxDateTime(8, wxDateTime::Month::Dec, 0) }, // Immaculate Conception of the Blessed Virgin Mary + { wxDateTime(25, wxDateTime::Month::Dec, 0) } // Christmas +}; + +wxDateTime wxDateTimeUSCatholicFeasts::GetEaster(int year) +{ + // Adjust for miscalculation in Gauss formula + if (year == 1734 || year == 1886) + { + return wxDateTime(25, wxDateTime::Apr, year); + } + + // All calculations done + // on the basis of + // Gauss Easter Algorithm + const float A = year % 19; + const float B = year % 4; + const float C = year % 7; + const float P = std::floor((float)year / 100.0); + + const float Q = std::floor((float)(13 + 8 * P) / 25.0); + + const float M = (int)(15 - Q + P - std::floor((float)P / 4)) % 30; + + const float N = (int)(4 + P - std::floor((float)P / 4)) % 7; + + const float D = (int)(19 * A + M) % 30; + + const float E = (int)(2 * B + 4 * C + 6 * D + N) % 7; + + const int days = (int)(22 + D + E); + + // A corner case, + // when D is 29 + if ((D == 29) && (E == 6)) + { + wxASSERT_MSG( + wxDateTime(19, wxDateTime::Apr, year).GetWeekDay() == + wxDateTime::WeekDay::Sun, + "Error in Easter calculation!"); + return wxDateTime(19, wxDateTime::Apr, year); + } + // Another corner case, + // when D is 28 + else if ((D == 28) && (E == 6)) + { + wxASSERT_MSG( + wxDateTime(18, wxDateTime::Apr, year).GetWeekDay() == + wxDateTime::WeekDay::Sun, + "Error in Easter calculation!"); + return wxDateTime(18, wxDateTime::Apr, year); + } + else + { + // If days > 31, move to April + // April = 4th Month + if (days > 31) + { + wxASSERT_MSG( + wxDateTime((days - 31), wxDateTime::Apr, year).GetWeekDay() == + wxDateTime::WeekDay::Sun, + "Error in Easter calculation!"); + return wxDateTime((days - 31), wxDateTime::Apr, year); + } + else + { + // Otherwise, stay on March + // March = 3rd Month + wxASSERT_MSG( + wxDateTime(days, wxDateTime::Mar, year).GetWeekDay() == + wxDateTime::WeekDay::Sun, + "Error in Easter calculation!"); + return wxDateTime(days, wxDateTime::Mar, year); + } + } +} + +size_t wxDateTimeUSCatholicFeasts::DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const +{ + holidays.Clear(); + + for (wxDateTime dt = dtStart; dt <= dtEnd; dt += wxDateSpan::Day()) + { + if (DoIsHoliday(dt) ) + { + holidays.Add(dt); + continue; + } + } + + return holidays.size(); +} + +// ---------------------------------------------------------------------------- +// wxDateTimeChristianHolidays +// ---------------------------------------------------------------------------- + +size_t wxDateTimeChristianHolidays::DoGetHolidaysInRange(const wxDateTime& dtStart, + const wxDateTime& dtEnd, + wxDateTimeArray& holidays) const +{ + holidays.Clear(); + + for (wxDateTime dt = dtStart; dt <= dtEnd; dt += wxDateSpan::Day()) + { + if (DoIsHoliday(dt) ) + { + holidays.Add(dt); + continue; + } + } + + return holidays.size(); +} + // ============================================================================ // other helper functions // ============================================================================ diff --git a/tests/datetime/datetimetest.cpp b/tests/datetime/datetimetest.cpp index 3d4c735342..43c3d94cf3 100644 --- a/tests/datetime/datetimetest.cpp +++ b/tests/datetime/datetimetest.cpp @@ -2025,4 +2025,568 @@ TEST_CASE("wxDateTime::UNow", "[datetime][now][unow]") CHECK( gotMS ); } +TEST_CASE("Easter", "[datetime][holiday][easter]") +{ + std::vector easters = + { + wxDateTime( 2, wxDateTime::Apr, 1600), + wxDateTime(22, wxDateTime::Apr, 1601), + wxDateTime( 7, wxDateTime::Apr, 1602), + wxDateTime(30, wxDateTime::Mar, 1603), + wxDateTime(18, wxDateTime::Apr, 1604), + wxDateTime(10, wxDateTime::Apr, 1605), + wxDateTime(26, wxDateTime::Mar, 1606), + wxDateTime(15, wxDateTime::Apr, 1607), + wxDateTime( 6, wxDateTime::Apr, 1608), + wxDateTime(19, wxDateTime::Apr, 1609), + wxDateTime(11, wxDateTime::Apr, 1610), + wxDateTime( 3, wxDateTime::Apr, 1611), + wxDateTime(22, wxDateTime::Apr, 1612), + wxDateTime( 7, wxDateTime::Apr, 1613), + wxDateTime(30, wxDateTime::Mar, 1614), + wxDateTime(19, wxDateTime::Apr, 1615), + wxDateTime( 3, wxDateTime::Apr, 1616), + wxDateTime(26, wxDateTime::Mar, 1617), + wxDateTime(15, wxDateTime::Apr, 1618), + wxDateTime(31, wxDateTime::Mar, 1619), + wxDateTime(19, wxDateTime::Apr, 1620), + wxDateTime(11, wxDateTime::Apr, 1621), + wxDateTime(27, wxDateTime::Mar, 1622), + wxDateTime(16, wxDateTime::Apr, 1623), + wxDateTime( 7, wxDateTime::Apr, 1624), + wxDateTime(30, wxDateTime::Mar, 1625), + wxDateTime(12, wxDateTime::Apr, 1626), + wxDateTime( 4, wxDateTime::Apr, 1627), + wxDateTime(23, wxDateTime::Apr, 1628), + wxDateTime(15, wxDateTime::Apr, 1629), + wxDateTime(31, wxDateTime::Mar, 1630), + wxDateTime(20, wxDateTime::Apr, 1631), + wxDateTime(11, wxDateTime::Apr, 1632), + wxDateTime(27, wxDateTime::Mar, 1633), + wxDateTime(16, wxDateTime::Apr, 1634), + wxDateTime( 8, wxDateTime::Apr, 1635), + wxDateTime(23, wxDateTime::Mar, 1636), + wxDateTime(12, wxDateTime::Apr, 1637), + wxDateTime( 4, wxDateTime::Apr, 1638), + wxDateTime(24, wxDateTime::Apr, 1639), + wxDateTime( 8, wxDateTime::Apr, 1640), + wxDateTime(31, wxDateTime::Mar, 1641), + wxDateTime(20, wxDateTime::Apr, 1642), + wxDateTime( 5, wxDateTime::Apr, 1643), + wxDateTime(27, wxDateTime::Mar, 1644), + wxDateTime(16, wxDateTime::Apr, 1645), + wxDateTime( 1, wxDateTime::Apr, 1646), + wxDateTime(21, wxDateTime::Apr, 1647), + wxDateTime(12, wxDateTime::Apr, 1648), + wxDateTime( 4, wxDateTime::Apr, 1649), + wxDateTime(17, wxDateTime::Apr, 1650), + wxDateTime( 9, wxDateTime::Apr, 1651), + wxDateTime(31, wxDateTime::Mar, 1652), + wxDateTime(13, wxDateTime::Apr, 1653), + wxDateTime( 5, wxDateTime::Apr, 1654), + wxDateTime(28, wxDateTime::Mar, 1655), + wxDateTime(16, wxDateTime::Apr, 1656), + wxDateTime( 1, wxDateTime::Apr, 1657), + wxDateTime(21, wxDateTime::Apr, 1658), + wxDateTime(13, wxDateTime::Apr, 1659), + wxDateTime(28, wxDateTime::Mar, 1660), + wxDateTime(17, wxDateTime::Apr, 1661), + wxDateTime( 9, wxDateTime::Apr, 1662), + wxDateTime(25, wxDateTime::Mar, 1663), + wxDateTime(13, wxDateTime::Apr, 1664), + wxDateTime( 5, wxDateTime::Apr, 1665), + wxDateTime(25, wxDateTime::Apr, 1666), + wxDateTime(10, wxDateTime::Apr, 1667), + wxDateTime( 1, wxDateTime::Apr, 1668), + wxDateTime(21, wxDateTime::Apr, 1669), + wxDateTime( 6, wxDateTime::Apr, 1670), + wxDateTime(29, wxDateTime::Mar, 1671), + wxDateTime(17, wxDateTime::Apr, 1672), + wxDateTime( 2, wxDateTime::Apr, 1673), + wxDateTime(25, wxDateTime::Mar, 1674), + wxDateTime(14, wxDateTime::Apr, 1675), + wxDateTime( 5, wxDateTime::Apr, 1676), + wxDateTime(18, wxDateTime::Apr, 1677), + wxDateTime(10, wxDateTime::Apr, 1678), + wxDateTime( 2, wxDateTime::Apr, 1679), + wxDateTime(21, wxDateTime::Apr, 1680), + wxDateTime( 6, wxDateTime::Apr, 1681), + wxDateTime(29, wxDateTime::Mar, 1682), + wxDateTime(18, wxDateTime::Apr, 1683), + wxDateTime( 2, wxDateTime::Apr, 1684), + wxDateTime(22, wxDateTime::Apr, 1685), + wxDateTime(14, wxDateTime::Apr, 1686), + wxDateTime(30, wxDateTime::Mar, 1687), + wxDateTime(18, wxDateTime::Apr, 1688), + wxDateTime(10, wxDateTime::Apr, 1689), + wxDateTime(26, wxDateTime::Mar, 1690), + wxDateTime(15, wxDateTime::Apr, 1691), + wxDateTime( 6, wxDateTime::Apr, 1692), + wxDateTime(22, wxDateTime::Mar, 1693), + wxDateTime(11, wxDateTime::Apr, 1694), + wxDateTime( 3, wxDateTime::Apr, 1695), + wxDateTime(22, wxDateTime::Apr, 1696), + wxDateTime( 7, wxDateTime::Apr, 1697), + wxDateTime(30, wxDateTime::Mar, 1698), + wxDateTime(19, wxDateTime::Apr, 1699), + wxDateTime(11, wxDateTime::Apr, 1700), + wxDateTime(27, wxDateTime::Mar, 1701), + wxDateTime(16, wxDateTime::Apr, 1702), + wxDateTime( 8, wxDateTime::Apr, 1703), + wxDateTime(23, wxDateTime::Mar, 1704), + wxDateTime(12, wxDateTime::Apr, 1705), + wxDateTime( 4, wxDateTime::Apr, 1706), + wxDateTime(24, wxDateTime::Apr, 1707), + wxDateTime( 8, wxDateTime::Apr, 1708), + wxDateTime(31, wxDateTime::Mar, 1709), + wxDateTime(20, wxDateTime::Apr, 1710), + wxDateTime( 5, wxDateTime::Apr, 1711), + wxDateTime(27, wxDateTime::Mar, 1712), + wxDateTime(16, wxDateTime::Apr, 1713), + wxDateTime( 1, wxDateTime::Apr, 1714), + wxDateTime(21, wxDateTime::Apr, 1715), + wxDateTime(12, wxDateTime::Apr, 1716), + wxDateTime(28, wxDateTime::Mar, 1717), + wxDateTime(17, wxDateTime::Apr, 1718), + wxDateTime( 9, wxDateTime::Apr, 1719), + wxDateTime(31, wxDateTime::Mar, 1720), + wxDateTime(13, wxDateTime::Apr, 1721), + wxDateTime( 5, wxDateTime::Apr, 1722), + wxDateTime(28, wxDateTime::Mar, 1723), + wxDateTime(16, wxDateTime::Apr, 1724), + wxDateTime( 1, wxDateTime::Apr, 1725), + wxDateTime(21, wxDateTime::Apr, 1726), + wxDateTime(13, wxDateTime::Apr, 1727), + wxDateTime(28, wxDateTime::Mar, 1728), + wxDateTime(17, wxDateTime::Apr, 1729), + wxDateTime( 9, wxDateTime::Apr, 1730), + wxDateTime(25, wxDateTime::Mar, 1731), + wxDateTime(13, wxDateTime::Apr, 1732), + wxDateTime( 5, wxDateTime::Apr, 1733), + wxDateTime(25, wxDateTime::Apr, 1734), + wxDateTime(10, wxDateTime::Apr, 1735), + wxDateTime( 1, wxDateTime::Apr, 1736), + wxDateTime(21, wxDateTime::Apr, 1737), + wxDateTime( 6, wxDateTime::Apr, 1738), + wxDateTime(29, wxDateTime::Mar, 1739), + wxDateTime(17, wxDateTime::Apr, 1740), + wxDateTime( 2, wxDateTime::Apr, 1741), + wxDateTime(25, wxDateTime::Mar, 1742), + wxDateTime(14, wxDateTime::Apr, 1743), + wxDateTime( 5, wxDateTime::Apr, 1744), + wxDateTime(18, wxDateTime::Apr, 1745), + wxDateTime(10, wxDateTime::Apr, 1746), + wxDateTime( 2, wxDateTime::Apr, 1747), + wxDateTime(14, wxDateTime::Apr, 1748), + wxDateTime( 6, wxDateTime::Apr, 1749), + wxDateTime(29, wxDateTime::Mar, 1750), + wxDateTime(11, wxDateTime::Apr, 1751), + wxDateTime( 2, wxDateTime::Apr, 1752), + wxDateTime(22, wxDateTime::Apr, 1753), + wxDateTime(14, wxDateTime::Apr, 1754), + wxDateTime(30, wxDateTime::Mar, 1755), + wxDateTime(18, wxDateTime::Apr, 1756), + wxDateTime(10, wxDateTime::Apr, 1757), + wxDateTime(26, wxDateTime::Mar, 1758), + wxDateTime(15, wxDateTime::Apr, 1759), + wxDateTime( 6, wxDateTime::Apr, 1760), + wxDateTime(22, wxDateTime::Mar, 1761), + wxDateTime(11, wxDateTime::Apr, 1762), + wxDateTime( 3, wxDateTime::Apr, 1763), + wxDateTime(22, wxDateTime::Apr, 1764), + wxDateTime( 7, wxDateTime::Apr, 1765), + wxDateTime(30, wxDateTime::Mar, 1766), + wxDateTime(19, wxDateTime::Apr, 1767), + wxDateTime( 3, wxDateTime::Apr, 1768), + wxDateTime(26, wxDateTime::Mar, 1769), + wxDateTime(15, wxDateTime::Apr, 1770), + wxDateTime(31, wxDateTime::Mar, 1771), + wxDateTime(19, wxDateTime::Apr, 1772), + wxDateTime(11, wxDateTime::Apr, 1773), + wxDateTime( 3, wxDateTime::Apr, 1774), + wxDateTime(16, wxDateTime::Apr, 1775), + wxDateTime( 7, wxDateTime::Apr, 1776), + wxDateTime(30, wxDateTime::Mar, 1777), + wxDateTime(19, wxDateTime::Apr, 1778), + wxDateTime( 4, wxDateTime::Apr, 1779), + wxDateTime(26, wxDateTime::Mar, 1780), + wxDateTime(15, wxDateTime::Apr, 1781), + wxDateTime(31, wxDateTime::Mar, 1782), + wxDateTime(20, wxDateTime::Apr, 1783), + wxDateTime(11, wxDateTime::Apr, 1784), + wxDateTime(27, wxDateTime::Mar, 1785), + wxDateTime(16, wxDateTime::Apr, 1786), + wxDateTime( 8, wxDateTime::Apr, 1787), + wxDateTime(23, wxDateTime::Mar, 1788), + wxDateTime(12, wxDateTime::Apr, 1789), + wxDateTime( 4, wxDateTime::Apr, 1790), + wxDateTime(24, wxDateTime::Apr, 1791), + wxDateTime( 8, wxDateTime::Apr, 1792), + wxDateTime(31, wxDateTime::Mar, 1793), + wxDateTime(20, wxDateTime::Apr, 1794), + wxDateTime( 5, wxDateTime::Apr, 1795), + wxDateTime(27, wxDateTime::Mar, 1796), + wxDateTime(16, wxDateTime::Apr, 1797), + wxDateTime( 8, wxDateTime::Apr, 1798), + wxDateTime(24, wxDateTime::Mar, 1799), + wxDateTime(13, wxDateTime::Apr, 1800), + wxDateTime( 5, wxDateTime::Apr, 1801), + wxDateTime(18, wxDateTime::Apr, 1802), + wxDateTime(10, wxDateTime::Apr, 1803), + wxDateTime( 1, wxDateTime::Apr, 1804), + wxDateTime(14, wxDateTime::Apr, 1805), + wxDateTime( 6, wxDateTime::Apr, 1806), + wxDateTime(29, wxDateTime::Mar, 1807), + wxDateTime(17, wxDateTime::Apr, 1808), + wxDateTime( 2, wxDateTime::Apr, 1809), + wxDateTime(22, wxDateTime::Apr, 1810), + wxDateTime(14, wxDateTime::Apr, 1811), + wxDateTime(29, wxDateTime::Mar, 1812), + wxDateTime(18, wxDateTime::Apr, 1813), + wxDateTime(10, wxDateTime::Apr, 1814), + wxDateTime(26, wxDateTime::Mar, 1815), + wxDateTime(14, wxDateTime::Apr, 1816), + wxDateTime( 6, wxDateTime::Apr, 1817), + wxDateTime(22, wxDateTime::Mar, 1818), + wxDateTime(11, wxDateTime::Apr, 1819), + wxDateTime( 2, wxDateTime::Apr, 1820), + wxDateTime(22, wxDateTime::Apr, 1821), + wxDateTime( 7, wxDateTime::Apr, 1822), + wxDateTime(30, wxDateTime::Mar, 1823), + wxDateTime(18, wxDateTime::Apr, 1824), + wxDateTime( 3, wxDateTime::Apr, 1825), + wxDateTime(26, wxDateTime::Mar, 1826), + wxDateTime(15, wxDateTime::Apr, 1827), + wxDateTime( 6, wxDateTime::Apr, 1828), + wxDateTime(19, wxDateTime::Apr, 1829), + wxDateTime(11, wxDateTime::Apr, 1830), + wxDateTime( 3, wxDateTime::Apr, 1831), + wxDateTime(22, wxDateTime::Apr, 1832), + wxDateTime( 7, wxDateTime::Apr, 1833), + wxDateTime(30, wxDateTime::Mar, 1834), + wxDateTime(19, wxDateTime::Apr, 1835), + wxDateTime( 3, wxDateTime::Apr, 1836), + wxDateTime(26, wxDateTime::Mar, 1837), + wxDateTime(15, wxDateTime::Apr, 1838), + wxDateTime(31, wxDateTime::Mar, 1839), + wxDateTime(19, wxDateTime::Apr, 1840), + wxDateTime(11, wxDateTime::Apr, 1841), + wxDateTime(27, wxDateTime::Mar, 1842), + wxDateTime(16, wxDateTime::Apr, 1843), + wxDateTime( 7, wxDateTime::Apr, 1844), + wxDateTime(23, wxDateTime::Mar, 1845), + wxDateTime(12, wxDateTime::Apr, 1846), + wxDateTime( 4, wxDateTime::Apr, 1847), + wxDateTime(23, wxDateTime::Apr, 1848), + wxDateTime( 8, wxDateTime::Apr, 1849), + wxDateTime(31, wxDateTime::Mar, 1850), + wxDateTime(20, wxDateTime::Apr, 1851), + wxDateTime(11, wxDateTime::Apr, 1852), + wxDateTime(27, wxDateTime::Mar, 1853), + wxDateTime(16, wxDateTime::Apr, 1854), + wxDateTime( 8, wxDateTime::Apr, 1855), + wxDateTime(23, wxDateTime::Mar, 1856), + wxDateTime(12, wxDateTime::Apr, 1857), + wxDateTime( 4, wxDateTime::Apr, 1858), + wxDateTime(24, wxDateTime::Apr, 1859), + wxDateTime( 8, wxDateTime::Apr, 1860), + wxDateTime(31, wxDateTime::Mar, 1861), + wxDateTime(20, wxDateTime::Apr, 1862), + wxDateTime( 5, wxDateTime::Apr, 1863), + wxDateTime(27, wxDateTime::Mar, 1864), + wxDateTime(16, wxDateTime::Apr, 1865), + wxDateTime( 1, wxDateTime::Apr, 1866), + wxDateTime(21, wxDateTime::Apr, 1867), + wxDateTime(12, wxDateTime::Apr, 1868), + wxDateTime(28, wxDateTime::Mar, 1869), + wxDateTime(17, wxDateTime::Apr, 1870), + wxDateTime( 9, wxDateTime::Apr, 1871), + wxDateTime(31, wxDateTime::Mar, 1872), + wxDateTime(13, wxDateTime::Apr, 1873), + wxDateTime( 5, wxDateTime::Apr, 1874), + wxDateTime(28, wxDateTime::Mar, 1875), + wxDateTime(16, wxDateTime::Apr, 1876), + wxDateTime( 1, wxDateTime::Apr, 1877), + wxDateTime(21, wxDateTime::Apr, 1878), + wxDateTime(13, wxDateTime::Apr, 1879), + wxDateTime(28, wxDateTime::Mar, 1880), + wxDateTime(17, wxDateTime::Apr, 1881), + wxDateTime( 9, wxDateTime::Apr, 1882), + wxDateTime(25, wxDateTime::Mar, 1883), + wxDateTime(13, wxDateTime::Apr, 1884), + wxDateTime( 5, wxDateTime::Apr, 1885), + wxDateTime(25, wxDateTime::Apr, 1886), + wxDateTime(10, wxDateTime::Apr, 1887), + wxDateTime( 1, wxDateTime::Apr, 1888), + wxDateTime(21, wxDateTime::Apr, 1889), + wxDateTime( 6, wxDateTime::Apr, 1890), + wxDateTime(29, wxDateTime::Mar, 1891), + wxDateTime(17, wxDateTime::Apr, 1892), + wxDateTime( 2, wxDateTime::Apr, 1893), + wxDateTime(25, wxDateTime::Mar, 1894), + wxDateTime(14, wxDateTime::Apr, 1895), + wxDateTime( 5, wxDateTime::Apr, 1896), + wxDateTime(18, wxDateTime::Apr, 1897), + wxDateTime(10, wxDateTime::Apr, 1898), + wxDateTime( 2, wxDateTime::Apr, 1899), + wxDateTime(15, wxDateTime::Apr, 1900), + wxDateTime( 7, wxDateTime::Apr, 1901), + wxDateTime(30, wxDateTime::Mar, 1902), + wxDateTime(12, wxDateTime::Apr, 1903), + wxDateTime( 3, wxDateTime::Apr, 1904), + wxDateTime(23, wxDateTime::Apr, 1905), + wxDateTime(15, wxDateTime::Apr, 1906), + wxDateTime(31, wxDateTime::Mar, 1907), + wxDateTime(19, wxDateTime::Apr, 1908), + wxDateTime(11, wxDateTime::Apr, 1909), + wxDateTime(27, wxDateTime::Mar, 1910), + wxDateTime(16, wxDateTime::Apr, 1911), + wxDateTime( 7, wxDateTime::Apr, 1912), + wxDateTime(23, wxDateTime::Mar, 1913), + wxDateTime(12, wxDateTime::Apr, 1914), + wxDateTime( 4, wxDateTime::Apr, 1915), + wxDateTime(23, wxDateTime::Apr, 1916), + wxDateTime( 8, wxDateTime::Apr, 1917), + wxDateTime(31, wxDateTime::Mar, 1918), + wxDateTime(20, wxDateTime::Apr, 1919), + wxDateTime( 4, wxDateTime::Apr, 1920), + wxDateTime(27, wxDateTime::Mar, 1921), + wxDateTime(16, wxDateTime::Apr, 1922), + wxDateTime( 1, wxDateTime::Apr, 1923), + wxDateTime(20, wxDateTime::Apr, 1924), + wxDateTime(12, wxDateTime::Apr, 1925), + wxDateTime( 4, wxDateTime::Apr, 1926), + wxDateTime(17, wxDateTime::Apr, 1927), + wxDateTime( 8, wxDateTime::Apr, 1928), + wxDateTime(31, wxDateTime::Mar, 1929), + wxDateTime(20, wxDateTime::Apr, 1930), + wxDateTime( 5, wxDateTime::Apr, 1931), + wxDateTime(27, wxDateTime::Mar, 1932), + wxDateTime(16, wxDateTime::Apr, 1933), + wxDateTime( 1, wxDateTime::Apr, 1934), + wxDateTime(21, wxDateTime::Apr, 1935), + wxDateTime(12, wxDateTime::Apr, 1936), + wxDateTime(28, wxDateTime::Mar, 1937), + wxDateTime(17, wxDateTime::Apr, 1938), + wxDateTime( 9, wxDateTime::Apr, 1939), + wxDateTime(24, wxDateTime::Mar, 1940), + wxDateTime(13, wxDateTime::Apr, 1941), + wxDateTime( 5, wxDateTime::Apr, 1942), + wxDateTime(25, wxDateTime::Apr, 1943), + wxDateTime( 9, wxDateTime::Apr, 1944), + wxDateTime( 1, wxDateTime::Apr, 1945), + wxDateTime(21, wxDateTime::Apr, 1946), + wxDateTime( 6, wxDateTime::Apr, 1947), + wxDateTime(28, wxDateTime::Mar, 1948), + wxDateTime(17, wxDateTime::Apr, 1949), + wxDateTime( 9, wxDateTime::Apr, 1950), + wxDateTime(25, wxDateTime::Mar, 1951), + wxDateTime(13, wxDateTime::Apr, 1952), + wxDateTime( 5, wxDateTime::Apr, 1953), + wxDateTime(18, wxDateTime::Apr, 1954), + wxDateTime(10, wxDateTime::Apr, 1955), + wxDateTime( 1, wxDateTime::Apr, 1956), + wxDateTime(21, wxDateTime::Apr, 1957), + wxDateTime( 6, wxDateTime::Apr, 1958), + wxDateTime(29, wxDateTime::Mar, 1959), + wxDateTime(17, wxDateTime::Apr, 1960), + wxDateTime( 2, wxDateTime::Apr, 1961), + wxDateTime(22, wxDateTime::Apr, 1962), + wxDateTime(14, wxDateTime::Apr, 1963), + wxDateTime(29, wxDateTime::Mar, 1964), + wxDateTime(18, wxDateTime::Apr, 1965), + wxDateTime(10, wxDateTime::Apr, 1966), + wxDateTime(26, wxDateTime::Mar, 1967), + wxDateTime(14, wxDateTime::Apr, 1968), + wxDateTime( 6, wxDateTime::Apr, 1969), + wxDateTime(29, wxDateTime::Mar, 1970), + wxDateTime(11, wxDateTime::Apr, 1971), + wxDateTime( 2, wxDateTime::Apr, 1972), + wxDateTime(22, wxDateTime::Apr, 1973), + wxDateTime(14, wxDateTime::Apr, 1974), + wxDateTime(30, wxDateTime::Mar, 1975), + wxDateTime(18, wxDateTime::Apr, 1976), + wxDateTime(10, wxDateTime::Apr, 1977), + wxDateTime(26, wxDateTime::Mar, 1978), + wxDateTime(15, wxDateTime::Apr, 1979), + wxDateTime( 6, wxDateTime::Apr, 1980), + wxDateTime(19, wxDateTime::Apr, 1981), + wxDateTime(11, wxDateTime::Apr, 1982), + wxDateTime( 3, wxDateTime::Apr, 1983), + wxDateTime(22, wxDateTime::Apr, 1984), + wxDateTime( 7, wxDateTime::Apr, 1985), + wxDateTime(30, wxDateTime::Mar, 1986), + wxDateTime(19, wxDateTime::Apr, 1987), + wxDateTime( 3, wxDateTime::Apr, 1988), + wxDateTime(26, wxDateTime::Mar, 1989), + wxDateTime(15, wxDateTime::Apr, 1990), + wxDateTime(31, wxDateTime::Mar, 1991), + wxDateTime(19, wxDateTime::Apr, 1992), + wxDateTime(11, wxDateTime::Apr, 1993), + wxDateTime( 3, wxDateTime::Apr, 1994), + wxDateTime(16, wxDateTime::Apr, 1995), + wxDateTime( 7, wxDateTime::Apr, 1996), + wxDateTime(30, wxDateTime::Mar, 1997), + wxDateTime(12, wxDateTime::Apr, 1998), + wxDateTime( 4, wxDateTime::Apr, 1999), + wxDateTime(23, wxDateTime::Apr, 2000), + wxDateTime(15, wxDateTime::Apr, 2001), + wxDateTime(31, wxDateTime::Mar, 2002), + wxDateTime(20, wxDateTime::Apr, 2003), + wxDateTime(11, wxDateTime::Apr, 2004), + wxDateTime(27, wxDateTime::Mar, 2005), + wxDateTime(16, wxDateTime::Apr, 2006), + wxDateTime( 8, wxDateTime::Apr, 2007), + wxDateTime(23, wxDateTime::Mar, 2008), + wxDateTime(12, wxDateTime::Apr, 2009), + wxDateTime( 4, wxDateTime::Apr, 2010), + wxDateTime(24, wxDateTime::Apr, 2011), + wxDateTime( 8, wxDateTime::Apr, 2012), + wxDateTime(31, wxDateTime::Mar, 2013), + wxDateTime(20, wxDateTime::Apr, 2014), + wxDateTime( 5, wxDateTime::Apr, 2015), + wxDateTime(27, wxDateTime::Mar, 2016), + wxDateTime(16, wxDateTime::Apr, 2017), + wxDateTime( 1, wxDateTime::Apr, 2018), + wxDateTime(21, wxDateTime::Apr, 2019), + wxDateTime(12, wxDateTime::Apr, 2020), + wxDateTime( 4, wxDateTime::Apr, 2021), + wxDateTime(17, wxDateTime::Apr, 2022), + wxDateTime( 9, wxDateTime::Apr, 2023), + wxDateTime(31, wxDateTime::Mar, 2024), + wxDateTime(20, wxDateTime::Apr, 2025), + wxDateTime( 5, wxDateTime::Apr, 2026), + wxDateTime(28, wxDateTime::Mar, 2027), + wxDateTime(16, wxDateTime::Apr, 2028), + wxDateTime( 1, wxDateTime::Apr, 2029), + wxDateTime(21, wxDateTime::Apr, 2030), + wxDateTime(13, wxDateTime::Apr, 2031), + wxDateTime(28, wxDateTime::Mar, 2032), + wxDateTime(17, wxDateTime::Apr, 2033), + wxDateTime( 9, wxDateTime::Apr, 2034), + wxDateTime(25, wxDateTime::Mar, 2035), + wxDateTime(13, wxDateTime::Apr, 2036), + wxDateTime( 5, wxDateTime::Apr, 2037), + wxDateTime(25, wxDateTime::Apr, 2038), + wxDateTime(10, wxDateTime::Apr, 2039), + wxDateTime( 1, wxDateTime::Apr, 2040), + wxDateTime(21, wxDateTime::Apr, 2041), + wxDateTime( 6, wxDateTime::Apr, 2042), + wxDateTime(29, wxDateTime::Mar, 2043), + wxDateTime(17, wxDateTime::Apr, 2044), + wxDateTime( 9, wxDateTime::Apr, 2045), + wxDateTime(25, wxDateTime::Mar, 2046), + wxDateTime(14, wxDateTime::Apr, 2047), + wxDateTime( 5, wxDateTime::Apr, 2048), + wxDateTime(18, wxDateTime::Apr, 2049), + wxDateTime(10, wxDateTime::Apr, 2050), + wxDateTime( 2, wxDateTime::Apr, 2051), + wxDateTime(21, wxDateTime::Apr, 2052), + wxDateTime( 6, wxDateTime::Apr, 2053), + wxDateTime(29, wxDateTime::Mar, 2054), + wxDateTime(18, wxDateTime::Apr, 2055), + wxDateTime( 2, wxDateTime::Apr, 2056), + wxDateTime(22, wxDateTime::Apr, 2057), + wxDateTime(14, wxDateTime::Apr, 2058), + wxDateTime(30, wxDateTime::Mar, 2059), + wxDateTime(18, wxDateTime::Apr, 2060), + wxDateTime(10, wxDateTime::Apr, 2061), + wxDateTime(26, wxDateTime::Mar, 2062), + wxDateTime(15, wxDateTime::Apr, 2063), + wxDateTime( 6, wxDateTime::Apr, 2064), + wxDateTime(29, wxDateTime::Mar, 2065), + wxDateTime(11, wxDateTime::Apr, 2066), + wxDateTime( 3, wxDateTime::Apr, 2067), + wxDateTime(22, wxDateTime::Apr, 2068), + wxDateTime(14, wxDateTime::Apr, 2069), + wxDateTime(30, wxDateTime::Mar, 2070), + wxDateTime(19, wxDateTime::Apr, 2071), + wxDateTime(10, wxDateTime::Apr, 2072), + wxDateTime(26, wxDateTime::Mar, 2073), + wxDateTime(15, wxDateTime::Apr, 2074), + wxDateTime( 7, wxDateTime::Apr, 2075), + wxDateTime(19, wxDateTime::Apr, 2076), + wxDateTime(11, wxDateTime::Apr, 2077), + wxDateTime( 3, wxDateTime::Apr, 2078), + wxDateTime(23, wxDateTime::Apr, 2079), + wxDateTime( 7, wxDateTime::Apr, 2080), + wxDateTime(30, wxDateTime::Mar, 2081), + wxDateTime(19, wxDateTime::Apr, 2082), + wxDateTime( 4, wxDateTime::Apr, 2083), + wxDateTime(26, wxDateTime::Mar, 2084), + wxDateTime(15, wxDateTime::Apr, 2085), + wxDateTime(31, wxDateTime::Mar, 2086), + wxDateTime(20, wxDateTime::Apr, 2087), + wxDateTime(11, wxDateTime::Apr, 2088), + wxDateTime( 3, wxDateTime::Apr, 2089), + wxDateTime(16, wxDateTime::Apr, 2090), + wxDateTime( 8, wxDateTime::Apr, 2091), + wxDateTime(30, wxDateTime::Mar, 2092), + wxDateTime(12, wxDateTime::Apr, 2093), + wxDateTime( 4, wxDateTime::Apr, 2094), + wxDateTime(24, wxDateTime::Apr, 2095), + wxDateTime(15, wxDateTime::Apr, 2096), + wxDateTime(31, wxDateTime::Mar, 2097), + wxDateTime(20, wxDateTime::Apr, 2098), + wxDateTime(12, wxDateTime::Apr, 2099) + }; + + for (const auto& easter : easters) + { + INFO("Checking year " << easter.GetYear()); + CHECK(wxDateTimeUSCatholicFeasts::GetEaster(easter.GetYear()).IsSameDate(easter)); + } +} + +TEST_CASE("US Catholic Holidays", "[datetime][holiday]") +{ + SECTION("Ascension") + { + wxDateTime ascension = wxDateTimeUSCatholicFeasts::GetThursdayAscension(2023); + CHECK(ascension.GetMonth() == wxDateTime::Month::May); + CHECK(ascension.GetDay() == 18); + + ascension = wxDateTimeUSCatholicFeasts::GetSundayAscension(2023); + CHECK(ascension.GetMonth() == wxDateTime::Month::May); + CHECK(ascension.GetDay() == 21); + } + SECTION("Fixed date feasts") + { + wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeUSCatholicFeasts); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime( 1, wxDateTime::Month::Jan, 2024))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(15, wxDateTime::Month::Aug, 2023))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime( 1, wxDateTime::Month::Nov, 2023))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime( 8, wxDateTime::Month::Dec, 2023))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(25, wxDateTime::Month::Dec, 2023))); + // random days that should not be feasts of obligation + CHECK_FALSE(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime( 1, wxDateTime::Month::Dec, 2023))); + CHECK_FALSE(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(31, wxDateTime::Month::Oct, 2023))); + CHECK_FALSE(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(14, wxDateTime::Month::Feb, 2023))); + } +} + +TEST_CASE("Christian Holidays", "[datetime][holiday][christian]") +{ + SECTION("Easter") + { + wxDateTime easter = wxDateTimeChristianHolidays::GetEaster(2023); + CHECK(easter.GetMonth() == wxDateTime::Month::Apr); + CHECK(easter.GetDay() == 9); + + easter = wxDateTimeChristianHolidays::GetEaster(2010); + CHECK(easter.GetMonth() == wxDateTime::Month::Apr); + CHECK(easter.GetDay() == 4); + } + SECTION("Christmas") + { + wxDateTimeHolidayAuthority::AddAuthority(new wxDateTimeChristianHolidays); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(25, wxDateTime::Month::Dec, 1990))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(25, wxDateTime::Month::Dec, 1700))); + CHECK(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(25, wxDateTime::Month::Dec, 2023))); + // random days that are not Christmas or weekends + CHECK_FALSE(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(1, wxDateTime::Month::Dec, 2023))); + CHECK_FALSE(wxDateTimeHolidayAuthority::IsHoliday(wxDateTime(29, wxDateTime::Month::Dec, 2023))); + } +} + #endif // wxUSE_DATETIME