Reverse of gmtime() ?

I've been trying to understand the time commands all day. To convert a UTC time value to a tm structure there are 2 possibilities: gmtime() breaks the time down into its components without taking the set time zone into account (so it remains UTC). localtime() takes the set time zone into account when converting to struct and thus generates a local time. This conversion can be undone with mktime(), taking into account the set time zone, in order to obtain a UTC time value again. But how I reverse the result of gmtime(), so UTC struct to UTC time value? Here my sketch (I hope I have understood everything correctly):

void setup() {

  Serial.begin(115200);
  Serial.println();

  time_t value_UTC = 1729601386; // Tue Oct 22 2024 12:49:46 UTC (14:49:46 in Berlin)
  tm tm_local;
  tm tm_UTC;

  localtime_r(&value_UTC, &tm_local);
  Serial.println(tm_local.tm_hour); // =12 converts to local, but with default setting (UTC+0)

  gmtime_r(&value_UTC, &tm_UTC);
  Serial.println(tm_UTC.tm_hour); // =12 remains UTC, regardless of the set TZ

  setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); // set TZ of Berlin
  tzset();

  localtime_r(&value_UTC, &tm_local);
  Serial.println(tm_local.tm_hour); // =14 converts to local, with TZ of Berlin

  gmtime_r(&value_UTC, &tm_UTC);
  Serial.println(tm_UTC.tm_hour); // =12 remains UTC, regardless of the set TZ

}

void loop() {}

I'm guess there's not much need to do that. Just do all your internal time keeping with Epoch (UTC) time_t values and only convert to local time when you need to display time to a person.

My problem: I have parsed a UTC time from an UTC timestamp. So I can easily fill the tm structure with it:

"20241021T095705" -->
tm_year = int("2024") - 1900
tm_mon = int("10") - 1
tm_mday = int("21")
tn_hour = ..... and so on

Now I need the corresponding UTC time value for this UTC tm structure (that is the missing path in my sketch above)...

Why? What do you intended to do with the "parsed" UTC time?

It's the same UTC time_t timestamp that you started with and "parsed" in the first place.

If you have a list of string formated UTC times (for example as text file or recieved from a html site) like "20241021T095705" or "2024-05-13, 14:00:32"and you are supposed to work with this data, then you have to read it in first! Logically, you have to make a time value from the strings (you have to "parse" the data).

Yes I could reset the time zone and use mktime() but my question was about a reverse function of gmtime()....

You can compute an offset (in seconds) between local time and UTC. Then apply that to the result of mktime(). Try this and see if it produces the offset between your local time and UTC:

	time_t epoch {0};
	tm *timeStruct {gmtime(&epoch)};
	time_t localOffset{mktime(timeStruct)};
	Serial.print("UTC Offset: ");
	Serial.println(localOffset);

I think this will not work. The offset is not a constant value here in Germany. The offset depends on the date! In summer the offset is 2 hours, in winter only 1 hour.

mktime() takes the local time (including DST) into account, so it might. Perhaps you should just try it today and then when your DST ends (Nov 2 in USA).

https://linux.die.net/man/3/timegm

The functions timelocal() and timegm() are the inverses of localtime(3) and gmtime(3).

These functions are nonstandard GNU extensions that are also present on the BSDs. Avoid their use; see NOTES.

For a portable version of timegm(), set the TZ environment variable to UTC, call mktime(3) and restore the value of TZ.

1 Like

OK, we note: In the ESP32 environment there is no inverse function to gmtime(). A practical solution is to reset the time zone to UTC-0 to perform a time zone independent conversion with mktime() and restore the previous time zone after this operation This works. Here my example code to parse formatted date strings (Z = Zulu = UTC) to convert them into UTC values:

// Converts UTC dates formatted as strings into UTC values
// works only for dates formatted like "20241020T182340Z"

time_t timegm_x(struct tm *datetime) { // my own timegm() function
  String TZ_previous = String(getenv("TZ")); // save the previous time zone
  setenv("TZ", "UTC0", 1); // reset time zone to default (UTC)
  tzset();
  time_t UTC = mktime(datetime); // convert to UTC value without time zone (UTC)
  setenv("TZ", TZ_previous.c_str(), 1); // set the previous time zone
  tzset();
  return UTC;
}

time_t ParseDateUTC(String stamp) {
  time_t UTC = -1; // return -1 if this function fails
  if ((stamp.length() == 16) and (stamp.charAt(8) == 'T') and (stamp.charAt(15) == 'Z')) {
    tm datetime = {0};
    datetime.tm_year  = stamp.substring( 0,  4).toInt() - 1900;
    datetime.tm_mon   = stamp.substring( 4,  6).toInt() - 1;
    datetime.tm_mday  = stamp.substring( 6,  8).toInt();
    datetime.tm_hour  = stamp.substring( 9, 11).toInt();
    datetime.tm_min   = stamp.substring(11, 13).toInt();
    datetime.tm_sec   = stamp.substring(13, 15).toInt();
    datetime.tm_isdst = -1; // important to indicate that tm_isdst is no input!!!
    UTC = timegm_x(&datetime);
  }
  return UTC;
}

void setup() {
  setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1); // set time zone Berlin
  tzset();
  Serial.begin(115200);
  Serial.println(ParseDateUTC("20240716T104055Z")); // Tue Jul 16 2024 10:40:55 UTC --> 1721126455
  Serial.println(ParseDateUTC("20221125T163210Z")); // Fri Nov 25 2022 16:32:10 UTC --> 1669393930
}

void loop() {}

Thanks @all for hints.

One problem with freely converting between local time and UTC is that there are cases when the same local time can map to two different UTC times. This can happen if daylight saving time is in operation.

I've also had this problem writing code to update an RTC and lashed a gmtime() inverse together out of bits I've found:

void DS1307RTCct::timegm_rA( time_t *tOut, const struct tm *tme) {

	/*
	 *  void timegm_rA( time_t *tOut, const struct tm *tme)
	 *
	 *  arguments:
	 *  tOut : output into reference to time_t
	 *  tme  : input struct reference (broken down time)
	 *
	 *  converts struct tme (broken down time) into time_t (seconds from 01.01.1970)
	 *  Returns -1 if tme represents a date between 01.01.1900 upto (but not including) 01.01.1970
	 *    which is supported by tm but not time_t
	 *  this is the reverse of gmtime_r
	 *  It is like mktime (but treats the input and output as UTC.
	 *    That is, it does not skew the results, depending on the curent time zone, DST rules etc.
	 *
	 *  Based heavily on the Arduino TimeLib.h / Time.cpp by Paul Stoffregen
	 *    https://github.com/PaulStoffregen/Time but changed for different epochs, enumerations etc.
	 *
	 *  Named with a suffix A (timegm_rA) to make a clash with any existing library
	 *    name less likely.
	 *
	 *  Tested in an ESP8266
	 *
	 *
	 *  see:
	 *  https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/newlib/time.h
	 *
	 */


	const time_t SECS_PER_MIN = 60UL ;
	const time_t SECS_PER_HOUR = 3600UL ;
	const time_t SECS_PER_DAY = 86400 ;

	//leap year calculator expects year argument as years offset from 1970
#define LEAP_YEAR_rA(Y)     ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) )

	static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // this array starts from 0

	*tOut = -1 ;

	int i;
	time_t seconds = 0 ;  // was uint32_t
	int year_ = tme->tm_year - 70 ;   // tm_year is years from 1900, time_t epoch from 1970.

	if ( year_ < 0 )  return ;  // tm structure is years from 1900 , time_t from 1970;

	// seconds from 1970 till 1 jan 00:00:00 of the given year
	seconds = year_*(SECS_PER_DAY * 365);
	for (i = 0; i < year_ ; i++) {
		if (LEAP_YEAR_rA(i)) {
			seconds += SECS_PER_DAY;   // add extra days for leap years
		}
	}

	// add days for this year, months start from 1
	for (i = 0; i < tme->tm_mon; i++) {
		if ( (i == 1) && LEAP_YEAR_rA(year_)) {
			seconds += SECS_PER_DAY * 29;
		} else {
			seconds += SECS_PER_DAY * monthDays[ i ];  // monthDay array starts from 0  //fdm
		}
	}

	seconds+= (tme->tm_mday-1) * SECS_PER_DAY;
	seconds+= tme->tm_hour * SECS_PER_HOUR;
	seconds+= tme->tm_min * SECS_PER_MIN;
	seconds+= tme->tm_sec;

	*tOut = seconds ;
}

See also this: time - Why there is no inverse function for gmtime in libc? - Stack Overflow

and this if you want all the gory details: Time, Clock, and Calendar Programming In C

and here, Andreas Spiess attributes (jocularly) the mess of Unix time handling to the hippie era in which it was developed and the prevalence of LSD : https://www.youtube.com/watch?v=r2UAmBLBBRM

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.