Search code examples
ctimeepoch

Convert epoch time to human date time string without daylight saving conversion


I am new to C and I'm currently working on a project where I need to convert an epoch time to a human date and time string.

I have a UTC timestamp of 1411210794 which is for 20th September 2014 10:59:54.

I then run the following function on the UTC timestamp to convert to a human readable timestamp:

void UTCoTimeDate(char** date, char** time, long UTC, BOOL includeSeconds)
{
     time_t epch = UTC;
     struct tm *timeptr =  gmtime(&epch);
     asprintf(date, "%d-%.2d-%.2d", 1900 + timeptr->tm_year, timeptr->tm_mon+1, timeptr->tm_mday);
     if (includeSeconds)
     {
        asprintf(time, "%.2d:%.2d:%.2d", timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
     }
     else
     {
        asprintf(time, "%.2d:%.2d", timeptr->tm_hour, timeptr->tm_min); 
     }
}

When I run this through gdb and look at the tm *timeptr contents the hour has become 11 instead of staying at 10. Below is the full content of the tm struct

{tm_sec = 54, tm_min = 59, tm_hour = 11, tm_mday = 20, tm_mon = 8, tm_year = 114, tm_wday = 6, tm_yday = 262, tm_isdst = 1, tm_gmtoff = 3600,
  tm_zone = 0x806c488 "BST"}

I was expecting the human date to come back as 10:59:54 not 11:59:54.


Solution

  • Here's a demonstration program (ttt.c):

    #include <stdio.h>
    #include <time.h>
    
    static void dump_tm(const char *tag, const struct tm *tm)
    {
        printf("%s: {tm_sec = %.2d, tm_min = %.2d, tm_hour = %.2d, tm_mday = %.2d,"
               " tm_mon = %.2d, tm_year = %.3d, tm_wday = %d, tm_yday = %.3d,"
               " tm_isdst = %d, tm_gmtoff = %.5ld, tm_zone = \"%s\"}\n",
               tag, tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, tm->tm_mon,
               tm->tm_year, tm->tm_wday, tm->tm_yday, tm->tm_isdst, tm->tm_gmtoff,
               tm->tm_zone);
    }
    
    int main(void)
    {
        time_t  t0 = 1411210794;
        struct tm *ut = gmtime(&t0);
        struct tm *lt = localtime(&t0);
    
        dump_tm("UTC", ut);
        dump_tm("Local", lt);
        return 0;
    }
    

    This is the output on my Mac (Mac OS X 10.10.1, GCC 4.9.1 — extra line breaks in output added for SO):

    $ ./ttt
    UTC: {tm_sec = 54, tm_min = 59, tm_hour = 10, tm_mday = 20, tm_mon = 08, tm_year = 114,
    tm_wday = 6, tm_yday = 262, tm_isdst = 0, tm_gmtoff = 00000, tm_zone = "UTC"}
    Local: {tm_sec = 54, tm_min = 59, tm_hour = 03, tm_mday = 20, tm_mon = 08, tm_year = 114,
    tm_wday = 6, tm_yday = 262, tm_isdst = 1, tm_gmtoff = -25200, tm_zone = "PDT"}
    $ TZ="Europe/London" ./ttt
    UTC: {tm_sec = 54, tm_min = 59, tm_hour = 10, tm_mday = 20, tm_mon = 08, tm_year = 114,
    tm_wday = 6, tm_yday = 262, tm_isdst = 0, tm_gmtoff = 00000, tm_zone = "UTC"}
    Local: {tm_sec = 54, tm_min = 59, tm_hour = 11, tm_mday = 20, tm_mon = 08, tm_year = 114,
    tm_wday = 6, tm_yday = 262, tm_isdst = 1, tm_gmtoff = 03600, tm_zone = "BST"}
    $
    

    The default setting of time zone for the machine is effectively America/Los_Angeles (though the TZ environment variable is formally unset in the first invocation). As you can see, the UTC values are consistent, but the local time values have a local time zone abbreviation.

    In the absence of information to the contrary, I have to conclude that despite you thinking otherwise, you're actually invoking localtime() and not gmtime(). You can try to ensure that you call the gmtime() function by adding #undef gmtime before the function call. If the gmtime() function still produces the wrong time information, then gmtime() on your machine is broken. However, that seems highly improbable.