ESP8266 time() etc.
TL;DR : Is there a reliable and robust timezone/DST-neutral way of converting a UTC time in a tm struct to a time_t UTC Unix epoch date. That is an equivalent of the timegm() extension found on some C/C++ platforms. ?
The details:
I'm attempting to update an old project which uses an ESP8266, NTP and a DS3231 RTC module. It uses various Arduino libraries including the TimeZone library. The ESP8266 Arduino platform now has built in functions for obtaining an NTP timestamp and updating the ESP8266 system time, so theoretically, this should now be a simple task.
I run the RTC in UTC and the RTC internally requires the date to be in "broken down time" format. That is day, month, year, hour, minute, second instead of a Unix time stamp.
That is no problem. There is a standard C++ time library function gmtime(). It takes the current time (UTC) and updates a struct of type tm. That is all standard C/C++ stuff:
time_t timeNowUTC = time( nullptr ) ;
tm timeStruc ; // broken down time
gmtime_r( &timeNowUTC, &timeStruc ) ;
So, with timeStruct, I can set the time on the RTC having obtained the "broken down" time format of the current UTC time, and maybe applying a few corrections to compensate for months sometimes being enumerated as a range 0 to 11 and at other times 1 to 12, different epochs etc. etc.
The problem now comes when reading the RTC because, in the standard C++ time library, there is no reverse of the gmtime() method of converting a "broken down" time into a UTC Unix time stamp. The nearest equivalent is mktime() but that is timezone aware and, in this case, does an unwanted conversion to the local time zone. Many C++ implementations have a non standard extension timegm() for doing just that. Not, however, the ESP8266.
Apparently, the GNU C Library documentation recommends the following equivalent, which effectively sets the time zone to UTC, uses the standard mktime, then reverts the timezone data to its original value ;
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz) {
setenv("TZ", tz, 1);
free(tz);
} else
unsetenv("TZ");
tzset();
return ret;
}
The problem is that that does not work with the ESP8266 Arduino core. The combination of setenv("TZ", "", 1) and tzset() does not prevent mktime() from applying an unwanted time zone skew to the returned result.
However, the ESP8266 Arduino time.h comes with something similar, namely configTime(). But there are 2 signatures for it, an "old" format and a "new" format.
The new format of configtime() is as simple as this: configTime( "" , "ch.pool.ntp.org" ) where the first argument is the timezone, in this case, the empty string which indicates UTC but could also be something like "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" when setting the time zone (and DST rules).
The problem, in this case, is that the new format signature also fails to clean out the current time zone / DST rules and revert to UTC.
The old format of configtime() that is: configTime( 0 , 0, "ch.pool.ntp.org" . . . ) where the first and second arguments are timezone offset Hours and DST respectively does, however, work in this case.
The simple solution would be to use the old format of configtime(). However, the risk is that I will be relying on some bizarre side effects of some "old" function which may be changed at some later date.
Is there another way of achieving the same thing, namely a robust, timezone/DST-neutral way of converting a tm struct to a UTC unix time stamp?
I've included a test sketch (a slightly bloated MCVE). 5 constants have to be set at the top of the sketch to demonstrate the failure and success conditions. It simply takes a time_t UTC time value, converts it into a tm struct, then un-converts it back to a UTC time_t value with varying degress of success.
I'll probably open an issue at some stage but that would be unlikely to solve my immediate problem.
ConfigTimeErrorDemo.ino (6.17 KB)