Converting Between Local Times and Gmt/Utc in C/C++

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

You're supposed to use combinations of gmtime/localtime and timegm/mktime. That should give you the orthogonal tools to do conversions between struct tm and time_t.

For UTC/GMT:

time_t t;
struct tm tm;
struct tm * tmp;
...
t = timegm(&tm);
...
tmp = gmtime(t);

For localtime:

t = mktime(&tm);
...
tmp = localtime(t);

All tzset() does is set the internal timezone variable from the TZ environment variable. I don't think this is supposed to be called more than once.

If you're trying to convert between timezones, you should modify the struct tm's tm_gmtoff.

How to convert from UTC to local time in C?

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.

Converting string containing localtime into UTC in C

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
}

Convert time_t from localtime zone to UTC

I'm going to show two ways of doing this:

  1. Using the C API.
  2. Using a modern C++11/14 library based on top of <chrono>.

For the purposes of this demo, I'm assuming that the current number of seconds in the local time zone is 1,470,003,841. My local time zone is America/New_York, and so the results I get reflect that we are currently at -0400 UTC.

First the C API:

This API is not type-safe and is very error prone. I made several mistakes just while coding up this answer, but I was able to quickly detect these mistakes because I was checking the answers against the 2nd technique.

#include <ctime>
#include <iostream>

int
main()
{
std::time_t lt = 1470003841;
auto local_field = *std::gmtime(<);
local_field.tm_isdst = -1;
auto utc = std::mktime(&local_field);
std::cout << utc << '\n'; // 1470018241
char buf[30];
std::strftime(buf, sizeof(buf), "%F %T %Z\n", &local_field);
std::cout << buf;
auto utc_field = *std::gmtime(&utc);
std::strftime(buf, sizeof(buf), "%F %T UTC\n", &utc_field);
std::cout << buf;
}

First I initialize the time_t. Now there is no C API to go from a local time_t to a UTC time_t. However you can use gmtime to go from a UTC time_t to a UTC tm (from serial to field type, all in UTC). So the first step is to lie to gmtime, telling it you've got a UTC time_t. And then when you get the result back you just pretend you've got a local tm instead of a UTC tm. Clear so far? This is:

auto local_field = *std::gmtime(<);

Now before you go (and I personally messed this part up the first time through) you have to augment this field type to say that you don't know if it is currently daylight saving or not. This causes subsequent steps to figure that out for you:

local_field.tm_isdst = -1;

Next you can use make_time to convert a local tm to a UTC time_t:

auto utc = std::mktime(&local_field);

You can print that out, and for me it is:

1470018241

which is 4h greater. The rest of the function is to print out these times in human readable format so that you can debug this stuff. For me it output:

2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC

A modern C++ API:

There exist no facilities in the std::lib to do this. However you can use this free, open source (MIT license) library for this.

#include "date/tz.h"
#include <iostream>

int
main()
{
using namespace date;
using namespace std::chrono_literals;
auto zt = make_zoned(current_zone(), local_seconds{1470003841s});
std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";
}

The first step is to create the local time in terms of seconds since the epoch:

local_seconds{1470003841s}

The next thing to do is to create a zoned_time which is a pairing of this local time and the current time zone:

auto zt = make_zoned(current_zone(), local_seconds(1470003841s));

Then you can simply print out the UTC number of seconds of this pairing:

std::cout << zt.get_sys_time().time_since_epoch() << '\n';

This output for me:

1470018241s

(4h later than the input). To print out this result as I did in the C API:

std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";

which outputs:

2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC

In this modern C++ approach, the local time and the UTC time are different types, making it much more likely that I catch accidental mixing of these two concepts at compile time (as opposed to creating run time errors).

Update for C++20

The second technique will be available in C++20 with the following syntax:

#include <chrono>
#include <iostream>

int
main()
{
using namespace std::chrono;
zoned_time zt{current_zone(), local_seconds{1470003841s}};
std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";
}

Time conversion between timezones

Perhaps this free, open source, C++11/14 timezone library could be of help. You can use it to convert UTC to or from your local timezone, to or from any arbitrary IANA timezone, or between any two arbitrary IANA timezones. The library has been ported to gcc, clang, and VS. But it requires <chrono>.

Here is a short example of how you can get the current UTC time, convert that to Berlin local time, then convert the Berlin local time to your local time, then convert the Berlin local time back to UTC. It is all quite easy, using modern C++ syntax and type safety:

#include "tz.h"
#include <iostream>

int
main()
{
using namespace date;
using namespace std::chrono;
auto utc = system_clock::now();
auto berlin = make_zoned("Europe/Berlin", utc);
auto local = make_zoned(current_zone(), berlin);
auto utc2 = berlin.get_sys_time();
std::cout << format("%F %T %Z", utc) << '\n';
std::cout << format("%F %T %Z", berlin) << '\n';
std::cout << format("%F %T %Z", local) << '\n';
std::cout << format("%F %T %Z", utc2) << '\n';
}

This just output for me:

2016-07-05 01:41:29.207335 UTC
2016-07-05 03:41:29.207335 CEST
2016-07-04 21:41:29.207335 EDT
2016-07-05 01:41:29.207335 UTC

Yes, you can even transition timezones with whatever precision your std::chrono::sytem_clock supports, and parse and format that precision.

If this library gives you any trouble, you can get help here on stack overflow, or chat here, or open an issue here, or search for my email address and contact me directly.

This library is fully documented, and designed to stay up-to-date with the very latest IANA timezone database (even if you need the latest mess the Egyptian politicians have made -- making a timezone rule change with a whole 3 days advance notice). And if your timestamps are antiquated (say from the 1940s), do not worry. This library seamlessly supports the full timezone history that IANA provides. That is, if there is data in the IANA timezone database, this library extracts and uses it without error.

You can also easily and efficiently form date literals local to Berlin if that is helpful to your application. For example, here is how you would specify 15:30 on the first Monday of July 2016 in Berlin (<chrono> literals require C++14):

auto meet = make_zoned("Europe/Berlin", local_days{2016_y/jul/mon[1]} + 15h + 30min);
std::cout << meet << '\n';

Output:

2016-07-04 15:30:00 CEST

If you want to find out what time the New York office has to telecommute in for this meeting:

std::cout <<  make_zoned("America/New_York", meet) << '\n';

Output:

2016-07-04 09:30:00 EDT

Hope this helps.

Converting between timezones in C

As comments do not allow posting the code, posting as a separate answer.. If you know "local" time and "UTC" time, you can calculate the offset of the "other" time from your "local" time. Then you convert the struct tm into calendar time, add the desired number of seconds (being the offset of the target time), and convert it back to struct tm:

(edited to account for another scenario to use mktime's normalization)

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

int main(int argc, char *argv) {
struct timeval tv_utc;
struct tm *local_tm, *other_tm;

/* 'synthetic' time_t to convert to struct tm for the other time */
time_t other_t_synt;
/* Other time is 1 hour ahead of local time */
int other_local_delta = 1*3600;

/* the below two lines are just to set local_tm to something */
gettimeofday(&tv_utc, NULL);
local_tm = localtime(&tv_utc.tv_sec);

printf("Local time: %s", asctime(local_tm));

#ifdef DO_NOT_WRITE_TO_LOCAL_TM
other_t_synt = mktime(local_tm) + other_local_delta;
#else
local_tm->tm_sec += other_local_delta;
/* mktime will normalize the seconds to a correct calendar date */
other_t_synt = mktime(local_tm);
#endif

other_tm = localtime(&other_t_synt);

printf("Other time: %s", asctime(other_tm));

exit(0);
}

Java Convert GMT/UTC to Local time doesn't work as expected

I also recommend using Joda as mentioned before.

Solving your problem using standard Java Date objects only can be done as follows:

    // **** YOUR CODE **** BEGIN ****
long ts = System.currentTimeMillis();
Date localTime = new Date(ts);
String format = "yyyy/MM/dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(format);

// Convert Local Time to UTC (Works Fine)
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date gmtTime = new Date(sdf.format(localTime));
System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:"
+ gmtTime.toString() + "," + gmtTime.getTime());

// **** YOUR CODE **** END ****

// Convert UTC to Local Time
Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime()));
System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:"
+ fromGmt.toString() + "-" + fromGmt.getTime());

Output:

Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000
UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000


Related Topics



Leave a reply



Submit