ESP32 settimeofday functionality giving odd results

I have an ESP32 project that needs to know the current datetime. I noticed an odd issue where the time was sometimes wroing.

After lots of testing, I have now narrowed the issue down to something specific and I’m baffled as to why it’s happening.

For the purposes of demonstrating the issue, I have a function that sets a fixed datetime (midnight on Saturday 26th September 2020) and then prints that datetime.

This function is called from my main loop.

I have also copy-pasted the code from that function into the main loop directly.

Here is the code (this is the entirety of it, nothing omitted):

#include <Esp.h>
#include <esp_system.h>
#include <sys/time.h>

void setup()
{
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    esp_log_level_set("*", ESP_LOG_VERBOSE);
}

void time_sync_loop()
{   
    struct tm timeinfo;
    timeval tv;
    timezone tz;
    timezone tz_utc = {0,0};

    // Set a fixed time of 2020-09-26 00:00:00, UTC
    tv.tv_sec = time_t(1601078400);
    tz.tz_minuteswest = 0;
    tz.tz_dsttime = 0;
    settimeofday(&tv, &tz_utc);

    // Get the time and print it
    time_t now = time(nullptr);
    gmtime_r(&now, &timeinfo);

    Serial.print("Function: ");
    Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

void loop()
{  
    // Runs the function (prints a time that is behind)

    time_sync_loop();
    delay(1000);

    // Run the same code directly (prints a time that is 2m14s fast)

    struct tm timeinfo;   
    timeval tv;
    timezone tz;
    timezone tz_utc = {0,0};

    tv.tv_sec = time_t(1601078400);
    tz.tz_minuteswest = 0;
    tz.tz_dsttime = 0;
    settimeofday(&tv, &tz_utc);

    time_t now = time(nullptr);
    gmtime_r(&now, &timeinfo);

    Serial.print("Main: ");
    Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

    delay(1000);
}

Here is the serial output:

Function: Friday, September 25 2020 23:34:44
Main: Saturday, September 26 2020 00:02:14
Function: Friday, September 25 2020 23:24:13
Main: Saturday, September 26 2020 00:02:14
Function: Friday, September 25 2020 23:24:13
Main: Saturday, September 26 2020 00:02:14
Function: Friday, September 25 2020 23:24:13
Main: Saturday, September 26 2020 00:02:14
Function: Friday, September 25 2020 23:24:13
Main: Saturday, September 26 2020 00:02:14

The function prints a time that is 35m47s behind ahead of the set time (except for the first call, where it is 25m16s ahead). The code in the main loop prints the a time that is 2m14s shead of the set time.
This behaviour is consistent across resets of the ESP32.

I have no idea why this would be happening. The code in the function and the loop are identical, they do not share any variables.

Can anyone shed any light on this?

it might be the timezone struct is deprecated in favor of environment variable

try something like this

// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html

#include <sys/time.h>

void setup() {
  Serial.begin(115200);

  // set current day/time
  struct timeval tv;
  tv.tv_sec =   1601216683;  // enter UTC UNIX time (get it from https://www.unixtimestamp.com )
  settimeofday(&tv, NULL);

  // Set timezone to France (Europe/Paris)
  setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/ 3", 1); // https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
  tzset();
}

void loop() {
  time_t now;
  struct tm timeDetails;

  time(&now);
  localtime_r(&now, &timeDetails);

  Serial.print("The current date/time in Paris is ");
  Serial.println(&timeDetails, "%A, %B %d %Y %H:%M:%S");

  delay(1000);
}

OK, that works, thanks. I get the same, correct, result from both the function and the code in the loop.

I'd still like to answer the deeper question of why it's wrong (and differently wrong) in both functions.

The Arduino ESP32 platform uses the esp-idf framework, as I understand it?

In that implementation of settimeofday, the tz pointer isn't used at all, so I'm not sure how passing a non-NULL pointer is affecting things.

Yeah it’s weird.

Just a guess, as I did not dig much into it

The doc states

Timezones

To set local timezone, use setenv and tzset POSIX functions. First, call setenv to set TZ environment variable to the correct value depending on device location. Format of the time string is described in libc documentation. Next, call tzset to update C library runtime data for the new time zone. Once these steps are done, localtime function will return correct local time, taking time zone offset and daylight saving time into account.

An assumption would be that if you don’t set the time zone you might just have random garbage in memory depending on context of execution of the call to gmtime_r() ? Would be good to locate source code for this function you call. (In my version I call localtime_r() )