3

I want to convert a struct tm that describes a UTC date to a time_t value. How can I do that?

The C library has the mktime() function but AFAICS it isn't up for the job because it converts local time to time_t.

So what is the suggested way of converting a UTC date stored inside a struct tm to a time_t value? Do I have to use mktime() and then manually apply the timezone bias or is there a dedicated C function which does this job?

Andreas
  • 9,245
  • 9
  • 49
  • 97
  • It depends on you platform. You might have a `timegm()` function to do what you want. If not, you have to roll your own. Neither standard C nor POSIX provides such a function. – Jonathan Leffler Feb 01 '18 at 15:39
  • 1
    `[mktime](http://pubs.opengroup.org/onlinepubs/009695399/functions/mktime.html)` takes a `struct tm` as an argument and returns `time_t` as described in the documentation – amine.ahd Feb 01 '18 at 15:43
  • 1
    @amine.ahd: That won't help as described in my post. – Andreas Feb 01 '18 at 15:59
  • No standard C library function does the job - sigh. Various approaches: 1) change timezone to UTC, call mktime(). 2) Deduce timezone offset for `struct tm` through various means, 3) extended library functions 4) dark arts. Certainly a dupe. – chux - Reinstate Monica Feb 01 '18 at 15:59
  • @chux: Even though not standard C `timegm()` seems to be widely available and Win32 has the `_mkgmtime()` equivalent. So I can use those two instead of manually messing with it. – Andreas Feb 01 '18 at 16:01
  • @Andreas For Y/M/DTh:m:s UTC to `time_t` (in seconds), consider the _only_ reason to call a `mktime()` like function is to convert the year and month to UTC as the day,hour,min,sec are trivially added. Code a simple `time_t foo(year,month);` function. – chux - Reinstate Monica Feb 01 '18 at 16:08
  • Well, "simple" is probably an understatement. There are quite some things that a `timegm()` implementation has to take into account. For reference, here is the boost implementation: https://stackoverflow.com/questions/16647819/timegm-cross-platform – Andreas Feb 01 '18 at 16:18
  • @Andreas Are you interested is a highly portable answer or one that assumes `time_t` is a count of seconds since Jan 1, 1970 (no leap seconds)? (That assumption is common, yet not specified by C.) – chux - Reinstate Monica Feb 01 '18 at 16:48
  • 1
    I want the `time_t` to be consistent to whatever `mktime()` would return... no idea if this includes leap seconds and stuff. – Andreas Feb 01 '18 at 17:01
  • Useful info here (but no single does everything solution) https://stackoverflow.com/questions/283166/easy-way-to-convert-a-struct-tm-expressed-in-utc-to-time-t-type – pico-help Jun 02 '18 at 16:22

1 Answers1

2

Here's how I'm going about it:

  1. Pretending against better knowledge the tm structure holds local time (non-DST if anyone asks; it doesn't matter, but must be consistent with step 3), convert it to time_t.
  2. Convert the date back into a tm structure, but this time in UTC representation.
  3. Pretending against better knowledge that tm structure to also hold local (non-DST if anyone asks, but more importantly consistent with step 1), and convert it to time_t once more.
  4. From the two time_t results I can now compute the difference between local time (non-DST if anyone asks) and UTC in time_t units.
  5. Adding that difference to the first time_t result gives me the proper time in UTC.

Convoluted, but I'm confident it should work. Unless the computer uses a TAI-based clock, in which case on rare occasions the result might be off by a second. The consistent DST setting (as opposed to having mktime auto-detect it) is necessary so that I don't get thrown off by an entire hour when trying to compute a date/time close to the start or end of the DST period.

tm tt;
// populate tt here
tt.tm_isdst = 0;
time_t tLoc = mktime(&tt);
tt = *gmtime(&tLoc);
tt.tm_isdst = 0;
time_t tRev = mktime(&tt);
time_t tDiff = tLoc - tRev;
time_t tUTC = tLoc + tDiff;

However, this approach comes with the caveat that gmtime() is not thread-safe. So if anyone has any better solution, I'd love to hear it.

Christoph Lipka
  • 652
  • 4
  • 15
  • 1
    Use [`gmtime_r()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime_r.html). – Andrew Henle Aug 12 '21 at 16:49
  • I consider this to be the preferred solution if `timegm` is not available. I have used it myself, and I have seen it recommended (with caveats) by people who know what they are talking about. Not sure why it wasn't mentioned at [that much older question](https://stackoverflow.com/questions/283166), but I see you've added it now. – Steve Summit Aug 12 '21 at 18:07
  • @AndrewHenle `gmtime_r()` has the drawback of being less portable, in that (1) it is only now being added to the C standard (new in C23), and (2) it also can't be ported to standard C++. – Christoph Lipka Aug 12 '21 at 19:01
  • @ChristophLipka Well, `gmtime_r()` has been [POSIX standard for 20 years](https://pubs.opengroup.org/onlinepubs/009695399/functions/gmtime_r.html). And on Windows it's a simple `#define gmtime_r gmtime_s` to use the Microsoft's benighted `*_s` "safer" call. – Andrew Henle Aug 12 '21 at 19:11