44

How can I get an std::chrono::duration since a fixed date? I need this to convert a std::chrono::time_point to an unix timestamp.

Insert code into XXX

auto unix_epoch_start = XXX;
auto time = std::chrono::system_clock::now();
auto delta = time - unix_epoc_start;
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(delta).count();

I know time_point has a method time_since_epoch() but it's not guaranteed that this is the same as the unix epoch (00:00:00 UTC on 1 January 1970).

Ali
  • 56,466
  • 29
  • 168
  • 265
Daniel
  • 2,993
  • 2
  • 23
  • 40
  • 2
    doesn't this help? `std::time_t t = std::chrono::system_clock::to_time_t(tp);` (where `tp` is of type `time_point`) – Andy Prowl Jan 24 '13 at 15:38
  • 1
    @AndyProwl: There's no guarantee (on non-POSIX systems) that `time_t` is based on the Unix epoch either. – Mike Seymour Jan 24 '13 at 15:44
  • Mike is right. It's basically the same problem as with time_since_epoch() – Daniel Jan 24 '13 at 15:46
  • Why did I got a downvote? – Daniel Jan 24 '13 at 15:46
  • I see. and are you sure you need a `duration` and not a `time_point`? It seems what you're trying to fill up is a specific date. Am I getting it wrong? – Andy Prowl Jan 24 '13 at 15:50
  • @Andy: no you are right. unix_epoch_start needs to be a time_point which is set to "00:00:00 UTC on 1 January 1970" - but I couldn't figure out how to do that – Daniel Jan 24 '13 at 15:52
  • can you use `std::mktime`? that creates a `time_t` value for the specific year (1970), month (1), day (1), etc. that you pass in a structure (the only issue is that it takes the current zone into account, but you can work around that i believe) – Andy Prowl Jan 24 '13 at 15:56
  • 1
    @AndyProwl: `mktime` takes its input in local time, not UTC. I don't think there's any way to use the standard library to convert a UTC date/time to `time_t`. – Mike Seymour Jan 24 '13 at 15:59
  • Okay wasn't aware of the problem mike came up with + a pure std::chrono solution would feel more "clean" – Daniel Jan 24 '13 at 15:59
  • 1
    @MikeSeymour: true, so how about using `gmttime()` somehow? that one seems to work with UTC time – Andy Prowl Jan 24 '13 at 16:02
  • Something like: use `mktime()` to convert the desired date encoded in a `tm` structure into a *local time* `time_t` value, then use `gmttime()` to convert that value into a *UTC* `tm` structure, and figure out which `tm` structure yields the desired UTC `time_t` when given in input to `mktime()`... – Andy Prowl Jan 24 '13 at 16:08
  • @Andy: I will accept this as answer when nobody else can give me a cleaner solution in the next few hours – Daniel Jan 24 '13 at 16:16
  • @Daniel: there's no answer to accept, these are just comments ;) – Andy Prowl Jan 24 '13 at 16:19
  • I know but i would expect that you would create an answer which I can possibly accept ;) – Daniel Jan 24 '13 at 16:21
  • @Daniel: all right, done :-) – Andy Prowl Jan 24 '13 at 16:34

5 Answers5

38

A unix time stamp is defined as the number of seconds since January 1, 1970 UTC, except not counting all the seconds. This is somewhat ridiculous and one has to wonder what the point of it is, so I agree that this is a silly question.

Anyway, lets look at some platform documentation for time_t and time().

Linux:

time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

POSIX.1 defines seconds since the Epoch using a formula that approximates the number of seconds between a specified time and the Epoch. This formula takes account of the facts that all years that are evenly divisible by 4 are leap years, but years that are evenly divisible by 100 are not leap years unless they are also evenly divisible by 400, in which case they are leap years. This value is not the same as the actual number of seconds between the time and the Epoch, because of leap seconds and because system clocks are not required to be synchronized to a standard reference. The intention is that the interpretation of seconds since the Epoch values be consistent; see POSIX.1-2008 Rationale A.4.15 for further rationale.

Windows:

The time function returns the number of seconds elapsed since midnight (00:00:00), January 1, 1970, Coordinated Universal Time (UTC), according to the system clock.

Mac OS X:

The functions ctime(), gmtime(), and localtime() all take as an argument a time value representing the time in seconds since the Epoch (00:00:00 UTC, January 1, 1970;

The asctime(), ctime(), difftime(), gmtime(), localtime(), and mktime() functions conform to ISO/IEC 9899:1990 (ISO C90''), and conform to ISO/IEC 9945-1:1996 (POSIX.1'') provided the selected local timezone does not contain a leap-second table (see zic(8)).

Similar documentation can be found for other systems, such as AIX, HP-UX, Solaris, etc.

So although not specified in C++ there is an easy and widely portable way to get a Unix timestamp:

auto unix_timestamp = std::chrono::seconds(std::time(NULL));

And if you want a number of milliseconds since 1 Jan 1970 UTC (similarly not counting all of them) then you can do this:

int unix_timestamp_x_1000 = std::chrono::milliseconds(unix_timestamp).count();

Just remember that these values aren't real times, so you can't in general use unix timestamps in arithmetic. For example subtracting unix timestamps does not give you an accurate count of seconds between the times. Or if you did something like:

std::chrono::steady_clock::now() - unix_timestamp;

you would not get a time point actually corresponding to 1970-01-01 00:00:00+0000.


As Andy Prowl suggests you could do something silly like:

// 1 Jan 1970 (no time zone)
std::tm c = { 0, 0, 0, 1, 0, 70, 0, 0, -1};

// treat it as 1 Jan 1970 (your system's time zone) and get the
// number of seconds since your system's epoch (leap seconds may
// or may not be included)
std::time_t l = std::mktime(&c);

// get a calender time for that time_point in UTC. When interpreted
// as UTC this represents the same calendar date and time as the
// original, but if we change the timezone to the system TZ then it
// represents a time offset from the original calendar time by as
// much as UTC differs from the local timezone.
std::tm m = *std::gmtime(&l);

// Treat the new calendar time as offset time in the local TZ. Get
// the number of seconds since the system epoch (again, leap seconds
// may or may not be counted).
std::time_t n = std::mktime(&m);

l -= (n-l); // subtract the difference

l should now represent the (wrong) number of seconds since 1 Jan 1970 UTC. As long as there are no leap seconds between the system epoch and 1 Jan 1970 (system time zone), or within an equal amount of time in the other direction from the system epoch, then any counted leap seconds should cancel out and l will be wrong in just the way that unix timestamps are wrong.


Another option is to use a decent date library such as Howard Hinnant's chrono::date. (Howard Hinnant was one of the guys that worked on the C++11 <chrono> library.)

auto now = system_clock::now();
sys_days today = time_point_cast<days>(now);
system_clock::time_point this_morning = today;

sys_days unix_epoch = day(1)/jan/1970;
days days_since_epoch = today - unix_epoch;

auto s = now - this_morning;

auto tz_offset = hours(0);
int unix_timestamp = (days_since_epoch + s + tz_offset) / seconds(1);

If you want to handle leap seconds Howard Hinnant also provides a library that includes facilities for handling them as well as for parsing time zone databases as the source for leap second data.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
bames53
  • 86,085
  • 15
  • 179
  • 244
  • 1
    see discussion in the comments above – Daniel Jan 24 '13 at 15:47
  • okay that's a nice overview + the proposal of Howard Hinnant looks very promising. do you know if it is proposed for the c++ standard? couldn't find any information. – Daniel Jan 24 '13 at 18:44
  • 2
    It doesn't look like Howard Hinnant's date class is specifically being proposed but it seems that the commitee is looking at standardizing some sort of date format. See [link](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3344.pdf). – John Schug Jan 24 '13 at 18:52
  • Thank you, this is really instructive! – Jonathan H Mar 06 '13 at 02:26
  • Link is now dead for Howard Hinnant. – Eric May 26 '15 at 06:00
  • @Eric Thanks. Coincidentally it looks like Howard just updated his date library. I've fixed the link by updating it to point to the new version. – bames53 May 26 '15 at 15:44
  • 1
    My date library is also based on Unix Time (as is every implementation of `chrono::system_clock`). Translation: it doesn't take leap seconds into account either. Bottom line: Computers in general deal with leap seconds as common corrections to the system time, not as additions to the count of seconds since New Years 1970. – Howard Hinnant May 26 '15 at 21:42
  • 1
    I don't think it is a silly question. It is useful to exchange a calendar date/time between two systems: the two systems must agree on a representation (e.g. seconds since Epoch) but c++ does not offer such guarantee. In particular, time() does not specify its return value's format. – Julien-L Nov 10 '17 at 20:46
24

what about this C++11 implementation

auto microsecondsUTC = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
siamoss
  • 249
  • 2
  • 2
8

Long story short, here is the function I use to get Unix timestamp (seconds count since Jan 1, 1970 UTC):

static uint64_t getUnixTimeStamp(const std::time_t* t = nullptr)
{
    //if specific time is not passed then get current time
    std::time_t st = t == nullptr ? std::time(nullptr) : *t;
    auto secs = static_cast<std::chrono::seconds>(st).count();
    return static_cast<uint64_t>(secs);
}

The advantage of this function is that the default parameter value just gives you current time. However if you wanted to convert some specific time to unix timestamp than you can do so as well.

Shital Shah
  • 63,284
  • 17
  • 238
  • 185
  • 1
    fails on windows with `unsigned long`. changing it to `unsigned long long` works fine – atkawa7 Sep 01 '17 at 17:37
  • You are correct. I'd updated my function sometime ago and its now updated in this answer as well. – Shital Shah Sep 05 '17 at 07:30
  • Note that Unix Timestamps are *seconds*, not milliseconds. The code returns the correct result (in seconds) because the cast does nothing at all. Effectively you are just converting time_t to uint64_t. As a side effect you set the current time into the parameter t. – VolkA Jan 26 '18 at 09:59
  • @VolkA - good points! I've updated the answer. However I think cast is required for `long long` to `uint64_t` conversion. – Shital Shah Jan 29 '18 at 23:59
  • This doesn't answer the question though? This converts `time_t` to timestamp, not `chrono::time_point` – scry Jul 26 '21 at 17:04
2

You could use mktime() to convert the desired date encoded in a tm structure into a local-time time_t value.

If you need a UTC time, then use gmttime() to convert that time_t value into a UTC tm structure, and figure out from the output you get which tm structure yields the desired UTC time_t when given in input to mktime().

A bit elaborate, but hopefully it will work or at least provide a hint.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • okay my current actual implementation looks a bit different (http://liveworkspace.org/code/1OVIQ7$0). i'm still looking out for a pure c++11 solution. will accept this in a few hours if nobody comes up with a better solution – Daniel Jan 24 '13 at 17:10
  • @Daniel That gets the time since 00:00:00 1 Jan 1970 in the current timezone. You want the time since 00:00:00 1 Jan 1970 UTC. – bames53 Jan 24 '13 at 18:14
0

I know time_point has a method time_since_epoch() but it's not guaranteed that this is the same as the unix epoch (00:00:00 UTC on 1 January 1970).

As of C++20 and P0355R7, std::chrono::time_point::time_since_epoch can be guaranteed to be the UNIX epoch when the std::chrono::time_point is a std::chrono::system_clock.

Tim
  • 9
  • 1