Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 217
Howard Hinnant Avatar asked Nov 17 '25 03:11

Howard Hinnant


1 Answers

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:

  • If the weekday of date is already wd, this adds 0 days and returns date.
  • The underlying encoding of weekdays is encapsulated away so that you don't have to care about it. No matter what values wd and starting_wd have, num_days will always be in the range of days{0} to days{6}. You can think of weekdays as a "circular range".
  • This is essentially an "add a number of days to a date" problem, and year_month_day is correct but inefficient for this operation. Trafficking in sys_days would be more efficient.

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:

  • year_month_day is a {y, m, d} data structure. sys_days is a {count of days since epoch} data structure. Both represent a date. And conversions from one to the other do not lose information.
  • The output is still 2025-05-02.
  • The client code does not change because there is an implicit conversion from year_month_day (the type of the expression April/28/2025) to sys_days.
  • There is now no need to convert date to sys_days before adding num_days to it.
  • This is more efficient. And if clients want the input or the output or both to be year_month_day, those conversions are made implicitly. Thus those conversions only cost the clients that need them (pay only for what you use design principle).

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);
like image 65
Howard Hinnant Avatar answered Nov 18 '25 15:11

Howard Hinnant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!