C++20’s Extensions to Chrono Available in Visual Studio 2019 version 16.10

Miya Natsuhara

While the <chrono> header has been available as part of the STL since C++11, among the changes included in C++20 there were several extensions to chrono including support for calendrical types, time zones, leap seconds, and integration with format. A great deal of innovation and work was required in order to complete these additional features; shipping these new C++20 additions so soon would not have been possible without the amazing support of the open-source community. In particular, I would like to recognize the significant contributions from Matt Stephanson, statementreply, and Daniel Marshall in helping complete our implementation.

Overview & Examples

There were numerous additions to the <chrono> header in the changes for C++20, and here I’d like to highlight a few.

Calendrical Types

The PR GH-323: <chrono> Partially implement P0355R7, authored by Daniel Marshall added a long list of new calendrical types to the header for date support. For example:

#include <chrono>
using namespace std::chrono;

int main() {
   year y{2021};
   year_month_day world_bee_day = May/20d/y;
   month_weekday mwd = May/Thurs[3];
   year_month_weekday world_bee_day2 = 2021/mwd;
}

Here we create a year object for the year 2021, then use it to create a year_month_day object representing May 20, 2021 or World Bee Day. We then use an alternate form to represent the exact same day by making a month_weekday object representing the third Thursday of May (indicated with the Thurs[3] which is creating a weekday_indexed object) in an unspecified year, and then using that to create a year_month_weekday object by combining It with the integer 2021 which is interpreted as the year.

You can see that the library now includes a variety of different calendrical types that allow users to create dates in a variety of different ways. These different forms of dates are intended to be very easy-to-use and intuitive, so that users can choose the method most comfortable or natural to them.

Time Zones

Awareness of time zones were another significant addition to the <chrono> header, which included new types (e.g., time_zone, tzdb, zoned_time) as well as significant consideration for how to manage the data that library’s newfound time-zone-awareness would require. A small example displaying an example of the new time zone feature follows:

#include <chrono>
using namespace std::chrono;

int main() {
   const auto& my_tzdb = get_tzdb();
   const time_zone* la_tz = my_tzdb.locate_zone("America/Los_Angeles");
   auto world_nutella_day = local_days{2021y/Feb/5};
   zoned_time la_nutella_day{la_tz, world_nutella_day + 3h + 44min + 12s};
   zoned_time utc_nutella_day{"Etc/UTC", la_nutella_day};
}

This is only a small example of the functionalities included in newly-added time zone support. The feature also supports converting between different time zones, notions of “local” time, and possibilities of ambiguous or nonexistent conversions to/from time zones due to daylight savings transitions.  In this example, we first attain a reference to the most up-to-date tzdb object which contains data about time zones. We then use it to look up the America/Los Angeles time zone (GMT-07:00) by name, storing a time_zone pointer pointing to that entry in the tzdb. Next we create a specific date (corresponding to World Nutella Day) using local_days and some of the calendrical types mentioned above, which we can then use to create a zoned_time object which pairs a specific date/time (in this case World Nutella Day at 03:44:12) with a given time zone (America/Los Angeles in this case).  We then use the America/Los Angeles zoned_time to create a zoned_time corresponding to UTC time, illustrating the time zone conversion functionality.

One specific challenge that we encountered in our implementation was how to actually access the data that the time zones required. The C++20 Standard defines the time zone functionality in terms of the IANA database, however MSVC’s STL would not be able to ship the entire database with its implementation due to its size and the issue of how to provide updates to it. We had to explore alternate options as we pondered how to support this Standard-mandated feature without subjecting our customers to absurd header size increases. We eventually discovered the ICU library which ships as part of the Windows 10 operating system in more recent versions (19H1 and after) and derives its data from the IANA database itself. As a result, updates to the time zone data will be executed along with updates to the OS through Windows Update. While our current implementation relies on the availability of the ICU DLL in more recent OS versions, we have plans to revisit the issue and investigate implementing a fallback for older operating systems. While there are a few discrepancies between the IANA database and the ICU library, the data should largely be the same.

Leap Seconds

Our recent changes also include support for tracking leap seconds, largely implemented in MattStephanson’s GH-1671: C++20 clocks, clock_cast, tzdb::leap_seconds, with valuable input from statementreply. With this new support, you can ask whether a given time occurs during a leap second insertion or removal (fun fact, both positive and negative leap seconds are possible!) or not. C++20 also added several new clock types (in addition to system_clock, steady_clock, and high_resolution_clock which have existed since C++11), some of them leap-second-aware (such as utc_clock) while others are not (such as system_clock). We detect new leap seconds through Windows registries (with the guidance of Dan Cuomo’s networking blogpost), so any new leap seconds will need to be detected through Windows updates as well.

“Chronat”

The intersection of chrono and std::format, lovingly dubbed “chronat” in our repo, brings two of C++20’s largest features together. “Chronat” includes both parsing and formatting for chrono’s new types with format specifiers / parse flags that are for the most part analogous to strftime’s formatting codes. For the formatting side, there are struct formatter specializations for almost every new type added to chrono, allowing it to seamlessly integrate with std::format’s interface. See Charlie Barto’s blogpost for more information about std::format. A brief example of chrono’s parsing and formatting follows:

#include <chrono>
#include <sstream>
#include <string>
using namespace std;
using namespace std::chrono;

int main() {
   day d;
   basic_stringstream<char> sstr{"22"};
   basic_string<char> s{"%d"};
   sstr >> parse(s, d);
   cout << d << "\n";

   year_month_day programmers_day{January/7d/2021};
   cout << format("International Programmer’s day: {%F}", programmers_day);
}

We first have an example where we parse a stringstream into a day which can then be output to std::cout, then we also see an example where we use the "%F" format specifier and std::format and nicely format a year_month_day object to std::cout as well.

Implementing in the Open

Given the magnitude of the feature, we used several tools to help the maintainers and the community organize and track the work that needed to be done. The additions to the <chrono> header were tracked through the feature’s GitHub issue, and work was organized through the Extensions to <chrono> GitHub project and the Tracking Issue. You can read more about the changes in code required for the feature and specific considerations we had to account for during implementation there.

Give it a try!

This has been a moderately brief overview of the extensions to <chrono> included in C++20, but there has been much more added to the header than has been covered here. These features are available for public consumption under /std:c++latest as of Visual Studio 2019 version 10 Previews 3, 4, and GA. I encourage you to use them to implement all of your wildest calendar-, time-zone-, and leap-second-related dreams and let us know what you think!

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Pepe perez 0

    Thanks!!

  • unituniverse2@yahoo.com 0

    When would we get the C++2x Concepts’ ‘requires-expression’ support in VC++?

  • David Armour 0

    Does this explain why VS 2022 Pre does not properly print the timezone?

    Try this code in VS:

    auto tm = std::chrono::system_clock::now();
    std::cout << std::format("{:%X %Z}", std::chrono::zoned_time("America/New_York", tm));

    and you get the current time followed by GMT-4.

    It should print the current time followed by EDT.

    When will this be fixed?

Feedback usabilla icon