最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - What are all the various reasons for `mktime()` and `timegm()` to return `(time_t)-1`? - Stack Overflow

programmeradmin0浏览0评论

Various existing posts (especially non-C ones) ask why mktime() or timegm() return (time_t)-1 yet only 1 or 2 reasons usually appears: result is out of time_t range or negative.

What are all the C reasons for a return of (time_t) -1?

void time_test(const char *s, time_t (*f)(struct tm *tm), const struct tm *ts) {
  struct tm ts1 = *ts;
  errno = 0;
  time_t t = f(&ts1);
  printf("%s %11lld/%02lld/%02d %2d:%02d:%02d "
      "-->%11lld/%02lld/%02d %2d:%02d:%02d errno:%3d time_t:%.19g %s\n", //
      f == mktime ? "mktime" : "timegm", //
      ts->tm_year + 1900LL, ts->tm_mon + 1LL, ts->tm_mday, //
      ts->tm_hour, ts->tm_min, ts->tm_sec, //
      ts1.tm_year + 1900LL, ts1.tm_mon + 1LL, ts1.tm_mday, //
      ts1.tm_hour, ts1.tm_min, ts1.tm_sec, //
      errno, (double) t, s);
}

Various existing posts (especially non-C ones) ask why mktime() or timegm() return (time_t)-1 yet only 1 or 2 reasons usually appears: result is out of time_t range or negative.

What are all the C reasons for a return of (time_t) -1?

void time_test(const char *s, time_t (*f)(struct tm *tm), const struct tm *ts) {
  struct tm ts1 = *ts;
  errno = 0;
  time_t t = f(&ts1);
  printf("%s %11lld/%02lld/%02d %2d:%02d:%02d "
      "-->%11lld/%02lld/%02d %2d:%02d:%02d errno:%3d time_t:%.19g %s\n", //
      f == mktime ? "mktime" : "timegm", //
      ts->tm_year + 1900LL, ts->tm_mon + 1LL, ts->tm_mday, //
      ts->tm_hour, ts->tm_min, ts->tm_sec, //
      ts1.tm_year + 1900LL, ts1.tm_mon + 1LL, ts1.tm_mday, //
      ts1.tm_hour, ts1.tm_min, ts1.tm_sec, //
      errno, (double) t, s);
}
Share Improve this question asked Mar 12 at 2:06 chuxchux 155k17 gold badges151 silver badges301 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 11

So far, 9 potential reasons standard library functions mktime() or timegm() (new in C23) return (time_t) -1 are listed.
I see #4 as the most surprising one.

  1. Out of time_t encodable range
    This is common when time_t is a 32-bit signed integer that counts seconds since Jan 1, 1970 0:00:00 GMT/UTC. 2038 Jan 19 03:14:07 UTC is (time_t) 0x7FFFFFFF and the next second is not representable as a positive time_t. This Y2038 problem is well known such that many systems use a wider or unsigned time_t.

  2. time_t is encodable yet negative (or too small/large)
    Some implementations simply disallow generation of a negative time_t, other than (time_t) -1 to indicate an error. A few cap the smallest/largest year/month/day allowed. Example: Upper limit of 3000 Dec 31 even though ample time_t range remains.

  3. Out of .tm_year range
    mktime() and gmtime() allow struct tm members to contain values outside their primary range. These functions then normalize the members. With extreme years, for example, when .tm_year == INT_MAX and .tm_mon == 24, normalization of .tm_year cannot take place with the value of INT_MAX + 2. Even if the corresponding time_t, perhaps as a 64-bit value, is not so limited.

  4. Non-existent time stamp: year/month/day hour:min:sec
    Only expected with mktime(), some time zones have historically changed their offset from UTC for various reasons. This can result in gaps of valid struct tm. Example Bissau 1975: local time 1974 Dec 31 23:59:59 was followed by 1975 Jan 1 1:00:00. This is not due to a change in daylight savings time. Attempting to convert an in-between struct tm of 1975 Jan 1 0:30:00 can result in (time_t) -1. Many examples exists. As tested, 188 of 590 time zones (32%) had at least 1 such discontinuity - much more common than I thought.

  5. Non-existent time stamp: .tm_isdst > 0
    Like #4 but relating to .tm_isdst > 0. Some time zones never employed daylight savings time. In some of these time zones, .tm_isdst > 0 is allowed and does not affect the the time_t result. .tm_isdst is cleared when the function returns (i.e. daylight time shift is 0). Others do not change the struct tm and the function returns (time_t)-1. This was noted with my GCC timegm() and mktime() and their C compliance is not clear. Notably, timegm() functioned differently than mktime() with time zone "GMT" - which returned (time_t)-1.

  6. Invalid parameter
    If the pointer to the struct tm equals NULL, some implementations are specified to return (time_t) -1. This goes beyond the C spec for which this case is undefined behavior. Example

  7. Normal result
    Implementations that allow negative time_t results and so may normally return -1. When returning -1, an implementation may use some means like errno to indicate if this is an error return. errno with mktime()/timegm() is not specified in the C standard.

  8. Undefined behavior (UB) / bad code/data
    Of course, should UB occur somewhere, any subsequent result is possible. Coding and time zone database errors are possible concerns too.

  9. Function failure
    Some implementations, example, will return (time_t) -1 when failing to converge on a result, possible due to leap seconds. Another function failure (still need to find a code example) involves the function temporarily changing an environment variable and using a mutex lock to prevent race conditions in a mutli-threaded application. When that lock fails, code may return (time_t)-1.


Some example code to demo some of these cases.
Your output can readily differ from this GCC one.

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void time_test(const char *s, time_t (*f)(struct tm *tm), const struct tm *ts) {
  struct tm ts1 = *ts;
  errno = 0;
  time_t t = f(&ts1);
  printf("%s %11lld/%02lld/%02d %2d:%02d:%02d "
      "-->%11lld/%02lld/%02d %2d:%02d:%02d errno:%3d time_t:%.19g %s\n", //
      f == mktime ? "mktime" : "timegm", //
      ts->tm_year + 1900LL, ts->tm_mon + 1LL, ts->tm_mday, //
      ts->tm_hour, ts->tm_min, ts->tm_sec, //
      ts1.tm_year + 1900LL, ts1.tm_mon + 1LL, ts1.tm_mday, //
      ts1.tm_hour, ts1.tm_min, ts1.tm_sec, //
      errno, (double) t, s);
}

#define PROPRERTY_MIN(X) _Generic((X), \
    int: INT_MIN, \
    unsigned: 0, \
    long: LONG_MIN, \
    unsigned long: 0, \
    long long: LLONG_MIN, \
    unsigned long long: 0 \
)

#define PROPRERTY_MAX(X) _Generic((X), \
    int: INT_MAX, \
    unsigned: UINT_MAX, \
    long: LONG_MAX, \
    unsigned long: ULONG_MAX, \
    long long: LLONG_MAX, \
    unsigned long long: ULLONG_MAX \
)

extern void tzset(void);
extern int setenv(const char *name, const char *value, int overwrite);
int set_tz(const char *tz) {
  //tz = getenv("TZ");
  //if (tz) {
  printf("%s\n", tz);
  if (setenv("TZ", tz, 1))
    return 1;
  tzset();
  return 0;
}

void time_tests(void) {
  time_t t;
  (void) t;
  printf("time_t min %+lld\n", (long long) PROPRERTY_MIN(t));
  printf("time_t max %+lld\n", (long long) PROPRERTY_MAX(t));

  struct tm tm0;
  timegm(&tm0);
  struct tm ts_0 = {.tm_year = 1970-1900, .tm_mday = 1 };
  time_test("OK", mktime, &ts_0);
  time_test("OK", timegm, &ts_0);
  ts_0.tm_sec--;
  time_test("OK yet -1 (maybe)", timegm, &ts_0);
  struct tm ts_ok = {.tm_year = INT_MAX-1, .tm_mday = 1 };
  time_test("OK", mktime, &ts_ok);
  struct tm ts_ok2 = {.tm_year = INT_MIN, .tm_mday = 1};
  time_test("OK", timegm, &ts_ok2);
  struct tm ts_range_min = {.tm_year = INT_MIN, .tm_mday = 0};
  time_test(".tm_year range", timegm, &ts_range_min);
  struct tm ts_range_max = {.tm_year = INT_MAX, .tm_mon = 12, .tm_mday = 1};
  time_test(".tm_year range", timegm, &ts_range_max);
  time_t y2038 = 0x7FFFFFFF;
  struct tm *ts_y2038 = gmtime(&y2038);
  time_test("OK", timegm, ts_y2038);
  ts_y2038->tm_sec++;
  time_test("time_t (maybe)", timegm, ts_y2038);
  if (set_tz("Africa/Bissau") == 0) {
    struct tm ts_Bissau1 = {.tm_year = 1974-1900, .tm_mon = 12-1, .tm_mday = 31, .tm_hour = 23, .tm_min = 30 };
    time_test("OK", mktime, &ts_Bissau1);
    struct tm ts_Bissau2 = {.tm_year = 1975-1900, .tm_mon = 1-1, .tm_mday = 1, .tm_hour = 0, .tm_min = 30 };
    time_test("No such time", mktime, &ts_Bissau2);
    struct tm ts_Bissau3 = {.tm_year = 1975-1900, .tm_mon = 1-1, .tm_mday = 1, .tm_hour = 1, .tm_min = 30 };
    time_test("OK", mktime, &ts_Bissau3);
  }
}

int main(void) {
  time_tests();
}

Sample output:

time_t min -9223372036854775808
time_t max +9223372036854775807
mktime        1970/01/01  0:00:00 -->       1970/01/01  0:00:00 errno:  2 time_t:21600 OK
timegm        1970/01/01  0:00:00 -->       1970/01/01  0:00:00 errno:  0 time_t:0 OK
timegm        1970/01/01  0:00:-1 -->       1969/12/31 23:59:59 errno:  0 time_t:-1 OK yet -1 (maybe)
mktime  2147485546/01/01  0:00:00 --> 2147485546/01/01  0:00:00 errno:  0 time_t:67768036128626400 OK
timegm -2147481748/01/01  0:00:00 -->-2147481748/01/01  0:00:00 errno:  0 time_t:-67768040609740800 OK
timegm -2147481748/01/00  0:00:00 -->-2147481748/01/00  0:00:00 errno:139 time_t:-1 .tm_year range
timegm  2147485547/13/01  0:00:00 --> 2147485547/13/01  0:00:00 errno:139 time_t:-1 .tm_year range
timegm        2038/01/19  3:14:07 -->       2038/01/19  3:14:07 errno:  0 time_t:2147483647 OK
timegm        2038/01/19  3:14:08 -->       2038/01/19  3:14:08 errno:  0 time_t:2147483648 time_t (maybe)
Africa/Bissau
mktime        1974/12/31 23:30:00 -->       1974/12/31 23:30:00 errno:  0 time_t:157768200 OK
mktime        1975/01/01  0:30:00 -->       1975/01/01  0:30:00 errno:139 time_t:-1 No such time
mktime        1975/01/01  1:30:00 -->       1975/01/01  1:30:00 errno:  0 time_t:157771800 OK
发布评论

评论列表(0)

  1. 暂无评论