diff options
-rw-r--r-- | src/time/__map_file.c | 20 | ||||
-rw-r--r-- | src/time/__month_to_secs.c | 10 | ||||
-rw-r--r-- | src/time/__secs_to_tm.c | 81 | ||||
-rw-r--r-- | src/time/__time.h | 9 | ||||
-rw-r--r-- | src/time/__time_to_tm.c | 83 | ||||
-rw-r--r-- | src/time/__tm_to_secs.c | 24 | ||||
-rw-r--r-- | src/time/__tm_to_time.c | 33 | ||||
-rw-r--r-- | src/time/__tz.c | 389 | ||||
-rw-r--r-- | src/time/__year_to_secs.c | 47 | ||||
-rw-r--r-- | src/time/gmtime.c | 9 | ||||
-rw-r--r-- | src/time/gmtime_r.c | 21 | ||||
-rw-r--r-- | src/time/localtime.c | 9 | ||||
-rw-r--r-- | src/time/localtime_r.c | 20 | ||||
-rw-r--r-- | src/time/mktime.c | 36 | ||||
-rw-r--r-- | src/time/strftime.c | 8 | ||||
-rw-r--r-- | src/time/time_impl.h | 9 | ||||
-rw-r--r-- | src/time/timegm.c | 17 | ||||
-rw-r--r-- | src/time/tzset.c | 172 |
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; -} |