Search code examples
c++timec++20c++-chrono

How to use make_zoned from c++ date library?


I'm trying to use the date (https://howardhinnant.github.io/date/date.html) library to convert a data/time string into date/time belonging to a specific time zone.

I'm using this:

std::string t = "2024-01-02T14:42:27";
std::stringstream str(t);
std::chrono::time_point< std::chrono::system_clock, std::chrono::seconds > src_timestamp;
date::from_stream( str, "%Y-%m-%dT%H:%M:%S", result );
const auto src_tz = locate_zone(std::string("CET"));
auto zoned_src_timestamp = make_zoned(src_tz, src_timestamp);
std::cout << zoned_src_timestamp.get_local_time() << " TZ\n";

When I print zoned_src_timestamp.get_local_time() I expect to see my local time, but I get "2024-01-02 15:42:27" instead. This is not the local time that I'm expecting.

I'm trying to parse a date/time string into a time_point and use it with a timezone to have a local time that I can convert into a different timezone.


Solution

  • I'm trying to parse a date/time string into a time_point and use it with a timezone to have a local time that I can convert into a different timezone.

    std::string t = "2024-01-02T14:42:27";
    std::stringstream str(t);
    

    So far so good. But I'm interpreting "use it with a timezone" to mean, "parse the time as local time in a timezone", namely "CET". To do this the type of src_timestamp must be a local_time instantiation as opposed to a sys_time instantiation.

    sys_time is that family of time_point based on system_clock and always means Unix Time. local_time represents a local time that has yet to be paired with a specific time_zone. There is a specific type alias for seconds-precision called local_seconds that can be handy to use:

    date::local_seconds src_timestamp;
    

    If you prefer another precision, say milliseconds, then that is:

    date::local_time<std::chrono::millliseconds> src_timestamp;
    

    Then do the parse with the local_time:

    date::from_stream( str, "%Y-%m-%dT%H:%M:%S", src_timestamp );
    

    If you print this back out, you'll get exactly the same time. Now use it to create a zoned_time with the desired time_zone. You can use either a time_zone const* or a string to specify the time_zone:

    auto zoned_src_timestamp = date::zoned_time("CET", src_timestamp);
    

    This assumes C++17 or higher. make_zoned is convenient with C++11/14 as it works around the lack of CTAD.

    The construction of zoned_src_timestamp creates a pair data structure of {time_zone const*, sys_time}. The sys_time is computed at construction by using the time_zone const* to convert the local_time to sys_time. If you ask this object for the local time: zoned_src_timestamp.get_local_time() then it will just convert the sys_time back to the local_time you used to construct it with.

    One can also construct a zoned_time with a sys_time, in which case the conversion from local is avoided at construction time, and the sys_time is simply stored.

    One can also construct a zoned_time with another zoned_time. I believe this is what you're looking for:

    auto zoned_local_timestamp = date::zoned_time{date::current_zone(),
                                                  zoned_src_timestamp};
    

    This uses current_zone() as the time_zone, and zoned_src_timestamp as the "time stamp".

    • current_zone() simply returns a time_zone const* to your computer's currently set local time zone.

    • Construction with zoned_src_timestamp uses its sys_time to construct the new zoned_time, so that both zoned_time represent the same instant in time, though in (potentially) different timezones. I.e. they have the same UTC time, but different local times.

    Then you can get the local time out of zoned_local_timestamp:

    std::cout << zoned_local_timestamp.get_local_time() << " TZ\n";
    

    zoned_time is meant to simply be a convenience wrapper. You can also work directly with time_zone to accomplish the same thing:

    std::string t = "2024-01-02T14:42:27";
    std::stringstream str(t);
    date::local_seconds src_timestamp;
    date::from_stream( str, "%Y-%m-%dT%H:%M:%S", src_timestamp );
    auto utc_timestamp = date::locate_zone("CET")->to_sys(src_timestamp);
    auto local_timestamp = date::current_zone()->to_local(utc_timestamp);
    std::cout << local_timestamp << " TZ\n";
    

    zoned_time becomes much more convenient when you want to format with UTC offsets or time zone abbreviations as the zoned_time collects everything there is to know about a time_zone/local_time combination into a single object.

    zoned_time would also be more convenient in generic code where the "time stamp" could be either sys_time or local_time.

    Finally, all of this is now part of C++20. Implementations are still coming on line. MSVC has it. gcc-14 has it, with partial implementations in earlier versions. LLVM has partial implementations. If you are lucky enough to be working with a C++20 implementation, prefer that over date. Everything will be in namespace std::chrono instead of namespace date.