summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2013-07-17 05:24:50 -0400
committerRich Felker <dalias@aerifal.cx>2013-07-17 05:24:50 -0400
commit1cc81f5cb0df2b66a795ff0c26d7bbc4d16e13c6 (patch)
treed8839fbfd8cb1fb5658394cad6a6a06688ab6a1b /src
parentf1292e3d28309bbc81f61671164843cec4319bfa (diff)
downloadmusl-1cc81f5cb0df2b66a795ff0c26d7bbc4d16e13c6.tar.gz
the big time handling overhaul
this commit has two major user-visible parts: zoneinfo-format time zones are now supported, and overflow handling is intended to be complete in the sense that all functions return a correct result if and only if the result fits in the destination type, and otherwise return an error. also, some noticable bugs in the way DST detection and normalization worked have been fixed, and performance may be better than before, but it has not been tested.
Diffstat (limited to 'src')
-rw-r--r--src/time/__map_file.c20
-rw-r--r--src/time/__month_to_secs.c10
-rw-r--r--src/time/__secs_to_tm.c81
-rw-r--r--src/time/__time.h9
-rw-r--r--src/time/__time_to_tm.c83
-rw-r--r--src/time/__tm_to_secs.c24
-rw-r--r--src/time/__tm_to_time.c33
-rw-r--r--src/time/__tz.c389
-rw-r--r--src/time/__year_to_secs.c47
-rw-r--r--src/time/gmtime.c9
-rw-r--r--src/time/gmtime_r.c21
-rw-r--r--src/time/localtime.c9
-rw-r--r--src/time/localtime_r.c20
-rw-r--r--src/time/mktime.c36
-rw-r--r--src/time/strftime.c8
-rw-r--r--src/time/time_impl.h9
-rw-r--r--src/time/timegm.c17
-rw-r--r--src/time/tzset.c172
18 files changed, 649 insertions, 348 deletions
diff --git a/src/time/__map_file.c b/src/time/__map_file.c
new file mode 100644
index 00000000..b6bf272a
--- /dev/null
+++ b/src/time/__map_file.c
@@ -0,0 +1,20 @@
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "syscall.h"
+
+void *__mmap(void *, size_t, int, int, int, off_t);
+
+const char unsigned *__map_file(const char *pathname, size_t *size)
+{
+ struct stat st;
+ const unsigned char *map = MAP_FAILED;
+ int flags = O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK;
+ int fd = __syscall(SYS_open, pathname, flags);
+ if (fd < 0) return 0;
+ if (!__syscall(SYS_fstat, fd, &st))
+ map = __mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ __syscall(SYS_close);
+ *size = st.st_size;
+ return map == MAP_FAILED ? 0 : map;
+}
diff --git a/src/time/__month_to_secs.c b/src/time/__month_to_secs.c
new file mode 100644
index 00000000..43248fb3
--- /dev/null
+++ b/src/time/__month_to_secs.c
@@ -0,0 +1,10 @@
+int __month_to_secs(int month, int is_leap)
+{
+ static const int secs_through_month[] = {
+ 0, 31*86400, 59*86400, 90*86400,
+ 120*86400, 151*86400, 181*86400, 212*86400,
+ 243*86400, 273*86400, 304*86400, 334*86400 };
+ int t = secs_through_month[month];
+ if (is_leap && month >= 2) t+=86400;
+ return t;
+}
diff --git a/src/time/__secs_to_tm.c b/src/time/__secs_to_tm.c
new file mode 100644
index 00000000..f3c1cf92
--- /dev/null
+++ b/src/time/__secs_to_tm.c
@@ -0,0 +1,81 @@
+#include "time_impl.h"
+#include <limits.h>
+
+/* 2000-03-01 (mod 400 year, immediately after feb29 */
+#define LEAPOCH (946684800LL + 86400*(31+29))
+
+#define DAYS_PER_400Y (365*400 + 97)
+#define DAYS_PER_100Y (365*100 + 24)
+#define DAYS_PER_4Y (365*4 + 1)
+
+int __secs_to_tm(long long t, struct tm *tm)
+{
+ long long days, secs;
+ int remdays, remsecs, remyears;
+ int qc_cycles, c_cycles, q_cycles;
+ int years, months;
+ int wday, yday, leap;
+ static const char days_in_month[] = {31,30,31,30,31,31,30,31,30,31,31,29};
+
+ /* Reject time_t values whose year would overflow int */
+ if (t < INT_MIN * 31622400LL || t > INT_MAX * 31622400LL)
+ return -1;
+
+ secs = t - LEAPOCH;
+ days = secs / 86400;
+ remsecs = secs % 86400;
+ if (remsecs < 0) {
+ remsecs += 86400;
+ days--;
+ }
+
+ wday = (3+days)%7;
+ if (wday < 0) wday += 7;
+
+ qc_cycles = days / DAYS_PER_400Y;
+ remdays = days % DAYS_PER_400Y;
+ if (remdays < 0) {
+ remdays += DAYS_PER_400Y;
+ qc_cycles--;
+ }
+
+ c_cycles = remdays / DAYS_PER_100Y;
+ if (c_cycles == 4) c_cycles--;
+ remdays -= c_cycles * DAYS_PER_100Y;
+
+ q_cycles = remdays / DAYS_PER_4Y;
+ if (q_cycles == 25) q_cycles--;
+ remdays -= q_cycles * DAYS_PER_4Y;
+
+ remyears = remdays / 365;
+ if (remyears == 4) remyears--;
+ remdays -= remyears * 365;
+
+ leap = !remyears && (q_cycles || !c_cycles);
+ yday = remdays + 31 + 28 + leap;
+ if (yday >= 365+leap) yday -= 365+leap;
+
+ years = remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
+
+ for (months=0; days_in_month[months] <= remdays; months++)
+ remdays -= days_in_month[months];
+
+ if (years+100 > INT_MAX || years+100 < INT_MIN)
+ return -1;
+
+ tm->tm_year = years + 100;
+ tm->tm_mon = months + 2;
+ if (tm->tm_mon >= 12) {
+ tm->tm_mon -=12;
+ tm->tm_year++;
+ }
+ tm->tm_mday = remdays + 1;
+ tm->tm_wday = wday;
+ tm->tm_yday = yday;
+
+ tm->tm_hour = remsecs / 3600;
+ tm->tm_min = remsecs / 60 % 60;
+ tm->tm_sec = remsecs % 60;
+
+ return 0;
+}
diff --git a/src/time/__time.h b/src/time/__time.h
deleted file mode 100644
index 967e5180..00000000
--- a/src/time/__time.h
+++ /dev/null
@@ -1,9 +0,0 @@
-time_t __tm_to_time(struct tm *);
-struct tm *__time_to_tm(time_t, struct tm *);
-void __tzset(void);
-struct tm *__dst_adjust(struct tm *tm);
-
-extern long __timezone;
-extern int __daylight;
-extern int __dst_offset;
-extern char *__tzname[2];
diff --git a/src/time/__time_to_tm.c b/src/time/__time_to_tm.c
deleted file mode 100644
index e2d782be..00000000
--- a/src/time/__time_to_tm.c
+++ /dev/null
@@ -1,83 +0,0 @@
-#include <time.h>
-
-/* C defines the rounding for division in a nonsensical way */
-#define Q(a,b) ((a)>0 ? (a)/(b) : -(((b)-(a)-1)/(b)))
-
-#define DAYS_PER_400Y (365*400 + 97)
-#define DAYS_PER_100Y (365*100 + 24)
-#define DAYS_PER_4Y (365*4 + 1)
-
-/* FIXME: use lldiv once it's fixed to compute quot,rem together */
-struct tm *__time_to_tm(time_t t, struct tm *tm)
-{
- /* months are march-based */
- static const int days_thru_month[] = {31,61,92,122,153,184,214,245,275,306,337,366};
- long long bigday;
- unsigned int day, year4, year100;
- int year, year400;
- int month;
- int leap;
- int hour, min, sec;
- int wday, mday, yday;
-
- /* start from 2000-03-01 (multiple of 400 years) */
- t += -946684800 - 86400*(31+29);
-
- bigday = Q(t, 86400);
- sec = t-bigday*86400;
-
- hour = sec/3600;
- sec -= hour*3600;
- min = sec/60;
- sec -= min*60;
-
- /* 2000-03-01 was a wednesday */
- wday = (3+bigday)%7;
- if (wday < 0) wday += 7;
-
- t = -946684800LL - 86400*(31+29) + 9000000;
-
- year400 = Q(bigday, DAYS_PER_400Y);
- day = bigday-year400*DAYS_PER_400Y;
-
- year100 = day/DAYS_PER_100Y;
- if (year100 == 4) year100--;
- day -= year100*DAYS_PER_100Y;
-
- year4 = day/DAYS_PER_4Y;
- if (year4 == 25) year4--;
- day -= year4*DAYS_PER_4Y;
-
- year = day/365;
- if (year == 4) year--;
- day -= year*365;
-
- leap = !year && (year4 || !year100);
- yday = day + 31+28 + leap;
- if (yday >= 365+leap) yday -= 365+leap;
-
- year += 4*year4 + 100*year100 + 400*year400 + 2000-1900;
-
- for (month=0; days_thru_month[month] <= day; month++);
- if (month) day -= days_thru_month[month-1];
- month += 2;
- if (month >= 12) {
- month -= 12;
- year++;
- }
-
- mday = day+1;
-
- tm->tm_sec = sec;
- tm->tm_min = min;
- tm->tm_hour= hour;
- tm->tm_mday= mday;
- tm->tm_mon = month;
- tm->tm_year= year;
- tm->tm_wday= wday;
- tm->tm_yday= yday;
- tm->__tm_zone = 0;
- tm->__tm_gmtoff = 0;
-
- return tm;
-}
diff --git a/src/time/__tm_to_secs.c b/src/time/__tm_to_secs.c
new file mode 100644
index 00000000..c29fa985
--- /dev/null
+++ b/src/time/__tm_to_secs.c
@@ -0,0 +1,24 @@
+#include "time_impl.h"
+
+long long __tm_to_secs(const struct tm *tm)
+{
+ int is_leap;
+ long long year = tm->tm_year;
+ int month = tm->tm_mon;
+ if (month >= 12 || month < 0) {
+ int adj = month / 12;
+ month %= 12;
+ if (month < 0) {
+ adj--;
+ month += 12;
+ }
+ year += adj;
+ }
+ long long t = __year_to_secs(year, &is_leap);
+ t += __month_to_secs(month, is_leap);
+ t += 86400LL * (tm->tm_mday-1);
+ t += 3600LL * tm->tm_hour;
+ t += 60LL * tm->tm_min;
+ t += tm->tm_sec;
+ return t;
+}
diff --git a/src/time/__tm_to_time.c b/src/time/__tm_to_time.c
deleted file mode 100644
index 9f11805d..00000000
--- a/src/time/__tm_to_time.c
+++ /dev/null
@@ -1,33 +0,0 @@
-#include <time.h>
-
-/* C defines the rounding for division in a nonsensical way */
-#define Q(a,b) ((a)>0 ? (a)/(b) : -(((b)-(a)-1)/(b)))
-
-time_t __tm_to_time(struct tm *tm)
-{
- time_t year = tm->tm_year + -100;
- int month = tm->tm_mon;
- int day = tm->tm_mday;
- int z4, z100, z400;
-
- /* normalize month */
- if (month >= 12) {
- year += month/12;
- month %= 12;
- } else if (month < 0) {
- year += month/12;
- month %= 12;
- if (month) {
- month += 12;
- year--;
- }
- }
- z4 = Q(year - (month < 2), 4);
- z100 = Q(z4, 25);
- z400 = Q(z100, 4);
- day += year*365 + z4 - z100 + z400 +
- month[(const int []){0,31,59,90,120,151,181,212,243,273,304,334}];
- return (long long)day*86400
- + tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec
- - -946684800; /* the dawn of time :) */
-}
diff --git a/src/time/__tz.c b/src/time/__tz.c
new file mode 100644
index 00000000..a76a7b48
--- /dev/null
+++ b/src/time/__tz.c
@@ -0,0 +1,389 @@
+#include "time_impl.h"
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libc.h"
+
+long __timezone = 0;
+int __daylight = 0;
+char *__tzname[2] = { 0, 0 };
+
+weak_alias(__timezone, timezone);
+weak_alias(__daylight, daylight);
+weak_alias(__tzname, tzname);
+
+static char std_name[TZNAME_MAX+1];
+static char dst_name[TZNAME_MAX+1];
+
+static int dst_off;
+static int r0[5], r1[5];
+
+static const unsigned char *zi, *trans, *index, *types, *abbrevs;
+static size_t map_size;
+
+static char old_tz_buf[32];
+static char *old_tz = old_tz_buf;
+static size_t old_tz_size = sizeof old_tz_buf;
+
+static int lock[2];
+
+static int getint(const char **p)
+{
+ unsigned x;
+ for (x=0; **p-'0'<10U; (*p)++) x = **p-'0' + 10*x;
+ return x;
+}
+
+static int getsigned(const char **p)
+{
+ if (**p == '-') {
+ ++*p;
+ return -getint(p);
+ }
+ if (**p == '+') ++*p;
+ return getint(p);
+}
+
+static int getoff(const char **p)
+{
+ int off = 3600*getsigned(p);
+ if (**p == ':') {
+ ++*p;
+ off += 60*getint(p);
+ if (**p == ':') {
+ ++*p;
+ off += getint(p);
+ }
+ }
+ return off;
+}
+
+static void getrule(const char **p, int rule[5])
+{
+ int r = rule[0] = **p;
+
+ if (r!='M') {
+ if (r=='J') ++*p;
+ else rule[0] = 0;
+ rule[1] = getint(p);
+ } else {
+ ++*p; rule[1] = getint(p);
+ ++*p; rule[2] = getint(p);
+ ++*p; rule[3] = getint(p);
+ }
+
+ if (**p=='/') {
+ ++*p;
+ rule[4] = getoff(p);
+ } else {
+ rule[4] = 7200;
+ }
+}
+
+static void getname(char *d, const char **p)
+{
+ int i;
+ if (**p == '<') {
+ ++*p;
+ for (i=0; **p!='>' && i<TZNAME_MAX; i++)
+ d[i] = (*p)[i];
+ ++*p;
+ } else {
+ for (i=0; ((*p)[i]|32)-'a'<26U && i<TZNAME_MAX; i++)
+ d[i] = (*p)[i];
+ }
+ *p += i;
+ d[i] = 0;
+}
+
+#define VEC(...) ((const unsigned char[]){__VA_ARGS__})
+
+static uint32_t zi_read32(const unsigned char *z)
+{
+ return (unsigned)z[0]<<24 | z[1]<<16 | z[2]<<8 | z[3];
+}
+
+static size_t zi_dotprod(const unsigned char *z, const unsigned char *v, size_t n)
+{
+ size_t y;
+ uint32_t x;
+ for (y=0; n; n--, z+=4, v++) {
+ x = zi_read32(z);
+ y += x * *v;
+ }
+ return y;
+}
+
+int __munmap(void *, size_t);
+
+static void do_tzset()
+{
+ char buf[NAME_MAX+25], *pathname=buf+24;
+ const char *try, *s;
+ const unsigned char *map = 0;
+ size_t i;
+ static const char search[] =
+ "/usr/share/zoneinfo/\0/share/zoneinfo/\0/etc/zoneinfo/\0";
+
+ s = getenv("TZ");
+ if (!s) s = "";
+
+ if (old_tz && !strcmp(s, old_tz)) return;
+
+ if (zi) __munmap((void *)zi, map_size);
+
+ /* Cache the old value of TZ to check if it has changed. Avoid
+ * free so as not to pull it into static programs. Growth
+ * strategy makes it so free would have minimal benefit anyway. */
+ i = strlen(s);
+ if (i > PATH_MAX+1) s = "", i = 0;
+ if (i >= old_tz_size) {
+ old_tz_size *= 2;
+ if (i >= old_tz_size) old_tz_size = i+1;
+ if (old_tz_size > PATH_MAX+2) old_tz_size = PATH_MAX+2;
+ old_tz = malloc(old_tz_size);
+ }
+ if (old_tz) memcpy(old_tz, s, i+1);
+
+ if (*s == ':') s++;
+
+ /* Non-suid can use an absolute tzfile pathname or a relative
+ * pathame beginning with "."; in secure mode, only the
+ * standard path will be searched. */
+ if (*s == '/' || *s == '.') {
+ if (!libc.secure) map = __map_file(s, &map_size);
+ } else {
+ for (i=0; s[i] && s[i]!=','; i++) {
+ if (s[i]=='/') {
+ size_t l = strlen(s);
+ if (l > NAME_MAX || strchr(s, '.'))
+ break;
+ memcpy(pathname, s, l+1);
+ pathname[l] = 0;
+ for (try=search; !map && *try; try+=l) {
+ l = strlen(try);
+ memcpy(pathname-l, try, l);
+ map = __map_file(pathname-l, &map_size);
+ }
+ break;
+ }
+ }
+ }
+
+ zi = map;
+ if (map) {
+ int scale = 2;
+ if (sizeof(time_t) > 4 && map[4]=='2') {
+ size_t skip = zi_dotprod(zi, VEC(1,1,8,5,6,1), 6);
+ trans = zi+skip+44+20;
+ scale++;
+ } else {
+ trans = zi+44;
+ }
+ index = trans + (zi_read32(trans-12) << scale);
+ types = index + zi_read32(trans-12);
+ abbrevs = types + 6*zi_read32(trans-8);
+ if (zi[map_size-1] == '\n') {
+ for (s = (const char *)zi+map_size-2; *s!='\n'; s--);
+ s++;
+ } else {
+ s = 0;
+ }
+ }
+
+ if (!s) s = "GMT0";
+ getname(std_name, &s);
+ __tzname[0] = std_name;
+ __timezone = getoff(&s);
+ getname(dst_name, &s);
+ __tzname[1] = dst_name;
+ if (dst_name[0]) {
+ __daylight = 1;
+ if (*s == '+' || *s=='-' || *s-'0'<10U)
+ dst_off = getoff(&s);
+ else
+ dst_off = __timezone - 3600;
+ } else {
+ __daylight = 0;
+ dst_off = 0;
+ }
+
+ if (*s == ',') s++, getrule(&s, r0);
+ if (*s == ',') s++, getrule(&s, r1);
+}
+
+/* Search zoneinfo rules to find the one that applies to the given time,
+ * and determine alternate opposite-DST-status rule that may be needed. */
+
+static size_t scan_trans(long long t, int local, size_t *alt)
+{
+ int scale = 3 - (trans == zi+44);
+ uint64_t x;
+ int off = 0;
+
+ size_t a = 0, n = (index-trans)>>scale, m;
+
+ if (!n) {
+ if (alt) *alt = 0;
+ return 0;
+ }
+
+ /* Binary search for 'most-recent rule before t'. */
+ while (n > 1) {
+ m = a + n/2;
+ x = zi_read32(trans + (m<<scale));
+ if (scale == 3) x = x<<32 | zi_read32(trans + (m<<scale) + 4);
+ else x = (int32_t)x;
+ if (local) off = (int32_t)zi_read32(types + 6 * index[m-1]);
+ if (t - off < (int64_t)x) {
+ n /= 2;
+ } else {
+ a = m;
+ n -= n/2;
+ }
+ }
+
+ /* First and last entry are special. First means to use lowest-index
+ * non-DST type. Last means to apply POSIX-style rule if available. */
+ n = (index-trans)>>scale;
+ if (a == n-1) return -1;
+ if (a == 0) {
+ x = zi_read32(trans + (a<<scale));
+ if (scale == 3) x = x<<32 | zi_read32(trans + (a<<scale) + 4);
+ else x = (int32_t)x;
+ if (local) off = (int32_t)zi_read32(types + 6 * index[a-1]);
+ if (t - off < (int64_t)x) {
+ for (a=0; a<(abbrevs-types)/6; a++) {
+ if (types[6*a+4] != types[4]) break;
+ }
+ if (a == (abbrevs-types)/6) a = 0;
+ if (types[6*a+4]) {
+ *alt = a;
+ return 0;
+ } else {
+ *alt = 0;
+ return a;
+ }
+ }
+ }
+
+ /* Try to find a neighboring opposite-DST-status rule. */
+ if (alt) {
+ if (a && types[6*index[a-1]+4] != types[6*index[a]+4])
+ *alt = index[a-1];
+ else if (a+1<n && types[6*index[a+1]+4] != types[6*index[a]+4])
+ *alt = index[a+1];
+ else
+ *alt = index[a];
+ }
+
+ return index[a];
+}
+
+static int days_in_month(int m, int is_leap)
+{
+ if (m==2) return 28+is_leap;
+ else return 30+((0xad5>>(m-1))&1);
+}
+
+/* Convert a POSIX DST rule plus year to seconds since epoch. */
+
+static long long rule_to_secs(const int *rule, int year)
+{
+ int is_leap;
+ long long t = __year_to_secs(year, &is_leap);
+ int x, m, n, d;
+ if (rule[0]!='M') {
+ x = rule[1];
+ if (rule[0]=='J' && (x < 60 || !is_leap)) x--;
+ t += 86400 * x;
+ } else {
+ m = rule[1];
+ n = rule[2];
+ d = rule[3];
+ t += __month_to_secs(m-1, is_leap);
+ int wday = (int)((t + 4*86400) % (7*86400)) / 86400;
+ int days = d - wday;
+ if (days < 0) days += 7;
+ if (n == 5 && days+28 >= days_in_month(m, is_leap)) n = 4;
+ t += 86400 * (days + 7*(n-1));
+ }
+ t += rule[4];
+ return t;
+}
+
+/* Determine the time zone in effect for a given time in seconds since the
+ * epoch. It can be given in local or universal time. The results will
+ * indicate whether DST is in effect at the queried time, and will give both
+ * the GMT offset for the active zone/DST rule and the opposite DST. This
+ * enables a caller to efficiently adjust for the case where an explicit
+ * DST specification mismatches what would be in effect at the time. */
+
+void __secs_to_zone(long long t, int local, int *isdst, long *offset, long *oppoff, const char **zonename)
+{
+ LOCK(lock);
+
+ do_tzset();
+
+ if (zi) {
+ size_t alt, i = scan_trans(t, local, &alt);
+ if (i != -1) {
+ *isdst = types[6*i+4];
+ *offset = -(int32_t)zi_read32(types+6*i);
+ *zonename = (const char *)abbrevs + types[6*i+5];
+ if (oppoff) *oppoff = -(int32_t)zi_read32(types+6*alt);
+ UNLOCK(lock);
+ return;
+ }
+ }
+
+ if (!__daylight) goto std;
+
+ /* FIXME: may be broken if DST changes right at year boundary?
+ * Also, this could be more efficient.*/
+ long long y = t / 31556952 + 70;
+ while (__year_to_secs(y, 0) > t) y--;
+ while (__year_to_secs(y+1, 0) < t) y++;
+
+ long long t0 = rule_to_secs(r0, y);
+ long long t1 = rule_to_secs(r1, y);
+
+ if (t0 < t1) {
+ if (!local) {
+ t0 += __timezone;
+ t1 += dst_off;
+ }
+ if (t >= t0 && t < t1) goto dst;
+ goto std;
+ } else {
+ if (!local) {
+ t1 += __timezone;
+ t0 += dst_off;
+ }
+ if (t >= t1 && t < t0) goto std;
+ goto dst;
+ }
+std:
+ *isdst = 0;
+ *offset = __timezone;
+ if (oppoff) *oppoff = dst_off;
+ *zonename = __tzname[0];
+ UNLOCK(lock);
+ return;
+dst:
+ *isdst = 1;
+ *offset = dst_off;
+ if (oppoff) *oppoff = __timezone;
+ *zonename = __tzname[1];
+ UNLOCK(lock);
+}
+
+void __tzset()
+{
+ LOCK(lock);
+ do_tzset();
+ UNLOCK(lock);
+}
+
+weak_alias(__tzset, tzset);
diff --git a/src/time/__year_to_secs.c b/src/time/__year_to_secs.c
new file mode 100644
index 00000000..2824ec6d
--- /dev/null
+++ b/src/time/__year_to_secs.c
@@ -0,0 +1,47 @@
+long long __year_to_secs(long long year, int *is_leap)
+{
+ if (year-2ULL <= 136) {
+ int y = year;
+ int leaps = (y-68)>>2;
+ if (!((y-68)&3)) {
+ leaps--;
+ if (is_leap) *is_leap = 1;
+ } else if (is_leap) *is_leap = 0;
+ return 31536000*(y-70) + 86400*leaps;
+ }
+
+ int cycles, centuries, leaps, rem;
+
+ if (!is_leap) is_leap = &(int){0};
+ cycles = (year-100) / 400;
+ rem = (year-100) % 400;
+ if (rem < 0) {
+ cycles--;
+ rem += 400;
+ }
+ if (!rem) {
+ *is_leap = 1;
+ centuries = 0;
+ leaps = 0;
+ } else {
+ if (rem >= 200) {
+ if (rem >= 300) centuries = 3, rem -= 300;
+ else centuries = 2, rem -= 200;
+ } else {
+ if (rem >= 100) centuries = 1, rem -= 100;
+ else centuries = 0;
+ }
+ if (!rem) {
+ *is_leap = 0;
+ leaps = 0;
+ } else {
+ leaps = rem / 4U;
+ rem %= 4U;
+ *is_leap = !rem;
+ }
+ }
+
+ leaps += 97*cycles + 24*centuries - *is_leap;
+
+ return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
+}
diff --git a/src/time/gmtime.c b/src/time/gmtime.c
index d4d5d1f1..3791b24c 100644
--- a/src/time/gmtime.c
+++ b/src/time/gmtime.c
@@ -1,11 +1,10 @@
-#include <time.h>
+#include "time_impl.h"
+#include <errno.h>
-#include "__time.h"
+struct tm *__gmtime_r(const time_t *restrict, struct tm *restrict);
struct tm *gmtime(const time_t *t)
{
static struct tm tm;
- __time_to_tm(*t, &tm);
- tm.tm_isdst = 0;
- return &tm;
+ return __gmtime_r(t, &tm);
}
diff --git a/src/time/gmtime_r.c b/src/time/gmtime_r.c
index 13a2548f..0272870d 100644
--- a/src/time/gmtime_r.c
+++ b/src/time/gmtime_r.c
@@ -1,10 +1,17 @@
-#include <time.h>
+#include "time_impl.h"
+#include <errno.h>
+#include "libc.h"
-#include "__time.h"
-
-struct tm *gmtime_r(const time_t *restrict t, struct tm *restrict result)
+struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm)
{
- __time_to_tm(*t, result);
- result->tm_isdst = 0;
- return result;
+ if (__secs_to_tm(*t, tm) < 0) {
+ errno = EINVAL;
+ return 0;
+ }
+ tm->tm_isdst = 0;
+ tm->__tm_gmtoff = 0;
+ tm->__tm_zone = "GMT";
+ return tm;
}
+
+weak_alias(__gmtime_r, gmtime_r);
diff --git a/src/time/localtime.c b/src/time/localtime.c
index abd5e84d..bb6718c3 100644
--- a/src/time/localtime.c
+++ b/src/time/localtime.c
@@ -1,12 +1,9 @@
-#include <time.h>
+#include "time_impl.h"
-#include "__time.h"
+struct tm *__localtime_r(const time_t *restrict, struct tm *restrict);
struct tm *localtime(const time_t *t)
{
static struct tm tm;
- __tzset();
- __time_to_tm(*t - __timezone, &tm);
- tm.tm_isdst = -1;
- return __dst_adjust(&tm);
+ return __localtime_r(t, &tm);
}
diff --git a/src/time/localtime_r.c b/src/time/localtime_r.c
index 389a5917..b36c1d82 100644
--- a/src/time/localtime_r.c
+++ b/src/time/localtime_r.c
@@ -1,11 +1,15 @@
-#include <time.h>
+#include "time_impl.h"
+#include <errno.h>
+#include "libc.h"
-#include "__time.h"
-
-struct tm *localtime_r(const time_t *restrict t, struct tm *restrict result)
+struct tm *__localtime_r(const time_t *restrict t, struct tm *restrict tm)
{
- __tzset();
- __time_to_tm(*t - __timezone, result);
- result->tm_isdst = -1;
- return __dst_adjust(result);
+ __secs_to_zone(*t, 0, &tm->tm_isdst, &tm->__tm_gmtoff, 0, &tm->__tm_zone);
+ if (__secs_to_tm((long long)*t - tm->__tm_gmtoff, tm) < 0) {
+ errno = EINVAL;
+ return 0;
+ }
+ return tm;
}
+
+weak_alias(__localtime_r, localtime_r);
diff --git a/src/time/mktime.c b/src/time/mktime.c
index 858cd50d..e38b4619 100644
--- a/src/time/mktime.c
+++ b/src/time/mktime.c
@@ -1,24 +1,30 @@
-#include <time.h>
-
-#include "__time.h"
+#include "time_impl.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
time_t mktime(struct tm *tm)
{
- int isdst = tm->tm_isdst;
- time_t t, lt;
+ struct tm new;
+ long opp;
+ long long t = __tm_to_secs(tm);
+
+ __secs_to_zone(t, 1, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone);
- __tzset();
+ if (tm->tm_isdst>=0 && new.tm_isdst!=tm->tm_isdst)
+ t += opp - new.__tm_gmtoff;
- tm->tm_sec += __timezone;
- if (isdst > 0) tm->tm_sec += __dst_offset;
+ t += new.__tm_gmtoff;
+ if ((time_t)t != t) goto error;
- t = __tm_to_time(tm);
-
- lt = t - __timezone;
- if (isdst > 0) lt -= __dst_offset;
- __time_to_tm(lt, tm);
+ __secs_to_zone(t, 0, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone);
- __dst_adjust(tm);
-
+ if (__secs_to_tm(t - new.__tm_gmtoff, &new) < 0) goto error;
+
+ *tm = new;
return t;
+
+error:
+ errno = EINVAL;
+ return -1;
}
diff --git a/src/time/strftime.c b/src/time/strftime.c
index 57687058..d16e8134 100644
--- a/src/time/strftime.c
+++ b/src/time/strftime.c
@@ -3,7 +3,6 @@
#include <langinfo.h>
#include <time.h>
#include <limits.h>
-#include "__time.h"
// FIXME: integer overflows
@@ -182,14 +181,11 @@ do_fmt:
fmt = "%04d";
goto number;
case 'z':
- if (tm->tm_isdst < 0) continue;
- val = -__timezone - (tm->tm_isdst ? __dst_offset : 0);
+ val = -tm->__tm_gmtoff;
l += snprintf(s+l, n-l, "%+.2d%.2d", val/3600, abs(val%3600)/60);
continue;
case 'Z':
- if (tm->tm_isdst < 0 || !__tzname[0] || !__tzname[0][0])
- continue;
- l += snprintf(s+l, n-l, "%s", __tzname[!!tm->tm_isdst]);
+ l += snprintf(s+l, n-l, "%s", tm->__tm_zone);
continue;
default:
return 0;
diff --git a/src/time/time_impl.h b/src/time/time_impl.h
new file mode 100644
index 00000000..2e9a2c09
--- /dev/null
+++ b/src/time/time_impl.h
@@ -0,0 +1,9 @@
+#include <time.h>
+
+int __days_in_month(int, int);
+int __month_to_secs(int, int);
+long long __year_to_secs(long long, int *);
+long long __tm_to_secs(const struct tm *);
+int __secs_to_tm(long long, struct tm *);
+void __secs_to_zone(long long, int, int *, long *, long *, const char **);
+const unsigned char *__map_file(const char *, size_t *);
diff --git a/src/time/timegm.c b/src/time/timegm.c
index 6d08917e..7148fca3 100644
--- a/src/time/timegm.c
+++ b/src/time/timegm.c
@@ -1,9 +1,18 @@
#define _GNU_SOURCE
-#include <time.h>
-
-#include "__time.h"
+#include "time_impl.h"
+#include <errno.h>
time_t timegm(struct tm *tm)
{
- return __tm_to_time(tm);
+ struct tm new;
+ long long t = __tm_to_secs(tm);
+ if (__secs_to_tm(t, &new) < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ *tm = new;
+ tm->tm_isdst = 0;
+ tm->__tm_gmtoff = 0;
+ tm->__tm_zone = "GMT";
+ return t;
}
diff --git a/src/time/tzset.c b/src/time/tzset.c
deleted file mode 100644
index 7e836c2f..00000000
--- a/src/time/tzset.c
+++ /dev/null
@@ -1,172 +0,0 @@
-#include <time.h>
-#include <ctype.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-#include "libc.h"
-
-#include "__time.h"
-
-long __timezone = 0;
-int __daylight = 0;
-char *__tzname[2] = { 0, 0 };
-int __dst_offset = 0;
-
-weak_alias(__timezone, timezone);
-weak_alias(__daylight, daylight);
-weak_alias(__tzname, tzname);
-
-static char std_name[TZNAME_MAX+1];
-static char dst_name[TZNAME_MAX+1];
-
-/* all elements are zero-based */
-static struct rule {
- signed char month;
- signed char week;
- short day;
- int time;
-} __dst_start, __dst_end;
-
-static void zname(char *d, char **s)
-{
- int i;
- for (i=0; i<TZNAME_MAX && isalpha(d[i]=**s); i++, (*s)++);
- d[i] = 0;
-}
-
-static int hhmmss(char **s)
-{
- int ofs = strtol(*s, s, 10)*3600;
- if (ofs >= 0) {
- if (**s == ':') ofs += strtol(*s+1, s, 10)*60;
- if (**s == ':') ofs += strtol(*s+1, s, 10);
- } else {
- if (**s == ':') ofs -= strtol(*s+1, s, 10)*60;
- if (**s == ':') ofs -= strtol(*s+1, s, 10);
- }
- return ofs;
-}
-
-static int dstrule(struct rule *rule, char **s)
-{
- if (**s != ',') return -1;
- switch (*++*s) {
- case 'J':
- rule->month = 'J';
- rule->day = strtol(*s+1, s, 10)-1;
- break;
- case 'M':
- rule->month = strtol(*s+1, s, 10)-1;
- if (**s != '.' || rule->month < 0 || rule->month > 11)
- return -1;
- rule->week = strtol(*s+1, s, 10)-1;
- if (**s != '.' || rule->week < 0 || rule->week > 4)
- return -1;
- rule->day = strtol(*s+1, s, 10);
- if (rule->day < 0 || rule->day > 6)
- return -1;
- break;
- default:
- rule->month = 'L';
- rule->day = strtol(*s+1, s, 10);
- break;
- }
- if (**s == '/') {
- (*s)++;
- rule->time = hhmmss(s);
- } else rule->time = 7200;
- return 0;
-}
-
-void tzset(void)
-{
- char *z, *a;
-
- strcpy(std_name, "GMT");
- strcpy(dst_name, "GMT");
- __tzname[0] = std_name;
- __tzname[1] = dst_name;
- __timezone = 0;
- __daylight = 0;
-
- if (!(z = getenv("TZ")) || !isalpha(*z)) return;
-
- zname(std_name, &z);
- __timezone = hhmmss(&z);
-
- zname(dst_name, &z);
- if (dst_name[0]) __daylight=1;
- a = z;
- __dst_offset = hhmmss(&z) - __timezone;
- if (z==a) __dst_offset = -3600;
-
- if (dstrule(&__dst_start, &z) || dstrule(&__dst_end, &z))
- __daylight = 0;
-}
-
-void __tzset(void)
-{
- static int lock[2], init;
- if (init) return;
- LOCK(lock);
- if (!init) tzset();
- init=1;
- UNLOCK(lock);
-}
-
-static int is_leap(int year)
-{
- year -= 100;
- return !(year&3) && ((year%100) || !(year%400));
-}
-
-static int cutoff_yday(struct tm *tm, struct rule *rule)
-{
- static const char days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
- static const int first_day[] = {0,31,59,90,120,151,181,212,243,273,304,335};
- int yday, mday, leap;
-
- switch (rule->month) {
- case 'J':
- return rule->day + (tm->tm_mon > 1 && is_leap(tm->tm_year));
- case 'L':
- return rule->day;
- default:
- yday = first_day[rule->month];
- leap = is_leap(tm->tm_year);
- if (rule->month > 1 && leap) yday++;
- mday = (rule->day - (yday + tm->tm_wday - tm->tm_yday) + 1400)%7 + 7*rule->week;
- if (mday >= days_in_month[rule->month] + (leap && rule->month == 1))
- mday -= 7;
- return mday + yday;
- }
-}
-
-struct tm *__dst_adjust(struct tm *tm)
-{
- time_t t;
- int start, end, secs;
- int after_start, before_end;
-
- if (tm->tm_isdst >= 0) return tm;
- if (!__daylight) {
- tm->tm_isdst = 0;
- return tm;
- }
-
- secs = tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec;
- start = cutoff_yday(tm, &__dst_start);
- end = cutoff_yday(tm, &__dst_end);
-
- after_start = (tm->tm_yday > start || (tm->tm_yday == start && secs >= __dst_start.time));
- before_end = (tm->tm_yday < end || (tm->tm_yday == end && secs < __dst_end.time));
-
- if ((after_start && before_end) || ((end < start) && (after_start || before_end))) {
- tm->tm_sec -= __dst_offset;
- tm->tm_isdst = 1;
- t = __tm_to_time(tm);
- return __time_to_tm(t, tm);
- } else tm->tm_isdst = 0;
-
- return tm;
-}