c++c++20c++-chrono

Given the year, how do I compute when Easter is, using chrono


How do I write:

auto  // either sys_days or year_month_day
easter(std::chrono::year y);

I'm aware of the SO question Function to return date of Easter for the given year but I would like to know how to do this in C++ using <chrono>, and I would like to know if the range of validity on the computation is limited.


Solution

  • Easter is defined as the Sunday after the paschal full moon. If the paschal full moon falls on a Sunday, Easter is the following Sunday.

    So, once we compute paschal_full_moon(y), easter is trivial in C++20 <chrono>:

    // Precondition: y >= 0y
    auto
    easter(std::chrono::year y)
    {
        using namespace std::chrono;
        auto x = paschal_full_moon(y) + days{1};
        return x + (Sunday - weekday{x});
    }
    
    

    I'm using a function-local using directive to bring days, Sunday and weekday into scope. This using directive only exposes these names within the scope of this short function and no further. If you don't like using directives (even at function scope), that's fine. Explicitly put std::chrono:: on each of these names. This is a stylistic issue.

    Note the addition of 1 day to the date of the paschal full moon. This ensures that if the full moon is on Sunday, Easter is the following Sunday.

    The precondition is there because this precondition is on the computation of the paschal full moon.

    The return type of easter is std::chrono::sys_days, which (if desired) can be implicitly converted into any calendar such as std::chrono::year_month_day, std::chrono::year_month_weekday, or a user-written calendar such as iso_week_date.

    Now, for the hard part:

    // Precondition: y >= 0y
    auto
    paschal_full_moon(std::chrono::year y)
    {
        int yr{y};
        auto century = yr/100 + 1;
        auto n = 14 + 11*(yr%19) - 3*century/4 + (5 + 8*century)/25;
        auto d = (n >= 0 ? n : n-29) / 30;
        auto shifted_epact = n - d * 30;
        shifted_epact += shifted_epact == 0 || (shifted_epact == 1 && 10 < yr % 19);
        return std::chrono::sys_days{y/4/19} - std::chrono::days{shifted_epact};
    }
    
    

    This algorithm is slightly altered from "Calendrical Calculations, 3rd edition" by Dershowitz and Reingold. The main alteration is to adjust the computation of shifted_epact to use floored division. This extends the range of validity from 3400y out to year::max() (32'767y). This is necessary because the temporary value labeled n goes negative for the input of 3401y.

    I have put no effort into trying to extend the range backwards (into negative years).