hookenz
hookenz

Reputation: 38879

Converting GMT time to Epoch

I'm having much trouble converting a GMT time to a unix EPOCH time.

The problem seems to relate to mktime. My current timezone is +13 hrs ahead. I think this is the issue, mktime assumes localtime and when it calculates an epoch is returning a value that is about 12hrs before what the real GMT time is.

Here is my code.

time_t t;
struct tm tm;
strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm);
tm.tm_isdst = -1; // -1 tells mktime figure it out
t = mktime(&tm);

When s = "2018-01-19T03:35:12Z", it's computing the epoch to be 1516286112 Instead of an epoch which should be about 12hrs ahead.

On a system where the BIOS clock is set to GMT, this error does not occur. So I can only conclude that mktime is working as expected when that situation is true.

Incidentally, the string is coming from a nosql server which is displaying the correct time on a document. I'm grabbing the current epoch and comparing it to the parsed date from the document and computing the difference. This is where it all goes haywire!

Upvotes: 1

Views: 1158

Answers (3)

xuhuleon
xuhuleon

Reputation: 31

You need to use tm_gmtoff, use code as following :

int64_t time = mktime(&tm_time);
time += tm_time.tm_gmtoff;
if (tm_time.tm_isdst == 1){
    time -= 3600;
}

Upvotes: 0

chux
chux

Reputation: 153338

Converting GMT time to Epoch

mktime() does the heavy work of Y-M-D.h:m:s to linear time conversion, yet it assumes a local time.

For fun, below is a portable solution that does not change the timezone, nor assumes time_t is itself "seconds since Jan 1, 1970".

It calculates the 2 time_t for the GMT time-stamp and Jan 2, 1970 as if they where local times. Their difference found with difftime() is in seconds (Unix time like), regardless of the units used with time_t. The quotient of difference/seconds-per-day, rounded to the nearest whole number (to address DST and timezone adjustments < 12 hours) is the day offset since the Unix epoch. The local time-ness of mktime() is effectively canceled in the difference and rounding. Jan 2, instead of Jan 1, is used to avoid Local Jan 1, 1970 may be before GMT Jan 1,1970. Some mktime() report -1 in those cases.

#include <stdio.h>
#include <time.h>
#include <math.h>

static time_t ymd_to_time_t(int year1900, int monthJan, int day) {
  struct tm tm = {0};
  tm.tm_year = year1900;
  tm.tm_mon = monthJan;
  tm.tm_mday = day;
  return mktime(&tm);
}

#define SECS_PER_DAY (24.0*60*60)

// Only 6 members of gmt are relevant
long long GMT_time_to_Unix_Epoch(const struct tm *gmt) {
  time_t t0 = ymd_to_time_t(1970-1900, 1-1, 2);
  if (t0 == (time_t)-1) return t0;
  time_t t1 = ymd_to_time_t(gmt->tm_year, gmt->tm_mon, gmt->tm_mday);
  if (t1 == (time_t)-1) return t1;
  double d = difftime(t1, t0);
  long days = lrint(d/SECS_PER_DAY) + 1;  // + 1 for (Jan 2 - Jan 1)
  long long Unix_Epoch = ((days*24LL + gmt->tm_hour)*60 + gmt->tm_min)*60 + gmt->tm_sec;
  return Unix_Epoch;
}

int main(void) {
  struct tm tm = {0};
  tm.tm_year = 2018 - 1900;
  tm.tm_mon = 1 -1 ;
  tm.tm_mday = 19;
  tm.tm_hour = 3;
  tm.tm_min = 35;
  tm.tm_sec = 12;
  printf("%lld\n", GMT_time_to_Unix_Epoch(&tm));
}

Output

1516332912

Pedantically, if a timezone shifted which side of the international date line it was on since 1970, this approach is off. E.g kiribati

Upvotes: 1

hookenz
hookenz

Reputation: 38879

So the issue is mktime is respecting the local timezone.

To fix that use timegm where available. Or roll your own from this answer.

Minimal implementation of gmtime algorithm?

Upvotes: 1

Related Questions