I'm looking for something that I presumed would be very simple - given local Unix time in a specific time zone (specified as a string, e.g., "America/New_York" - note that's not my local time), get the corresponding time value in GMT. I.e., something along the lines of
time_t get_gmt_time(time_t local_time,
const char* time_zone);
As deceptively simple as it sounds, the closest I could find was the following code snippet from timegm's man page:
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz)
setenv("TZ", tz, 1);
else
unsetenv("TZ");
tzset();
return ret;
}
There gotta be a better way than this belligerently not thread-safe abomination, right? Right??
From tzfile(5), which documents the files in /usr/share/zoneinfo (on my system) in gruesome detail:
Again, this is probably not what you're looking for (ie. an API), but the information is there and you can parse it without too much pain.
Why can't you use the
gmtime_r()
? Following worked fine for me:Similar to the Python answer, I can show you what R does:
but R uses an internal representation that extends the usual
struct tm
--- see R-2.9.1/src/main/datetime.c.Still, this is a hairy topic and it would be nice if it were the standard library. As it isn't maybe your best bet is to use Boost Date_Time (example)
Wanted to add a bit more detail here.
If you try the following:
you'll notice that timezone conversions make sure that:
mktime(localtime(t)) == t
, andmktime(gmtime(t)) == t + timezone
,therefore:
difftime(mktime(gmtime(t)), mktime(localtime(t))) == timezone
(the latter is a global variable initialized by either
tzset()
or the invocation of any timezone conversion function).Example output of the above:
In that sense, you're attempting to "do it backwards" -
time_t
is treated as absolute in UN*X, i.e. always relative to the "EPOCH" (0:00 UTC on 01/01/1970).The difference between UTC and the current timezone (last
tzset()
call) is always in theexternal long timezone
global.That doesn't get rid of the environment manipulation uglyness, but you can save yourself the effort of going through
mktime()
.I really thought there was something in glib, but seem to have misremembered. I know you're probably looking for straight-up C code, but here's the best I've got:
I know that Python has some notion of timezones through a
tzinfo
class - you can read about it in the datetime documentation. You can have a look at the source code for the module (in the tarball, it's in Modules/datetime.c) - it appears to have some documentation, so maybe you can get something out of it.The problem with gmtime, localtime and their variants is the reliance on the TZ environment variable. The time functions first call tzset(void), which reads TZ to determine offsets DST, etc. If TZ is not set in the user's environment, (g)libc uses the system timezone. So if you have a local struct tm in, say, 'Europe/Paris' and your machine or environment is set to 'America/Denver', the wrong offset will be applied when you convert to GMT. All the time functions call tzset(void) which reads TZ to set char *tzname[2], long timezone (diff, in seconds, from GMT) and int daylight (boolean for DST). Setting these directly has no affect, because tzset() will overwrite them the next time you call localtime, etc.
I was faced with the same issue as 'igor' in the original question, while setenv works it seems problematic (re-entran?). I decided to look further to see if I could modify tzset (void) to tzset(char*) to explicitly set the above mentioned variables. Well, of course, that's just a bad idea... but in probing the glibc source and the IANA TZ database source, I came to the conclusion that the setenv approach ain't so bad.
First, setenv only modifies the process global 'char **environ' (not the calling shell, so the 'real' TZ is not affected). And, second, glibc actually puts a lock in setenv. The drawback is that setenv/tzset calls are not atomic, so another thread could conceivably write to TZ before the original thread call tzset. But a well-implemented application that uses threads should watch for that anyway.
It would be cool if POSIX defined tzset to take a char* for look up in the extensive IANA TZ database (and take NULL to mean, 'use the user or system TZ/), but failing that, setenv seems to be ok.