3

I'm using strptime(3) to parse a string representing a date:

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

int main () {
  struct tm t;
  strptime("2015-04-19 12:00:00", "%F %T", &t); /* Sunday */
  printf("%d\n", t.tm_wday); /* Should print 0 */
  return 0;
}

That date is a Sunday, according to the output of cal -y 2015. But when I compile this on OSX (presumably with clang) it prints 6:

$ gcc timetest.c ; ./a.out
6

whereas on Debian it prints the (correct) 0:

$ gcc timetest.c ; ./a.out
0

Any explanation for the difference?

UPDATE

Here is the same program, except that t is initialised with a valid time and I'm reporting the return value of strptime():

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

int main () {
  time_t epoch = 0;
  struct tm t;
  char *ret;
  t = *localtime(&epoch);
  ret = strptime("2015-04-19 12:00:00", "%F %T", &t); /* Sunday */
  printf("%d\n", t.tm_wday); /* Should print 0 */
  printf("strptime() returned %p (%d)\n", ret, *ret);
  return 0;
}

Here is the output:

$ gcc timetest.c ; ./a.out
6
strptime() returned 0x10c72af83 (0)

Here is the clang version I use:

$ clang -v
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin16.1.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
lindelof
  • 34,556
  • 31
  • 99
  • 140
  • What's the return value of strptime() on OSX? What happens when you initialize `t` with a valid date before calling strptime()? – Jens Nov 23 '16 at 12:21
  • I've updated the program according to @Jens's suggestion. – lindelof Nov 23 '16 at 12:30
  • It gives 0 for me on OS X. `clang-700.1.81` – John Zwinck Nov 23 '16 at 12:32
  • Taking your code verbatim and running on macOS Sierra 10.12.1 (using either GCC 6.2.0 or Clang from XCode 8.0), I get the result 6. I added two lines: `time_t rt = mktime(&t);` after the call to `strptime()` and `printf("%lld\n", (long long)rt);` and the result changed to 0. Now, for that, I have no explanation! – Jonathan Leffler Nov 23 '16 at 21:14
  • @JonathanLeffler right, I can confirm that following the call to `strptime()` with a call to `mktime()` is a usable workaround for me. But I am at a loss to explain the observed behaviour. – lindelof Nov 23 '16 at 21:16
  • Use `struct tm t = {0}; tm.tm_isdst = -1;` _before_ using `&t` in later code. – chux - Reinstate Monica Dec 12 '16 at 19:06

2 Answers2

1

I think the reason is simply that, by design, the strptime function only sets the fields that appear in the format. Essentially, strptime(3) just parses fields from a given string using a supplied format into a referenced structure and performs no other computation or logic. Since your code uses the format %F %T then only the fields corresponding to %Y-%m-%d and %H:%M:%S (namely tm_{year,mon,mday,hour,min,sec}) are modified.

You can experiment by explicitly setting t.tm_wday to some known value that shouldn't by set by strptime (e.g. 123) and verify that it is not changed by that call. Note that you should probably initialize your struct tm before playing with it since any of those fields may contain random values, e.g. struct tm t; memset((void *) &t, 0, sizeof(t));.

Moreover, this Linux strptime(3) man page contains the following note which leads me to believe that the special behavior it describes is non-standard (though obviously desirable):

The glibc implementation does not touch those fields which are not explicitly specified, except that it recomputes the tm_wday and tm_yday field if any of the year, month, or day elements changed.

This answer shows how you can use the trio of strptime/mktime/localtime (or gmtime) to populate the tm.tm_wday field for dates after the UNIX epoch.

Community
  • 1
  • 1
maerics
  • 151,642
  • 46
  • 269
  • 291
0

Following on from the observation in the comments, here's a program derived from your code which illustrates, even if it does not explain, what is going on:

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

static void dump_struct_tm(const char *tag, const struct tm *t)
{
    printf("%s:\n", tag);
    printf("  Time: %.2d:%.2d:%.2d  ", t-> tm_hour, t->tm_min, t->tm_sec);
    printf("  Date: %.4d-%.2d-%.2d\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday);
    printf("  Wday: %d Yday: %.3d (DST %d Zone [%s] offset %ld)\n", 
           t->tm_wday, t->tm_yday, t->tm_isdst, t->tm_zone, t-> tm_gmtoff);
}

int main(void)
{
    time_t epoch = 0;
    struct tm t;
    char *ret;
    t = *localtime(&epoch);
    dump_struct_tm("Epoch", &t);
    putchar('\n');

    ret = strptime("2015-04-19 12:00:00", "%F %T", &t);
    dump_struct_tm("strptime()", &t);
    time_t rt = mktime(&t);
    dump_struct_tm("mktime()", &t);
    printf("Weekday: %d\n", t.tm_wday);
    printf("strptime() returned %p (%d)\n", ret, *ret);
    printf("Unix time: %lld\n\n", (long long)rt);

    t.tm_isdst = -1;
    ret = strptime("2015-04-19 12:00:00", "%F %T", &t);
    dump_struct_tm("strptime()", &t);
    rt = mktime(&t);
    dump_struct_tm("mktime()", &t);
    printf("Weekday: %d\n", t.tm_wday);
    printf("strptime() returned %p (%d)\n", ret, *ret);
    printf("Unix time: %lld\n\n", (long long)rt);

    return 0;
}

This analyzes the struct tm (as defined by the manual page on macOS Sierra) at different points. Note how the setting of tm_isdst alters the behaviour.

Epoch:
  Time: 16:00:00    Date: 1969-12-31
  Wday: 3 Yday: 364 (DST 0 Zone [PST] offset -28800)

strptime():
  Time: 12:00:00    Date: 2015-04-19
  Wday: 6 Yday: 108 (DST 0 Zone [(null)] offset -28800)
mktime():
  Time: 13:00:00    Date: 2015-04-19
  Wday: 0 Yday: 108 (DST 1 Zone [PDT] offset -25200)
Weekday: 0
strptime() returned 0x100c82f0c (0)
Unix time: 1429473600

strptime():
  Time: 12:00:00    Date: 2015-04-19
  Wday: 6 Yday: 108 (DST -1 Zone [(null)] offset -25200)
mktime():
  Time: 12:00:00    Date: 2015-04-19
  Wday: 0 Yday: 108 (DST 1 Zone [PDT] offset -25200)
Weekday: 0
strptime() returned 0x100c82f0c (0)
Unix time: 1429470000

I'm still not clear why strptime() mispopulates the tm_wday field, especially since it seems to get the tm_yday field correct. The 19th of April is day 108 of the year when the 1st of January is day 0.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278