tmxdate.c revision da2e3ebdc1edfbc5028edf1354e7dd2fa69a7968
/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1985-2007 AT&T Knowledge Ventures *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Knowledge Ventures *
* *
* A copy of the License is available at *
* http://www.opensource.org/licenses/cpl1.0.txt *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* Glenn Fowler <gsf@research.att.com> *
* David Korn <dgk@research.att.com> *
* Phong Vo <kpv@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* Time_t conversion support
*
* relative times inspired by Steve Bellovin's netnews getdate(3)
*/
#include <tmx.h>
#include <ctype.h>
#define dig1(s,n) ((n)=((*(s)++)-'0'))
#define dig2(s,n) ((n)=((*(s)++)-'0')*10,(n)+=(*(s)++)-'0')
#define dig3(s,n) ((n)=((*(s)++)-'0')*100,(n)+=((*(s)++)-'0')*10,(n)+=(*(s)++)-'0')
#define dig4(s,n) ((n)=((*(s)++)-'0')*1000,(n)+=((*(s)++)-'0')*100,(n)+=((*(s)++)-'0')*10,(n)+=(*(s)++)-'0')
#define BREAK (1<<0)
#define CCYYMMDDHHMMSS (1<<1)
#define CRON (1<<2)
#define DAY (1<<3)
#define EXACT (1<<4)
#define FINAL (1<<5)
#define HOLD (1<<6)
#define HOUR (1<<7)
#define LAST (1<<8)
#define MDAY (1<<9)
#define MINUTE (1<<10)
#define MONTH (1<<11)
#define NEXT (1<<12)
#define NSEC (1<<13)
#define SECOND (1<<14)
#define THIS (1L<<15)
#define WDAY (1L<<16)
#define YEAR (1L<<17)
#define ZONE (1L<<18)
/*
* parse cron range into set
* return: -1:error 0:* 1:some
*/
static int
range(register char* s, char** e, char* set, int lo, int hi)
{
int n;
int m;
int i;
char* t;
while (isspace(*s) || *s == '_')
s++;
if (*s == '*')
{
*e = s + 1;
return 0;
}
memset(set, 0, hi + 1);
for (;;)
{
n = strtol(s, &t, 10);
if (s == t || n < lo || n > hi)
return -1;
i = 1;
if (*(s = t) == '-')
{
m = strtol(++s, &t, 10);
if (s == t || m < n || m > hi)
return -1;
if (*(s = t) == '/')
{
i = strtol(++s, &t, 10);
if (s == t || i < 1)
return -1;
s = t;
}
}
else
m = n;
for (; n <= m; n += i)
set[n] = 1;
if (*s != ',')
break;
s++;
}
*e = s;
return 1;
}
/*
* parse date expression in s and return Time_t value
*
* if non-null, e points to the first invalid sequence in s
* now provides default values
*/
Time_t
tmxdate(register const char* s, char** e, Time_t now)
{
register Tm_t* tm;
register long n;
register int w;
unsigned long set;
unsigned long state;
unsigned long flags;
Time_t fix;
char* t;
char* u;
const char* x;
char* last;
char* type;
int day;
int dir;
int dst;
int zone;
int c;
int f;
int i;
int j;
int k;
int l;
long m;
long p;
long q;
Tm_zone_t* zp;
char skip[UCHAR_MAX + 1];
/*
* check DATEMSK first
*/
fix = tmxscan(s, &last, NiL, &t, now, 0);
if (t && !*last)
{
if (e)
*e = last;
return fix;
}
reset:
/*
* use now for defaults
*/
tm = tmxmake(now);
tm_info.date = tm_info.zone;
day = -1;
dst = TM_DST;
set = state = 0;
type = 0;
zone = TM_LOCALZONE;
skip[0] = 0;
for (n = 1; n <= UCHAR_MAX; n++)
skip[n] = isspace(n) || strchr("_,;@=|!^()[]{}", n);
/*
* get <weekday year month day hour minutes seconds ?[ds]t [ap]m>
*/
for (;;)
{
state &= (state & HOLD) ? ~(HOLD) : ~(EXACT|LAST|NEXT|THIS);
if ((set|state) & (YEAR|MONTH|DAY))
skip['/'] = 1;
for (;;)
{
if (*s == '.' || *s == '-' || *s == '+')
{
if (((set|state) & (YEAR|MONTH|HOUR|MINUTE|ZONE)) == (YEAR|MONTH|HOUR|MINUTE) && (i = tmgoff(s, &t, TM_LOCALZONE)) != TM_LOCALZONE)
{
zone = i;
state |= ZONE;
if (!*(s = t))
break;
}
else if (*s == '+')
break;
}
else if (!skip[*s])
break;
s++;
}
if (!*(last = (char*)s))
break;
if (*s == '#')
{
if (isdigit(*++s))
{
now = strtoull(s, &t, 0);
sns:
if (*(s = t) == '.')
{
fix = 0;
m = 1000000000;
while (isdigit(*++s))
fix += (*s - '0') * (m /= 10);
now = tmxsns(now, fix);
}
else if (now <= 0x7fffffff)
now = tmxsns(now, 0);
goto reset;
}
else if (*s++ == '#')
{
now = tmxtime(tm, zone);
goto reset;
}
break;
}
f = -1;
if (*s == '+')
{
while (isspace(*++s) || *s == '_');
n = strtol(s, &t, 0);
if (w = t - s)
{
for (s = t; skip[*s]; s++);
state |= (f = n) ? NEXT : THIS;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
}
else
s = last;
}
if (!(state & CRON))
{
/*
* check for cron date
*
* min hour day-of-month month day-of-week
*
* if it's cron then determine the next time
* that satisfies the specification
*
* NOTE: the only spacing is ' '||'_'||';'
*/
i = 0;
n = *(t = (char*)s);
for (;;)
{
if (n == '*')
n = *++s;
else if (!isdigit(n))
break;
else
while ((n = *++s) == ',' || n == '-' || n == '/' || isdigit(n));
if (n != ' ' && n != '_' && n != ';')
{
if (!n)
i++;
break;
}
i++;
while ((n = *++s) == ' ' || n == '_');
}
if (i == 5)
{
Time_t tt;
char hit[60];
char mon[12];
char day[7];
state |= CRON;
flags = 0;
tm->tm_sec = 0;
tm->tm_min++;
tmfix(tm);
/*
* minute
*/
if ((k = range(t, &t, hit, 0, 59)) < 0)
break;
if (k && !hit[i = tm->tm_min])
{
hit[i] = 1;
do if (++i > 59)
{
i = 0;
if (++tm->tm_hour > 59)
{
tm->tm_min = i;
tmfix(tm);
}
} while (!hit[i]);
tm->tm_min = i;
}
/*
* hour
*/
if ((k = range(t, &t, hit, 0, 23)) < 0)
break;
if (k && !hit[i = tm->tm_hour])
{
hit[i] = 1;
do if (++i > 23)
{
i = 0;
if (++tm->tm_mday > 28)
{
tm->tm_hour = i;
tmfix(tm);
}
} while (!hit[i]);
tm->tm_hour = i;
}
/*
* day of month
*/
if ((k = range(t, &t, hit, 1, 31)) < 0)
break;
if (k)
flags |= DAY|MDAY;
/*
* month
*/
if ((k = range(t, &t, mon, 1, 12)) < 0)
break;
if (k)
flags |= MONTH;
else
for (i = 1; i <= 12; i++)
mon[i] = 1;
/*
* day of week
*/
if ((k = range(t, &t, day, 0, 6)) < 0)
break;
if (k)
flags |= WDAY;
s = t;
if (flags & (MONTH|MDAY|WDAY))
{
fix = tmxtime(tm, zone);
tm = tmxmake(fix);
i = tm->tm_mon + 1;
j = tm->tm_mday;
k = tm->tm_wday;
for (;;)
{
if (!mon[i])
{
if (++i > 12)
{
i = 1;
tm->tm_year++;
}
tm->tm_mon = i - 1;
tm->tm_mday = 1;
tt = tmxtime(tm, zone);
if (tt < fix)
goto done;
tm = tmxmake(tt);
i = tm->tm_mon + 1;
j = tm->tm_mday;
k = tm->tm_wday;
continue;
}
if (flags & (MDAY|WDAY))
{
if ((flags & (MDAY|WDAY)) == (MDAY|WDAY))
{
if (hit[j] && day[k])
break;
}
else if ((flags & MDAY) && hit[j])
break;
else if ((flags & WDAY) && day[k])
break;
if (++j > 28)
{
tm->tm_mon = i - 1;
tm->tm_mday = j;
tm = tmxmake(tmxtime(tm, zone));
i = tm->tm_mon + 1;
j = tm->tm_mday;
k = tm->tm_wday;
}
else if ((flags & WDAY) && ++k > 6)
k = 0;
}
else if (flags & MONTH)
break;
}
tm->tm_mon = i - 1;
tm->tm_mday = j;
tm->tm_wday = k;
}
continue;
}
s = t;
}
n = -1;
if (isdigit(*s))
{
n = strtol(s, &t, 10);
if ((w = t - s) && *t == '.' && isdigit(*(t + 1)) && isdigit(*(t + 2)) && isdigit(*(t + 3)))
{
now = n;
goto sns;
}
u = t + (*t == '-');
if ((w == 2 || w == 4) && (*u == 'W' || *u == 'w') && isdigit(*(u + 1)))
{
t = u;
if (w == 4)
{
if ((n -= 1900) < TM_WINDOW)
break;
}
else if (n < TM_WINDOW)
n += 100;
m = n;
n = strtol(s = t + 1, &t, 0);
if ((i = (t - s)) < 2 || i > 3)
break;
if (dig2(s, j) < 0 || j > 53)
break;
if (!(t - s) && *t == '-')
n = strtol(s = t + 1, &t, 0);
if (!(i = (t - s)))
k = 1;
else if (i != 1 || dig1(s, k) < 1 || k > 7)
break;
else if (k == 7)
k = 0;
tm->tm_year = m;
tmweek(tm, 2, j, k);
set |= YEAR|MONTH|DAY;
continue;
}
else if ((w == 6 || w == 8) && (*u == 'T' || *u == 't') && isdigit(*(u + 1)))
{
t = u;
flags = 0;
if (w == 8)
{
dig4(s, m);
if ((m -= 1900) < TM_WINDOW)
break;
}
else
{
dig2(s, m);
if (m < TM_WINDOW)
m += 100;
}
flags |= YEAR;
if (dig2(s, l) <= 0 || l > 12)
break;
flags |= MONTH;
if (dig2(s, k) < 1 || k > 31)
break;
n = strtol(s = t + 1, &t, 0);
if ((t - s) < 2)
break;
if (dig2(s, j) > 24)
break;
if ((t - s) < 2)
{
if ((t - s) == 1 || *t++ != '-')
break;
n = strtol(s = t, &t, 0);
if ((t - s) < 2)
break;
}
if (dig2(s, i) > 59)
break;
flags |= HOUR|MINUTE;
if ((t - s) == 2)
{
if (dig2(s, n) > (59 + TM_MAXLEAP))
break;
flags |= SECOND;
}
else if (t - s)
break;
else
n = 0;
p = 0;
if (*t == '.')
{
q = 1000000000;
while (isdigit(*++t))
p += (*t - '0') * (q /= 10);
set |= NSEC;
}
if (n > (59 + TM_MAXLEAP))
break;
goto save;
}
else if (f == -1 && isalpha(*t) && tmlex(t, &t, tm_info.format + TM_ORDINAL, TM_ORDINALS - TM_ORDINAL, NiL, 0) >= 0)
{
ordinal:
state |= (f = n) ? NEXT : THIS;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
for (s = t; skip[*s]; s++);
if (isdigit(*s))
{
n = strtol(s, &t, 10);
s = t;
if (*s == '_')
s++;
}
else
n = -1;
}
else
{
if (!(state & (LAST|NEXT|THIS)) && ((i = t - s) == 4 && (*t == '.' && isdigit(*(t + 1)) && isdigit(*(t + 2)) && *(t + 3) != '.' || (!*t || isspace(*t) || *t == '_' || isalnum(*t)) && n >= 0 && (n % 100) < 60 && ((m = (n / 100)) < 20 || m < 24 && !((set|state) & (YEAR|MONTH|HOUR|MINUTE)))) || i > 4 && i <= 12))
{
/*
* various { date(1) touch(1) } formats
*
* [[cc]yy[mm]]ddhhmm[.ss[.nn...]]
* [cc]yyjjj
* hhmm[.ss[.nn...]]
*/
flags = 0;
if (state & CCYYMMDDHHMMSS)
break;
state |= CCYYMMDDHHMMSS;
p = 0;
if ((i == 7 || i == 5) && !*t)
{
if (i == 7)
{
dig4(s, m);
if ((m -= 1900) < TM_WINDOW)
break;
}
else if (dig2(s, m) < TM_WINDOW)
m += 100;
dig3(s, k);
l = 1;
j = 0;
i = 0;
n = 0;
flags |= MONTH;
}
else if (i & 1)
break;
else
{
u = t;
if (i == 12)
{
x = s;
dig2(x, m);
if (m <= 12)
{
u -= 4;
i -= 4;
x = s + 8;
dig4(x, m);
}
else
dig4(s, m);
m -= 1900;
}
else if (i == 10)
{
x = s;
if (!dig2(x, m) || m > 12 || !dig2(x, m) || m > 31 || dig2(x, m) > 24 || dig2(x, m) > 60 || dig2(x, m) <= 60 && !(tm_info.flags & TM_DATESTYLE))
dig2(s, m);
else
{
u -= 2;
i -= 2;
x = s + 8;
dig2(x, m);
}
if (m < TM_WINDOW)
m += 100;
}
else
m = tm->tm_year;
if ((u - s) < 8)
l = tm->tm_mon + 1;
else if (dig2(s, l) <= 0 || l > 12)
break;
else
flags |= MONTH;
if ((u - s) < 6)
k = tm->tm_mday;
else if (dig2(s, k) < 1 || k > 31)
break;
else
flags |= DAY;
if ((u - s) < 4)
break;
if (dig2(s, j) > 24)
break;
if (dig2(s, i) > 59)
break;
flags |= HOUR|MINUTE;
if ((u - s) == 2)
{
dig2(s, n);
flags |= SECOND;
}
else if (u - s)
break;
else if (*t != '.')
n = 0;
else
{
n = strtol(t + 1, &t, 10);
flags |= SECOND;
if (*t == '.')
{
q = 1000000000;
while (isdigit(*++t))
p += (*t - '0') * (q /= 10);
set |= NSEC;
}
}
if (n > (59 + TM_MAXLEAP))
break;
}
save:
tm->tm_year = m;
tm->tm_mon = l - 1;
tm->tm_mday = k;
tm->tm_hour = j;
tm->tm_min = i;
tm->tm_sec = n;
tm->tm_nsec = p;
s = t;
set |= flags;
continue;
}
for (s = t; skip[*s]; s++);
if (*s == ':' || *s == '.' && ((set|state) & (YEAR|MONTH|DAY|HOUR)) == (YEAR|MONTH|DAY))
{
c = *s;
if ((state & HOUR) || n > 24)
break;
while (isspace(*++s) || *s == '_');
if (!isdigit(*s))
break;
i = n;
n = strtol(s, &t, 10);
for (s = t; isspace(*s) || *s == '_'; s++);
if (n > 59)
break;
j = n;
m = 0;
if (*s == c)
{
while (isspace(*++s) || *s == '_');
if (!isdigit(*s))
break;
n = strtol(s, &t, 10);
s = t;
if (n > (59 + TM_MAXLEAP))
break;
set |= SECOND;
while (isspace(*s))
s++;
if (*s == '.')
{
q = 1000000000;
while (isdigit(*++s))
m += (*s - '0') * (q /= 10);
set |= NSEC;
}
}
else
n = 0;
set |= HOUR|MINUTE;
skip[':'] = 1;
k = tm->tm_hour;
tm->tm_hour = i;
l = tm->tm_min;
tm->tm_min = j;
tm->tm_sec = n;
tm->tm_nsec = m;
while (isspace(*s))
s++;
switch (tmlex(s, &t, tm_info.format, TM_NFORM, tm_info.format + TM_MERIDIAN, 2))
{
case TM_MERIDIAN:
s = t;
if (i == 12)
tm->tm_hour = i = 0;
break;
case TM_MERIDIAN+1:
if (i < 12)
tm->tm_hour = i += 12;
break;
}
if (f >= 0 || (state & (LAST|NEXT)))
{
state &= ~HOLD;
if (f < 0)
{
if (state & LAST)
f = -1;
else if (state & NEXT)
f = 1;
else
f = 0;
}
if (f > 0)
{
if (i > k || i == k && j > l)
f--;
}
else if (i < k || i == k && j < l)
f++;
if (f > 0)
{
tm->tm_hour += f * 24;
while (tm->tm_hour >= 24)
{
tm->tm_hour -= 24;
tm->tm_mday++;
}
}
else if (f < 0)
{
tm->tm_hour += f * 24;
while (tm->tm_hour < 24)
{
tm->tm_hour += 24;
tm->tm_mday--;
}
}
}
continue;
}
}
}
for (;;)
{
if (*s == '-' || *s == '+')
{
if (((set|state) & (MONTH|DAY|HOUR|MINUTE)) == (MONTH|DAY|HOUR|MINUTE) || *s == '+' && (!isdigit(s[1]) || !isdigit(s[2]) || s[3] != ':' && (s[3] != '.' || ((set|state) & (YEAR|MONTH)) != (YEAR|MONTH))))
break;
s++;
}
else if (skip[*s])
s++;
else
break;
}
if (isalpha(*s) && n < 1000)
{
if ((j = tmlex(s, &t, tm_info.format, TM_NFORM, tm_info.format + TM_SUFFIXES, TM_PARTS - TM_SUFFIXES)) >= 0)
{
s = t;
switch (tm_data.lex[j])
{
case TM_EXACT:
state |= HOLD|EXACT;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
continue;
case TM_LAST:
state |= HOLD|LAST;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
continue;
case TM_THIS:
state |= HOLD|THIS;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
n = 0;
continue;
case TM_NEXT:
/*
* disambiguate english "last ... in"
*/
if (!((state|set) & LAST))
{
state |= HOLD|NEXT;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
continue;
}
/*FALLTHROUGH*/
case TM_FINAL:
state |= HOLD|THIS|FINAL;
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS|FINAL);
continue;
case TM_ORDINAL:
j += TM_ORDINALS - TM_ORDINAL;
/*FALLTHROUGH*/
case TM_ORDINALS:
n = j - TM_ORDINALS + 1;
goto ordinal;
case TM_MERIDIAN:
if (f >= 0)
f++;
else if (state & LAST)
f = -1;
else if (state & THIS)
f = 1;
else if (state & NEXT)
f = 2;
else
f = 0;
if (n > 0)
{
if (n > 24)
goto done;
tm->tm_hour = n;
}
for (k = tm->tm_hour; k < 0; k += 24);
k %= 24;
if (j == TM_MERIDIAN)
{
if (k == 12)
tm->tm_hour -= 12;
}
else if (k < 12)
tm->tm_hour += 12;
if (n > 0)
goto clear_min;
continue;
case TM_DAY_ABBREV:
j += TM_DAY - TM_DAY_ABBREV;
/*FALLTHROUGH*/
case TM_DAY:
case TM_PARTS:
case TM_HOURS:
state |= set & (EXACT|LAST|NEXT|THIS);
if (!(state & (LAST|NEXT|THIS)))
for (;;)
{
while (skip[*s])
s++;
if ((k = tmlex(s, &t, tm_info.format + TM_LAST, TM_NOISE - TM_LAST, NiL, 0)) >= 0)
{
s = t;
if (k <= 2)
state |= LAST;
else if (k <= 5)
state |= THIS;
else if (k <= 8)
state |= NEXT;
else
state |= EXACT;
}
else
{
state |= (n > 0) ? NEXT : THIS;
break;
}
set &= ~(EXACT|LAST|NEXT|THIS);
set |= state & (EXACT|LAST|NEXT|THIS);
}
/*FALLTHROUGH*/
case TM_DAYS:
if (n == -1)
{
/*
* disambiguate english "second"
*/
if (j == TM_PARTS && f == -1)
{
n = 2;
goto ordinal;
}
n = 1;
}
if (state & LAST)
n = -n;
else if (!(state & NEXT))
n--;
m = (f > 0) ? f * n : n;
switch (j)
{
case TM_DAYS+0:
tm->tm_mday--;
set |= DAY;
goto clear_hour;
case TM_DAYS+1:
set |= DAY;
goto clear_hour;
case TM_DAYS+2:
tm->tm_mday++;
set |= DAY;
goto clear_hour;
case TM_PARTS+0:
set |= SECOND;
if ((m < 0 ? -m : m) > (365L*24L*60L*60L))
{
now = tmxtime(tm, zone) + tmxsns(m, 0);
goto reset;
}
tm->tm_sec += m;
goto clear_nsec;
case TM_PARTS+1:
tm->tm_min += m;
set |= MINUTE;
goto clear_sec;
case TM_PARTS+2:
tm->tm_hour += m;
set |= MINUTE;
goto clear_min;
case TM_PARTS+3:
tm->tm_mday += m;
if (!(set & FINAL))
set |= HOUR;
goto clear_hour;
case TM_PARTS+4:
tm = tmxmake(tmxtime(tm, zone));
tm->tm_mday += 7 * m - tm->tm_wday + 1;
set |= DAY;
goto clear_hour;
case TM_PARTS+5:
tm->tm_mon += m;
set |= MONTH;
goto clear_mday;
case TM_PARTS+6:
tm->tm_year += m;
goto clear_mon;
case TM_HOURS+0:
tm->tm_mday += m;
set |= DAY;
goto clear_hour;
case TM_HOURS+1:
tm->tm_mday += m;
tm->tm_hour = 6;
set |= HOUR;
goto clear_min;
case TM_HOURS+2:
tm->tm_mday += m;
tm->tm_hour = 12;
set |= HOUR;
goto clear_min;
case TM_HOURS+3:
tm->tm_mday += m;
tm->tm_hour = 18;
set |= HOUR;
goto clear_min;
}
tm = tmxmake(tmxtime(tm, zone));
day = j -= TM_DAY;
dir = m;
j -= tm->tm_wday;
if (state & (LAST|NEXT|THIS))
{
if (j < 0)
j += 7;
}
else if (j > 0)
j -= 7;
tm->tm_mday += j + m * 7;
set |= DAY;
if (state & (LAST|NEXT|THIS))
goto clear_hour;
continue;
case TM_MONTH_ABBREV:
j += TM_MONTH - TM_MONTH_ABBREV;
/*FALLTHROUGH*/
case TM_MONTH:
if (state & MONTH)
goto done;
state |= MONTH;
i = tm->tm_mon;
tm->tm_mon = j - TM_MONTH;
if (n < 0)
{
while (skip[*s])
s++;
if (isdigit(*s))
{
n = strtol(s, &t, 10);
if (n <= 31 && *t != ':')
s = t;
else
n = -1;
}
}
if (n >= 0)
{
if (n > 31)
goto done;
state |= DAY|MDAY;
tm->tm_mday = n;
if (f > 0)
tm->tm_year += f;
}
if (state & (LAST|NEXT|THIS))
{
n = i;
goto rel_month;
}
continue;
case TM_UT:
if (state & ZONE)
goto done;
state |= ZONE;
zone = tmgoff(s, &t, 0);
s = t;
continue;
case TM_DT:
if (!dst)
goto done;
if (!(state & ZONE))
{
dst = tm_info.zone->dst;
zone = tm_info.zone->west;
}
zone += tmgoff(s, &t, dst);
s = t;
dst = 0;
state |= ZONE;
continue;
case TM_NOISE:
continue;
}
}
if (!(state & ZONE) && (zp = tmzone(s, &t, type, &dst)))
{
s = t;
zone = zp->west + dst;
tm_info.date = zp;
state |= ZONE;
continue;
}
if (!type && (zp = tmtype(s, &t)))
{
s = t;
type = zp->type;
continue;
}
state |= BREAK;
}
else if (*s == '/')
{
if (!(state & (YEAR|MONTH)) && n >= 1900 && n < 3000 && (i = strtol(s + 1, &t, 10)) > 0 && i <= 12)
{
state |= YEAR;
tm->tm_year = n - 1900;
s = t;
i--;
}
else
{
if ((state & MONTH) || n <= 0 || n > 31)
break;
if (isalpha(*++s))
{
if ((i = tmlex(s, &t, tm_info.format, TM_DAY_ABBREV, NiL, 0)) < 0)
break;
if (i >= TM_MONTH)
i -= TM_MONTH;
s = t;
}
else
{
i = n - 1;
n = strtol(s, &t, 10);
s = t;
if (n <= 0 || n > 31)
break;
if (*s == '/' && !isdigit(*(s + 1)))
break;
}
state |= DAY;
tm->tm_mday = n;
}
state |= MONTH;
n = tm->tm_mon;
tm->tm_mon = i;
if (*s == '/')
{
n = strtol(++s, &t, 10);
w = t - s;
s = t;
if (*s == '/' || *s == ':' || *s == '-' || *s == '_')
s++;
}
else
{
if (state & (LAST|NEXT|THIS))
{
rel_month:
if (state & LAST)
tm->tm_year -= (tm->tm_mon < n) ? 0 : 1;
else
tm->tm_year += ((state & NEXT) ? 1 : 0) + ((tm->tm_mon < n) ? 1 : 0);
if (state & MDAY)
goto clear_hour;
goto clear_mday;
}
continue;
}
}
if (n < 0 || w > 4)
break;
if (w == 4)
{
if ((state & YEAR) || n < 1900 || n >= 3000)
break;
state |= YEAR;
tm->tm_year = n - 1900;
}
else if (w == 3)
{
if (state & (MONTH|MDAY|WDAY))
break;
state |= MONTH|DAY|MDAY;
tm->tm_mon = 0;
tm->tm_mday = n;
}
else if (w == 2 && !(state & YEAR))
{
state |= YEAR;
if (n < TM_WINDOW)
n += 100;
tm->tm_year = n;
}
else if (!(state & MONTH) && n >= 1 && n <= 12)
{
state |= MONTH;
tm->tm_mon = n - 1;
}
else if (!(state & (MDAY|WDAY)) && n >= 1 && n <= 31)
{
state |= DAY|MDAY|WDAY;
tm->tm_mday = n;
}
else
break;
if (state & BREAK)
{
last = t;
break;
}
continue;
clear_mon:
if ((set|state) & (EXACT|MONTH))
continue;
tm->tm_mon = 0;
clear_mday:
set |= MONTH;
if ((set|state) & (EXACT|DAY|HOUR))
continue;
tm->tm_mday = 1;
clear_hour:
set |= DAY;
if ((set|state) & (EXACT|HOUR))
continue;
tm->tm_hour = 0;
clear_min:
set |= HOUR;
if ((set|state) & (EXACT|MINUTE))
continue;
tm->tm_min = 0;
clear_sec:
set |= MINUTE;
if ((set|state) & (EXACT|SECOND))
continue;
tm->tm_sec = 0;
clear_nsec:
set |= SECOND;
if ((set|state) & (EXACT|NSEC))
continue;
tm->tm_nsec = 0;
}
done:
if (day >= 0 && !(state & (MDAY|WDAY)))
{
if ((m = dir) > 0)
m--;
if (state & MONTH)
tm->tm_mday = 1;
else if (m < 0)
m++;
tm = tmxmake(tmxtime(tm, zone));
j = day - tm->tm_wday;
if (j < 0)
j += 7;
tm->tm_mday += j + m * 7;
if (state & FINAL)
for (n = tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year)); (tm->tm_mday + 7) <= n; tm->tm_mday += 7);
}
else if (day < 0 && (state & FINAL) && (set & DAY))
tm->tm_mday = tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year));
if (e)
*e = last;
return tmxtime(tm, zone);
}