How to convert from UTC to local time in C?

aseq picture aseq · Jan 31, 2012 · Viewed 48.2k times · Source

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.

Answer

R.. GitHub STOP HELPING ICE picture R.. GitHub STOP HELPING ICE · Jan 31, 2012

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.