2

Our system receives data from a vendor in ASCII format "20210715083015". This time is US Eastern time, and is already adjusted for Daylight Savings.

Our backend needs this time in nanoseconds since Epoch. I'm trying to use mktime() to do the conversion, but mktime() insists on adding an hour for DST, even when I force tm_isdst to 0.

Here's the code snippet:

std::tm tmstr{};
<breakdown ASCII time into tm structure>

tmstr.tm_isdst = 0;
cout << "isdst before: " tmstr.tm_isdst;
time_t seconds = std::mktime(&tmstr);
cout << ", isdst after: " tmstr.tm_isdst << endl;

Here's the output:

isdst before: 0, isdst after: 1

It's ignoring the set value of 0, and applying its own conversion.

How do I use mktime(), or something equivalent, without it trying to adjust the time to my timezone? I'd rather not have to internally set timezones, I just want it to do a straight conversion from a tm structure to seconds.

This is g++ version 7.3.1, under Redhat version 6.10.

  • Can you use [`gmtime`](https://en.cppreference.com/w/c/chrono/gmtime)? You'll have to adjust your string to UTC, of course. – Paul Sanders Jul 21 '21 at 21:29
  • If you look at the man page for mktime, you'll see that is_dst is updated after the call, regardless what you set it to, to give an /indication/ as to whether dst would be in operation at said time. – Bib Jul 21 '21 at 21:32
  • @bib Unfortunately, so documentation I've seen for mktime() says it respects the setting of is_dst on entry, but obviously that's incorrect. – Flyboy Wilson Jul 22 '21 at 17:14
  • @PaulSanders That would require checking to see if we'd cross the day boundary, and also I'd have to know if DST was currently in effect. – Flyboy Wilson Jul 22 '21 at 17:15
  • why don't just use `std::chrono`? – phuclv Sep 26 '22 at 03:07

3 Answers3

0

Easier to post here...

From the mktime() man page, you're not reading what is written...

The mktime() function converts a broken-down time structure, expressed as local time, to calendar time representation. The function ignores the values supplied by the caller in the tm_wday and tm_yday fields. The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure: a positive value means DST is in effect; zero means that DST is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.

The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. Calling mktime() also sets the external variable tzname with information about the current timezone.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Bib
  • 922
  • 1
  • 5
  • 10
  • I did read it, I just misinterpreted that it would adjust it whether I wanted it or not, even when I provided valid values for the other fields. – Flyboy Wilson Jul 22 '21 at 20:24
0

It's ignoring the set value of 0

No, it is not ignoring that member.

mktime() takes .tm_isdst into account and then adjusts all the struct tm members to their usual values for that date, thus a change to .tm_isdst and .tm_hour is expected in OP's case as the tmstr is in daylight time for "20210715" July 15th.


How do I use mktime(), or something equivalent, without it trying to adjust the time to my timezone?

mktime() is a local time conversion. The usual approach is let mktime() deduce the daylight flag.

tmstr.tm_isdst = -1;

This time is US Eastern time, and is already adjusted for Daylight Savings.

No, you are asserting it is US standard Eastern time (EST). US Eastern time (ET) has daylight adjustments.

Change your time zone from one with daylight and standard periods to a zone with only standard. Research setenv(). Perhaps as simple as:

// Implementation dependent code.
setenv("TZ", "EST5", 1);
tzset();
time_t seconds = mktime(&tmstr);

Key issue missing: was the time_t result correct?

Ignore tmstr for a moment. What time_t value was reported? What time_t value was expected?

I suspect OP did get the right time_t value, just not the expected result in struct tm.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I don't bother to look at the tm structure after the conversion. It gave back the time_t set an hour ahead. – Flyboy Wilson Jul 22 '21 at 20:23
  • @FlyboyWilson Please post all the value of all members of `tmstr` just before `gmtime()` function and the function's return numeric result. What is environment var `TZ`? "20210715083015" does not show how `tmstr` was set. Likely it was done right, but perhaps not? – chux - Reinstate Monica Jul 22 '21 at 20:29
  • @FlyboyWilson "I don't bother to look at the tm structure after the conversion." is unclear given the question's `cout << ", isdst after: " tmstr.tm_isdst << endl;` after `mktime()`. – chux - Reinstate Monica Jul 22 '21 at 20:33
0

Hopefully, you could use the timegm() function (not to confound with the gmtime() function... see below for more info).

The mktime() function has a state. If the last time it was used with isdst set to 0, then using isdst = -1 will use 0. If the last time you used it with isdst set to 1, then using isdst = -1 will use 1. This is true only if the function is called with a time when the time change happens (within that 1h window). Other functions are not unlikely to change that variable too (i.e. the localtime() may also affect that state, I haven't tested that).

// a date when the time change happened (Sun Oct 26 01:08:48 PST 1980)
time_t et = 341399328;

struct tm t = {};
localtime_r(&et, &t);

struct tm ot = {};
ot.tm_year = t.tm_year;
ot.tm_mon = t.tm_mon;
ot.tm_mday = t.tm_mday;
ot.tm_hour = t.tm_hour;
ot.tm_min = t.tm_min;
ot.tm_sec = t.tm_sec;

ot.tm_isdst = 1;
std::cerr << " +--> " << mktime(&ot) << " (1)\n";
ot.tm_isdst = 0;
std::cerr << " +--> " << mktime(&ot) << " (0)\n";
ot.tm_isdst = -1;
std::cerr << " +--> " << mktime(&ot) << " (-1)\n"; // same as 0

ot.tm_isdst = 0;
std::cerr << " +--> " << mktime(&ot) << " (0)\n";
ot.tm_isdst = 1;
std::cerr << " +--> " << mktime(&ot) << " (1)\n";
ot.tm_isdst = -1;
std::cerr << " +--> " << mktime(&ot) << " (-1)\n"; // same as 1

The output should always be the same as the input (i.e. convert from time_t to struct tm and vice versa). Instead I get:

341399328 -> 1980/10/26 1:8:48
+--> 341395728 (1)
+--> 341399328 (0)
+--> 341399328 (-1)
+--> 341399328 (0)
+--> 341395728 (1)
+--> 341395728 (-1)

So, more or less, you can't trust the mktime() conversions.

There are now two new functions: timegm() and timelocal(). The timelocal() function is the same as the mktime(). The timegm() totally ignores the locale timezone, so you can convert UTC time back and forth properly. The converse being gmtime().

In other words:

  1. time_t to struct tm with gmtime()
  2. struct tm to time_t with timegm()

and the input time_t in (1) will always equal the output time_t in (2).

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156