views:

945

answers:

3

I have a string containing a local date/time and I need to convert it to a time_t value (in UTC) - I've been trying this:

char* date = "2009/09/01/00";
struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
strptime(date, "%Y/%m/%d/%H", &cal);
time_t t = mktime(&cal);

but the time_t value I get back is the value that I would expect if the string was being parsed as UTC not local time. Maybe I have misunderstood what strptime is supposed to do, but in my timezone (UK) on the 1st September we are using BST (ie UTC + 1 hour) so I would expect the value I end up with to be 1 hour ahead of UTC.

Is there a way to interpret the string as localtime, automatically taking into account the UTC offset that would have been in effect on that date? Note that I need the time_t value not a struct tm, in the example above I want the time_t value to correspond to 2009-09-01 01:00:00 GMT

+3  A: 

You can use mktime to interpret a struct tm in the local timezone. When you do so, be careful to set the tm_isdst flag. It's 0 for summertime, 1 for wintertime, and to -1 to have mktime figure it out. Here's some example code:

void main()
{
    char* date = "2009/09/01/00";
    struct tm cal = {};
    // Read string into struct tm
    strptime(date, "%Y/%m/%d/%H", &cal);
    // Tell mktime to figure out the daylight saving time
    cal.tm_isdst = -1;
    printf("%20s: %s", "Before mktime", asctime(&cal));
    // Convert struct tm to time_t
    time_t t = mktime(&cal);
    // Convert time_t to localtime
    struct tm localcal = *localtime(&t);
    printf("%20s: %s", "Local time", asctime(&localcal));
    printf("%20s: %i\n", "Local DST", localcal.tm_isdst);
    // Convert time_t to GMT
    struct tm gmcal = *gmtime(&t);
    printf("%20s: %s", "GM time", asctime(&gmcal));
    printf("%20s: %i\n", "GM DST", gmcal.tm_isdst);
}

This prints (I live in GMT+1, and it's wintertime now):

   Before mktime: Tue Sep  1 00:00:00 2009
      Local time: Tue Sep  1 00:00:00 2009
       Local DST: 1
         GM time: Mon Aug 31 22:00:00 2009
          GM DST: 0

It looks like mktime converts a date in September based on the current daylight savings time. It's November now, so it's actually one hour off. I haven't found a way to correct that.

Andomar
thanks, but its the time_t value that I'm interested in - if I do a mktime() on the 'localcal' struct in your code it gives me the same result as doing mktime() on 'cal'. I need some way to produce a time_t value equivalent to '2009-09-01 01:00 GMT'
codebox_rob
The C convention is to store time_t as the number of seconds since 1970-01-01 in UTC. It's changed it to localtime only for interpretation or display. If you'd like to introduce rob_t as the localtime variant, you can calculate the number of seconds between gmtime() and localtime() and add that to your time_t. :)
Andomar
ok, but I want to interpret the string as local time, not produce a localtime equivalent of a UTC time string. In our example, assuming that we live in GMT+1, the string "2009/09/01/00" is our local time, and so it is equivalent to "2009/09/01/01" in GMT, and so the code should return the time_t value corresponding to "2009/09/01/01". Does that make sense?
codebox_rob
Right, this stuff is complex. Turns out `mktime()` **does** expect a localtime struct. The 1 hour difference in my example was the daylight saving time, which was set to 0 in `cal`, but is `1` at the moment. I'll edit the example.
Andomar
A: 

Here's my version, using tm_gmtoff. Hopefully, the library takes care of daylight savings time ...

#define _BSD_SOURCE
#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>

int gmtoffset(void) {
  struct tm *dummy;
  time_t t = 0;
  dummy = localtime(&t);
  return dummy->tm_gmtoff; /* _BSD_SOURCE */
}

int main(void) {
  int off;
  const char *date = "2009/09/01/00";
  struct tm cal = {0};
  time_t t;

  off = gmtoffset();

  strptime(date, "%Y/%m/%d/%H", &cal); /* _XOPEN_SOURCE */
  t = mktime(&cal);
  printf("t     --> %s", ctime(&t)); /* ctime includes a final '\n' */
  t -= off;
  printf("t-off --> %s", ctime(&t));
  return 0;
}
$ /usr/bin/gcc ptime.c
$ ./a.out
t     --> Tue Sep  1 01:00:00 2009
t-off --> Tue Sep  1 00:00:00 2009
pmg
The `struct tm` you pass to `mktime` has `tm_isdst` set to 0, so the time is read without DST. Then you pass it to `ctime`, which does correct for DST by adding 1 hour. Next you subtract the offset, which is confusingly also 1 hour, to get some random time. This stuff makes my head spin but I'd like to get to the bottom of it :)
Andomar
This DST is funny business. I just realized that DST is not in effect currently, but it was on the 1st of September! Hmmm ... I guess there's no way for the library to "know" when DST starts and ends at a specific place. I think you have to manage DST by hand ( http://timeanddate.com/time/dst2009.html )
pmg
A: 

I think I've cracked it now, thanks to Andomar - this code does what I need and appears to work regardless of the current DST status (I changed the clock on my PC to check this):

#include <time.h>
#include <assert.h>

time_t parseLocalDate(char* date){
    struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, -1, 0, NULL};
    strptime(date, "%Y/%m/%d/%H", &cal);
    return mktime(&cal);
}

int main(int argc, char *argv[]){
 // DST is effect, Local Time = GMT+1
    assert(1251759600 == parseLocalDate("2009/09/01/00")); // Mon, 31 Aug 2009 23:00:00 GMT
    assert(1254351600 == parseLocalDate("2009/10/01/00")); // Wed, 30 Sep 2009 23:00:00 GMT
 // DST not in effect, Local Time = GMT
    assert(1257033600 == parseLocalDate("2009/11/01/00")); // Sun, 01 Nov 2009 00:00:00 GMT
}
codebox_rob