the big time handling overhaul
authorRich Felker <dalias@aerifal.cx>
Wed, 17 Jul 2013 09:24:50 +0000 (05:24 -0400)
committerRich Felker <dalias@aerifal.cx>
Wed, 17 Jul 2013 09:24:50 +0000 (05:24 -0400)
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.

18 files changed:
src/time/__map_file.c [new file with mode: 0644]
src/time/__month_to_secs.c [new file with mode: 0644]
src/time/__secs_to_tm.c [new file with mode: 0644]
src/time/__time.h [deleted file]
src/time/__time_to_tm.c [deleted file]
src/time/__tm_to_secs.c [new file with mode: 0644]
src/time/__tm_to_time.c [deleted file]
src/time/__tz.c [new file with mode: 0644]
src/time/__year_to_secs.c [new file with mode: 0644]
src/time/gmtime.c
src/time/gmtime_r.c
src/time/localtime.c
src/time/localtime_r.c
src/time/mktime.c
src/time/strftime.c
src/time/time_impl.h [new file with mode: 0644]
src/time/timegm.c
src/time/tzset.c [deleted file]

diff --git a/src/time/__map_file.c b/src/time/__map_file.c
new file mode 100644 (file)
index 0000000..b6bf272
--- /dev/null
@@ -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 (file)
index 0000000..43248fb
--- /dev/null
@@ -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 (file)
index 0000000..f3c1cf9
--- /dev/null
@@ -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 (file)
index 967e518..0000000
+++ /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 (file)
index e2d782b..0000000
+++ /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 (file)
index 0000000..c29fa98
--- /dev/null
@@ -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 (file)
index 9f11805..0000000
+++ /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 (file)
index 0000000..a76a7b4
--- /dev/null
@@ -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 (file)
index 0000000..2824ec6
--- /dev/null
@@ -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;
+}
index d4d5d1f1ca19b72ba74b5408e02a99bccd8bd47c..3791b24c663754717b36f630dd254b55f2c3064f 100644 (file)
@@ -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);
 }
index 13a2548fd44b9427e0a3450fb9f6fc9842b1661e..0272870d6e46eb49019239bc8cd938aae7d40f93 100644 (file)
@@ -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);
index abd5e84d2a6fa87efb66b2f5485fb743ce5253ec..bb6718c33ff27afa636b6acfbfa89cb5840878f1 100644 (file)
@@ -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);
 }
index 389a5917f2bbe1e9c7ddba579e853e76e4309271..b36c1d825f077f2f53c427b6f683fb03f475b936 100644 (file)
@@ -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);
index 858cd50d012bfacd0aabf735e35d956a51e4967b..e38b46191c50b1346cb8cafddfe0b85f5ecd14ac 100644 (file)
@@ -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;
 }
index 576870589c1a71edeb0fc612e15b22cd35366a8c..d16e81343891dc15159bd69f8d63a7d58d61c4b3 100644 (file)
@@ -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 (file)
index 0000000..2e9a2c0
--- /dev/null
@@ -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 *);
index 6d08917e449518bb00179d56d7e38e8a2e61a98d..7148fca32b1c35f8e57ec062465d5be9a878d3d1 100644 (file)
@@ -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 (file)
index 7e836c2..0000000
+++ /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;
-}