Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Time zone conversion C API on Linux, anyone?

Tags:

c

timezone

linux

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??

like image 980
igor Avatar asked Aug 03 '09 18:08

igor


People also ask

How do I enable auto time zone in Linux?

Open the Activities overview and start typing Settings. Click on Settings. Click Date & Time in the sidebar to open the panel. If you have the Automatic Time Zone switch set to on, your time zone should update automatically if you have an internet connection and the location services feature is enabled.

How do I change the timeZone in Linux shell?

Select the Terminal program from your Linux programs, or press Ctrl + Alt + T on your keyboard. Enter the time zone menu command. Depending on your Linux distribution, this command will vary: Ubuntu and Mint - sudo dpkg-reconfigure tzdata followed by the admin/user password.

How do I convert one time zone to another in C #?

TimeZoneInfo timeZone = TimeZoneInfo. FindSystemTimeZoneById("Eastern Standard Time"); var time = timeZoneInfo. ConvertTime(gmTime, timeZone);


5 Answers

Wanted to add a bit more detail here.

If you try the following:

#include <stdio.h> #include <time.h>    /* defines 'extern long timezone' */  int main(int argc, char **argv) {     time_t t, lt, gt;     struct tm tm;      t = time(NULL);     lt = mktime(localtime(&t));     gt = mktime(gmtime(&t));      printf( "(t = time(NULL)) == %x,\n"         "mktime(localtime(&t)) == %x,\n"         "mktime(gmtime(&t)) == %x\n"         "difftime(...) == %f\n"         "timezone == %d\n", t, lt, gt,         difftime(gt, lt), timezone);     return 0; } 

you'll notice that timezone conversions make sure that:

  • mktime(localtime(t)) == t, and
  • mktime(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:

 $ TZ=GMT ./xx (t = time(NULL)) == 4dd13bac, mktime(localtime(&t)) == 4dd13bac, mktime(gmtime(&t)) == 4dd13bac difftime(...) == 0.000000 timezone == 0  $ TZ=EST ./xx (t = time(NULL)) == 4dd13baf, mktime(localtime(&t)) == 4dd13baf, mktime(gmtime(&t)) == 4dd181ff difftime(...) == 18000.000000 timezone == 18000  $ TZ=CET ./xx (t = time(NULL)) == 4dd13bb2, mktime(localtime(&t)) == 4dd13bb2, mktime(gmtime(&t)) == 4dd12da2 difftime(...) == -3600.000000 timezone == -3600 

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 the external long timezone global.

That doesn't get rid of the environment manipulation uglyness, but you can save yourself the effort of going through mktime().

like image 135
FrankH. Avatar answered Oct 11 '22 08:10

FrankH.


From tzfile(5), which documents the files in /usr/share/zoneinfo (on my system) in gruesome detail:

It seems that timezone uses tzfile internally, but glibc refuses to expose it to userspace. This is most likely because the standardised functions are more useful and portable, and actually documented by glibc.

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.

like image 33
bstpierre Avatar answered Oct 11 '22 08:10

bstpierre


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.

like image 44
Doug Avatar answered Oct 11 '22 07:10

Doug


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.

like image 29
Cascabel Avatar answered Oct 11 '22 06:10

Cascabel


Similar to the Python answer, I can show you what R does:

R> now <- Sys.time()       # get current time
R> format(now)             # format under local TZ
[1] "2009-08-03 18:55:57"
R> format(now,tz="Europe/London")   # format under explicit TZ
[1] "2009-08-04 00:55:57"
R> format(now,tz="America/Chicago") # format under explicit TZ
[1] "2009-08-03 18:55:57"
R> 

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)

like image 22
Dirk Eddelbuettel Avatar answered Oct 11 '22 06:10

Dirk Eddelbuettel