12

Working on an Ubuntu 12.04.3 LTS box, I just noticed that localtime() and localtime_r() behave differently when the system's timezone changes during the lifetime of a process: localtime() picks up the timezone change immediately, whereas localtime_r() does not, it seems to stick to what was the timezone at the launch of the process. Is this expected behavior? I haven't seen this covered anywhere.

More precisely, when I use the following code ...

#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

int main() {
  while (1) {
    time_t t = time(NULL);
    struct tm *tm = localtime(&t);
    printf("localtime:%02d/%02d/%02d-%02d:%02d:%02d\n",
           tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
           tm->tm_hour, tm->tm_min, tm->tm_sec);
    sleep(1);
  }
  return 0;
}

... and change the timezone from UTC via ...

# echo 'Europe/Berlin' > /etc/timezone 
# sudo dpkg-reconfigure --frontend noninteractive tzdata

... then the code produces the following, ...

localtime:10/04/2013-01:11:33
localtime:10/04/2013-01:11:34
localtime:10/04/2013-01:11:35
localtime:10/03/2013-23:11:36
localtime:10/03/2013-23:11:37
localtime:10/03/2013-23:11:38

... but if I use:

#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

int main() {
  while (1) {
    time_t t = time(NULL);
    struct tm local_tm;
    struct tm *tm = localtime_r(&t, &local_tm);    
    printf("localtime_r:%02d/%02d/%02d-%02d:%02d:%02d\n",
           tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
           tm->tm_hour, tm->tm_min, tm->tm_sec);
    sleep(1);
  }
  return 0;
}

... then there's no change when doing a similar timezone change:

localtime_r:10/04/2013-01:15:37
localtime_r:10/04/2013-01:15:38
localtime_r:10/04/2013-01:15:39
localtime_r:10/04/2013-01:15:40
localtime_r:10/04/2013-01:15:41
localtime_r:10/04/2013-01:15:42

UPDATE: adding a call to tzset() before invoking localtime_r() produces the expected behavior. Whether that's clear from the spec/manpage or not (see discussion below) is a question for mentalhealth.stackexchange.com...

Christian
  • 1,499
  • 2
  • 12
  • 28

1 Answers1

8

See this following documentation:

The localtime() function converts the calendar time timep to broken-down time representation, expressed relative to the user's specified timezone. The 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, and daylight to a nonzero value if daylight savings time rules apply during some part of the year. The return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions. The localtime_r() function does the same, but stores the data in a user-supplied struct. It need not set tzname, timezone, and daylight.

From: http://linux.die.net/man/3/localtime_r

So as far as I can tell, it appears that the code is working as I'd expect.

Edited to add more from the same documentation:

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

It'sPete
  • 5,083
  • 8
  • 39
  • 72
  • 1
    Thanks Pete, I saw that. To me the sentence "It does not set tzname, timezone, and daylight" conveys something very different -- these are external values. My code uses none of these, and my question is about the return value of localtime()/localtime_r(), and the snippet you post says "does the same". – Christian Oct 03 '13 at 23:33
  • 1
    "The function acts as if it called tzset(3)" in localtime(), not localtime_r(). The timezone is never set when you call localtime_r(). So basically, as the documentation is showing, localtime_r() never sets the timezone, and therefore you will always get UTC time. Try calling tzset() prior to localtime_r and they should behave the exact same way. – It'sPete Oct 03 '13 at 23:39
  • 2
    I see your point -- but then shouldn't the text say something about the extent to which these two functions *rely* on the value of the tzname/timezone/daylight globals, not about the extent to which they *set* it? I find this really confusing, because *my* code doesn't care in any way about the values of these globals. – Christian Oct 03 '13 at 23:41
  • Also, for the record, the spec is correct -- adding tzset() produces consistent behavior. – Christian Oct 03 '13 at 23:46
  • Yea, Linux man pages are cyptic. I find that a strong ale prior to attempting to read them helps. Cheers! – It'sPete Oct 03 '13 at 23:46
  • *lol* -- less coffee and more beer sounds just about perfect now... Cheers! – Christian Oct 03 '13 at 23:47