There are lots of Stack Overflow Q&A's solving this problem in other languages, but not so much in C++.
How do I find the Nth weekday of the month using <chrono>
? For
example, what is the day of the month for the second Sunday in June, 2025?
What if I ask for the 5th weekday and there isn't one for that year and month combination? Do I get an error?
How can I ask for the last weekday of a month without knowing or testing whether it has 4 or 5 weekdays in that month/year?
How do I schedule repeating events? For example, I want to set up a meeting for the second Sunday of every month.
Given that different parts of the world are on different days depending on the time of day, how do I localize this to a time zone? For example I want my meeting to be on the second Sunday of every month as defined by Sydney Australia. But Sydney is one of the first places on Earth to have a Sunday. If it is Sunday morning in Sydney, most of the rest of the planet is still on Saturday.
Can the second Sunday morning (say, 9 am) meeting in Sydney be translated into local times in other places?
<chrono>
has a dedicated calendrical type just for this purpose.
First a refresher: In <chrono>
you can specify a date in the civil calendar
using year
, month
and day
in one of three ways:
year/month/day
month/day/year
day/month/year
All of these expressions produce a type known as std::chrono::year_month_day
.
Anywhere above that you can specify the day
, you can also specify the day by using a
std::chrono::weekday_indexed
which is conveniently constructed with the syntax
wd[n]
where wd
is a std::chrono::weekday
and n
is unsigned
and in the
range [1, 5].
For example, Canadian Rivers Day
is the second Sunday in June of every year. In <chrono>
this would look like
June/Sunday[2]
, or Sunday[2]/June
. In either expression one could replace
June
with 6
, or a variable of type std::chrono::month
with the value
June
, and still get the same result.
And one can pair this with a year
simply as:
auto date = 2025y/June/Sunday[2];
auto date = June/Sunday[2]/2025;
auto date = Sunday[2]/June/2025;
I.e. You can still use any of the three orderings: y/m/d, m/d/y or d/m/y. And
anywhere you want to specify a day, you can use a weekday_indexed
in that
position. The type of each of these expressions is std::chrono::year_month_weekday
.
When you stream it out, it will output the exact information you put into it:
using namespace std::chrono;
auto date = Sunday[2]/June/2025;
std::cout << date << '\n'; // 2025/Jun/Sun[2]
If you want this in a {year, month, day}
data structure, simply explicitly
convert to it:
std::cout << year_month_day{date} << '\n'; // 2025-06-08
Essentially year_month_weekday
is
yet another calendar supplied by <chrono>
. And you can convert to and
from year_month_day
, or any other calendar that interoperates with the <chrono>
calendrical system. The conversion happens by implicitly converting to the
canonical calendar sys_days
under the hood as an intermediate step.
What if I ask for the 5th weekday and there isn't one for that year and month combination?
For example, what if one asks for the fifth Friday of June in 2025:
auto date = Friday[5]/June/2025;
std::cout << date << '\n';
std::cout << date.ok() << '\n';
This outputs:
2025/Jun/Fri[5]
0
That is, the construction works just fine, and you can print it out and it gives you exactly what you put in. But every calendrical type in <chrono>
has a .ok()
member function that prints out true
if it might be ok, and otherwise false. Since there are only four Fridays in June 2025, the output in this example is false. But you can still convert it to a year_month_day
and it will simply roll over to the
first Friday in July:
std::cout << year_month_day{date} << '\n'; // 2025-07-04
You can depend on an invariant that wd[n]
is always seven days after wd[n-1]
.
Though the implementation of weekday_indexed
is only required to hold the
least significant 3 bits of the index.
So don't get carried away with arbitrarily large indices.
The rationale for this design is to make year and month arithmetic on year_month_weekday
"regular". That is, no matter what the value of the year_month_weekday
is, if you add years
and/or months
to it, and then subtract the same amount, you are guaranteed to get back the original value.
Example:
auto print = [](auto date)
{
std::cout << date << " is ";
if (date.ok())
std::cout << "valid\n";
else
std::cout << "not valid\n";
};
using namespace std::chrono;
auto date = Sunday[5]/June/2025;
print(date);
date += months{1};
print(date);
date -= months{1};
print(date);
Output:
2025/Jun/Sun[5] is valid
2025/Jul/Sun[5] is not valid
2025/Jun/Sun[5] is valid
In this example the fifth Sunday of July is merely an intermediate result, and not an error. But you can query each result for validity and take whatever action is appropriate for your application (ignore, throw an exception, wrap to the end of the month, print out "not valid", etc.).
How can I ask for the last weekday of a month without knowing or testing whether it has 4 or 5 weekdays in that month/year?
You can index a weekday
with last
to get the last weekday of the year and month:
Example:
auto print = [](auto date)
{
std::cout << date << " is ";
if (date.ok())
std::cout << "valid\n";
else
std::cout << "not valid\n";
};
using namespace std::chrono;
auto date = Sunday[last]/June/2025;
print(date);
date += months{1};
print(date);
date -= months{1};
print(date);
Output:
2025/Jun/Sun[last] is valid
2025/Jul/Sun[last] is valid
2025/Jun/Sun[last] is valid
When you perform year and month arithmetic on this value, the last
sticks with it
even if the meaning of last jumps between 4 and 5. And at any stage you can still
explicitly convert such a date to a year_month_day
:
date += months{1}; // date is 2025/Jul/Sun[last]
std::cout << year_month_day{date}; // 2025-07-27
How do I schedule repeating events?
So it should now be clear that if you need to schedule an event on the second Sunday
of every month, you can just choose a start date, e.g. Sunday[2]/January/2025
,
and add months{1}
to get the next event, and so on.
Ditto if it is the last weekday of the month: Sunday[last]/January/2025
.
How do I localize this to a time zone?
You can convert the year_month_weekday
to a local_days
, optionally add a time
of day to it, and pair it with a time_zone
to create a repeating meeting on
the local Nth weekday of the month.
std::chrono::zoned_time
(used in the example below) is simply a convenience wrapper around a time_zone
and
sys_time
time point. The zoned_time
constructor's 2nd argument is
a time point that can be specified in one of 3 ways:
sys_time
(UTC).local_time
in terms of the time_zone
supplied as the 1st argument.zoned_time
in which case the newly constructed zoned_time
will have the
same UTC equivalent as the argument zoned_time
. That is, both zoned_time
s will refer
to the same instant in time.Example:
#include <chrono>
#include <iostream>
int
main()
{
// Meet at 9 am on the second Sunday of every month of 2025 in Sydney
// Send notice of the equivalent time in both UTC and Los Angeles local time
using namespace std::chrono;
auto tz_sy = locate_zone("Australia/Sydney");
auto tz_la = locate_zone("America/Los_Angeles");
for (auto date = Sunday[2]/January/2025; date.year() == 2025y; date += months{1})
{
zoned_time zt{tz_sy, local_days{date} + 9h};
std::cout << zt << " == "
<< zt.get_sys_time() << " UTC == "
<< zoned_time{tz_la, zt} << '\n';
}
}
Output:
2025-01-12 09:00:00 AEDT == 2025-01-11 22:00:00 UTC == 2025-01-11 14:00:00 PST
2025-02-09 09:00:00 AEDT == 2025-02-08 22:00:00 UTC == 2025-02-08 14:00:00 PST
2025-03-09 09:00:00 AEDT == 2025-03-08 22:00:00 UTC == 2025-03-08 14:00:00 PST
2025-04-13 09:00:00 AEST == 2025-04-12 23:00:00 UTC == 2025-04-12 16:00:00 PDT
2025-05-11 09:00:00 AEST == 2025-05-10 23:00:00 UTC == 2025-05-10 16:00:00 PDT
2025-06-08 09:00:00 AEST == 2025-06-07 23:00:00 UTC == 2025-06-07 16:00:00 PDT
2025-07-13 09:00:00 AEST == 2025-07-12 23:00:00 UTC == 2025-07-12 16:00:00 PDT
2025-08-10 09:00:00 AEST == 2025-08-09 23:00:00 UTC == 2025-08-09 16:00:00 PDT
2025-09-14 09:00:00 AEST == 2025-09-13 23:00:00 UTC == 2025-09-13 16:00:00 PDT
2025-10-12 09:00:00 AEDT == 2025-10-11 22:00:00 UTC == 2025-10-11 15:00:00 PDT
2025-11-09 09:00:00 AEDT == 2025-11-08 22:00:00 UTC == 2025-11-08 14:00:00 PST
2025-12-14 09:00:00 AEDT == 2025-12-13 22:00:00 UTC == 2025-12-13 14:00:00 PST
Demo.
Notes:
<chrono>
allows you to compute at a higher level of abstraction than dealing with UTC offsets
directly.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With