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 constexpr constructor from sys_days and an explicit constexpr constructor from local_days for days-oriented arithmetic.
  • Notice that duration types end with s, while calendrical types have a singular form, like day, 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);

Run @Compiler Explorer

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());

Run @Compiler Explorer

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);

Run @Compiler Explorer

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