396 lines
11 KiB
C
396 lines
11 KiB
C
#include "time_header.h"
|
|
|
|
#ifndef WRONG
|
|
# define WRONG (-1)
|
|
#endif
|
|
|
|
bool increment_overflow32(int_fast32_t *const lp, int const m)
|
|
{
|
|
int_fast32_t const l = *lp;
|
|
|
|
if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) {
|
|
return true;
|
|
}
|
|
|
|
*lp += m;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool normalize_overflow(int *const tensptr, int *const unitsptr, const int base)
|
|
{
|
|
int tensdelta;
|
|
|
|
tensdelta = (*unitsptr >= 0) ?
|
|
(*unitsptr / base) :
|
|
(-1 - (-1 - *unitsptr) / base);
|
|
|
|
*unitsptr -= tensdelta * base;
|
|
|
|
return increment_overflow(tensptr, tensdelta);
|
|
}
|
|
|
|
bool normalize_overflow32(
|
|
int_fast32_t *const tensptr,
|
|
int *const unitsptr,
|
|
const int base)
|
|
{
|
|
int tensdelta;
|
|
|
|
tensdelta = (*unitsptr >= 0) ?
|
|
(*unitsptr / base) :
|
|
(-1 - (-1 - *unitsptr) / base);
|
|
|
|
*unitsptr -= tensdelta * base;
|
|
|
|
return increment_overflow32(tensptr, tensdelta);
|
|
}
|
|
|
|
int tmcomp(const struct tm *const atmp, const struct tm *const btmp)
|
|
{
|
|
int result;
|
|
|
|
if (atmp->tm_year != btmp->tm_year) {
|
|
return atmp->tm_year < btmp->tm_year ? -1 : 1;
|
|
}
|
|
|
|
if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 &&
|
|
(result = (atmp->tm_mday - btmp->tm_mday)) == 0 &&
|
|
(result = (atmp->tm_hour - btmp->tm_hour)) == 0 &&
|
|
(result = (atmp->tm_min - btmp->tm_min)) == 0) {
|
|
result = atmp->tm_sec - btmp->tm_sec;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
time_t time2sub(
|
|
struct tm *const tmp,
|
|
struct tm *(*funcp)(struct state const *,
|
|
time_t const *,
|
|
int_fast32_t, struct tm *),
|
|
struct state const *sp,
|
|
const int_fast32_t offset,
|
|
bool *okayp,
|
|
bool do_norm_secs)
|
|
{
|
|
int dir;
|
|
int i, j;
|
|
int saved_seconds;
|
|
int_fast32_t li;
|
|
time_t lo;
|
|
time_t hi;
|
|
int_fast32_t y;
|
|
time_t newt;
|
|
time_t t;
|
|
struct tm yourtm;
|
|
struct tm mytm;
|
|
|
|
*okayp = false;
|
|
yourtm = *tmp;
|
|
if (do_norm_secs) {
|
|
if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
|
|
SECSPERMIN))
|
|
return WRONG;
|
|
}
|
|
|
|
if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
|
|
return WRONG;
|
|
|
|
if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
|
|
return WRONG;
|
|
|
|
y = yourtm.tm_year;
|
|
|
|
if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR))
|
|
return WRONG;
|
|
|
|
/*
|
|
** Turn y into an actual year number for now.
|
|
** It is converted back to an offset from TM_YEAR_BASE later.
|
|
*/
|
|
if (increment_overflow32(&y, TM_YEAR_BASE))
|
|
return WRONG;
|
|
|
|
while (yourtm.tm_mday <= 0) {
|
|
if (increment_overflow32(&y, -1))
|
|
return WRONG;
|
|
li = y + (1 < yourtm.tm_mon);
|
|
yourtm.tm_mday += year_lengths[isleap(li)];
|
|
}
|
|
|
|
while (yourtm.tm_mday > DAYSPERLYEAR) {
|
|
li = y + (1 < yourtm.tm_mon);
|
|
yourtm.tm_mday -= year_lengths[isleap(li)];
|
|
if (increment_overflow32(&y, 1))
|
|
return WRONG;
|
|
}
|
|
|
|
for ( ; ; ) {
|
|
i = mon_lengths[isleap(y)][yourtm.tm_mon];
|
|
if (yourtm.tm_mday <= i)
|
|
break;
|
|
yourtm.tm_mday -= i;
|
|
if (++yourtm.tm_mon >= MONSPERYEAR) {
|
|
yourtm.tm_mon = 0;
|
|
if (increment_overflow32(&y, 1))
|
|
return WRONG;
|
|
}
|
|
}
|
|
|
|
if (increment_overflow32(&y, -TM_YEAR_BASE))
|
|
return WRONG;
|
|
|
|
if (! (INT_MIN <= y && y <= INT_MAX))
|
|
return WRONG;
|
|
|
|
yourtm.tm_year = y;
|
|
if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) {
|
|
saved_seconds = 0;
|
|
} else if (y + TM_YEAR_BASE < EPOCH_YEAR) {
|
|
/*
|
|
** We can't set tm_sec to 0, because that might push the
|
|
** time below the minimum representable time.
|
|
** Set tm_sec to 59 instead.
|
|
** This assumes that the minimum representable time is
|
|
** not in the same minute that a leap second was deleted from,
|
|
** which is a safer assumption than using 58 would be.
|
|
*/
|
|
if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
|
|
return WRONG;
|
|
saved_seconds = yourtm.tm_sec;
|
|
yourtm.tm_sec = SECSPERMIN - 1;
|
|
} else {
|
|
saved_seconds = yourtm.tm_sec;
|
|
yourtm.tm_sec = 0;
|
|
}
|
|
|
|
/*
|
|
** Do a binary search (this works whatever time_t's type is).
|
|
*/
|
|
lo = time_t_min;
|
|
hi = time_t_max;
|
|
|
|
for ( ; ; ) {
|
|
t = lo / 2 + hi / 2;
|
|
if (t < lo)
|
|
t = lo;
|
|
else if (t > hi)
|
|
t = hi;
|
|
if (! funcp(sp, &t, offset, &mytm)) {
|
|
/*
|
|
** Assume that t is too extreme to be represented in
|
|
** a struct tm; arrange things so that it is less
|
|
** extreme on the next pass.
|
|
*/
|
|
dir = (t > 0) ? 1 : -1;
|
|
} else dir = tmcomp(&mytm, &yourtm);
|
|
if (dir != 0) {
|
|
if (t == lo) {
|
|
if (t == time_t_max)
|
|
return WRONG;
|
|
++t;
|
|
++lo;
|
|
} else if (t == hi) {
|
|
if (t == time_t_min)
|
|
return WRONG;
|
|
--t;
|
|
--hi;
|
|
}
|
|
if (lo > hi)
|
|
return WRONG;
|
|
if (dir > 0)
|
|
hi = t;
|
|
else lo = t;
|
|
continue;
|
|
}
|
|
|
|
if (mytm.tm_gmtoff != yourtm.tm_gmtoff
|
|
&& (yourtm.tm_gmtoff < 0
|
|
? (-SECSPERDAY <= yourtm.tm_gmtoff
|
|
&& (mytm.tm_gmtoff <=
|
|
(SMALLEST (INT_FAST32_MAX, LONG_MAX)
|
|
+ yourtm.tm_gmtoff)))
|
|
: (yourtm.tm_gmtoff <= SECSPERDAY
|
|
&& ((BIGGEST (INT_FAST32_MIN, LONG_MIN)
|
|
+ yourtm.tm_gmtoff)
|
|
<= mytm.tm_gmtoff)))) {
|
|
/* MYTM matches YOURTM except with the wrong UTC offset.
|
|
YOURTM.tm_gmtoff is plausible, so try it instead.
|
|
It's OK if YOURTM.tm_gmtoff contains uninitialized data,
|
|
since the guess gets checked. */
|
|
time_t altt = t;
|
|
int_fast32_t diff = mytm.tm_gmtoff - yourtm.tm_gmtoff;
|
|
if (!increment_overflow_time(&altt, diff)) {
|
|
struct tm alttm;
|
|
if (funcp(sp, &altt, offset, &alttm)
|
|
&& alttm.tm_isdst == mytm.tm_isdst
|
|
&& alttm.tm_gmtoff == yourtm.tm_gmtoff
|
|
&& tmcomp(&alttm, &yourtm) == 0) {
|
|
t = altt;
|
|
mytm = alttm;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
|
|
break;
|
|
|
|
/*
|
|
** Right time, wrong type.
|
|
** Hunt for right time, right type.
|
|
** It's okay to guess wrong since the guess
|
|
** gets checked.
|
|
*/
|
|
if (sp == NULL)
|
|
return WRONG;
|
|
for (i = sp->typecnt - 1; i >= 0; --i) {
|
|
if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
|
|
continue;
|
|
for (j = sp->typecnt - 1; j >= 0; --j) {
|
|
if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
|
|
continue;
|
|
newt = t + sp->ttis[j].tt_gmtoff -
|
|
sp->ttis[i].tt_gmtoff;
|
|
if (! funcp(sp, &newt, offset, &mytm))
|
|
continue;
|
|
if (tmcomp(&mytm, &yourtm) != 0)
|
|
continue;
|
|
if (mytm.tm_isdst != yourtm.tm_isdst)
|
|
continue;
|
|
/*
|
|
** We have a match.
|
|
*/
|
|
t = newt;
|
|
goto label;
|
|
}
|
|
}
|
|
return WRONG;
|
|
}
|
|
|
|
label:
|
|
|
|
newt = t + saved_seconds;
|
|
if ((newt < t) != (saved_seconds < 0)) {
|
|
return WRONG;
|
|
}
|
|
|
|
t = newt;
|
|
|
|
if (funcp(sp, &t, offset, tmp)) {
|
|
*okayp = true;
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
time_t time2(struct tm * const tmp,
|
|
struct tm *(*funcp)(struct state const *,
|
|
time_t const *,
|
|
int_fast32_t,
|
|
struct tm *),
|
|
struct state const *sp,
|
|
const int_fast32_t offset,
|
|
bool *okayp)
|
|
{
|
|
time_t t;
|
|
|
|
/*
|
|
** First try without normalization of seconds
|
|
** (in case tm_sec contains a value associated with a leap second).
|
|
** If that fails, try with normalization of seconds.
|
|
*/
|
|
t = time2sub(tmp, funcp, sp, offset, okayp, false);
|
|
return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true);
|
|
}
|
|
|
|
time_t time1(
|
|
struct tm *const tmp,
|
|
struct tm *(*funcp) (struct state const *,
|
|
time_t const *,
|
|
int_fast32_t, struct tm *),
|
|
struct state const *sp,
|
|
const int_fast32_t offset)
|
|
{
|
|
time_t t;
|
|
int samei, otheri;
|
|
int sameind, otherind;
|
|
int i;
|
|
int nseen;
|
|
char seen[TZ_MAX_TYPES];
|
|
unsigned char types[TZ_MAX_TYPES];
|
|
bool okay;
|
|
|
|
if (tmp == NULL) {
|
|
errno = EINVAL;
|
|
return WRONG;
|
|
}
|
|
|
|
if (tmp->tm_isdst > 1) {
|
|
tmp->tm_isdst = 1;
|
|
}
|
|
|
|
t = time2(tmp, funcp, sp, offset, &okay);
|
|
if (okay) {
|
|
return t;
|
|
}
|
|
|
|
if (tmp->tm_isdst < 0) {
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
** We're supposed to assume that somebody took a time of one type
|
|
** and did some math on it that yielded a "struct tm" that's bad.
|
|
** We try to divine the type they started from and adjust to the
|
|
** type they need.
|
|
*/
|
|
if (sp == NULL) {
|
|
return WRONG;
|
|
}
|
|
|
|
for (i = 0; i < sp->typecnt; ++i) {
|
|
seen[i] = false;
|
|
}
|
|
|
|
nseen = 0;
|
|
for (i = sp->timecnt - 1; i >= 0; --i) {
|
|
if (!seen[sp->types[i]]) {
|
|
seen[sp->types[i]] = true;
|
|
types[nseen++] = sp->types[i];
|
|
}
|
|
}
|
|
|
|
for (sameind = 0; sameind < nseen; ++sameind) {
|
|
samei = types[sameind];
|
|
if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
|
|
continue;
|
|
for (otherind = 0; otherind < nseen; ++otherind) {
|
|
otheri = types[otherind];
|
|
if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst)
|
|
continue;
|
|
tmp->tm_sec += sp->ttis[otheri].tt_gmtoff -
|
|
sp->ttis[samei].tt_gmtoff;
|
|
tmp->tm_isdst = !tmp->tm_isdst;
|
|
t = time2(tmp, funcp, sp, offset, &okay);
|
|
if (okay)
|
|
return t;
|
|
tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff -
|
|
sp->ttis[samei].tt_gmtoff;
|
|
tmp->tm_isdst = !tmp->tm_isdst;
|
|
}
|
|
}
|
|
|
|
return WRONG;
|
|
}
|
|
|
|
time_t mktime_tzname(struct state *sp, struct tm *tmp, bool setname)
|
|
{
|
|
if (sp) {
|
|
return time1(tmp, localsub, sp, setname);
|
|
} else {
|
|
gmtcheck();
|
|
return time1(tmp, gmtsub, gmtptr, 0);
|
|
}
|
|
}
|