From c7d3d9f6e2fd903788320dd7d0fedbf4c540c9f4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Feb 2020 17:11:14 -0500 Subject: time: Implement xtimegm() without mktime() Using setenv("TZ") is ugly and adds unnecessary failure paths. --- time.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 32 deletions(-) (limited to 'time.c') diff --git a/time.c b/time.c index 3575abd..d955a6c 100644 --- a/time.c +++ b/time.c @@ -16,9 +16,9 @@ #include "time.h" #include +#include #include #include -#include #include int xlocaltime(const time_t *timep, struct tm *result) { @@ -64,47 +64,114 @@ int xmktime(struct tm *tm, time_t *timep) { return 0; } -int xtimegm(struct tm *tm, time_t *timep) { - // Some man pages for timegm() recommend this as a portable approach - int ret = -1; - int error; - - char *old_tz = getenv("TZ"); - if (old_tz) { - old_tz = strdup(old_tz); - if (!old_tz) { - error = errno; - goto fail; +static int safe_add(int *value, int delta) { + if (*value >= 0) { + if (delta > INT_MAX - *value) { + return -1; + } + } else { + if (delta < INT_MIN - *value) { + return -1; } } - if (setenv("TZ", "UTC0", true) != 0) { - error = errno; - goto fail; + *value += delta; + return 0; +} + +static int floor_div(int n, int d) { + int a = n < 0; + return (n + a)/d - a; +} + +static int wrap(int *value, int max, int *next) { + int carry = floor_div(*value, max); + *value -= carry * max; + return safe_add(next, carry); +} + +static int month_length(int year, int month) { + static const int month_lengths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int ret = month_lengths[month]; + if (month == 1 && year%4 == 0 && (year%100 != 0 || (year + 300)%400 == 0)) { + ++ret; } + return ret; +} - ret = xmktime(tm, timep); - error = errno; +int xtimegm(struct tm *tm, time_t *timep) { + tm->tm_isdst = 0; - if (old_tz) { - if (setenv("TZ", old_tz, true) != 0) { - ret = -1; - error = errno; - goto fail; - } + if (wrap(&tm->tm_sec, 60, &tm->tm_min) != 0) { + goto overflow; + } + if (wrap(&tm->tm_min, 60, &tm->tm_hour) != 0) { + goto overflow; + } + if (wrap(&tm->tm_hour, 24, &tm->tm_mday) != 0) { + goto overflow; + } + + // In order to wrap the days of the month, we first need to know what + // month it is + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } + + if (tm->tm_mday < 1) { + do { + --tm->tm_mon; + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } + + tm->tm_mday += month_length(tm->tm_year, tm->tm_mon); + } while (tm->tm_mday < 1); } else { - if (unsetenv("TZ") != 0) { - ret = -1; - error = errno; - goto fail; + while (true) { + int days = month_length(tm->tm_year, tm->tm_mon); + if (tm->tm_mday <= days) { + break; + } + + tm->tm_mday -= days; + ++tm->tm_mon; + if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + goto overflow; + } } } - tzset(); -fail: - free(old_tz); - errno = error; - return ret; + tm->tm_yday = 0; + for (int i = 0; i < tm->tm_mon; ++i) { + tm->tm_yday += month_length(tm->tm_year, i); + } + tm->tm_yday += tm->tm_mday - 1; + + int leap_days; + // Compute floor((year - 69)/4) - floor((year - 1)/100) + floor((year + 299)/400) without overflows + if (tm->tm_year >= 0) { + leap_days = floor_div(tm->tm_year - 69, 4) - floor_div(tm->tm_year - 1, 100) + floor_div(tm->tm_year - 101, 400) + 1; + } else { + leap_days = floor_div(tm->tm_year + 3, 4) - floor_div(tm->tm_year + 99, 100) + floor_div(tm->tm_year + 299, 400) - 17; + } + + long long epoch_days = 365LL*(tm->tm_year - 70) + leap_days + tm->tm_yday; + tm->tm_wday = (epoch_days + 4)%7; + if (tm->tm_wday < 0) { + tm->tm_wday += 7; + } + + long long epoch_time = tm->tm_sec + 60*(tm->tm_min + 60*(tm->tm_hour + 24*epoch_days)); + *timep = (time_t)epoch_time; + if ((long long)*timep != epoch_time) { + goto overflow; + } + return 0; + +overflow: + errno = EOVERFLOW; + return -1; } /** Parse some digits from a timestamp. */ -- cgit v1.2.3