How do I convert "2016-Dec-31 23:59:60" UTC time with c++20 chrono to local time and print correct leap seconds?

444 Views Asked by At

The point in time "2016-Dec-31 23:59:60" is a valid time. It is 1483228826 seconds after "1970-Jan-1". The ":60" in the seconds display is correct, because a leap second was inserted there (see wikipedia leap seconds).

My Question: How do I convert a std::chrono::utc_clock::time_point with this time to local time (or any timezone) and print it including the ":60" for the seconds part?

std::chrono::utc_clock::time_point then;
then += seconds(1483228826);
// Output: 2016-12-31 23:59:60
cout << std::format("{}", time_point_cast<seconds>(then));

// Now convert it to local time
// But this will fail, since zoned_time requires a sys_time
auto local = zoned_time{"Europe/Berlin", then};

My problem is: zoned_time, which is responsible for handling time zones, only accepts a sys_time, not a utc_time. But sys_time ignores leap seconds.

If I convert my utc_time to sys_time before constructing a zoned_time, my output will be "..23:59:59" and the leap second is lost.

auto local = zoned_time{"Europe/Berlin", clock_cast<system_clock>(then));
cout << std::format("{}", time_point_cast<seconds>(local));

I think the leap second should be shown even in local time. The leap day after all is also shown in local time.

1

There are 1 best solutions below

8
On

Unfortunately there is no clean way to do this. The time zone database that is used does not recognize leap seconds.

However there is always a way to work around things.

For seconds precision output it is fairly easy to just brute-force things by running then through get_leap_second_info to find out if then points into a leap second:

utc_seconds then{};
then += 1483228826s;
cout << format("{}", then) << '\n';
auto local = zoned_time{"Europe/Berlin", clock_cast<system_clock>(then)};
auto [is_leap_second, elapsed] = get_leap_second_info(then);
if (is_leap_second)
    cout << format("{:%F %H:%M:60}", local) << '\n';
else
    cout << format("{}", local) << '\n';

Output:

2017-01-01 00:59:60

This can be extended to subsecond precision, but is just a little messier. Here it is for milliseconds:

utc_time<milliseconds> then{};
then += 1483228826023ms;
cout << format("{}", then) << '\n';
auto local = zoned_time{"Europe/Berlin", clock_cast<system_clock>(then)};
auto [is_leap_second, elapsed] = get_leap_second_info(then);
if (is_leap_second)
{
    cout << format("{:%F %H:%M:60.}", local);
    cout << setfill('0') << setw(3) << (then-floor<seconds>(then))/1ms << '\n';
}
else
    cout << format("{}", local) << '\n';

Output:

2017-01-01 00:59:60.023

So in a nutshell, find out if you're in a leap second. If you're not just print the local time. Otherwise hard-wire the ":60", and compute/format the subseconds if applicable.