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

Find next weekday using chrono


Given a year, month and day, how do I find the next weekday on or after that date? For example, If I have 2025-04-28, how can I find the Friday following 2025-04-28, which is 2025-05-02?

What about finding the previous weekday instead of the next one?

Can I start with int values, put them into chrono and then get int values back out?

If the input date is already at the target weekday, what is needed to change between getting the input date, or getting a week from the input date?


Solution

  • To answer the first question it is best to break it down into parts:

    Let's call the starting date: date, and the target weekday: wd.

    1. Determine the weekday of date. Call that starting_wd.
    2. Determine the number of days wd is ahead of starting_wd. Call that num_days.
    3. Add num_days to date.

    This might look like:

    std::chrono::year_month_day
    next_weekday(std::chrono::year_month_day date, std::chrono::weekday wd)
    {
        using namespace std::chrono;
        weekday starting_wd{date};
        days num_days = wd - starting_wd;
        return sys_days{date} + num_days;
    }
    

    This can be exercised like this:

    cout << next_weekday(April/28/2025, Friday) << '\n';
    

    which prints out:

    2025-05-02
    

    Notes:

    To address this last bullet, we could change both the return type, and the parameter input type of date to sys_days:

    std::chrono::sys_days
    next_weekday(std::chrono::sys_days date, std::chrono::weekday wd)
    {
        std::chrono::weekday starting_wd{date};
        auto num_days = wd - starting_wd;
        return date + num_days;
    }
    

    Notes:

    It is now easy to see that it is trivial to eliminate the local temporaries and make this a one-liner:

    std::chrono::sys_days
    next_weekday(std::chrono::sys_days date, std::chrono::weekday wd)
    {
        return date + (wd - std::chrono::weekday{date});
    }
    

    What about finding the previous weekday instead of the next one?

    In this case you need to subtract the number of days date is ahead of wd:

    std::chrono::sys_days
    prev_weekday(std::chrono::sys_days date, std::chrono::weekday wd)
    {
        return date - (std::chrono::weekday{date} - wd);
    }
    

    If driven by:

    cout << prev_weekday(April/28/2025, Friday) << '\n';
    

    the output is now:

    2025-04-25
    

    Can I start with int values, put them into chrono and then get int values back out?

    If you have your year, month and day as integers this looks like:

    int iy = 2025;
    int im = 4;
    int id = 28;
    year_month_day next_friday = next_weekday(year{iy}/im/id, Friday);
    

    The return type is sys_days which is implicitly convertible to year_month_day. Use the getters: .year(), .month() and .day():

    auto y = next_friday.year();
    auto m = next_friday.month();
    auto d = next_friday.day();
    

    In this case y has type year, m has type month, and d has type day. If you want it as int, just explicitly cast to int:

    int iy{y};  // iy == 2025
    

    month and day have explicit casts to unsigned for this purpose.

    unsigned im{m};  // im == 5
    unsigned id{d};  // id == 2
    

    Note: These explicit casts are the exact reverse of going the other way: From integral to chrono types:

    year  y{iy};
    month m{im};
    day   d{id};
    

    This is a symmetric, easy-to-remember API.

    Note: To enable the compiler to detect logic errors in your code, it is best not to convert to integral types, but instead stay inside the chrono type system. This turns logic errors into compile-time errors. But if you have to interoperate with another date library, integers are often the only way to do that.


    If the input date is already at the target weekday, what is needed to change between getting the input date, or getting a week from the input date?

    This is a very easy transformation: just increment the date prior to the algorithm (or decrement in the case of prev_weekday):

    std::chrono::sys_days
    next_weekday(std::chrono::sys_days date, std::chrono::weekday wd)
    {
        ++date;
        return date + (wd - std::chrono::weekday{date});
    }
    

    Note: LLVM has not yet implemented the increment operator on time_point (as of April 2025). You can work around this with date += std::chrono::days{1}; instead.


    Bonus points: Slap constexpr onto next_weekday and it will work at compile-time too:

    static_assert(next_weekday(April/28/2025, Friday) == May/2/2025);