summaryrefslogtreecommitdiffstats
path: root/time.c
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2020-02-13 17:11:14 -0500
committerTavian Barnes <tavianator@tavianator.com>2020-02-13 17:11:14 -0500
commitc7d3d9f6e2fd903788320dd7d0fedbf4c540c9f4 (patch)
tree63e567bfcd4a977a0069b660e663763553edac53 /time.c
parent1ca8b4d9e81bfe7bed51fa6b858d59b721143054 (diff)
downloadbfs-c7d3d9f6e2fd903788320dd7d0fedbf4c540c9f4.tar.xz
time: Implement xtimegm() without mktime()
Using setenv("TZ") is ugly and adds unnecessary failure paths.
Diffstat (limited to 'time.c')
-rw-r--r--time.c131
1 files changed, 99 insertions, 32 deletions
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 <errno.h>
+#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
-#include <string.h>
#include <time.h>
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. */