<ctime> gmtime() returns wrong results?

Hi Guys, I am trying to use the default c datetime functions, but it seems like there are bugs in the code.
When create a struct tm type from a timestamp, it is 30 years and 1 day off !!?! (I tried multiple timestamps, The time is constantly right):

#include <stdint.h>
#include <time.h>
#include <stdio.h>

void setup() {
  Serial.begin(9600);
}

void loop() 
{  
    Serial.println("Timestamp:  1537216259 is equivalent to");
    Serial.println("GMT time:   2018-09-17 20:30:59");
    Serial.println();
    time_t t = 1537216259;
    tm* dt = gmtime(&t);
    dt->tm_isdst = 0;
    Serial.print("Year  should be 2018 - 1900 = 118, got: "); Serial.println(dt->tm_year);    
    Serial.print("Month should be        09 - 1 = 8, got: "); Serial.println(dt->tm_mon);
    Serial.print("Day   should be                17, got: "); Serial.println(dt->tm_mday);
    Serial.print("Got Time: ");    
    Serial.print(dt->tm_hour);    Serial.print(":");
    Serial.print(dt->tm_min);     Serial.print(":");
    Serial.println(dt->tm_sec);   Serial.println();    
    while(1);
}

Prints:

Timestamp:  1537216259 is equivalent to
GMT time:   2018-09-17 20:30:59

Year  should be 2018 - 1900 = 118, got: 148
Month should be        09 - 1 = 8, got: 8
Day   should be                17, got: 16
Got Time: 20:30:59

Hmm. I ran your code on my Arduino Primo and got this:

Timestamp: 1537216259 is equivalent to
GMT time: 2018-09-17 20:30:59

Year should be 2018 - 1900 = 118, got: 118
Month should be 09 - 1 = 8, got: Timestamp: 1537216259 is equivalent to
GMT time: 2018-09-17 20:30:59

Year should be 2018 - 1900 = 118, got: 118
Month should be 09 - 1 = 8, got: 8
Day should be 17, got: 17
Got Time: 20:30:59

Looks like things match. May be integer resolution issue.

https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html

Though not specified in the standard, it is often expected that time_t is a signed integer representing an offset in seconds from Midnight Jan 1 1970... i.e. 'Unix time'. This implementation uses an unsigned 32 bit integer offset from Midnight Jan 1 2000. The use of this 'epoch' helps to simplify the conversion functions, while the 32 bit value allows time to be properly represented until Tue Feb 7 06:28:15 2136 UTC. The macros UNIX_OFFSET and NTP_OFFSET are defined to assist in converting to and from Unix and NTP time stamps.

Ah thanks for the info, I was suspecting maybe they moved the AVR epoch 30 years into the future to extend the present working age a bit, but what confuses me is the one day off issue, on my mega only it seems. Very strange.

I first wrote some code and ran it on my computer GNU C++ compiler and it worked fine, then I saw these issues when running on the AVR.

mangelozzi:
but what confuses me is the one day off issue, on my mega only it seems. Very strange.

There are 7 leap days between 1 Jan 1970 and 1 Jan 2000 (1972, '76, '80, '84, '88, '92, '96),
but 8 leap days between 17 Sep 2018 and 16 Sep 2048 (2020, '24, '28, '32, '26, '40, '44, '48).
The difference is 30*365+7 days in both cases.

Thanks for the AVR info oqibidipo, and explanation about the leap day.

I see now one gets the expected result by subtracting the AVR epoch offset:
i.e. changing this line:

time_t t = 1537216259

to

time_t t = 1537216259 - UNIX_OFFSET; //946684800;

I don't see how DaleScott got the correct result without taking this into account.

DaleScott:
I ran your code on my Arduino Primo

Different board, not based on AVR but nRF52, different C library.

Ah thanks, I see see, I didnt realise there were non AVR arduino boards.

So heres the other half of the puzzle, converting from tm to time, and after much confusion, I finally narrowed it down to a single print statement which is changing the timestamp?!?! :o I kid you not, if you uncomment out the line as indicated below, the correct timestamp is displayed! Shows something going on I don't understand, which makes me concerned to use the function.

#include <stdint.h>
#include <time.h>
#include <stdio.h>

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

void loop() 
{  
    Serial.println("GMT time:           2018-09-17 20:30:59 is equivalent to");
    Serial.println("Timestamp:          1537216259");
    Serial.println();
    
    tm dt1;
    dt1.tm_year = 2018-1900;
    dt1.tm_mon  = 9-1;
    dt1.tm_mday = 17;
    
    dt1.tm_hour = 20;
    dt1.tm_min  = 30;
    dt1.tm_sec  = 59;
    
    time_t t1 = mktime(&dt1);    
    Serial.print("Got Y2K timestamp:  ");   
    Serial.println(t1);                   // !!! Comment out this line
    
    t1 += UNIX_OFFSET; //946684800;    
    Serial.print("Got UNIX timestamp: ");
    Serial.println(t1);
    while(1);
}

Prints:

GMT time:           2018-09-17 20:30:59 is equivalent to
Timestamp:          1537216259

Got Y2K timestamp:  590530179
Got UNIX timestamp: 1537214979

As you can see the value is 1280s short (21 mins and 20s).

You have random daylight saving time.
Add

dt1.tm_isdst = 0;

or use mk_gmtime() instead of mktime().

oqibidipo:
You have random daylight saving time.
Add

dt1.tm_isdst = 0;

or use mk_gmtime() instead of mktime().

Thanks again for great help! I see mk_gmtime saves almost 1000 bytes, nice!

I read about daylight savings and timezone, and my deduction was it defaults to UTC with no daylight savings, but I guess I was wrong!