mirror of
git://git.musl-libc.org/musl
synced 2024-12-28 09:53:24 +00:00
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.
This commit is contained in:
parent
f1292e3d28
commit
1cc81f5cb0
20
src/time/__map_file.c
Normal file
20
src/time/__map_file.c
Normal file
@ -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;
|
||||
}
|
10
src/time/__month_to_secs.c
Normal file
10
src/time/__month_to_secs.c
Normal file
@ -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;
|
||||
}
|
81
src/time/__secs_to_tm.c
Normal file
81
src/time/__secs_to_tm.c
Normal file
@ -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;
|
||||
}
|
@ -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];
|
@ -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;
|
||||
}
|
24
src/time/__tm_to_secs.c
Normal file
24
src/time/__tm_to_secs.c
Normal file
@ -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;
|
||||
}
|
@ -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 :) */
|
||||
}
|
389
src/time/__tz.c
Normal file
389
src/time/__tz.c
Normal file
@ -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);
|
47
src/time/__year_to_secs.c
Normal file
47
src/time/__year_to_secs.c
Normal file
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
__tzset();
|
||||
__secs_to_zone(t, 1, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone);
|
||||
|
||||
tm->tm_sec += __timezone;
|
||||
if (isdst > 0) tm->tm_sec += __dst_offset;
|
||||
if (tm->tm_isdst>=0 && new.tm_isdst!=tm->tm_isdst)
|
||||
t += opp - new.__tm_gmtoff;
|
||||
|
||||
t = __tm_to_time(tm);
|
||||
|
||||
lt = t - __timezone;
|
||||
if (isdst > 0) lt -= __dst_offset;
|
||||
__time_to_tm(lt, tm);
|
||||
t += new.__tm_gmtoff;
|
||||
if ((time_t)t != t) goto error;
|
||||
|
||||
__dst_adjust(tm);
|
||||
|
||||
__secs_to_zone(t, 0, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone);
|
||||
|
||||
if (__secs_to_tm(t - new.__tm_gmtoff, &new) < 0) goto error;
|
||||
|
||||
*tm = new;
|
||||
return t;
|
||||
|
||||
error:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
@ -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;
|
||||
|
9
src/time/time_impl.h
Normal file
9
src/time/time_impl.h
Normal file
@ -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 *);
|
@ -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;
|
||||
}
|
||||
|
172
src/time/tzset.c
172
src/time/tzset.c
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user