4

I have the number of nanoseconds since Epoch and I'd like to print this to be readable.

The examples I found online printing time_point were using system_clock and then converting to a std::time_t. However, I am using high_resolution_clock (because of the nanoseconds), hence struggling to understand how I can print this.

What is the best technique, given I am dealing with nanoseconds? I'd prefer to stick with standard libraries if possible.

I'm stuck with C++17 for now, but please do mention if C++20 makes this much easier to do.

#include <chrono>
#include <iostream>

using Clock = std::chrono::high_resolution_clock;
using TimePoint = std::chrono::time_point<Clock>;

int main()
{
    const uint64_t nanosSinceEpoch = 1517812763001883383;
    const Clock::duration duration_ns_since_epoch = std::chrono::nanoseconds(nanosSinceEpoch);

    const TimePoint tp(duration_ns_since_epoch);

    // Would like to print tp in readable format
}
user997112
  • 29,025
  • 43
  • 182
  • 361
  • related/dupe: https://stackoverflow.com/questions/15777073/how-do-you-print-a-c11-time-point – NathanOliver Jul 17 '20 at 12:44
  • @NathanOliver That example is using system_clock, which causes a compile error creating from chrono::nanoseconds – user997112 Jul 17 '20 at 12:45
  • @user997112 But the accepted answer ends with conversion to `std::time_t`, which is much more printing friendly, and mentions common ways to print such structure. – Yksisarvinen Jul 17 '20 at 12:48
  • @Yksisarvinen I completely agree, i wanted to use that approach but I'm unsure how to create time_t from high_resolution_clock? – user997112 Jul 17 '20 at 12:50
  • 1
    Ah, right, function `to_time_t` only exists in `system_clock`, my bad. Though it seems that using `high_resolution_clock` directly is discouraged (or at least cppreference discourages that). – Yksisarvinen Jul 17 '20 at 12:54
  • @Yksisarvinen to be honest I think the high_resolution_clock might not matter. I just need whatever creates a std::time_t using nanoseconds since epoch. – user997112 Jul 17 '20 at 13:34

1 Answers1

3

high_resolution_clock does not have a portable epoch. It might be 1970. It might be whenever your device booted up. Thus when printing its time_point, the best that can be done is to print the underlying duration.

system_clock can represent nanoseconds even if system_clock::time_point does not. The trick is to use the more generic form of time_point which is:

template <class Clock, class Duration>
class time_point;

You can specify a clock and a duration, for example:

time_point<system_clock, nanoseconds> tp;

I like to set up a templated type-alias to do this:

template <class Duration>
    using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;

And now I can use this simpler syntax:

sys_time<nanoseconds> tp;

In C++20, sys_time is provided for you by <chrono>. And C++20 allows you to simply print out system_clock-based time_points.

Unfortunately I don't believe anyone is shipping this part of C++20 yet. However there's a header-only, open source, free preview of C++20 <chrono> that works with C++11/14/17:

#include "date/date.h"

#include <chrono>
#include <iostream>

int main()
{
    const uint64_t nanosSinceEpoch = 1517812763001883383;

    const std::chrono::nanoseconds d(nanosSinceEpoch);
    using date::operator<<;
    std::cout << date::sys_time<std::chrono::nanoseconds>{d} << '\n';
}

Output:

2018-02-05 06:39:23.001883383

Update to address time zones

There is a second <chrono> preview library at the same link in the header tz.h which deals with time zones. This library is not header-only. There's a single source file associated with it, tz.cpp. Here are directions for compiling it.

This library can be used to translate sys_time (aka Unix Time / UTC) into any IANA time zone.

For example if you need to display the above output in "America/Chicago" (even if your computer isn't in Chicago), then it can be done like this:

#include "date/tz.h"

#include <chrono>
#include <iostream>

int main()
{
    const uint64_t nanosSinceEpoch = 1517812763001883383;

    using namespace std::chrono;
    date::sys_time<nanoseconds> tp{nanoseconds(nanosSinceEpoch)};
    std::cout << date::zoned_time{"America/Chicago", tp} << '\n';
}

Output:

2018-02-05 00:39:23.001883383 CST

This is also part of C++20 <chrono>. zoned_time is a pairing of a time zone and a sys_time, of any precision as long as it is seconds or finer. Its streaming operator includes the time zone abbreviation. There is also a format function (std::format in C++20, date::format in the library) that can be used to customize the output. For example:

date::zoned_time zt{"America/Chicago", tp};
std::cout << date::format("%F %T%z", zt) << '\n';

Output:

2018-02-05 00:39:23.001883383-0600
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Thank you Howard. I will give that a try. And thank you for getting this in to C++20. There's far too many trivial tasks which are 5-10 lines in C++. This is one of them. – user997112 Jul 17 '20 at 13:59
  • Unfortunately getting compiler error date.h:1066:23: error: 'uncaught_exception' is deprecated. !std::uncaught_exception() && – user997112 Jul 17 '20 at 14:27
  • 1
    There are two ways to deal with this: 1) Don't convert warnings into errors. Or 2) Define `HAS_UNCAUGHT_EXCEPTIONS=1` prior to including date.h. – Howard Hinnant Jul 17 '20 at 14:30
  • Howard, got a question if that's okay? The nanos I have was taken in timezone CET but my machine is in a different timezone. How can I instruct the formatter to use CET? – user997112 Jul 17 '20 at 15:05
  • 1
    There's a way to do that, and I'll update my answer to address it. But first, can you be more specific about your target time zone? CET is an abbreviation of several time zones. Perhaps "Europe/Berlin"? – Howard Hinnant Jul 17 '20 at 15:21
  • Apologies, my source is Chicago time (CPT i think?) – user997112 Jul 17 '20 at 15:44
  • 1
    No problem. Updated my answer to use "America/Chicago". – Howard Hinnant Jul 17 '20 at 15:51
  • Is there anything different about the time zone header? I get a linker error when I include it. tz.h:896: undefined reference to `date::time_zone::get_info_impl(std::chrono::time_point > >) const' – user997112 Jul 17 '20 at 15:59
  • This means you haven't compiled and linked to tz.cpp. Here are the instructions for different platforms and options: https://howardhinnant.github.io/date/tz.html#Installation – Howard Hinnant Jul 17 '20 at 16:00
  • Hi Howard, I've started using the time zone, but got linker errors. I've added add_subdirectory("/cpp20_datelib/") and added date::date to target_link_libraries, but I'm getting "cpp20_datelib/include/date/tz.h:314: undefined reference to `date::locate_zone(std::basic_string_view >)'" Am I integrating this correctly with CMake? – user997112 Jul 28 '20 at 13:50
  • Sorry, I don't personally use the CMake system that is published with my date library. It is maintained by other contributors. For me CMake causes many more problems than it solves for this simple library. This library contains only 1 source file: tz.cpp. If you compile that source along with whatever source contains your `main()` function, then it just works. The installation instructions linked about contain some additional configuration options. – Howard Hinnant Jul 28 '20 at 15:25