1

I'm parsing datetimes from text file using this:

#define DATETIME_FORMAT "%Y-%m-%d %H:%M:%S"
  if (!strptime(datetimestr, DATETIME_FORMAT, &record->datetime)) {
    return 0;
  } else {
    record->message_size = strlen(messagestr) - 1;
    messagestr[record->message_size] = '\0';
    strcpy(record->message, messagestr);
    printf("parsed '%s' '%s' %ld\n", messagestr, datetimestr, mktime(&record->datetime));
    return 1;
  }

And I get these prints

parsed 'ETC' '2023-04-03 09:00:19' 1680508819
parsed 'StandUp' '2023-04-03 09:00:47' 1680508847
parsed 'Stop' '2023-04-03 09:11:55' 1680505915

But when I paste them to online epoch converter I get that:

  • 1680508819 => GMT: Monday 3. April 2023 8:00:19
  • 1680508847 => GMT: Monday 3. April 2023 8:00:47
  • 1680505915 => GMT: Monday 3. April 2023 7:11:55

So why is strptime and mktime transforming third date completely differently?

tino415
  • 109
  • 2
  • 10
  • Can you please [edit] your question to elaborate on your question. What did you expect to happen? Which of the three date-time stamps are wrong? All three? One? Can you provide a link to the converter you used (there are many available)? – Some programmer dude May 01 '23 at 08:28
  • On a totally different (and off-topic) note, do you get `messagestr` from `fgets`? Because if you do and try to remove the trailing newline, your method is flawed and could lead to problems. The first problem is if the string is empty, because then `message_size` will become `-1` which is wrong. Or a very large value if it's an unsigned type. Neither value can be used as index into the array. The second problem is that `fgets` might not add the newline, if the input is too long. Making you loose the last character of the input. – Some programmer dude May 01 '23 at 08:34
  • For a simple solution to your (possible) `fgets` problems, see [Removing trailing newline character from fgets() input](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input) – Some programmer dude May 01 '23 at 08:34
  • 4
    The function `strptime()` fills only fields of `struct tm` it can read. In particular what's the value of **Daylight Saving Time flag** (tm_isdst). You must reset the `record->datetime` before calling `strptime` to deactivate **Daylight Saving Time flag**. – dalfaB May 01 '23 at 10:21
  • 2
    Expanding on @dalfaB's comment: Before calling `mktime`, you need to set `record->datetime.tm_isdst`. You should probably set it to -1, indicating that you're not sure whether daylight saving time applied, meaning that `mktime` should figure it out. – Steve Summit May 01 '23 at 10:27
  • 1
    Also, it would probably be a good idea on general principles to call `memset(&record->datetime, 0, sizeof(struct tm))` right before the call to `strptime`. – Steve Summit May 01 '23 at 11:44
  • Please publish a [minimal, complete and verifiable example](https://stackoverflow.com/help/minimal-reproducible-example) as you don't show your variable definitions, type definitions of pointers you use, and how do you initialize your variables. – Luis Colorado May 03 '23 at 17:49
  • Between second and third timestamp there's a time lapse of 48' 52'', which is exaclty the time diffrerence shown in the online tester... where does this come from? cannot say, as you don't whos a complete and verifiable example. – Luis Colorado May 03 '23 at 17:53

2 Answers2

5

why is strptime and mktime transforming third date completely differently?

mktime() risks inconsistent behavior when struct tm not completely assigned.


strptime(datetimestr, DATETIME_FORMAT, &record->datetime) fills some of the struct tm members of record->datetime. tm should be initialized before the call.

mktime() uses the members assigned by strptime(..., %Y-%m-%d %H:%M:%S", ... plus additional members including .tm_isdst, which accounts for an off-by-one hour.

Before strptime(), zero fill*1 the entire struct tm and set .tm_isdst to -1 (Let mktime() determine DST setting). @dalfaB

// Add
memset(&record->datetime, 0, sizeof record->datetime);
record->datetime.tm_isdst = -1;

if (!strptime(datetimestr, DATETIME_FORMAT, &record->datetime)) {
  ...
  printf("parsed '%s' '%s' %lld\n", 
      messagestr, datetimestr, (long long) mktime(&record->datetime));

Aside: Use a matching print specifier. "%ld" may not match time_t. Suggest a cast to a wide type.


Note that mktime() reads the struct tm as a local time, not certainly GMT nor UTC.


*1 Detail: struct tm has at least 9 members. 2 (.tm_yday, .tm_wday) are ignored by gmtime() when determining the return time_t. As various implementations may have more than the 9 standard members, we want to initialize/assign all the members for consistent results.

struct tm tm = { 0 }; or memset(&tm, 0, sizeof tm); are 2 ways to do so.

strptime(datetimestr, %Y-%m-%d %H:%M:%S", &record->datetime) sets the the 6 expected members (and .tm_yday, .tm_wday), but not .tm_isdst, nor possibly other tm members.

Code could zero initialize and set .tm_isdst:

struct tm tm = { .tm_isdst = -1 };

Code could assign .tm_isdst and zero other members with a compound literal.

record->datetime = (struct tm){ .tm_isdst = -1 };
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
3
  1. Initialize struct tm.

  2. strptime() return NULL or a pointer to where the parser stopped. Presumably it's an error if it's NULL or it doesn't point to a \0.

  3. The way you trim off the last character of messagestr is problematic. Use messagestr[strcspn(messagestr, "\n")] = '\0'; if you are trimming a newline.

I am not able to reproduce your findings using the following minimal example. Please let me know what timezone (TZ) you are using and what result you get for the last datetime string.

#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>

#define DATETIME_FORMAT "%Y-%m-%d %H:%M:%S"

int main(void) {
    char *dts[] = {
        "2023-04-03 09:00:19",
        "2023-04-03 09:00:47",
        "2023-04-03 09:11:55"
    };
    for(size_t i = 0; i < sizeof dts / sizeof *dts; i++) {
        struct tm tm = {0};
        char *s = strptime(dts[i], DATETIME_FORMAT, &tm);
        if(!s || *s) {
            printf("'%s' failed\n", dts[i]);
            continue;
        }
        printf("'%s' %ld\n", dts[i], mktime(&tm));
    }
}

and here is the corresponding output:

'2023-04-03 09:00:19' 1680526819
'2023-04-03 09:00:47' 1680526847
'2023-04-03 09:11:55' 1680527515
Allan Wind
  • 23,068
  • 5
  • 28
  • 38
  • 1
    Thank you, it looks like problem was that I was not initialising that tm struct and I got randomly some timezone there – tino415 May 04 '23 at 20:55