How can I access the timezone global variable set by tzset() in Arduino/ESP32

899 views Asked by At

I must be missing something I'm attempting to get the timezone global variable value which is supposed to be set after TZset is called.

void setTimezone(String timezonestr){
   Serial.printf("  Setting Timezone to %s\n",timezonestr.c_str());
   setenv("TZ",timezonestr.c_str(),1);  
  tzset();
    Serial.print(timezone);

}

The error that I'm receiving is that

sketch.ino: In function 'void setup()': sketch.ino:75:24: error: expected primary-expression before ')' token
Serial.print(timezone);

Reading the docs I thought this was a global variable that should be set after tzset is called.

Feel free to take a look at the Messy trial and error code...

https://wokwi.com/projects/359035878235429889

After some searching I was able to find:

After calling the tzset() function in a C program, the timezone information is stored in a number of global variables that can be accessed by your program. These global variables are:

extern long timezone: This variable stores the difference in seconds between Coordinated Universal Time (UTC) and the local standard time. extern int daylight: This variable is non-zero if the local timezone is currently in daylight saving time, and zero otherwise. extern char *tzname[]: This is an array of two character strings that store the names of the local standard time and daylight saving time, respectively. To access these global variables in your program, you can simply refer to them by name, just like any other global variable. For example:

Thanks

                    ^
3

There are 3 answers

4
Steve Summit On

[This is a generic, C-heavy answer, not necessarily specific to a particular platform, let alone Arduino.]

I guess there are two questions here: how can you set the timezone, and how can you confirm what it's set to?

It's true that setting the time zone — like, sadly, many aspects of date and time handling in C — is pretty rinky-dink. The recommended way is indeed to set the TZ environment variable, either before your program starts, or from within it by calling setenv or putenv, followed by tzset.

As a comment pointed out, setting the environment like that is questionable on a bare-metal platform like Arduino.

Other than setting the TZ variable, your only other option for choosing the time zone to use involves the "BSD-inspired" tzalloc and localtime_rz functions, as described in this answer. But those functions are, also sadly, not at all widely available.

And then, once you've set the time zone (if you set it), how do you find out what it is?

You found some text documenting some global variables timezone, daylight, and tzname, but those are very, very old, and (IMO) totally obsolete. (But I might be wrong.) In a comment I suggested that "they may still exist in some traditional Unix environments" but that "I'd be surprised if they exist anywhere else". Well, I'm surprised: they exist right here on my Mac! And on Linux, too. But I'm not surprised if they don't exist on Arduino.

If you're not having any luck accessing a global variable named timezone, it would be worth trying _timezone and __timezone; there's a (slight) chance one of those might work.

But the much better way of discovering UTC offsets, which I recommend, is to use the semistandard tm_gmtoff field in struct tm. That is, after calling

struct tm *tmp = localtime(&t);

see if you can access tmp->tm_gmtoff along with tmp->tm_hour and tmp->tm_min and the rest. The tm_gmtoff field isn't standard, unfortunately, but in my experience most systems support it. If tm_gmtoff doesn't work, try _tm_gmtoff and __tm_gmtoff.

And, finally, if neither timezone nor tm_gmtoff are available to you, there's one last trick — try this squirrelly-looking code:

struct tm *tm2 = gmtime(&t);
tm2->tm_isdst = -1;
time_t t2 = mktime(tm2);
printf("UTC offset: %ld\n", t - t2);

This takes a time_t timestamp (that is, seconds since 1970), converts it to broken-down struct tm values without applying the local time zone offset, then converts it from struct tm right back to time_t while applying the local time zone, meaning that the two time_t values should differ by precisely the local time zone offset. Try that, it almost always works. (Even though it is, as I said, squirrelly, and not strictly portable.)

The remaining issue here is what we even mean by "the current time zone". Both tm_gmtoff and the gmtime/mktime trick I just showed will give you the UTC offset as of the time value you're converting, which means you'll get an offset including Daylight Saving Time adjustments. For example, when I run the code just now, those two methods give me an offset of 14400, or 4 hours, because I'm in the U.S. Eastern time zone, and DST just kicked in here last weekend. The timezone variable, on the other hand, contains 18000, or 5 hours, because Eastern time is nominally UTC-5 (except it's 5 hours behind UTC for much less than half the year, due to DST political follies).

Also, the timezone variable (at least on this system) seems to use the opposite sign convention.

1
Henrique Bucher On

Although Arduino includes a full C++ compiler (GCC) which comes with the C++ standard library (libstdc++), Arduino does not have an operating system and almost consequently, does not include the standard C library, which is the one you are referring to here.

There are some specialized libraries like this one that implement parts of that functionality but they are far from standard.

0
Phil R On

Thanks @SteveSummit,

The "squirrelly-looking code" worked for me although I wasn't sure where the t variable was coming from at first. I put together the following function for anyone who was similarly confused:

// Return the offset from UTC time in hours including
// any daylight savings adjustment 
float getGMTOffset(void) 
{ 
    time_t t, t2; 
    struct tm *tm2; 
    
    time(&t); // get seconds since 1970 
    tm2 = gmtime(&t); // Convert to UTC time 
    tm2->tm_isdst = -1; 
    t2 = mktime(tm2); // Convert back to seconds 
    return((t-t2)/3600); 
}