简体   繁体   中英

mktime() for non-local timezone

In C the function mktime() returns the epoch time according to the local timezone (the input struct is locally formatted).

The function timegm() returns the epoch time according to the UTC time (the input struct is formatted based off of UTC time).

The function localtime_r () takes in an epoch time and returns a local timezone formatted struct.

The function gmtime_r () takes in an epoch time and returns a UTC formatted struct.

I need to find out if a non-local timezone is currently daylight savings time or not, which would work with the localtime_r() function if it were local, but what if it were not local?

The gmtime_r() function always sets the tm_isdst field to zero, which won't work here.

Maybe there's some other function I am not aware of. Not sure.

If you (a) don't want to muck around with a global environment variable and (b) have the "NetBSD inspired" time functions available to you, there's an additional possibility: mktime_z() and localtime_rz() , which let you explicitly specify the zone you want to use. So you're not limited to your default local zone, or UTC.

Here's an example:

int main(int argc, char **argv)
{
    timezone_t tzp = tzalloc(argv[1]);
    if(tzp == NULL) return 1;
    time_t now = time(NULL);
    struct tm tm;
    struct tm *tmp = localtime_rz(tzp, &now, &tm);
    char tmpbuf[20];
    strftime(tmpbuf, sizeof(tmpbuf), "%H:%M:%S", tmp);
    printf("right now in zone %s is %s\n", argv[1], tmpbuf);

    tm.tm_year = 1976 - 1900;
    tm.tm_mon = 7 - 1;
    tm.tm_mday = 4;
    tm.tm_hour = 12;
    tm.tm_min = tm.tm_sec = 0;
    tm.tm_isdst = -1;

    time_t t = mktime_z(tzp, &tm);

    printf("in zone %s, %d-%02d-%02d %d:%02d was %ld\n", argv[1],
        1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, t);
}

When I invoke tzt America/New_York I see

right now in zone America/New_York is 11:58:23
in zone America/New_York, 1976-07-04 12:00 was 205344000

and when I invoke tzt America/Los_Angeles I see

right now in zone America/Los_Angeles is 08:58:49
in zone America/Los_Angeles, 1976-07-04 12:00 was 205354800

Now, with that said, two further comments, tied to my opening "if"s:

a. If you don't want to muck around with a global environment variable, I don't blame you one tiny bit. I positively hate mucking around with global variables (let alone environment variables) to affect how a function like mktime or localtime behaves. Unfortunately, however, this is the recommended way, in C, in this situation — see this question's other answers 1 , 2 for details.

b. Chances are unfortunately quite good that you don't , in fact, "have the NetBSD inspired time functions available to you". They're nonstandard and not even very popular. I was able to compile the test program above only because I had a copy of the IANA tz database and its code handy, which includes those functions if you also define NETBSD_INSPIRED . (That's why I broke the rules and didn't show a complete example, with all #include lines, since mine were weird and idiosyncratic.)

I need to find out if a non-local timezone is currently daylight savings time or not

  1. Get the current time as a time_t from time .
  2. Set env var TZ to the target time zone using putenv .
  3. Call tzset . (I don't know if this is required, but there's surely no harm in calling it.)
  4. Use localtime to convert the time_t into a struct tm according to the (modified) local time zone.
  5. Check the tm_isdst field of that struct tm .

Here's some code I wrote a decade ago that does what ikegami suggests in their answer :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static void time_convert(time_t t0, char const *tz_value)
{
    char old_tz[64] = "-none-";
    char *tz = getenv("TZ");
    if (tz != 0)
        strcpy(old_tz, tz);
    setenv("TZ", tz_value, 1);
    tzset();
    char new_tz[64];
    strcpy(new_tz, getenv("TZ"));
    char buffer[64];
    struct tm *lt = localtime(&t0);
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
    if (strcmp(old_tz, "-none-") == 0)
        unsetenv("TZ");
    else
        setenv("TZ", old_tz, 1);
    tzset();
    printf("%lld = %s (TZ=%s, DST = %d)\n",
           (long long)t0, buffer, new_tz, lt->tm_isdst);
}

int main(void)
{
    time_t t0 = time(0);
    char *tz = getenv("TZ");
    if (tz != 0)
        time_convert(t0, tz);
    time_convert(t0, "UTC0");
    time_convert(t0, "IST-5:30");
    time_convert(t0, "EST5");
    time_convert(t0, "EST5EDT");
    time_convert(t0, "PST8");
    time_convert(t0, "PST8PDT");
}

The output I get is:

1650647033 = 2022-04-22 17:03:53 (TZ=UTC0, DST = 0)
1650647033 = 2022-04-22 22:33:53 (TZ=IST-5:30, DST = 0)
1650647033 = 2022-04-22 12:03:53 (TZ=EST5, DST = 0)
1650647033 = 2022-04-22 13:03:53 (TZ=EST5EDT, DST = 1)
1650647033 = 2022-04-22 09:03:53 (TZ=PST8, DST = 0)
1650647033 = 2022-04-22 10:03:53 (TZ=PST8PDT, DST = 1)

Note that some of the time zones are specified without a daylight saving time, and the code reports DST = 0 for those zones.

Be wary of changing the time zone like this in multi-threaded applications. And be cautious about resetting the environment in case you fork() and exec() other programs with an unexpected value for the TZ environment variable.

Note: I've modified the code to:

  • Use long long instead of long for printing the time_t value. One of the annoying things is that there is no standard format string for printing the time_t type as an integer (indeed, the C standard doesn't even guarantee that the type is an integer, but it actually is on most systems, and POSIX requires it to be an integer type — see <sys/types.h> ). This change should allow 32-bit systems to work in 2038. That assumes that time_t is a 64-bit value even though it is a 32-bit system; if the system still uses a 32-bit time_t , it is terminally broken when time_t wraps around to -2 31 — but that shouldn't still be my problem then.
  • Print lt->tm_isdst which is the information wanted in the question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM