10

It's a simple question, but the solution appears to be far from simple. I would like to know how to convert from UTC to local time. I am looking for a solution in C that's standard and more or less guaranteed to work on any computer at any location.

I have read the following links carefully but I can't find a solution there:

Converting string containing localtime into UTC in C

Converting Between Local Times and GMT/UTC in C/C++

I have tried a number of variations, such as (datetime is a string with time and date in UTC):

strptime(datetime, "%A %B %d %Y %H %M %S", tp);
strftime(printtime, strlen(datetime), "%A %B %d %Y %H %M %S", tp);

Or

strptime(datetime, "%A %B %d %Y %H %M %S", tp);
lt=mktime(tp);
printtime=ctime(&lt);

No matter what I try printtime ends up being the same as UTC.

Edit 11-29-2013: based on the very helpful answer by "R" below I finally got around to create a working example. I found it to be working correct in the two timezones I tested it, CET and PST:

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

long long diff_tm(struct tm *a, struct tm *b)
{
  return a->tm_sec - b->tm_sec
          +60LL*(a->tm_min - b->tm_min)
          +3600LL*(a->tm_hour - b->tm_hour)
          +86400LL*(a->tm_yday - b->tm_yday)
          +(a->tm_year-70)*31536000LL
          -(a->tm_year-69)/4*86400LL
          +(a->tm_year-1)/100*86400LL
          -(a->tm_year+299)/400*86400LL
          -(b->tm_year-70)*31536000LL
          +(b->tm_year-69)/4*86400LL
          -(b->tm_year-1)/100*86400LL
          +(b->tm_year+299)/400*86400LL;
}


int main()
{
  time_t utc, local;
  char buf[100];
  const char datetime[]="2013 11 30 23 30 26 UTC"; /* hard coded date and time in UTC */

  struct tm *tp=malloc(sizeof(struct tm));
  if(tp==NULL)
    exit(-1);

  struct tm *localt=malloc(sizeof(struct tm));
  if(localt==NULL)
    exit(-1);

  memset(tp, 0, sizeof(struct tm));
  memset(localt, 0, sizeof(struct tm));

  printf("UTC date and time to be converted in local time: %s\n", datetime);

  /* put values of datetime into time structure *tp */
  strptime(datetime, "%Y %m %d %H %M %S %z", tp);

  /* get seconds since EPOCH for this time */
  utc=mktime(tp);
  printf("UTC date and time in seconds since EPOCH: %d\n", utc);

  /* lets convert this UTC date and time to local date and time */

  struct tm e0={ .tm_year = 70, .tm_mday = 1 }, e1, new;
  /* get time_t EPOCH value for e0 (Jan. 1, 1970) */
  time_t pseudo=mktime(&e0);

  /* get gmtime for this value */
  e1=*gmtime(&pseudo);

  /* calculate local time in seconds since EPOCH */
  e0.tm_sec += utc - diff_tm(&e1, &e0);

  /* assign to local, this can all can be coded shorter but I attempted to increase clarity */
  local=e0.tm_sec;
  printf("local date and time in seconds since EPOCH: %d\n", local);

  /* convert seconds since EPOCH for local time into localt time structure */
  localt=localtime(&local);

  /* get nicely formatted human readable time */
  strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z", localt);

  printf("local date and time: %s\n", buf);
}

It should compile without problems on most systems. I hard coded a time and date in UTC which then will be converted to the local time and date.

Community
  • 1
  • 1
aseq
  • 451
  • 1
  • 6
  • 23

10 Answers10

8

If you can assume POSIX (and thus the POSIX specification of time_t as seconds since the epoch), I would first use the POSIX formula to convert to seconds since the epoch:

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

Next, use localtime((time_t []){0}) to get a struct tm representing the epoch in local time. Add the seconds since the epoch to the tm_sec field of this struct tm, then call mktime to canonicalize it.

Edit: Actually the only POSIX dependency is having a known epoch which (time_t)0 corresponds to. Perhaps you can find a way around that if you really need to... for instance using calls to both gmtime and localtime at time_t 0..

Edit 2: A sketch of how to do this:

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

long long diff_tm(struct tm *a, struct tm *b)
{
        return a->tm_sec - b->tm_sec
                +60LL*(a->tm_min - b->tm_min)
                +3600LL*(a->tm_hour - b->tm_hour)
                +86400LL*(a->tm_yday - b->tm_yday)
                +(a->tm_year-70)*31536000LL
                -(a->tm_year-69)/4*86400LL
                +(a->tm_year-1)/100*86400LL
                -(a->tm_year+299)/400*86400LL
                -(b->tm_year-70)*31536000LL
                +(b->tm_year-69)/4*86400LL
                -(b->tm_year-1)/100*86400LL
                +(b->tm_year+299)/400*86400LL;
}

int main(int argc, char **argv)
{
        char buf[100];
        struct tm e0 = { .tm_year = 70, .tm_mday = 1 }, e1, new;
        time_t pseudo = mktime(&e0);
        e1 = *gmtime(&pseudo);
        e0.tm_sec += atoi(argv[1]) - diff_tm(&e1, &e0);
        mktime(&e0);
        strftime(buf, sizeof buf, "%c", &e0);
        puts(buf);
}

Please don't mind the ugly output code. This program takes an argument in the form of "seconds relative to the POSIX epoch" and outputs the resulting time in local time. You can convert any UTC time to seconds since the epoch using the formula I cited above. Note that this code does not in any way depend on POSIX, but it does assume the offset returned by diff_tm combined with the seconds-since-the-epoch value does not overflow int. A fix for this would be to use a long long offset and a loop that keeps adding increments no larger than INT_MAX/2 (or smaller than INT_MIN/2) and calling mktime to renormalize until the offset reaches 0.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 2
    Regarding edit: surely it's not true in general that the delta between UTC and localtime at the epoch was the same as the delta at the time you're converting. Maybe in Japan. – Steve Jessop Jan 31 '12 at 09:33
  • @Steve: It doesn't need to be the same. The only reason you need to compute it is to get the adjustment to the offset in seconds from the "epoch" in localtime so you can make the denormalized `struct tm` in localtime format then pass it to `mktime` for normalization. – R.. GitHub STOP HELPING ICE Jan 31 '12 at 17:29
  • Thank you for that answer. I will try it out and let you know how it turned out. – aseq Jan 31 '12 at 20:27
  • 1
    BTW, it's really sick that C makes you go to such lengths to work with standardized time in a sane way. Just having a UTC equivalent of the `mktime` function would avoid the whole issue. At least the above approach can be used to make such a function... – R.. GitHub STOP HELPING ICE Jan 31 '12 at 20:50
  • Yeah I know what you're saying. It was quite surprising to me. Maybe it's because such functionality supposedly falls outside the scope of C... – aseq Feb 01 '12 at 07:49
  • Ah, I lost track of what was going on, thanks. Regarding the missing inverse of `gmtime()`, I suppose they expect you to set the locale and use `mktime()`. But practically you can't modify+restore global state in responsible library code, especially in multi-threaded apps, so there's a serious lack there. – Steve Jessop Feb 01 '12 at 09:57
  • @Steve: It's a timezone matter, not a locale matter, and ISO C has no way to change the timezone. With POSIX you could change it with `setenv` or `putenv`, but like you said that's not thread-safe. You can use my approach and drop in POSIX `localtime_r` and `gmtime_r` to make a thread-safe solution, though. – R.. GitHub STOP HELPING ICE Feb 01 '12 at 17:16
  • @aseq: If the distinction between UTC and local time exists in C (via the `localtime` and `gmtime` functions) I find it hard to believe this is "outside the scope of C". – R.. GitHub STOP HELPING ICE Feb 01 '12 at 17:16
  • I agree actually. Though maybe it's because it is easier to calculate from local time to UTC than the opposite? Anyways, I'm just guessing... – aseq Feb 02 '12 at 09:43
  • The answer here is wrong for multiple reason. It doesn't take DST into account (try printing diff_tm(timeInSummerWithDST, sameTimeInUTC)), you would expect timezoneOffset + DSTOffset as output, and you only get timezoneOffset. Secondly, it's using tm.yday which can't be computed correctly without calling mktime first, that'll "fix" the structure DST field too. – xryl669 Apr 05 '13 at 14:30
  • @xryl669: Please show a case where it fails. It's possible you've found a bug, but the arguments you've made are not correct/complete as-is. In particular, `tm_yday` **is** set by `main` to 0, which is the intended value. – R.. GitHub STOP HELPING ICE Apr 05 '13 at 16:11
  • Here at my localtime, I've 1 hour offset in the output your program gives. It's because I'm currently in DST and the 1st of january 1970 00.00, I was not. So diff_tm gives 1 hour (which is correct, I'm GMT+1), but the expected diff time should be 2 hours (1 hour from my Timezone, and one hour due to DST). – xryl669 Apr 05 '13 at 18:22
  • From the documentation of mktime, depending on tm_isdst field of the tm struct, mktime will account for DST or not. You don't initialize this field, so it might work for you because the default value match your current DST value, but it's not my case. – xryl669 Apr 05 '13 at 18:29
  • I updated my question with a working code example based on this answer, thanks. – aseq Nov 30 '13 at 04:25
5

Ahm ... I might just be a beginner in C, but I got this working example:

#include <time.h>
#include <stdio.h>
int main(void)
{
        time_t abs_ts,loc_ts,gmt_ts;
        struct tm loc_time_info,gmt_time_info;

        /*Absolute time stamp.*/
        time(&abs_ts);

        /*Now get once the local time for this time stamp,
        **and once the GMT (UTC without summer time) time stamp.*/
        localtime_r(&abs_ts,&loc_time_info);
        gmtime_r(&abs_ts,&gmt_time_info);

        /*Convert them back.*/
        loc_ts=mktime(&loc_time_info);
        gmt_ts=mktime(&gmt_time_info);

        /*Unfortunately, GMT still has summer time. Get rid of it:*/
        if(gmt_time_info.tm_isdst==1)
                {gmt_ts-=3600;}

        printf("Local timestamp: %lu\n"
                "UTC timestamp: %lu\n"
                "Difference in hours: %lu\n\n",
                loc_ts,
                gmt_ts,
                (loc_ts-gmt_ts)/3600);

        return 0;
}

Which produces this output:

Local timestamp: 1412554119

GMT timestamp: 1412546919

Difference in hours: 2

Now you have the difference between UTC and local time in seconds. That should be enough to convert it.

One note to your code, aseq: you are using malloc without need here (you can memset values on the stack as well, and malloc can be expensive while stack allocation is often much faster), and you do not free it. That's very, very bad practise.

Another thing:

memset(tp, 0, sizeof(struct tm));

Would be better done if you'd pass sizeof(*tp) (or, if you put tp on the stack, sizeof(tp)) to memset. That ensures that even if the type of your object changes, it will still be fully memset.

Dachschaden
  • 134
  • 1
  • 2
  • Thanks i've tried this on ubuntu and it works for Summer and Wintertime in GMT+1 Berlin Regions – Oliver Jan 05 '15 at 23:10
  • 1
    Today I wouldn't even write it this way. The reason for this is because mktime is not reentrant (touches the global TZ variable, if I am not mistaken), and can blow up at any time. Which is a huge fail in my opinion ... but then again, the folks who standardized the API had other stuff to worry about. However, there is no sane way to do it. R.. already showed a good way to calculate the date by yourself. It's unfortunate that you have to carry along that function for such a mundane task, but on the other hand it takes ages to publish newer standards. – Dachschaden Jan 08 '15 at 06:20
  • From the [man pages for mktime()](http://linux.die.net/man/3/mktime): *a positive value means DST is in effect; zero means that DST is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.* However, later it says *tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time* but the behaviour seems to be to trust the value of `tm_isdst` on entry – ChisholmKyle May 05 '16 at 16:16
3

To sum-up: the conversion of a broken down date (struct tm) in UTC to a (local) calendar time (time_t) is achieved with timegm() - the opposite of mktime() - BUT timegm() is not a standard function (how logic is that). The C standard leaves us with only time(), gmtime(), mktime() and difftime().

A workaround found in other docs advises to emulate timegm() by setting first the environment variable TZ to a null string, then calling mktime() resulting in an UTC calendar time, then resetting TZ to its initial value, but once again, this is not standard.

Basically, as I understand it, the difference between a local time and UTC time is just an offset so if we can evaluate that offset, we can adjust the result of mktime(), so here's my proposition:

time_t my_timegm(struct tm *tm) {
    time_t epoch = 0;
    time_t offset = mktime(gmtime(&epoch));
    time_t utc = mktime(tm);
    return difftime(utc, offset);
}

A quick test:

int main(void) {
    time_t now = time(0);
    struct tm local = *localtime(&now);
    struct tm utc = *gmtime(&now);
    time_t t1 = mktime(&local);
    time_t t2 = my_timegm(&utc);
    assert(t1 == t2);
    printf("t =%lu\nt1=%lu\nt2=%lu\n",now,t1,t2);
    return 0;
}
0xFC
  • 161
  • 2
  • This is wrong if you are currently in DST. mktime(gmtime(X)) does not gives you the current applicable offset, but the timezone offset (so you'll likely miss one hour in summer if DST is on). – xryl669 Apr 05 '13 at 09:27
  • Also, it's important to remember that mktime is not re-entrant (and unlike localtime & gmtime, it's not obvious from the function signature), since it modifies the global timezone variable. So this code *might* appear to work, but any other thread that's doing a mktime can completely break your output. – xryl669 Apr 05 '13 at 09:30
2
//working stand alone function adjusting UTC to local date and time
//globals(unsigned integers): gps.Mth, gps.Yr, gps.Hm (eg:2115 for 21:15)
//adjust date and time according to UTC
//tz(timezone) eg: 1100, for 11 hours, tzdir: 1 forward, 0 backwards            





    void AdjustUTCToTimeZone(u16 tz, u8 tzdir){
    u8 maxDayInAnyMonth[13] = {0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //gps.Mth 1-12 (not zero)
        if(gps.Yr%4==0){maxDayInAnyMonth[2]=29;}//adjust for leapyear
        u8 maxDayUtcMth =maxDayInAnyMonth[gps.Mth];
        u8 maxDayPrevMth=maxDayInAnyMonth[gps.Mth-1];
        if(!maxDayPrevMth){maxDayPrevMth=31;} //month before utc month

        u16 hr=(gps.Hm/100)*100;u16 m=gps.Hm-hr;  //2115 --> 2100 hr and 15 min
        if(tzdir){//adjusting forwards
            tz+=gps.Hm;
            if(tz>2400){gps.Hm=tz-2400;gps.Day++;                //spill over to next day
                  if(gps.Day>maxDayUtcMth){ gps.Day=1;gps.Mth++; //spill over to next month
                      if(gps.Mth>12){gps.Mth=1; gps.Yr++;        //spill over to next year
                      }
                  }
            }else{gps.Hm=tz;}
        }else{//adjusting backwards
            if(tz>gps.Hm){gps.Hm=(2400-(tz-hr))+m;gps.Day--;  // back to previous day
                  if(gps.Day==0){                             //back to previous month
                     gps.Mth--;gps.Day=maxDayPrevMth;
                     if(!gps.Mth){gps.Mth=12;                 //back to previous year
                        gps.Yr--;
                     }
                  }
            }else{gps.Hm-=tz;}
        }
    }
2

I think it's easier than that; time.h defines three variables:

extern int    daylight;
extern long   timezone;
extern char  *tzname[];

which are loaded based on the TZ env variable when you call

 tzset();

if you have a utc time in

struct tm date;
date.tm_isdst = 0;

convert it to a time_t using mktime

time_t utc = mktime( &date );

then convert it to local time

time_t local = utc - timezone + ( daylight?3600:0 );

timezone is the number of seconds away from utc for the current timezone and daylight is 1 to indicate daylight savings time is in play and zero for not.

A small caution: When I coded this for a microcontroller and cross compiled, it's time.h defined those variables with initial underscores.

See the man page for time.h

1

I found that the solution the OP gave did not work in cases when DST applies. For example, in my case, at the current time, DST was not in effect, but if I set the initial date which should convert to local time with DST, then it would not work, i.e. today's date is 3/1/2018 and DST is not in effect, but if I set the date for conversion to, say, 8/1/2018 0:00:00 when DST is in effect, then the solution given would convert to local time, but would not take DST into account. I found that initializing e0 to the date and hour of the initial date/time string and its member tm_isdst to -1 solved the problem. I then created the following program with complementary functions which you can include in your code. The initial format of the date and time is the same that MySQL uses, because I needed it for such purposes.

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

long long diff_tm(struct tm *a, struct tm *b) {
 return a->tm_sec - b->tm_sec
      + 60LL * (a->tm_min - b->tm_min)
      + 3600LL * (a->tm_hour - b->tm_hour)
      + 86400LL * (a->tm_yday - b->tm_yday)
      + (a->tm_year - 70) * 31536000LL
      - (a->tm_year - 69) / 4 * 86400LL
      + (a->tm_year - 1) / 100 * 86400LL
      - (a->tm_year + 299) / 400 * 86400LL
      - (b->tm_year - 70) * 31536000LL
      + (b->tm_year - 69) / 4 * 86400LL
      - (b->tm_year - 1) / 100 * 86400LL
      + (b->tm_year + 299) /400 * 86400LL;
}

void localToUTC(char *buf, const char *localTime) {
 struct tm tp;
 strptime(localTime, "%Y-%m-%d %H:%M:%S", &tp);
 tp.tm_isdst = -1;
 time_t utc = mktime(&tp);
 struct tm res = *gmtime(&utc);
 strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &res);
}

void utcToLocal(char *buf, const char *utcTime) {
 struct tm tp;
 strptime(utcTime, "%Y-%m-%d %H:%M:%S", &tp);
 tp.tm_isdst = -1;
 time_t utc = mktime(&tp);
 struct tm e0 = { .tm_year = tp.tm_year, .tm_mday = tp.tm_mday, .tm_mon = tp.tm_mon, .tm_hour = tp.tm_hour, .tm_isdst = -1 };
 time_t pseudo = mktime(&e0);
 struct tm e1 = *gmtime(&pseudo);
 e0.tm_sec += utc - diff_tm(&e1, &e0);
 time_t local = e0.tm_sec;
 struct tm localt = *localtime(&local);
 strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &localt);
}

int main(void) {
 char mytime_1[20] = "2018-02-28 13:00:00";
 char utctime_1[20], back_1[20];
 localToUTC(utctime_1, mytime_1);
 utcToLocal(back_1, utctime_1);
 printf("My time: %s\n", mytime_1);
 printf("UTC time: %s\n", utctime_1);
 printf("Back: %s\n", back_1);

 printf("-------------------------------------------\n");

 char mytime_2[20] = "2018-07-28 17:00:00";
 char utctime_2[20], back_2[20];
 localToUTC(utctime_2, mytime_2);
 utcToLocal(back_2, utctime_2);
 printf("My time: %s\n", mytime_2);
 printf("UTC time: %s\n", utctime_2);
 printf("Back: %s\n", back_2);

 printf("-------------------------------------------\n");

 return 0;
}
Nikola Novak
  • 4,091
  • 5
  • 24
  • 33
0

I followed the answer by @Dachschaden and I made an example which also shows human-readable output and I remove the DST option for the difference in seconds between UTC and local time. Here it is:

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

#define DATE_MAX_STR_SIZE 26
#define DATE_FMT "%FT%TZ%z"

int main() {

    time_t now_time, now_time_local;
    struct tm now_tm_utc, now_tm_local;
    char str_utc[DATE_MAX_STR_SIZE];
    char str_local[DATE_MAX_STR_SIZE];

    time(&now_time);
    gmtime_r(&now_time, &now_tm_utc);
    localtime_r(&now_time, &now_tm_local);

    /* human readable */
    strftime(str_utc, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_utc);
    strftime(str_local, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_local);

    printf("\nUTC: %s", str_utc);
    printf("\nLOCAL: %s\n", str_local);

    /* seconds (epoch) */
    /* let's forget about DST for time difference calculation */
    now_tm_local.tm_isdst = 0;
    now_tm_utc.tm_isdst = 0;
    now_time_local = now_time + (mktime(&now_tm_local) - mktime(&now_tm_utc));

    printf("\nUTC in seconds: %lu", now_time);
    printf("\nLOCAL in seconds: %lu\n", now_time_local);

    return 0;
}

Output on my machine is:

UTC: 2016-05-05T15:39:11Z-0500
LOCAL: 2016-05-05T11:39:11Z-0400

UTC in seconds: 1462462751
LOCAL in seconds: 1462448351

Note that DST is on in this case (there's a 1 hour time zone offset difference between UTC and LOCAL).

ChisholmKyle
  • 449
  • 3
  • 10
0

try this, test output: utcEpochTime: 1487652688, localEpochTime: 1487699488, diff: 46800

$ python
>>>46800 / 60 / 60
13

the diff is 13 hours, which is good, as my timezone is UTC+8.

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

int main(int argc, char *argv[])
{
    time_t utcEpochTime = time(0);
    time_t localEpochTime = 0;

    struct tm tm = {0};
    localtime_r(&utcEpochTime, &tm);
    tm.tm_isdst = -1;
    localEpochTime = timegm(&tm);

    printf("utcEpochTime: %d, localEpochTime: %d, diff: %d\n", (int)utcEpochTime, (int)localEpochTime, (int)(localEpochTime - utcEpochTime));
    return 0;
}
Ted Feng
  • 829
  • 1
  • 17
  • 22
0

A simple and effective way: Add (or subtract) the number of seconds between your time zone and UTC (considering daylight saving time).

As an example that worked just fine a minute ago, on December 30, 2017, with U.S. Mountain Standard Time (no DST), which is 7 hours behind UTC:

time_t     current_time_UTC;
time_t     current_time_MST;

struct tm *current_broken_time_MST;

uint32_t seven_hours_in_seconds = 25200; // Get this any way you want

current_time_UTC = time (NULL);                                 // UTC
current_time_MST = current_time_UTC - seven_hours_in_seconds;   // MST

current_broken_time_MST = localtime (&current_time_MST);        // MST

Enjoy.

-8
void   CTestDlg::OnBtnTest()   
{ 
HANDLE   hFile; 
WIN32_FIND_DATA   wfd; 
SYSTEMTIME   systime; 
FILETIME   localtime; 
char   stime[32];     //
memset(&wfd,   0,   sizeof(wfd)); 

if((hFile=FindFirstFile( "F:\\VC\\MFC\\Test\\Release\\Test.exe ",        &wfd))==INVALID_HANDLE_VALUE) 
{ 
char   c[2]; 
DWORD   dw=GetLastError(); 
wsprintf(c,   "%d ",   dw); 
AfxMessageBox(c);   
return   ;//
} 
FileTimeToLocalFileTime(&wfd.ftLastWriteTime,&localtime); 
FileTimeToSystemTime(&localtime,&systime); 
sprintf(stime, "%4d-%02d-%02d   %02d:%02d:%02d ", 
      systime.wYear,systime.wMonth,systime.wDay,systime.wHour, 
      systime.wMinute,systime.wSecond); 
AfxMessageBox(stime);   
} 
zhaixiaohu
  • 82
  • 3
  • 3
    a) thats not C, b) explain to me where FindFirstFile() is in C99 standard. What a crap answer – Adrian Cornish Jan 31 '12 at 08:37
  • It would appear you never actually read the question. not to mention your code relies on a particular path and directory structure being present to work. try harder to work within the questions parameters next time. – mattr- Mar 05 '12 at 22:14