Last Update:
Exploring C++20 std::chrono - Calendar Types
Table of Contents
Before C++20, <chrono> gave us a solid foundation for working with clocks, durations, and time points - but it didn’t really know anything about the civil calendar. If you needed to represent “March 15”, check whether a date is valid, compute “the third Monday of November”, or add a few months to a date, you had to rely on custom utilities, std::tm, or external libraries like Howard Hinnant’s excellent date.
C++20 finally fills this gap by standardising a complete set of calendar types: year, month, day, weekday, year_month_day, month_day_last, and many more. These types let you express dates in a clear, strongly typed way, without fragile integer arithmetic or manual conversions.
All of this comes with a very clean API: more than 40 overloads of the / operator let you build date types fluently, almost like writing calendar expressions. In short, C++20 turns most everyday date manipulation tasks into expressive, readable, and fully standard C++.
So let’s review the core types and operations.
Introduction to Calendar Types
In C++20, the library extended durations types to cover days, months, and years:
using hours = duration<int, ratio<3600>>;
// new in C++20
using days = duration<int, ratio_multiply<ratio<24>, hours::period>>;
using weeks = duration<int, ratio_multiply<ratio<7>, days::period>>;
using years = duration<int, ratio_multiply<ratio<146097, 400>, days::period>>;
using months = duration<int, ratio_divide<years::period, ratio<12>>>;
And in C++20, we have the following new calendrical types:
| name | description |
|---|---|
last_(spec) |
The type is used in conjunction with other calendar types to specify the last in a sequence. For example, depending on the context, it can represent the last day of a month, or the last day of the week of a month. See examples below… |
day |
represents a day of a month. It normally holds values in the range 1 to 31, but may hold non-negative values outside this range. |
month |
represents a month of a year. It normally holds values in the range 1 to 12, but may hold non-negative values outside this range. |
year |
represents a year in the civil (Gregorian) calendar. It can represent values in the range [min(), max()] (static member functions). It can be constructed with any int value, which will be subsequently truncated to fit into year’s unspecified internal storage |
weekday |
represents a day of the week in the civil calendar. It normally holds values in the range 0 to 6, corresponding to Sunday through Saturday, but it may hold non-negative values outside this range |
weekday_indexed |
represents a weekday and a small index in the range 1 to 5. This class is used to represent the first, second, third, fourth, or fifth weekday of a month. |
weekday_last |
represents the last weekday of a month. |
month_day |
represents a specific day of a specific month, but with an unspecified year. |
month_day_last |
represents the last day of a month. |
month_weekday |
represents the n-th weekday of a month, of an as yet unspecified year. To do this, the month_weekday stores a month and a weekday_indexed. |
month_weekday_last |
represents the last weekday of a month, of an as yet unspecified year. To do this, the month_weekday_last stores a month and a weekday_last. |
year_month |
represents a specific month of a specific year, but with an unspecified day. year_month is a field-based time point with a resolution of months. |
year_month_day |
represents a specific year, month, and day. year_month_day is a field-based time point with a resolution of days. The class supports years- and months-oriented arithmetic, but not days-oriented arithmetic. For the latter, there is a conversion to sys_days, which efficiently supports days-oriented arithmetic. |
year_month_day_last |
represents the last day of a specific year and month. year_month_day_last is a field-based time point with a resolution of days, except that it is restricted to pointing to the last day of a year and month. The class supports years- and months-oriented arithmetic, but not days-oriented arithmetic. |
year_month_weekday |
represents a specific year, month, and n-th weekday of the month. year_month_weekday is a field-based time point with a resolution of days. The class supports years- and months-oriented arithmetic, but not days-oriented arithmetic. |
year_month_weekday_last |
represents a specific year, month, and last weekday of the month. year_month_weekday_last is a field-based time point with a resolution of days, except that it is restricted to pointing to the last weekday of a year and month. The class supports years- and months-oriented arithmetic, but not days-oriented arithmetic. |
And some notes:
- All above classes, except of
last_spec, are trivially copyable and standard-layout class types. - Most types have the
ok()member function that returns true if a given state is correct in terms of the type, or its subtypes. - They have an implicit
constexprconstructor fromsys_daysand an explicitconstexprconstructor fromlocal_daysfor days-oriented arithmetic. - Notice that duration types end with
s, while calendrical types have a singular form, likeday,month, etc.
Constants
Inside the std::chrono namespace, we also have the following calendrical constants:
inline constexpr last_spec last{};
inline constexpr weekday Sunday{0};
inline constexpr weekday Monday{1};
inline constexpr weekday Tuesday{2};
inline constexpr weekday Wednesday{3};
inline constexpr weekday Thursday{4};
inline constexpr weekday Friday{5};
inline constexpr weekday Saturday{6};
inline constexpr month January{1};
inline constexpr month February{2};
inline constexpr month March{3};
inline constexpr month April{4};
inline constexpr month May{5};
inline constexpr month June{6};
inline constexpr month July{7};
inline constexpr month August{8};
inline constexpr month September{9};
inline constexpr month October{10};
inline constexpr month November{11};
inline constexpr month December{12};
Creation and Operator /
Constructors of chrono date types taking an integral value are explicit, so that copy initialization with an integral won’t work:
std::chrono::day d{7}; // OK
std::chrono::day anotherDay = 3; // ERROR
std::chrono::month m{12}; // OK
std::chrono::month anotherMonth = 12; // ERROR
std::chrono exposes 40 overloads for the operator /, allowing us to create various calendar types with many combinations easily. Usually, you only have to provide the first type, and then the compiler will deduce the further sequence.
For example:
auto ymdOct = std::chrono::year { 1996 } / 10 / 31; // 31st october 1996, year_month_day
auto ydmOct = std::chrono::year { 1996 } / 31 / 10; // yyyy/dd/mm not possible, so this yields an invalid date!
auto ym {std::chrono::year{2025} / 11}; // year_month
auto mw {11/Monday[3]}; // 3rd Monday in Nov, month_weekday
Arithmetic
Some useful operations for working with calendrical types:
day
// member functions:
constexpr std::chrono::day& operator+=( const std::chrono::days& d ) noexcept;
constexpr std::chrono::day& operator-=( const std::chrono::days& d ) noexcept;
// non-member:
constexpr std::chrono::day operator+( const std::chrono::day& d,
const std::chrono::days& ds ) noexcept;
constexpr std::chrono::day operator+( const std::chrono::days& ds,
const std::chrono::day& d ) noexcept;
constexpr std::chrono::day operator-( const std::chrono::day& d,
const std::chrono::days& ds ) noexcept;
// notice it returns `days`, not `day`:
constexpr std::chrono::days operator-( const std::chrono::day& x,
const std::chrono::day& y ) noexcept;
Operators: ++ and -- are also supported.
Examples:
#include <chrono>
#include <print>
int main()
{
std::chrono::day oneDay{ 7 };
std::chrono::day anotherDay{ 30 };
oneDay += std::chrono::days(20);
std::print("7 + 20: {}\n", oneDay);
std::chrono::day future = anotherDay + std::chrono::days{ 10 };
std::print("oneDay + anotherDay: {}, ok: {}\n", future, future.ok());
std::chrono::days res = oneDay - anotherDay;
std::print("res: {}\n", res);
}
Play @Compiler Explorer
The output:
7 + 20: 27
oneDay + anotherDay: 40 is not a valid day, ok: false
res: -3d
Notice the type difference for the - operator. It’s days.
month
// member operations:
constexpr std::chrono::month& operator+=(const std::chrono::months& m) noexcept;
constexpr std::chrono::month& operator+=(const std::chrono::months& m) noexcept;
// non member:
constexpr std::chrono::month operator+( const std::chrono::month& m,
const std::chrono::months& ms ) noexcept;
constexpr std::chrono::month operator+( const std::chrono::months& ms,
const std::chrono::month& m ) noexcept;
constexpr std::chrono::month operator-( const std::chrono::month& m,
const std::chrono::months& ms ) noexcept;
// type change here: `months`!
constexpr std::chrono::months operator-( const std::chrono::month& m1,
const std::chrono::month& m2 ) noexcept;
Operators: ++ and -- are also supported.
The set of operations is similar to day.
std::chrono::month oneMonth{ std::chrono::September };
oneMonth += std::chrono::months(20);
std::print("Sept + 20 months: {}\n", oneMonth);
std::chrono::month future = oneMonth + std::chrono::months{ 10 };
std::print("oneMonth + months(10): {}, ok: {}\n", future, future.ok());
std::chrono::months res = std::chrono::March - std::chrono::January;
std::print("Mar - Jan: {}\n", res);
Play @Compiler Explorer
The output:
Sept + 20 months: May
oneMonth + months(10): Mar, ok: true
Mar - Jan: 2[2629746]s
year
// member functions:
constexpr std::chrono::year& operator+=( const std::chrono::years& y ) noexcept;
constexpr std::chrono::year& operator-=( const std::chrono::years& y ) noexcept;
constexpr std::chrono::year operator+() noexcept;
constexpr std::chrono::year operator-() noexcept; // negation!
// non-member functions:
constexpr std::chrono::year operator+( const std::chrono::year& y,
const std::chrono::years& ys ) noexcept;
constexpr std::chrono::year operator+( const std::chrono::years& ys,
const std::chrono::year& y ) noexcept;
constexpr std::chrono::year operator-( const std::chrono::year& y,
const std::chrono::years& ys ) noexcept;
// type change: years
constexpr std::chrono::years operator-( const std::chrono::year& y1,
const std::chrono::year& y2 ) noexcept;
Operators: ++ and -- are also supported.
See a basic example:
std::chrono::year xxiCentury{ 2001 };
xxiCentury += std::chrono::years{ 21 };
std::print("2001 + 21 years: {}\n", xxiCentury);
std::print("min {}, max {} year\n", std::chrono::year::min(), std::chrono::year::max());
std::chrono::year future = xxiCentury + std::chrono::years{ 100000 };
std::print("xxiCentury + years(100000): {}, ok: {}\n", future, future.ok());
std::chrono::years res = std::chrono::years{ 2022 } - std::chrono::years{ 22 };
std::print("Mar - Jan: {}\n", res);
The output:
2001 + 21 years: 2022
min -32767, max 32767 year
xxiCentury + years(100000): -29050, ok: true
Mar - Jan: 2000[31556952]s
year_month_day
This structure is a pack of year, month and day structures, but it exposes the following arithmetic operations:
// member functions:
// you can add/subtract only months or years
constexpr std::chrono::year_month_day&
operator+=( const std::chrono::years& dy ) const noexcept;
constexpr std::chrono::year_month_day&
operator+=( const std::chrono::months& dm ) const noexcept;
constexpr std::chrono::year_month_day&
operator-=( const std::chrono::years& dy ) const noexcept;
constexpr std::chrono::year_month_day&
operator-=( const std::chrono::months& dm ) const noexcept;
// non-member functions:
constexpr std::chrono::year_month_day operator+(
const std::chrono::year_month_day& ymd,
const std::chrono::months& dm) noexcept;
constexpr std::chrono::year_month_day operator+(
const std::chrono::months& dm,
const std::chrono::year_month_day& ymd) noexcept;
constexpr std::chrono::year_month_day operator+(
const std::chrono::year_month_day& ymd,
const std::chrono::years& dy) noexcept;
constexpr std::chrono::year_month_day operator+(
const std::chrono::years& dy, const std::chrono::year_month_day& ymd) noexcept;
constexpr std::chrono::year_month_day operator-(
const std::chrono::year_month_day& ymd,
const std::chrono::months& dm) noexcept;
constexpr std::chrono::year_month_day operator-(
const std::chrono::year_month_day& ymd,
const std::chrono::years& dy) noexcept;
This time, we can only add/subtract years or months, and there’s no operation that returns some duration type.
See an example:
std::chrono::year_month_day today{ std::chrono::year{2025}/11/22 };
auto future = today + std::chrono::years{ 10 };
std::print("2025/11/22 + 10 years: {}\n", future);
std::chrono::year_month_day again = future - std::chrono::months{ 120 };
std::print("future - months(120): {}, ok: {}\n", again, again.ok());
The output
2025/11/22 + 10 years: 2035-11-22
future - months(120): 2025-11-22, ok: true
weekday
// member functions:
constexpr std::chrono::weekday& operator+=(const std::chrono::days& d ) noexcept;
constexpr std::chrono::weekday& operator-=(const std::chrono::days& d ) noexcept;
// non-member functions:
constexpr std::chrono::weekday operator+(const std::chrono::weekday& wd,
const std::chrono::days& d ) noexcept;
constexpr std::chrono::weekday operator+(const std::chrono::days& d,
const std::chrono::weekday& wd ) noexcept;
constexpr std::chrono::weekday operator-(const std::chrono::weekday& wd,
const std::chrono::days& d ) noexcept;
// return type change to days:
constexpr std::chrono::days operator-( const std::chrono::weekday& wd1,
const std::chrono::weekday& wd2 ) noexcept;
We can operate with days and also two weekdays. The types use modulo arithmetic.
See the example:
std::chrono::weekday aday = std::chrono::Sunday;
std::print("{}\n", aday);
++aday;
std::print("{}\n", aday);
aday += std::chrono::days{ 22 };
std::print("after adding 22 days: {}\n", aday);
std::print("Monday - Tuesday = {}\n", std::chrono::Monday - std::chrono::Tuesday);
Play @Compiler Explorer
The output:
Sun
Mon
after adding 22 days: Tue
Monday - Tuesday = 6d
Days arithmetic for year_* and month_* types
As you saw in the table, types for years and months support month/year arithmetic:
The class supports years- and months-oriented arithmetic, but not days-oriented arithmetic. For the latter, there is a conversion to
sys_days, which efficiently supports days-oriented arithmetic.
But in most cases, we can easily convert to sys_days, perform the computations, and convert back to the desired type.
For example:
namespace chr = std::chrono;
const auto today = chr::sys_days{ chr::floor<chr::days>(chr::system_clock::now()) };
auto importantDate = chr::year{ 2011 } / chr::July / 21;
const auto delta = (today - chr::sys_days{ importantDate }).count();
std::print("{} was {} days ago!\n", importantDate, delta);
The output:
2011-07-21 was 5238 days ago!
PS: Do you know what significant event happened on that important date? (hint: something with space exploration).
Summary
C++20 significantly enhances <chrono> with a comprehensive set of calendar types - such as day, month, year, weekday, and year_month_day- that let you represent and manipulate civil dates in a clear, type-safe way. These types support convenient construction via the / operator, simple month/year arithmetic, validation with .ok(), and easy conversion to sys_days whenever day-precision logic is needed.
Next time, we’ll look at more examples of calendar types.
Documents and Resources
- C++ Standard Draft - Time section - N4868 (October 2020 pre-virtual-plenary working draft/C++20 plus editorial changes)
- C++20 - The Complete Guide by Nicolai M. Josuttis @Amazon
- Modern C++ Programming Cookbook: Master C++ core language and standard library features, with over 100 recipes, updated to C++20 2nd ed. Edition @Amazon
- https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes - examples and recipes for Howard Hinnant’s
tz.hlibrary, which is a base for chrono.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
