@ 261102.1

261102.1 · 5c001b18d7237cce1fdba63608732163f978dd23

Fix mktime/0 to allow the last two seconds of 1969 (#3671)

The mktime/0 function failed for 1969-12-31T23:59:59Z with "invalid gmtime representation", because its result -1 is also the failure value of timegm(), and for 1969-12-31T23:59:58Z with "mktime not supported on this platform", because of a leftover check for -2.

Tell a failure apart from the valid result -1 by tm_wday, which is set to -1 before the conversion and overwritten only on success, and remove the check for -2. Now `-2, -1 | gmtime | mktime` round-trip to -2 and -1, as do normalized inputs such as [1970,0,0,23,59,59,0,0], which gives -1. Inputs that the platform rejects, such as years before 1900 on macOS, still fail with "invalid gmtime representation".

reading declarations

src/builtin.c

@@ -1572,20 +1572,23 @@ static int setenv(const char *var, const char *val, int ovr)  * Our wrapper tries to use timegm() if available, or mktime() and  * correct for its side-effects if possible.  *- * Returns (time_t)-2 if mktime()'s side-effects cannot be corrected.+ * Returns (time_t)-1 on failure, which is also the valid result for+ * 1969-12-31T23:59:59Z. Tell them apart by tm_wday, which is set to -1 here+ * and overwritten by the conversion only on success.  */ static time_t my_mktime(struct tm *tm) {+  tm->tm_wday = -1; #ifdef HAVE_TIMEGM   return timegm(tm); #elif HAVE_TM_TM_GMT_OFF    time_t t = mktime(tm);-  if (t == (time_t)-1)+  if (t == (time_t)-1 && tm->tm_wday == -1)     return t;   return t + tm->tm_gmtoff; #elif HAVE_TM___TM_GMT_OFF   time_t t = mktime(tm);-  if (t == (time_t)-1)+  if (t == (time_t)-1 && tm->tm_wday == -1)     return t;   return t + tm->__tm_gmtoff; #elif WIN32@@ -1793,10 +1796,8 @@ static jv f_mktime(jq_state *jq, jv a) {   if (!jv2tm(a, &tm, 0))     return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));   time_t t = my_mktime(&tm);-  if (t == (time_t)-1)+  if (t == (time_t)-1 && tm.tm_wday == -1)     return jv_invalid_with_msg(jv_string("invalid gmtime representation"));-  if (t == (time_t)-2)-    return jv_invalid_with_msg(jv_string("mktime not supported on this platform"));   return jv_number(t); } 

tests/jq.test

@@ -2002,6 +2002,14 @@ null null [1900,0,1,10,15,0,1,0] +[-3,-2,-1,0,1 | gmtime | mktime]+null+[-3,-2,-1,0,1]++[1970,0,0,23,59,59,0,0] | mktime+null+-1+ # Check day-of-week and day of year computations # (should trip an assert if this fails) last(range(365 * 67)|("1970-03-01T01:02:03Z"|strptime("%Y-%m-%dT%H:%M:%SZ")|mktime) + (86400 * .)|strftime("%Y-%m-%dT%H:%M:%SZ")|strptime("%Y-%m-%dT%H:%M:%SZ"))