2

I have the following code to parse a datetime string received from GPS satellites into the struct tm, and then use mktime() to get the epoch from it, the result is correct on my Debian machine, but wrong on my ESP32 with ESP-IDF, do you have any suggestion on why this is happening, is something wrong with DST or timezone stuff?

#include "rs_time.h"

time_t time_from_gnss_info_time(const char * datetime_str){
    time_t epoch;
    struct tm tm;
    sscanf(
        datetime_str,
        "%4d%2d%2d%2d%2d%2d",
        &tm.tm_year,
        &tm.tm_mon,
        &tm.tm_mday,
        &tm.tm_hour,
        &tm.tm_min,
        &tm.tm_sec
    );
    epoch = mktime(&tm); // result is '1462765068' or Mon May  9 03:37:48 2016
    printf("the date and time is: %s %ld ",ctime(&epoch), time(NULL));
    return epoch;

}

the value for epoch after using mktime() when the datetime_str is '20210913221332' is: 1462765068, also the ctime() representation is : Mon May 9 03:37:48 2016

Ahmad Mansoori
  • 177
  • 1
  • 11
  • 2
    Your `tm_year` usage is wrong: From man `ctime`: `int tm_year; /* Year - 1900 */ `. It should be `2021 - 1900 = 31` – Mathieu Sep 27 '22 at 11:29
  • 3
    You don't initialize `tm` – stark Sep 27 '22 at 11:31
  • 3
    You must set `tm.tm_isdst` before passing `tm` to `mktime`. If unsure, you can simply set it to `-1` or some other negative value, in order to indicate that it is unknown. You should not leave that object uninitialized, as this could provide false information to `mktime` about whether daylight savings was in effect. – Andreas Wenzel Sep 27 '22 at 11:32
  • 4
    @Mathieu right, but 2021 - 1900 = 121 :-) Old people like me still remember Y2K, wehn `year % 100` became wrong – Ingo Leonhardt Sep 27 '22 at 11:37

1 Answers1

3
  • .tm_year is years since 1900. .tm_mon is months since January.

  • mktime() uses all members of struct tm (except for .tm_wday, .tm_yday) to form the return value. OP's code did not initialize/set .tm_isdst or others - struct tm may have more than usual 9 members. Initialize them all.

  • mktime() forms a time_t assuming struct tm is a local time, not universal time which is usually what GPS provides. TZ adjustment may be needed - not yet shown.

  • time_t does not certainly match a long. Cast to a wide type. (time_t may even be floating point.) and match specifiers.

  • Add error checking.

time_t time_from_gnss_info_time(const char *datetime_str) {
  // Code is likely scanning per the current daylight time setting.
  // Use .tm_isdst = -1 to let mktime to determine it.
  // Or use 0 for standard time or any + value for daylight time.
  // All other members are zeroed
  // struct tm tm; // Not this
  struct tm tm = {.tm_isdst = -1};
  if (sscanf(datetime_str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
      &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
    return -1;
  }
  // Adjust some members.
  tm.tm_year -= 1900;
  tm.tm_mon--;
  time_t epoch = mktime(&tm);
  printf("The date and time is: %s%lld\n", ctime(&epoch),
      (long long) time(NULL));
  return epoch;
}

int main(void) {
  time_t t = time_from_gnss_info_time("20210913221332");
  printf("%lld\n", (long long) t);
}

Output

The date and time is: Mon Sep 13 22:13:32 2021
1664290016
1631589212

Additional error checking possible: extra txt, spaces, signs, extreme values, ...


An improve string parsing

// if (sscanf(datetime_str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon,
//    &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {

// Look for trailing junk.
char ch;
if (sscanf(datetime_str, "%4d%2d%2d%2d%2d%2d%c", &tm.tm_year, &tm.tm_mon,
  &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &ch) != 6) {
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • **thank you** for sharing your knowledge, and especially for `sscanf` error checking and for your tip about casting the value, also I think the struct tm should be initialized with `tm_isdst` instead of `isdst` – Ahmad Mansoori Sep 27 '22 at 14:48
  • 1
    @AhmadMansoori Code corrected. – chux - Reinstate Monica Sep 27 '22 at 14:49
  • @AhmadMansoori Original code printed the current `time_t`. I'd expect `epoch` was desired. – chux - Reinstate Monica Sep 27 '22 at 14:51
  • 1
    yes, you are right, it was my desire too, this part was a mistake. – Ahmad Mansoori Sep 27 '22 at 14:53
  • Whats the reason for years since 1900? I see it in the man page, but like why – lmurdock12 Mar 17 '23 at 08:16
  • 1
    @lmurdock12 "the reason for years since 1900" is a good question. 1) When `struct tm` was designed it was _very_ common to quote the year in 2 digits, even more so than today as there was no centaury/millennium issue for over 70s years. 2) Design of `struct tm` was not focused on its longevity, but its simplicity. 3) 10 years later MS use 1980 as the epoch year" in file time-stamping. 4) Recall many decisions are based on cost. Think that then memory cost was a [million times](https://ourworldindata.org/grapher/historical-cost-of-computer-memory-and-storage?country=~OWID_WRL) more that today. – chux - Reinstate Monica Mar 17 '23 at 08:32