Get the time zone GMT offset in C
Asked Answered
L

13

20

I'm using the standard mktime function to turn a struct tm into an epoch time value. The tm fields are populated locally, and I need to get the epoch time as GMT. tm has a gmtoff field to allow you to set the local GMT offset in seconds for just this purpose.

But I can't figure out how to get that information. Surely there must be a standard function somewhere that will return the offset? How does localtime do it?

Lording answered 10/12, 2012 at 15:37 Comment(1)
"tm has a gmtoff field" --> that is a non-standard C library extension.Kobi
L
5

I guess I should have done a bit more searching before asking. It turns out there's a little known timegm function which does the opposite of gmtime. It's supported on GNU and BSD which is good enough for my purposes. A more portable solution is to temporarily set the value of the TZ environment variable to "UTC" and then use mktime, then set TZ back.

But timegm works for me.

Lording answered 10/12, 2012 at 17:27 Comment(1)
Link says "These functions are nonstandard GNU extensions that are also present on the BSDs. Avoid their use."Clementinaclementine
C
26

Just do the following:

#define _GNU_SOURCE /* for tm_gmtoff and tm_zone */

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

/* Checking errors returned by system calls was omitted for the sake of readability. */
int main(void)
{
  time_t t = time(NULL);
  struct tm lt = {0};

  localtime_r(&t, &lt);

  printf("Offset to GMT is %lds.\n", lt.tm_gmtoff);
  printf("The time zone is '%s'.\n", lt.tm_zone);

  return 0;
}

Note: The seconds since epoch returned by time() are measured as if in Greenwich.

Chloechloette answered 10/12, 2012 at 16:21 Comment(4)
I really like this simple solution, but I found one problem with your code. I think struct tm = {0}; should be struct tm result = {0}; and then you can use result instead of tm in the rest of the code. +1 from me if you could change that.Nosedive
Also I would like to see the code for windows. Any help appreciated!Rutheruthenia
tm_gmtoff, tm_zone are non-C Std extensions.Kobi
I guessed what is %lds. Then it turns out %ld and s :DPredispose
P
7

How does localtime do it?

According to localtime man page

The localtime() function acts as if it called tzset(3) and sets the external variables tzname with information about the current timezone, timezone with the difference between Coordinated Universal Time (UTC) and local standard time in seconds

So you could either call localtime() and you will have the difference in timezone or call tzset():

extern long timezone;
....
tzset();
printf("%ld\n", timezone);

Note: if you choose to go with localtime_r() note that it is not required to set those variables you will need to call tzset() first to set timezone:

According to POSIX.1-2004, localtime() is required to behave as though tzset() was called, while localtime_r() does not have this requirement. For portable code tzset() should be called before localtime_r()

Prana answered 10/12, 2012 at 16:32 Comment(1)
This method does not take the DST into account.Hl
A
7

The universal version of obtaining local time offset function is here.
I borrowed pieces of code from this answer in stackoverflow.

int time_offset()
{
    time_t gmt, rawtime = time(NULL);
    struct tm *ptm;

#if !defined(WIN32)
    struct tm gbuf;
    ptm = gmtime_r(&rawtime, &gbuf);
#else
    ptm = gmtime(&rawtime);
#endif
    // Request that mktime() looksup dst in timezone database
    ptm->tm_isdst = -1;
    gmt = mktime(ptm);

    return (int)difftime(rawtime, gmt);
}
Aiguillette answered 19/5, 2017 at 7:7 Comment(1)
Asking mktime() to determine DST is problematic, because it may be unable to do that, and because ptm is not a local time.Hl
L
5

I guess I should have done a bit more searching before asking. It turns out there's a little known timegm function which does the opposite of gmtime. It's supported on GNU and BSD which is good enough for my purposes. A more portable solution is to temporarily set the value of the TZ environment variable to "UTC" and then use mktime, then set TZ back.

But timegm works for me.

Lording answered 10/12, 2012 at 17:27 Comment(1)
Link says "These functions are nonstandard GNU extensions that are also present on the BSDs. Avoid their use."Clementinaclementine
D
3

This is the portable solution that should work on all standard C (and C++) platforms:

const std::time_t epoch_plus_11h = 60 * 60 * 11;
const int local_time = localtime(&epoch_plus_11h)->tm_hour;
const int gm_time = gmtime(&epoch_plus_11h)->tm_hour;
const int tz_diff = local_time - gm_time;

Add std:: namespace when using C++. The result is in hours in the range [-11, 12];

Explanation: We just convert the date-time "1970-01-01 11:00:00" to tm structure twice - with the local timezone and with the GMT. The result is the difference between hours part.

The "11:00::00" has been chosen because this is the only time point (considering GMT) when we have the same date in the whole globe. Because of that fact, we don't have to consider the additional magic with date changing in the calculations.

WARNING

Previous version of my answer worked only on linux:

// DO NOT DO THAT!!
int timezonez_diff = localtime(&epoch_plus_11h)->tm_hour -
                     gmtime(&epoch_plus_11h)->tm_hour;

This may not work because the storage for result tm object returned as a pointer from localtime and gmtime may be shared (and it is on windows/msvc). That's whe I've introduced temporaries for calculation.

Dampen answered 30/7, 2020 at 14:35 Comment(4)
Nice and simple. But not all Time Zones are in whole number of hours. Best to compute the minuets as wellPavilion
How does that handle DST?Heraldry
Upvoted for the nice idea, but there isn't really a moment when the globe shares the same date. The span of time zones is from UTC-12 to UTC+14 😂Guendolen
This does not handle DST correctly.Hl
Z
2

Here's a two-liner inspired by @Hill's and @friedo's answers:

#include <time.h>
...
time_t rawtime = time(0);
timeofs = timegm(localtime(&rawtime)) - rawtime;

Returns offset from UTC in seconds.

Doesn't need _GNU_SOURCE defined, but note that timegm is not a POSIX standard and may not be available outside of GNU and BSD.

Zinfandel answered 11/6, 2019 at 1:43 Comment(0)
U
1

I believe the following is true in linux at least: timezone info comes from /usr/share/zoneinfo/. localtime reads /etc/localtime which should be a copy of the appropriate file from zoneinfo. You can see whats inside by doing zdump -v on the timezone file (zdump may be in sbin but you don't need elevated permissions to read timezone files with it). Here is a snipped of one:

/usr/share/zoneinfo/EST5EDT  Sun Nov  6 05:59:59 2033 UTC = Sun Nov  6 01:59:59 2033 EDT isdst=1 gmtoff=-14400
/usr/share/zoneinfo/EST5EDT  Sun Nov  6 06:00:00 2033 UTC = Sun Nov  6 01:00:00 2033 EST isdst=0 gmtoff=-18000
/usr/share/zoneinfo/EST5EDT  Sun Mar 12 06:59:59 2034 UTC = Sun Mar 12 01:59:59 2034 EST isdst=0 gmtoff=-18000
/usr/share/zoneinfo/EST5EDT  Sun Mar 12 07:00:00 2034 UTC = Sun Mar 12 03:00:00 2034 EDT isdst=1 gmtoff=-14400
/usr/share/zoneinfo/EST5EDT  Sun Nov  5 05:59:59 2034 UTC = Sun Nov  5 01:59:59 2034 EDT 

I guess you could parse this yourself if you want. I'm not sure if there is a stdlib function that just returns the gmtoff (there may well be but I don't know...)

edit: man tzfile describes the format of the zoneinfo file. You should be able to simply mmap into a structure of the appropriate type. It appears to be what zdump is doing based on an strace of it.

Udella answered 10/12, 2012 at 16:0 Comment(0)
H
1
#include <time.h>
int time_offset()
{
    time_t tim1 = time(NULL);
    struct tm *tmp;
    time_t gm_time;
    int tm_isdst;

    // find out if DST is in effect
    tmp = localtime(&tim1);
    if((int)tmp == 0) return 0;
    tm_isdst = tmp->tm_isdst;

    // get the GM clock reading
    tmp = gmtime(&tim1);
    if ((int)tmp == 0) return 0;

    // treat the GM clock reading as a local time
    tmp->tm_isdst = tm_isdst;
    gm_time = mktime(tmp);
    if((int)gm_time == -1) return 0;

    return (int)difftime(tim1, gm_time);
}
Hl answered 29/5, 2023 at 15:26 Comment(0)
K
0

Ended up with this. Sure tm_secs is redundant, just for a sake of consistency.

int timezone_offset() {
    time_t zero = 0;
    const tm* lt = localtime( &zero );
    int unaligned = lt->tm_sec + ( lt->tm_min + ( lt->tm_hour * 60 ) ) * 60;
    return lt->tm_mon ? unaligned - 24*60*60 : unaligned;
}
Kistler answered 25/8, 2020 at 10:0 Comment(0)
C
0

Here is my way:

time_t z = 0;
struct tm * pdt = gmtime(&z);
time_t tzlag = mktime(pdt);

Alternative with automatic, local storage of struct tm:

struct tm dt;
memset(&dt, 0, sizeof(struct tm));
dt.tm_mday=1; dt.tm_year=70;
time_t tzlag = mktime(&dt);

tzlag, in seconds, will be the negative of the UTC offset; lag of your timezone Standard Time compared to UTC:

LocalST + tzlag = UTC

If you want to also account for "Daylight savings", subtract tm_isdst from tzlag, where tm_isdst is for a particular local time struct tm, after applying mktime to it (or after obtaining it with localtime ).

Why it works:
The set struct tm is for "epoch" moment, Jan 1 1970, which corresponds to a time_t of 0. Calling mktime() on that date converts it to time_t as if it were UTC (thus getting 0), then subtracts the UTC offset from it in order to produce the output time_t. Thus it produces negative of UTC_offset.

Cell answered 13/11, 2021 at 0:17 Comment(0)
S
0

Here is one threadsafe way taken from my answer to this post:
What is the correct way to get beginning of the day in UTC / GMT?

::time_t GetTimeZoneOffset ()
{ // This method is to be called only once per execution
  static const seconds = 0; // any arbitrary value works!
  ::tm tmGMT = {}, tmLocal = {}; 
  ::gmtime_r(&seconds, &tmGMT); // ::gmtime_s() for WINDOWS
  ::localtime_r(&seconds, &tmLocal);  // ::localtime_s() for WINDOWS
  return ::mktime(&tmGMT) - ::mktime(&tmLocal);
};
Sulfanilamide answered 3/12, 2022 at 5:25 Comment(0)
V
0

Thinking of Mariusz Jaskółka's answer, I came across with this solution:

tzset();
        
const time_t some_time = 60 * 60 * 100;
const tm local_time = *local_time(&some_time);
const tm gm_time = *gmtime(&some_time);

int32_t h_diff, m_diff, s_diff;

if (local_time.tm_yday > gm_time.tm_yday) {
    h_diff = local_time.tm_hour + (24 - gm_time.tm_hour);
} else if (local_time.tm_yday < gm_time.tm_yday) {
    h_diff = - (gm_time.tm_hour + (24 - local_time.tm_hour));
} else {
    h_diff = local_time.tm_hour - gm_time.tm_hour;
}

// resulting difference in seconds
s_diff = h_diff * 60 * 60 + local_time.tm_min * 60;

It takes the day difference into consideration.

Viscounty answered 7/7, 2023 at 10:38 Comment(0)
R
0

That's my portable approach by substracting local tz time from UTC+0 time:

int GetTzBias() 
{
    time_t currentTime = time(0);
    auto gmtTime = *std::gmtime(&currentTime);
    auto localTime = *std::localtime(&currentTime);

    uint64_t gmt = gmtTime.tm_year * 31556926 + gmtTime.tm_yday * 86400 + gmtTime.tm_hour * 3600 + gmtTime.tm_min * 60;
    uint64_t lcl = localTime.tm_year * 31556926 + localTime.tm_yday * 86400 + localTime.tm_hour * 3600 + localTime.tm_min* 60;

    return (lcl - gmt)/60;
}

Taking account day, month and year changes. Bias expressed in Minutes, value is negative in the WEST and positive in the EAST of Greenwich meridian

Runty answered 8/2 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.