1

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.

NorthStar
  • 81
  • 9
  • Set the `TZ` environment variable and then call the function that operates in the local timezone. – Barmar Apr 21 '22 at 20:46
  • See this answer: https://stackoverflow.com/a/7107036/5128826 – nsilent22 Apr 21 '22 at 20:51
  • 1
    "Set the TZ environment variable and then call the function that operates in the local timezone." Won't this set the timezone systemwide? Or only for the application environment? – NorthStar Apr 21 '22 at 20:56
  • 1
    No, changing a process's env var will only change the process's copy of the env var. (It will also change the value inherited by any later-created children of that process.) But it could cause problems if you have a multi-threaded application. You'll need to take steps to protect yourself in such a situation. – ikegami Apr 21 '22 at 20:58
  • Notes `.tm_isdst` is positive when a `struct tm` is using _daylight time_. That usually is 1, but could be some other positive value. Daylight time _almost always_ means 1 hour shift from standard time. Counter example [Lord Howe Island](https://en.wikipedia.org/wiki/Lord_Howe_Island). – chux - Reinstate Monica Apr 22 '22 at 17:58
  • 1
    BostonBSD, It would be insightful to know _why_ you want to know "find out if a non-local timezone is currently daylight savings time or not" as the higher level problem might be easier to solve than this. – chux - Reinstate Monica Apr 22 '22 at 17:59

3 Answers3

3

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

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
2

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.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • An example of setting the TZ env variable can be found [here](https://www.ibm.com/docs/en/zos/2.3.0?topic=functions-tzset-set-time-zone) – NorthStar Apr 21 '22 at 21:22
2

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 -231 — but that shouldn't still be my problem then.
  • Print lt->tm_isdst which is the information wanted in the question.
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • This is very similar to the code I wrote after the previous answer, thank you for this example. – NorthStar Apr 22 '22 at 15:11
  • 1
    Good points, @chux. I'm glad that ikegami's answer has been re-accepted. I've updated my code. – Jonathan Leffler Apr 22 '22 at 16:50
  • I wasn't sure if I could accept two answers, I reinstated it because I had selected it first, but the choice is arbitrary. – NorthStar Apr 22 '22 at 17:09
  • You can only accept one answer, @BostonBSD. I'm glad you chose ikegami's answer. Steve Summit has some interesting points too. The C library time handling code is a mess, but fixing it is messier still. Having to mess with environment variables to get a sane answer is antithetical to modern coding styles. – Jonathan Leffler Apr 22 '22 at 17:10
  • @BostonBSD "if I need to know if NYC is currently on DST or STD time?" --> Certainly `lt->tm_isdst` satisfies that. – chux - Reinstate Monica Apr 22 '22 at 17:28
  • What gets printed depends a bit on your system. I showed the output from a Mac running macOS Big Sur 11.6.5, but I got similar output a decade ago too (also on Macs). What you get on other systems depends on the vagaries of that system's C library. Yes, if you run this program during the winter, if you use `TZ=EST5DST`, it should report `DST = 0`. You might be able to use `America/New_York` as another name for that time zone. The whole area is fraught! IANA TZ is good if you're interested (and noisy if you're not). – Jonathan Leffler Apr 22 '22 at 17:40
  • @BostonBSD A lot here depends on how perfect and robust you need this solution to be. The question of "Is location X on DST at time Y?" is an almost impossible one to answer perfectly and in general. It's already hard, and it's getting worse: numerous locations are considering switching to "permanent DST", but it's not obvious what that means: maybe you're always on DST, or maybe daylight time is the new standard time, so you're always on standard time, never on daylight time. – Steve Summit Apr 22 '22 at 17:49
  • On FreeBSD 13.0 "America/New_York" does work as a TZ value with the correct tm_isdst value. This might be safer than using the EST5DST formats which imply a static difference from UTC. – NorthStar Apr 22 '22 at 17:50
  • @BostonBSD Yes, the reported `.tm_isdst` reflects that timezone's daylight time setting for that timestamp. Various answer, like this one, support that. – chux - Reinstate Monica Apr 22 '22 at 17:50
  • 1
    But, yes, looking at `tm_isdst` is probably the only sane way of doing this. (Do *not* look at time zone name strings and try to parse them yourself — you're guaranteed to get into trouble that way.) – Steve Summit Apr 22 '22 at 17:50
  • By the way, unless you're using Windows, you *are* using the IANA tz database. My answer refers to some reference code that comes along with that database, and you're probably not using that code, but whatever system and whatever `mktime`/`localtime` implementation you're using, it's probably running on the IANA tz data underneath. It's used by Unix, Linux, MacOS, Android, and afaik all phones. – Steve Summit Apr 22 '22 at 17:51
  • JonathanLeffler, "no standard format string for printing the time_t type as an integer", sadly true no simple modifier way. `printf("%.0Lf\n", truncl(some_time_t_value));` should do the trick for many cases. – chux - Reinstate Monica Apr 22 '22 at 22:33