Time zone conversion C API on Linux, anyone?

后端 未结 6 1196
终归单人心
终归单人心 2020-12-29 03:40

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 tha

相关标签:
6条回答
  • 2020-12-29 03:57

    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)

    0 讨论(0)
  • 2020-12-29 03:58

    Why can't you use the gmtime_r()? Following worked fine for me:

    int main()
    {
        time_t t_gmt, t_local=time(NULL);
        struct tm tm_gmt;
    
        gmtime_r(&t_local, &tm_gmt);
    
        t_gmt = mktime(&tm_gmt);
    
        printf("Time now is:    %s", ctime(&t_local));
        printf("Time in GMT is: %s", ctime(&t_gmt));
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-29 04:02

    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().

    0 讨论(0)
  • 2020-12-29 04:07

    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.

    0 讨论(0)
  • 2020-12-29 04:13

    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.

    0 讨论(0)
  • 2020-12-29 04:15

    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.

    0 讨论(0)
提交回复
热议问题