Tag Archive for chrono

C++20: Seemingly Unexpected Behavior with Date Arithmetic

C++20 has added support for calendars, dates, and time zones to the C++ Standard Library. This also allows you to perform arithmetic with dates. However, certain arithmetic might give seemingly the wrong results.
Let’s look at a simple example that works as expected:

using namespace std::chrono;
auto timestamp1 = sys_days{ 2022y / January / 19d } + 8h + 39min + 42s;
auto timestamp2 = timestamp1 + days{ 3 }; // Add 3 days
std::cout << timestamp1 << '\n' << timestamp2;

The output is as expected:

2022-01-19 08:39:42
2022-01-22 08:39:42

Now let’s try to add 1 year to timestamp1:

auto timestamp3 = timestamp1 + years{ 1 }; // Add 1 year
std::cout << timestamp1 << '\n' << timestamp3;

The output now is:

2022-01-19 08:39:42
2023-01-19 14:28:54

The date part is correctly incremented with 1 year, but the timestamp looks wrong on first sight. However, this is correct according to the C++ standard. The reason why it is seemingly off has to do with support for leap years. The C++ standard states that adding 1 year to a date must add 1 average year to keep leap years into account. So, while you could expect adding 1 year adds 86,400 * 365 = 31,536,000 seconds (86,400 = number of seconds in a day), it doesn’t. Instead, it adds 86,400 * ((365 * 400) + 97) / 400) = 31,556,952 seconds.

The reason why it is behaving like this is that the type of timestamp1, 2, and 3 is std::chrono::time_point which is a so-called serial type, i.e., it represents a date as a single number relative to a certain epoch (= clock origin). If you don’t want this behavior you can perform the arithmetic with a field-based type. The serial-based types above can be converted to a field-based representation as follows:

// Split timestamp1 into "days" and "remaining seconds".
sys_days timestamp1_days = time_point_cast<days>(timestamp1);
seconds timestamp1_seconds = timestamp1 - timestamp1_days;

// Convert the timestamp1_days serial type to a field-based year_month_day.
year_month_day ymd2 = timestamp1_days;

// Add 1 year.
year_month_day ymd3 = ymd2 + years{ 1 };

// Convert the result back to a serial type.
auto timestamp4 = sys_days{ ymd3 } + timestamp1_seconds;
std::cout << timestamp1 << '\n' << timestamp4;

The output now is:

2022-01-19 08:39:42
2023-01-19 08:39:42
Share