Using portable version of timegm() without affecting other processes

Hello everyone, currently I am using a portable version of the timegm() function (it's available on man page of timegm()) which temporarily sets the value of the global TZ environment variable to UTC. The current issue is that if at the same time another process makes a call to a time-related function (that depends on TZ) for getting local time,it'll get an unexpected result as TZ is set to UTC.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <time.h>
#include <stdlib.h>

time_t my_timegm (struct tm *tm){
  time_t ret;
  char *tz;

  tz = getenv("TZ");
  setenv("TZ", "", 1);
  tzset();
  ret = mktime(tm);
  if (tz)
     setenv("TZ", tz, 1);
  else
     unsetenv("TZ");
  tzset();
  return ret;
}

Is there any way I can safely modify the global variable TZ without negatively affecting other processes? I'd appreciate any help regarding this. Thanks!
Last edited on
From a Linux mktime man page:
tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time.

So you can call mktime directly and check the modified tm->tim_isdst to check it DST was applied, then subtract an hour from the returned time_t.

1
2
3
4
5
6
7
time_t my_timegm(struct tm *tm) {
  time_t ret = mktime(tm);
  if (tm->tm_isdst)
    ret -= 3600;

  return ret;
}
Last edited on
Thanks for your reply, buddy.

I want only UTC time, not local time. Suppose there are two processes A and B. Process A modifies the TZ variable temporarily to the UTC timezone, calls mktime(), to make it think thinks that it's input argument is in the UTC timezone and gets the desired results...BUT at the same time, Process B calls mktime() too.

What happens now is that Process B's mktime() will think it's input argument is in the UTC timezone and it'll return the result accordingly. BUT process B was expecting the result to be according to the local timezone.

This situation takes place because TZ is modified by process A and this has an adverse effect on process B.Is there any way we can prevent it?
I want only UTC time, not local time.
That's why I subtracted an hour if we converted a local time.

Suppose there are two processes A and B....
What you want is a thread-safe way of managing TZ. But mktime() does that for you, and returns tm->tm_isdst to tell you what it did.

The contention is not between processes, but threads within a process, as one process will not (normally) change the environment of another.
Last edited on
I really appreciate that you're taking your time out to help me. Thank you.

Is there any other way we can do this without modifying TZ?

My main objective here is to give a date and time in UTC timezone as an input to a function, and get milliseconds since Unix Epoch.

The issue with this is that mktime() considers it's input to be in local timezone and timegm() can't be used because it's a nonstandard GNU extension and the man page ( http://man7.org/linux/man-pages/man3/timegm.3.html#top_of_page ) says avoid it's use.

Then I used boost library to get the current UTC time in milliseconds since Unix Epoch.

ptime curTime(microsec_clock::universal_time());

This approach works, but my concern is... the boost documentation ( https://www.boost.org/doc/libs/1_67_0/doc/html/date_time/posix_time.html ) for microsec_clock::universal_time() says that on Unix systems it is implemented using GetTimeOfDay(). In my opinion, this implementation is possible only if we pass the second argument to it which is a struct timezone. But the catch here is that the man page ( https://linux.die.net/man/2/gettimeofday ) says that that argument should normally be null.

Can you please share your opinion on this?
Last edited on
My main objective here is to give a date and time in UTC timezone as an input to a function, and get milliseconds since Unix Epoch.
The milli-sec thing is tricky. time_t and struct tm only describe seconds. That's why struct timeval and struct timespec were created.

gettimeofday() will get the the time down to the nearest micro-sec, using a struct timeval. If you want sub-second, you should use that on POSIX.

The issue with this is that mktime() considers it's input to be in local timezone and timegm() can't be used because it's a nonstandard GNU extension and the man page ( http://man7.org/linux/man-pages/man3/timegm.3.html#top_of_page ) says avoid it's use.
There's nothing wrong with mktime(). The man page is asking you not to use timegm() or localtime(), and point out that you could use mktime() instead.

Can you please share your opinion on this?
I think you should use gettimeofday(). It gives you exactly what you want, and it's pretty standard to use this to get the current time.
Last edited on
I think you should use gettimeofday(). It gives you exactly what you want, and it's pretty standard to use this to get the current time.

Yes,we can do that but the only issue is that gettimeofday() refers to the current local time for giving us milliseconds since epoch.

My objective is to refer the current UTC time and convert it to milliseconds since epoch.

gettimeofday() can be used to do what I require but for that we'll have to pass in a second argument but we can't do so because it is supposed to be null according to the man page.

Is there any way this can be resolved?
Last edited on
Yes,we can do that but the only issue is that gettimeofday() refers to the current local time for giving us milliseconds since epoch.
Yes, you're right.

There are a couple of extensions to struct tm that can help. They're not described by Posix, but they're been around for ever and are available in Linux and BSD.

This is from my local Linux box:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* ISO C `broken-down time' structure.  */
struct tm
{
  int tm_sec;           /* Seconds. [0-60] (1 leap second) */
  int tm_min;           /* Minutes. [0-59] */
  int tm_hour;          /* Hours.   [0-23] */
  int tm_mday;          /* Day.     [1-31] */
  int tm_mon;           /* Month.   [0-11] */
  int tm_year;          /* Year - 1900.  */
  int tm_wday;          /* Day of week. [0-6] */
  int tm_yday;          /* Days in year.[0-365] */
  int tm_isdst;         /* DST.     [-1/0/1]*/

# ifdef __USE_MISC
  long int tm_gmtoff;       /* Seconds east of UTC.  */
  const char *tm_zone;      /* Timezone abbreviation.  */
# else
  long int __tm_gmtoff;     /* Seconds east of UTC.  */
  const char *__tm_zone;    /* Timezone abbreviation.  */
# endif
};


And from 4.3 BSD (reno):
1
2
3
4
5
6
7
8
9
10
11
12
13
struct tm {
    int tm_sec;     /* seconds after the minute [0-60] */
    int tm_min;     /* minutes after the hour [0-59] */
    int tm_hour;    /* hours since midnight [0-23] */
    int tm_mday;    /* day of the month [1-31] */
    int tm_mon;     /* months since January [0-11] */
    int tm_year;    /* years since 1900 */
    int tm_wday;    /* days since Sunday [0-6] */
    int tm_yday;    /* days since January 1 [0-365] */
    int tm_isdst;   /* Daylight Savings Time flag */
    long    tm_gmtoff;  /* offset from CUT in seconds */
    char    *tm_zone;   /* timezone abbreviation */
};


tm_gmtoff is the number of seconds you need to add to get you back to GMT/UTC.

For example, for EST, tm_gmtoff = -18000, and for BST tm_gmtoff = 3600.

So, putting it all together:
1. call gettimeofday() to get the localtime in seconds.microsec
2. call localtime to fill in a struct tm
3. check tm_isdst, if it's set, add tm_gmtoff from the struct timeval.

This C program demonstrates the idea:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sys/time.h>
#include <time.h>
#include <stdio.h>

struct timeval get_gmtime() {
    struct timeval tv;
    struct tm tm;

    gettimeofday(&tv, NULL);

    localtime_r(&tv.tv_sec, &tm);
    if (tm.tm_isdst)
        tv.tv_sec += tm.tm_gmtoff;

    return tv;
}

int main() {
    struct timeval tv = get_gmtime();

    printf("now: %ld.%06ld\n", tv.tv_sec, tv.tv_usec);
    return 0;
}


EDIT
I say add gmtoff, but it might be subtract. I haven't thought it thru fully. Please double check.
Last edited on
I got you. Also, thanks for the detailed explanation.

I have a small doubt though:

3. check tm_isdst, if it's set, add tm_gmtoff from the struct timeval.

Shouldn't I add/subtract tm_gmtoff regardless of whether tm_isdst is set? Because our objective here is to get back to UTC from local timezone.

So, I think it should be like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <sys/time.h>
#include <time.h>
#include <stdio.h>

struct timeval get_gmtime() {
    struct timeval tv;
    struct tm tm;

    gettimeofday(&tv, NULL);

    localtime_r(&tv.tv_sec, &tm);
    if (tm.tm_isdst) {
	
		tv.tv_sec -= 3600 // To cancel the effect of DST 
    }
	tv.tv_sec += tm.tm_gmtoff;   // Add or subtract to get back to UTC
	return tv;
}

int main() {
    struct timeval tv = get_gmtime();

    printf("now: %ld.%06ld\n", tv.tv_sec, tv.tv_usec);
    return 0;
}


Can you please share your thoughts on this?

And also:

There are a couple of extensions to struct tm that can help


It seems that these extensions are non-standard. Is it possible that if we use these extensions, some problem (e.g. miscalculation) might occur?

Or have these extensions, despite being non-standard, been working correctly for a long time?



Last edited on
Shouldn't I add/subtract tm_gmtoff regardless of whether tm_isdst is set? Because our objective here is to get back to UTC from local timezone.
Yes, you are correct.

So, I think it should be like:
Again, I think you're correct.

It seems that these extensions are non-standard. Is it possible that if we use these extensions, some problem (e.g. miscalculation) might occur?
No, I don't think so. The risk is the fields may not exist or have different names (as does Linux if __USE_MISC is not defined).
Last edited on
Ok. I got it.

You have been of great help,thanks a lot!
Topic archived. No new replies allowed.