C code to get local time offset in minutes relativ

2019-04-23 17:44发布

问题:

I didn't find a trivial way to get the time offset in minutes between the local time and the UTC time.

At first I intended to use tzset() but it doesn't provide the daylight saving time. According to the man page, it is simply an integer different of zero if day light saving is in effect. While it is usually an hour, it may be half an hour in some country.

I would prefer avoiding to compute the time difference between current UTC returned by gmtime() and localtime().

A more general solution would give me this information for a specified location and a positive time_t value, or at least locally.

Edit 1: the use case is to get the right local time offset for https://github.com/chmike/timez. BTW, If you thought libc functions to manipulate time were Ok, read this https://rachelbythebay.com/w/2013/03/17/time/.

Edit 2: the best and simplest solution I have so far to compute the time offset to UTC in minutes is

// Bogus: assumes DST is always one hour
tzset();
int offset = (int)(-timezone / 60 + (daylight ? 60 : 0));

The problem is to determine the real day light saving time.

Edit 3: Inspired by the answer of @trenki, I came up with the following solution. This is a hack in that it tricks mktime() to consider the output of gmtime() as the localtime. The result is inaccurate when the DST change is in the time span between UTC time and localtime.

#include <stdio.h>
#include <time.h>

int main()
{
    time_t rawtime = time(NULL);
    struct tm *ptm = gmtime(&rawtime);
    // Request that mktime() looksup dst in timezone database
    ptm->tm_isdst = -1;                
    time_t gmt = mktime(ptm);
    double offset = difftime(rawtime, gmt) / 60;
    printf("%f\n", offset);
    return 0;
}

回答1:

... to get local time offset ... relative to UTC?

@Serge Ballesta answer is good. So I though I would test it and clean-up a few details. I would have posted as a comment but obviously too big for that. I only exercised it for my timezone, but though others may want to try on their machine and zone.

I made to community wiki as not to garner rep. Imitation is the sincerest form of flattery

This answer is akin to @trenki except that it subtracts nearby struct tm values instead of assuming DST shift is 1 hour and time_t is in seconds.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// return difference in **seconds** of the tm_mday, tm_hour, tm_min, tm_sec members.
long tz_offset_second(time_t t) {
  struct tm local = *localtime(&t);
  struct tm utc = *gmtime(&t);
  long diff = ((local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min))
          * 60L + (local.tm_sec - utc.tm_sec);
  int delta_day = local.tm_mday - utc.tm_mday;
  // If |delta_day| > 1, then end-of-month wrap 
  if ((delta_day == 1) || (delta_day < -1)) {
    diff += 24L * 60 * 60;
  } else if ((delta_day == -1) || (delta_day > 1)) {
    diff -= 24L * 60 * 60;
  }
  return diff;
}

void testtz(void) {
  long off = -1;
  int delta = 600;
  for (time_t t = 0; t < LONG_MAX-delta; t+=delta) {
    long off2 = tz_offset_second(t);

    // Print time whenever offset changes.
    if (off != off2) {
      struct tm utc = *gmtime(&t);
      printf("%10jd %04d-%02d-%02dT%02d:%02d:%02dZ\n", (intmax_t) t,
              utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
              utc.tm_hour, utc.tm_min, utc.tm_sec);
      struct tm local = *localtime(&t);
      off = off2;
      printf("%10s %04d-%02d-%02d %02d:%02d:%02d %2d %6ld\n\n", "",
              local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
              local.tm_hour, local.tm_min, local.tm_sec, local.tm_isdst ,off);
      fflush(stdout);
    }
  }
  puts("Done");
}

Output

                                  v----v  Difference in seconds
         0 1970-01-01T00:00:00Z
           1969-12-31 18:00:00  0 -21600

   5731200 1970-03-08T08:00:00Z
           1970-03-08 03:00:00  1 -18000

  26290800 1970-11-01T07:00:00Z
           1970-11-01 01:00:00  0 -21600

...

2109222000 2036-11-02T07:00:00Z
           2036-11-02 01:00:00  0 -21600

2120112000 2037-03-08T08:00:00Z
           2037-03-08 03:00:00  1 -18000

2140671600 2037-11-01T07:00:00Z
           2037-11-01 01:00:00  0 -21600

Done


回答2:

This C code computes the local time offset in minutes relative to UTC. It assumes that DST is always one hour offset.

#include <stdio.h>
#include <time.h>

int main()
{
    time_t rawtime = time(NULL);
    struct tm *ptm = gmtime(&rawtime);
    time_t gmt = mktime(ptm);
    ptm = localtime(&rawtime);
    time_t offset = rawtime - gmt + (ptm->tm_isdst ? 3600 : 0);

    printf("%i\n", (int)offset);
}

It uses gmtime and localtime though. Why don't you want to use those functions?



回答3:

IMHO the only foolproof and portable way is to use localtime and gmtime and manually compute the delta in minute because those 2 functions exist on all known systems. For example:

int deltam() {
    time_t t = time(NULL);
    struct tm *loc = localtime(&t);
    /* save values because they could be erased by the call to gmtime */
    int loc_min = loc->tm_min;
    int loc_hour = loc->tm_hour;
    int loc_day = loc->tm_mday;
    struct tm *utc = gmtime(&t);
    int delta = loc_min - utc->tm_min;
    int deltaj = loc_day - utc->tm_mday;
    delta += (loc_hour - utc->tm_hour) * 60;
    /* hack for the day because the difference actually is only 0, 1 or -1 */
    if ((deltaj == 1) || (deltaj < -1)) {
        delta += 1440;
    }
    else if ((deltaj == -1) || (deltaj > 1)) {
        delta -= 1440;
    }
    return delta;
}

Beware, I did not test all possible corner cases, but it could be a starting point for your requirement.



回答4:

Does your system's strftime() function support the %z and %Z specifiers? On FreeBSD,

 %Z    is replaced by the time zone name.

 %z    is replaced by the time zone offset from UTC; a leading plus sign
       stands for east of UTC, a minus sign for west of UTC, hours and
       minutes follow with two digits each and no delimiter between them
       (common form for RFC 822 date headers).

and I can use this to print this:

$ date +"%Z: %z"
CEST: +0200

ISO C99 has this in 7.23.3.5 The strftime function:

%z     is replaced by the offset from UTC in the ISO 8601 format
       ‘‘−0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich),
       or by no characters if no time zone is determinable. [tm_isdst]
%Z     is replaced by the locale’s time zone name or abbreviation, or by no
       characters if no time zone is determinable. [tm_isdst]


标签: c time offset utc