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
1 Answer
Reset to default 11So 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.
Out of
time_t
encodable range
This is common whentime_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 positivetime_t
. This Y2038 problem is well known such that many systems use a wider or unsignedtime_t
.time_t
is encodable yet negative (or too small/large)
Some implementations simply disallow generation of a negativetime_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 ampletime_t
range remains.Out of
.tm_year
range
mktime()
andgmtime()
allowstruct 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 ofINT_MAX + 2
. Even if the correspondingtime_t
, perhaps as a 64-bit value, is not so limited.Non-existent time stamp: year/month/day hour:min:sec
Only expected withmktime()
, some time zones have historically changed their offset from UTC for various reasons. This can result in gaps of validstruct 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-betweenstruct 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.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 thetime_t
result..tm_isdst
is cleared when the function returns (i.e. daylight time shift is 0). Others do not change thestruct tm
and the function returns(time_t)-1
. This was noted with my GCCtimegm()
andmktime()
and their C compliance is not clear. Notably,timegm()
functioned differently thanmktime()
with time zone "GMT" - which returned(time_t)-1
.Invalid parameter
If the pointer to thestruct tm
equalsNULL
, some implementations are specified to return(time_t) -1
. This goes beyond the C spec for which this case is undefined behavior. ExampleNormal result
Implementations that allow negativetime_t
results and so may normally return -1. When returning -1, an implementation may use some means likeerrno
to indicate if this is an error return.errno
withmktime()/timegm()
is not specified in the C standard.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.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