531 lines
15 KiB
C
531 lines
15 KiB
C
#include "time_header.h"
|
|
#include "time_rule.h"
|
|
|
|
/*
|
|
** Given a pointer into a time zone string, scan until a character that is not
|
|
** a valid character in a zone name is found. Return a pointer to that
|
|
** character.
|
|
*/
|
|
|
|
const char *getzname(const char *strp)
|
|
{
|
|
char c;
|
|
|
|
while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && c != '+') {
|
|
++strp;
|
|
}
|
|
|
|
return strp;
|
|
}
|
|
|
|
/*
|
|
** Given a pointer into an extended time zone string, scan until the ending
|
|
** delimiter of the zone name is located. Return a pointer to the delimiter.
|
|
**
|
|
** As with getzname above, the legal character set is actually quite
|
|
** restricted, with other characters producing undefined results.
|
|
** We don't do any checking here; checking is done later in common-case code.
|
|
*/
|
|
|
|
const char*getqzname(const char *strp, const int delim)
|
|
{
|
|
int c;
|
|
|
|
while ((c = *strp) != '\0' && c != delim) {
|
|
++strp;
|
|
}
|
|
|
|
return strp;
|
|
}
|
|
|
|
|
|
/*
|
|
** Given a pointer into a time zone string, extract a number from that string.
|
|
** Check that the number is within a specified range; if it is not, return
|
|
** NULL.
|
|
** Otherwise, return a pointer to the first character not part of the number.
|
|
*/
|
|
|
|
const char *getnum(
|
|
const char *strp,
|
|
int *const nump,
|
|
const int min,
|
|
const int max
|
|
)
|
|
{
|
|
char c;
|
|
int num;
|
|
|
|
if (strp == NULL || !is_digit(c = *strp))
|
|
return NULL;
|
|
num = 0;
|
|
do {
|
|
num = num * 10 + (c - '0');
|
|
if (num > max)
|
|
return NULL; /* illegal value */
|
|
c = *++strp;
|
|
} while (is_digit(c));
|
|
if (num < min)
|
|
return NULL; /* illegal value */
|
|
*nump = num;
|
|
return strp;
|
|
}
|
|
|
|
/*
|
|
** Given a pointer into a time zone string, extract a number of seconds,
|
|
** in hh[:mm[:ss]] form, from the string.
|
|
** If any error occurs, return NULL.
|
|
** Otherwise, return a pointer to the first character not part of the number
|
|
** of seconds.
|
|
*/
|
|
|
|
const char *getsecs(
|
|
const char *strp,
|
|
int_fast32_t *const secsp
|
|
)
|
|
{
|
|
int num;
|
|
|
|
/*
|
|
** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
|
|
** "M10.4.6/26", which does not conform to Posix,
|
|
** but which specifies the equivalent of
|
|
** "02:00 on the first Sunday on or after 23 Oct".
|
|
*/
|
|
strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
|
|
if (strp == NULL)
|
|
return NULL;
|
|
*secsp = num * (int_fast32_t) SECSPERHOUR;
|
|
if (*strp == ':') {
|
|
++strp;
|
|
strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
|
|
if (strp == NULL)
|
|
return NULL;
|
|
*secsp += num * SECSPERMIN;
|
|
if (*strp == ':') {
|
|
++strp;
|
|
/* 'SECSPERMIN' allows for leap seconds. */
|
|
strp = getnum(strp, &num, 0, SECSPERMIN);
|
|
if (strp == NULL)
|
|
return NULL;
|
|
*secsp += num;
|
|
}
|
|
}
|
|
return strp;
|
|
}
|
|
|
|
const char *getoffset(const char *strp, int_fast32_t *const offsetp)
|
|
{
|
|
bool neg = false;
|
|
|
|
if (*strp == '-') {
|
|
neg = true;
|
|
++strp;
|
|
} else if (*strp == '+') {
|
|
++strp;
|
|
}
|
|
|
|
strp = getsecs(strp, offsetp);
|
|
if (strp == NULL)
|
|
return NULL; // illegal time
|
|
|
|
if (neg)
|
|
*offsetp = -*offsetp;
|
|
|
|
return strp;
|
|
}
|
|
|
|
/*
|
|
** Given a pointer into a time zone string, extract a rule in the form
|
|
** date[/time]. See POSIX section 8 for the format of "date" and "time".
|
|
** If a valid rule is not found, return NULL.
|
|
** Otherwise, return a pointer to the first character not part of the rule.
|
|
*/
|
|
|
|
const char *getrule(
|
|
const char *strp,
|
|
struct rule *const rulep
|
|
)
|
|
{
|
|
if (*strp == 'J') {
|
|
/*
|
|
** Julian day.
|
|
*/
|
|
rulep->r_type = JULIAN_DAY;
|
|
++strp;
|
|
strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
|
|
} else if (*strp == 'M') {
|
|
/*
|
|
** Month, week, day.
|
|
*/
|
|
rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
|
|
++strp;
|
|
strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
|
|
if (strp == NULL)
|
|
return NULL;
|
|
if (*strp++ != '.')
|
|
return NULL;
|
|
strp = getnum(strp, &rulep->r_week, 1, 5);
|
|
if (strp == NULL)
|
|
return NULL;
|
|
if (*strp++ != '.')
|
|
return NULL;
|
|
strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
|
|
} else if (is_digit(*strp)) {
|
|
/*
|
|
** Day of year.
|
|
*/
|
|
rulep->r_type = DAY_OF_YEAR;
|
|
strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
|
|
} else return NULL; /* invalid format */
|
|
if (strp == NULL)
|
|
return NULL;
|
|
if (*strp == '/') {
|
|
/*
|
|
** Time specified.
|
|
*/
|
|
++strp;
|
|
strp = getoffset(strp, &rulep->r_time);
|
|
} else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
|
|
return strp;
|
|
}
|
|
|
|
/*
|
|
** Given a year, a rule, and the offset from UT at the time that rule takes
|
|
** effect, calculate the year-relative time that rule takes effect.
|
|
*/
|
|
|
|
int_fast32_t transtime(
|
|
const int year,
|
|
const struct rule *const rulep,
|
|
const int_fast32_t offset
|
|
)
|
|
{
|
|
bool leapyear;
|
|
int_fast32_t value;
|
|
int i;
|
|
int d, m1, yy0, yy1, yy2, dow;
|
|
|
|
value = 0;
|
|
leapyear = isleap(year);
|
|
switch (rulep->r_type) {
|
|
|
|
case JULIAN_DAY:
|
|
/*
|
|
** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
|
|
** years.
|
|
** In non-leap years, or if the day number is 59 or less, just
|
|
** add SECSPERDAY times the day number-1 to the time of
|
|
** January 1, midnight, to get the day.
|
|
*/
|
|
value = (rulep->r_day - 1) * SECSPERDAY;
|
|
if (leapyear && rulep->r_day >= 60)
|
|
value += SECSPERDAY;
|
|
break;
|
|
|
|
case DAY_OF_YEAR:
|
|
/*
|
|
** n - day of year.
|
|
** Just add SECSPERDAY times the day number to the time of
|
|
** January 1, midnight, to get the day.
|
|
*/
|
|
value = rulep->r_day * SECSPERDAY;
|
|
break;
|
|
|
|
case MONTH_NTH_DAY_OF_WEEK:
|
|
/*
|
|
** Mm.n.d - nth "dth day" of month m.
|
|
*/
|
|
|
|
/*
|
|
** Use Zeller's Congruence to get day-of-week of first day of
|
|
** month.
|
|
*/
|
|
m1 = (rulep->r_mon + 9) % 12 + 1;
|
|
yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
|
|
yy1 = yy0 / 100;
|
|
yy2 = yy0 % 100;
|
|
dow = ((26 * m1 - 2) / 10 +
|
|
1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
|
|
if (dow < 0)
|
|
dow += DAYSPERWEEK;
|
|
|
|
/*
|
|
** "dow" is the day-of-week of the first day of the month. Get
|
|
** the day-of-month (zero-origin) of the first "dow" day of the
|
|
** month.
|
|
*/
|
|
d = rulep->r_day - dow;
|
|
if (d < 0)
|
|
d += DAYSPERWEEK;
|
|
for (i = 1; i < rulep->r_week; ++i) {
|
|
if (d + DAYSPERWEEK >=
|
|
mon_lengths[leapyear][rulep->r_mon - 1])
|
|
break;
|
|
d += DAYSPERWEEK;
|
|
}
|
|
|
|
/*
|
|
** "d" is the day-of-month (zero-origin) of the day we want.
|
|
*/
|
|
value = d * SECSPERDAY;
|
|
for (i = 0; i < rulep->r_mon - 1; ++i)
|
|
value += mon_lengths[leapyear][i] * SECSPERDAY;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
** "value" is the year-relative time of 00:00:00 UT on the day in
|
|
** question. To get the year-relative time of the specified local
|
|
** time on that day, add the transition time and the current offset
|
|
** from UT.
|
|
*/
|
|
return value + rulep->r_time + offset;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Given a POSIX section 8-style TZ string, fill in the rule tables as
|
|
* appropriate.
|
|
*/
|
|
bool tzparse(
|
|
const char *name,
|
|
struct state *const sp,
|
|
bool lastditch
|
|
)
|
|
{
|
|
const char* stdname;
|
|
const char* dstname;
|
|
size_t stdlen;
|
|
size_t dstlen;
|
|
size_t charcnt;
|
|
int_fast32_t stdoffset;
|
|
int_fast32_t dstoffset;
|
|
register char* cp;
|
|
register bool load_ok;
|
|
|
|
dstname = '\0';
|
|
stdname = name;
|
|
|
|
if (lastditch) {
|
|
stdlen = sizeof gmt - 1;
|
|
name += stdlen;
|
|
stdoffset = 0;
|
|
} else {
|
|
if (*name == '<') {
|
|
name++;
|
|
stdname = name;
|
|
name = getqzname(name, '>');
|
|
if (*name != '>')
|
|
return false;
|
|
stdlen = name - stdname;
|
|
name++;
|
|
} else {
|
|
name = getzname(name);
|
|
stdlen = name - stdname;
|
|
}
|
|
if (!stdlen)
|
|
return false;
|
|
name = getoffset(name, &stdoffset);
|
|
if (name == NULL)
|
|
return false;
|
|
}
|
|
charcnt = stdlen + 1;
|
|
if (sizeof sp->chars < charcnt)
|
|
return false;
|
|
load_ok = tzload(TZDEFRULES, sp, false) == 0;
|
|
if (!load_ok)
|
|
sp->leapcnt = 0; // so, we're off a little
|
|
if (*name != '\0') {
|
|
if (*name == '<') {
|
|
dstname = ++name;
|
|
name = getqzname(name, '>');
|
|
if (*name != '>')
|
|
return false;
|
|
dstlen = name - dstname;
|
|
name++;
|
|
} else {
|
|
dstname = name;
|
|
name = getzname(name);
|
|
dstlen = name - dstname; // length of DST zone name
|
|
}
|
|
if (!dstlen)
|
|
return false;
|
|
charcnt += dstlen + 1;
|
|
if (sizeof sp->chars < charcnt)
|
|
return false;
|
|
if (*name != '\0' && *name != ',' && *name != ';') {
|
|
name = getoffset(name, &dstoffset);
|
|
if (name == NULL)
|
|
return false;
|
|
} else dstoffset = stdoffset - SECSPERHOUR;
|
|
if (*name == '\0' && !load_ok)
|
|
name = TZDEFRULESTRING;
|
|
if (*name == ',' || *name == ';') {
|
|
struct rule start;
|
|
struct rule end;
|
|
int year;
|
|
int yearlim;
|
|
int timecnt;
|
|
time_t janfirst;
|
|
|
|
++name;
|
|
if ((name = getrule(name, &start)) == NULL)
|
|
return false;
|
|
if (*name++ != ',')
|
|
return false;
|
|
if ((name = getrule(name, &end)) == NULL)
|
|
return false;
|
|
if (*name != '\0')
|
|
return false;
|
|
sp->typecnt = 2; /* standard time and DST */
|
|
/*
|
|
** Two transitions per year, from EPOCH_YEAR forward.
|
|
*/
|
|
init_ttinfo(&sp->ttis[0], -dstoffset, true, stdlen + 1);
|
|
init_ttinfo(&sp->ttis[1], -stdoffset, false, 0);
|
|
sp->defaulttype = 0;
|
|
timecnt = 0;
|
|
janfirst = 0;
|
|
yearlim = EPOCH_YEAR + YEARSPERREPEAT;
|
|
for (year = EPOCH_YEAR; year < yearlim; year++) {
|
|
int_fast32_t
|
|
starttime = transtime(year, &start, stdoffset),
|
|
endtime = transtime(year, &end, dstoffset);
|
|
int_fast32_t
|
|
yearsecs = (year_lengths[isleap(year)]
|
|
* SECSPERDAY);
|
|
bool reversed = endtime < starttime;
|
|
if (reversed) {
|
|
int_fast32_t swap = starttime;
|
|
starttime = endtime;
|
|
endtime = swap;
|
|
}
|
|
if (reversed
|
|
|| (starttime < endtime
|
|
&& (endtime - starttime
|
|
< (yearsecs
|
|
+ (stdoffset - dstoffset))))) {
|
|
if (TZ_MAX_TIMES - 2 < timecnt)
|
|
break;
|
|
yearlim = year + YEARSPERREPEAT + 1;
|
|
sp->ats[timecnt] = janfirst;
|
|
if (increment_overflow_time
|
|
(&sp->ats[timecnt], starttime))
|
|
break;
|
|
sp->types[timecnt++] = reversed;
|
|
sp->ats[timecnt] = janfirst;
|
|
if (increment_overflow_time
|
|
(&sp->ats[timecnt], endtime))
|
|
break;
|
|
sp->types[timecnt++] = !reversed;
|
|
}
|
|
if (increment_overflow_time(&janfirst, yearsecs))
|
|
break;
|
|
}
|
|
sp->timecnt = timecnt;
|
|
if (!timecnt)
|
|
sp->typecnt = 1; /* Perpetual DST. */
|
|
} else {
|
|
int_fast32_t theirstdoffset;
|
|
int_fast32_t theirdstoffset;
|
|
int_fast32_t theiroffset;
|
|
bool isdst;
|
|
int i;
|
|
int j;
|
|
|
|
if (*name != '\0')
|
|
return false;
|
|
/*
|
|
** Initial values of theirstdoffset and theirdstoffset.
|
|
*/
|
|
theirstdoffset = 0;
|
|
for (i = 0; i < sp->timecnt; ++i) {
|
|
j = sp->types[i];
|
|
if (!sp->ttis[j].tt_isdst) {
|
|
theirstdoffset =
|
|
-sp->ttis[j].tt_gmtoff;
|
|
break;
|
|
}
|
|
}
|
|
theirdstoffset = 0;
|
|
for (i = 0; i < sp->timecnt; ++i) {
|
|
j = sp->types[i];
|
|
if (sp->ttis[j].tt_isdst) {
|
|
theirdstoffset =
|
|
-sp->ttis[j].tt_gmtoff;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
** Initially we're assumed to be in standard time.
|
|
*/
|
|
isdst = false;
|
|
theiroffset = theirstdoffset;
|
|
/*
|
|
** Now juggle transition times and types
|
|
** tracking offsets as you do.
|
|
*/
|
|
for (i = 0; i < sp->timecnt; ++i) {
|
|
j = sp->types[i];
|
|
sp->types[i] = sp->ttis[j].tt_isdst;
|
|
if (sp->ttis[j].tt_ttisgmt) {
|
|
/* No adjustment to transition time */
|
|
} else {
|
|
/*
|
|
** If summer time is in effect, and the
|
|
** transition time was not specified as
|
|
** standard time, add the summer time
|
|
** offset to the transition time;
|
|
** otherwise, add the standard time
|
|
** offset to the transition time.
|
|
*/
|
|
/*
|
|
** Transitions from DST to DDST
|
|
** will effectively disappear since
|
|
** POSIX provides for only one DST
|
|
** offset.
|
|
*/
|
|
if (isdst && !sp->ttis[j].tt_ttisstd) {
|
|
sp->ats[i] += dstoffset -
|
|
theirdstoffset;
|
|
} else {
|
|
sp->ats[i] += stdoffset -
|
|
theirstdoffset;
|
|
}
|
|
}
|
|
theiroffset = -sp->ttis[j].tt_gmtoff;
|
|
if (sp->ttis[j].tt_isdst)
|
|
theirdstoffset = theiroffset;
|
|
else theirstdoffset = theiroffset;
|
|
}
|
|
/*
|
|
** Finally, fill in ttis.
|
|
*/
|
|
init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
|
|
init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
|
|
sp->typecnt = 2;
|
|
sp->defaulttype = 0;
|
|
}
|
|
} else {
|
|
dstlen = 0;
|
|
sp->typecnt = 1; // only standard time
|
|
sp->timecnt = 0;
|
|
init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
|
|
sp->defaulttype = 0;
|
|
}
|
|
|
|
sp->charcnt = charcnt;
|
|
cp = sp->chars;
|
|
memcpy(cp, stdname, stdlen);
|
|
cp += stdlen;
|
|
*cp++ = '\0';
|
|
|
|
if (dstlen != 0) {
|
|
memcpy(cp, dstname, dstlen);
|
|
*(cp + dstlen) = '\0';
|
|
}
|
|
|
|
return true;
|
|
}
|