/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1985-2012 AT&T Intellectual Property * * and is licensed under the * * Eclipse Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.eclipse.org/org/documents/epl-v10.html * * (with md5 checksum b35adb5213ca9657e911e9befb180842) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * Phong Vo * * * ***********************************************************************/ #pragma prototyped /* * Glenn Fowler * AT&T Research * * Time_t conversion support * * relative times inspired by Steve Bellovin's netnews getdate(3) */ #include #include #include #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') #undef BREAK #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 ORDINAL (1<<14) #define SECOND (1<<15) #define THIS (1L<<16) #define WDAY (1L<<17) #define WORK (1L<<18) #define YEAR (1L<<19) #define ZONE (1L<<20) #define FFMT "%s%s%s%s%s%s%s|" #define FLAGS(f) (f&EXACT)?"|EXACT":"",(f&LAST)?"|LAST":"",(f&THIS)?"|THIS":"",(f&NEXT)?"|NEXT":"",(f&ORDINAL)?"|ORDINAL":"",(f&FINAL)?"|FINAL":"",(f&WORK)?"|WORK":"" /* * 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; } /* * normalize to power of 10 u in tm */ static void powerize(Tm_t* tm, unsigned long p, unsigned long q, unsigned long u) { Time_t t = p; while (q > u) { q /= 10; t /= 10; } while (q < u) { q *= 10; t *= 10; } tm->tm_nsec += (int)(t % TMX_RESOLUTION); tm->tm_sec += (int)(t / TMX_RESOLUTION); } #define K1(c1) (c1) #define K2(c1,c2) (((c1)<<8)|(c2)) #define K3(c1,c2,c3) (((c1)<<16)|((c2)<<8)|(c3)) #define K4(c1,c2,c3,c4) (((c1)<<24)|((c2)<<16)|((c3)<<8)|(c4)) #define P_INIT(n) w = n; p = q = 0; u = (char*)s + 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* o; 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; unsigned long p; unsigned long q; Tm_zone_t* zp; Tm_t ts; char skip[UCHAR_MAX + 1]; /* * check DATEMSK first */ debug((error(-1, "AHA tmxdate 2009-03-06"))); fix = tmxscan(s, &last, NiL, &t, now, 0); if (t && !*last) { if (e) *e = last; return fix; } o = s; reset: /* * use now for defaults */ tm = tmxtm(&ts, now, NiL); tm_info.date = tm->tm_zone; day = -1; dir = 0; 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 */ again: for (;;) { state &= (state & HOLD) ? ~(HOLD) : ~(EXACT|LAST|NEXT|THIS); if ((set|state) & (YEAR|MONTH|DAY)) skip['/'] = 1; message((-1, "AHA#%d state=" FFMT " set=" FFMT, __LINE__, FLAGS(state), FLAGS(set))); 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; } if ((*s == 'P' || *s == 'p') && (!isalpha(*(s + 1)) || (*(s + 1) == 'T' || *(s + 1) == 't') && !isalpha(*(s + 2)))) { Tm_t otm; /* * iso duration */ otm = *tm; t = (char*)s; m = 0; P_INIT('Y'); do { c = *++s; duration_next: switch (c) { case 0: m++; if ((char*)s > u) { s--; c = '_'; goto duration_next; } break; case 'T': case 't': m++; if ((char*)s > u) { s++; c = 'D'; goto duration_next; } continue; case 'Y': case 'y': m = 0; if (q > 1) tm->tm_sec += (365L*24L*60L*60L) * p / q; else tm->tm_year += p; P_INIT('M'); continue; case 'm': if (!m) m = 1; /*FALLTHROUGH*/ case 'M': switch (*(s + 1)) { case 'I': case 'i': s++; m = 1; w = 'S'; break; case 'O': case 'o': s++; m = 0; w = 'H'; break; case 'S': case 's': s++; m = 2; w = 's'; break; } switch (m) { case 0: m = 1; if (q > 1) tm->tm_sec += (3042L*24L*60L*60L) * p / q / 100L; else tm->tm_mon += p; break; case 1: m = 2; if (q > 1) tm->tm_sec += (60L) * p / q; else tm->tm_min += p; break; default: if (q > 1) powerize(tm, p, q, 1000UL); else tm->tm_nsec += p * 1000000L; break; } P_INIT(w); continue; case 'W': case 'w': m = 0; if (q > 1) tm->tm_sec += (7L*24L*60L*60L) * p / q; else tm->tm_mday += 7 * p; P_INIT('D'); continue; case 'D': case 'd': m = 0; if (q > 1) tm->tm_sec += (24L*60L*60L) * p / q; else tm->tm_mday += p; P_INIT('H'); continue; case 'H': case 'h': m = 1; if (q > 1) tm->tm_sec += (60L*60L) * p / q; else tm->tm_hour += p; P_INIT('m'); continue; case 'S': case 's': m = 2; /*FALLTHROUGH*/ case ' ': case '_': case '\n': case '\r': case '\t': case '\v': if (q > 1) powerize(tm, p, q, 1000000000UL); else tm->tm_sec += p; P_INIT('U'); continue; case 'U': case 'u': switch (*(s + 1)) { case 'S': case 's': s++; break; } m = 0; if (q > 1) powerize(tm, p, q, 1000000UL); else tm->tm_nsec += p * 1000L; P_INIT('N'); continue; case 'N': case 'n': switch (*(s + 1)) { case 'S': case 's': s++; break; } m = 0; if (q > 1) powerize(tm, p, q, 1000000000UL); else tm->tm_nsec += p; P_INIT('Y'); continue; case '.': if (q) goto exact; q = 1; continue; case '-': c = 'M'; u = (char*)s++; while (*++u && *u != ':') if (*u == '-') { c = 'Y'; break; } goto duration_next; case ':': c = 'm'; u = (char*)s++; while (*++u) if (*u == ':') { c = 'H'; break; } goto duration_next; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': q *= 10; p = p * 10 + (c - '0'); continue; default: exact: *tm = otm; s = (const char*)t + 1; if (*t == 'p') { state |= HOLD|EXACT; set &= ~(EXACT|LAST|NEXT|THIS); set |= state & (EXACT|LAST|NEXT|THIS); } goto again; } break; } while (c); continue; } 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[13]; 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 = tmxtm(tm, fix, tm->tm_zone); 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 = tmxtm(tm, tt, tm->tm_zone); 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 = tmxtm(tm, tmxtime(tm, zone), tm->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; } if ((*t == 'T' || *t == 't') && ((set|state) & (YEAR|MONTH|DAY)) == (YEAR|MONTH) && isdigit(*(t + 1))) t++; u = t + (*t == '-'); message((-1, "AHA#%d n=%d w=%d u='%c' f=%d t=\"%s\"", __LINE__, n, w, *u, f, t)); if ((w == 2 || w == 4) && (*u == 'W' || *u == 'w') && isdigit(*(u + 1))) { if (w == 4) { if ((n -= 1900) < TM_WINDOW) break; } else if (n < TM_WINDOW) n += 100; m = n; n = strtol(++u, &t, 10); if ((i = (t - u)) < 2 || i > 3) break; if (i == 3) { k = n % 10; n /= 10; } else if (*t != '-') k = 1; else if (*++t && dig1(t, k) < 1 || k > 7) break; if (n < 0 || n > 53) break; if (k == 7) k = 0; tm->tm_year = m; tmweek(tm, 2, n, k); set |= YEAR|MONTH|DAY; s = t; continue; } else if (w == 6 || w == 8 && (n / 1000000) > 12) { t = (char*)s; flags = 0; if (w == 8 || w == 6 && *u != 'T' && *u != 't') { dig4(t, m); if ((m -= 1900) < TM_WINDOW) break; } else { dig2(t, m); if (m < TM_WINDOW) m += 100; } flags |= YEAR; if (dig2(t, l) <= 0 || l > 12) break; flags |= MONTH; if (*t != 'T' && *t != 't' || !isdigit(*++t)) { if (w == 6) goto save_yymm; if (dig2(t, k) < 1 || k > 31) break; flags |= DAY; goto save_yymmdd; } n = strtol(s = t, &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) { message((-1, "AHA#%d n=%d", __LINE__, n)); ordinal: if (n) n--; message((-1, "AHA#%d n=%d", __LINE__, n)); state |= ((f = n) ? NEXT : THIS)|ORDINAL; set &= ~(EXACT|LAST|NEXT|THIS); set |= state & (EXACT|LAST|NEXT|THIS); for (s = t; skip[*s]; s++); if (isdigit(*s)) { if (n = strtol(s, &t, 10)) n--; s = t; if (*s == '_') s++; } else n = -1; dir = f; message((-1, "AHA#%d f=%d n=%d state=" FFMT, __LINE__, f, n, FLAGS(state))); } else { for (u = t; isspace(*u); u++); message((-1, "AHA#%d n=%d u=\"%s\"", __LINE__, n, u)); if ((j = tmlex(u, NiL, tm_info.format, TM_NFORM, tm_info.format + TM_SUFFIXES, TM_PARTS - TM_SUFFIXES)) >= 0 && tm_data.lex[j] == TM_PARTS) s = u; else { message((-1, "AHA#%d t=\"%s\"", __LINE__, t)); 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...]] */ message((-1, "AHA#%d t=\"%s\"", __LINE__, t)); flags = 0; if (state & CCYYMMDDHHMMSS) break; state |= CCYYMMDDHHMMSS; p = 0; if ((i == 7 || i == 5) && (!*t || *t == 'Z' || *t == 'z')) { 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); if (m < 1969 || m >= 3000) break; 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_hour = j; tm->tm_min = i; tm->tm_sec = n; tm->tm_nsec = p; save_yymmdd: tm->tm_mday = k; save_yymm: tm->tm_mon = l - 1; tm->tm_year = m; s = t; set |= flags; continue; } for (s = t; skip[*s]; s++); message((-1, "AHA#%d s=\"%s\"", __LINE__, 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))) { message((-1, "AHA#%d f=%d i=%d j=%d k=%d l=%d", __LINE__, f, i, j, k, l)); 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 (;;) { message((-1, "AHA#%d s=\"%s\"", __LINE__, s)); 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)) { if (n > 0) { x = s; q = *s++; message((-1, "AHA#%d n=%d q='%c'", __LINE__, n, q)); if (isalpha(*s)) { q <<= 8; q |= *s++; if (isalpha(*s)) { if (tmlex(s, &t, tm_info.format + TM_SUFFIXES, TM_PARTS - TM_SUFFIXES, NiL, 0) >= 0) s = t; if (isalpha(*s)) { q <<= 8; q |= *s++; if (isalpha(*s)) { q <<= 8; q |= *s++; if (isalpha(*s)) q = 0; } } } } switch (q) { case K1('y'): case K1('Y'): case K2('y','r'): case K2('Y','R'): tm->tm_year += n; set |= YEAR; continue; case K1('M'): case K2('m','o'): case K2('M','O'): tm->tm_mon += n; set |= MONTH; continue; case K1('w'): case K1('W'): case K2('w','k'): case K2('W','K'): tm->tm_mday += n * 7; set |= DAY; continue; case K1('d'): case K1('D'): case K2('d','a'): case K2('d','y'): case K2('D','A'): case K2('D','Y'): tm->tm_mday += n; set |= DAY; continue; case K1('h'): case K1('H'): case K2('h','r'): case K2('H','R'): tm->tm_hour += n; set |= HOUR; continue; case K1('m'): case K2('m','n'): case K2('M','N'): tm->tm_min += n; set |= MINUTE; continue; case K1('s'): case K2('s','c'): case K1('S'): case K2('S','C'): tm->tm_sec += n; set |= SECOND; continue; case K2('m','s'): case K3('m','s','c'): case K4('m','s','e','c'): case K2('M','S'): case K3('M','S','C'): case K4('M','S','E','C'): tm->tm_nsec += n * 1000000L; continue; case K1('u'): case K2('u','s'): case K3('u','s','c'): case K4('u','s','e','c'): case K1('U'): case K2('U','S'): case K3('U','S','C'): case K4('U','S','E','C'): tm->tm_nsec += n * 1000L; continue; case K2('n','s'): case K3('n','s','c'): case K4('n','s','e','c'): case K2('N','S'): case K3('N','S','C'): case K4('N','S','E','C'): tm->tm_nsec += n; continue; } s = x; } if ((j = tmlex(s, &t, tm_info.format, TM_NFORM, tm_info.format + TM_SUFFIXES, TM_PARTS - TM_SUFFIXES)) >= 0) { if (tm_data.lex[j] == TM_PARTS || n < 1000) { 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_WORK: message((-1, "AHA#%d WORK", __LINE__)); state |= WORK; set |= DAY; if (state & LAST) { state &= ~LAST; set &= ~LAST; state |= FINAL; set |= FINAL; } goto clear_hour; case TM_ORDINAL: j += TM_ORDINALS - TM_ORDINAL; message((-1, "AHA#%d j=%d", __LINE__, j)); /*FALLTHROUGH*/ case TM_ORDINALS: n = j - TM_ORDINALS + 1; message((-1, "AHA#%d n=%d", __LINE__, n)); 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: message((-1, "AHA#%d n=%d j=%d f=%d state=" FFMT, __LINE__, n, j, f, FLAGS(state))); if (n == -1) { /* * disambiguate english "second" */ if (j == TM_PARTS && f == -1) { state &= ~(LAST|NEXT|THIS|ORDINAL); /*AHA*/ n = 2; goto ordinal; } n = 1; } /* * disambiguate "last" vs. { "previous" "final" } */ while (isspace(*s)) s++; message((-1, "AHA#%d disambiguate LAST s='%s'", __LINE__, s)); if ((k = tmlex(s, &t, tm_info.format + TM_NEXT, TM_EXACT - TM_NEXT, NiL, 0)) >= 0 || (k = tmlex(s, &t, tm_info.format + TM_PARTS + 3, 1, NiL, 0)) >= 0) { s = t; if (state & LAST) { state &= ~LAST; set &= ~LAST; state |= FINAL; set |= FINAL; message((-1, "AHA#%d LAST => FINAL", __LINE__)); } else state &= ~(THIS|NEXT); } message((-1, "AHA#%d disambiguate LAST k=%d", __LINE__, k)); if (state & LAST) n = -n; else if (!(state & NEXT)) n--; m = (f > 0) ? f * n : n; message((-1, "AHA#%d f=%d n=%d i=%d j=%d k=%d l=%d m=%d state=" FFMT, __LINE__, f, n, i, j, k, l, m, FLAGS(state))); 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: message((-1, "AHA#%d DAY m=%d n=%d%s", __LINE__, m, n, (state & LAST) ? " LAST" : "")); if ((state & (LAST|NEXT|THIS)) == LAST) tm->tm_mday = tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year)); else if (state & ORDINAL) tm->tm_mday = m + 1; else tm->tm_mday += m; if (!(set & (FINAL|WORK))) set |= HOUR; goto clear_hour; case TM_PARTS+4: tm = tmxtm(tm, tmxtime(tm, zone), tm->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; } if (m >= 0 && (state & ORDINAL)) tm->tm_mday = 1; tm = tmxtm(tm, tmxtime(tm, zone), tm->tm_zone); day = j -= TM_DAY; if (!dir) dir = m; message((-1, "AHA#%d j=%d m=%d", __LINE__, j, m)); j -= tm->tm_wday; message((-1, "AHA#%d mday=%d wday=%d day=%d dir=%d f=%d i=%d j=%d l=%d m=%d", __LINE__, tm->tm_mday, tm->tm_wday, day, dir, f, i, j, l, m)); if (state & (LAST|NEXT|THIS)) { if (state & ORDINAL) { while (isspace(*s)) s++; if (isdigit(*s) || tmlex(s, &t, tm_info.format, TM_DAY_ABBREV, NiL, 0) >= 0) { state &= ~(LAST|NEXT|THIS); goto clear_hour; } } if (j < 0) j += 7; } else if (j > 0) j -= 7; message((-1, "AHA#%d day=%d mday=%d f=%d m=%d j=%d state=" FFMT, __LINE__, day, tm->tm_mday, f, m, j, FLAGS(state))); set |= DAY; if (set & (FINAL|WORK)) goto clear_hour; else if (state & (LAST|NEXT|THIS)) { if (f >= 0) day = -1; else if (m > 0 && (state & (NEXT|YEAR|MONTH)) == NEXT && j >= 0) m--; tm->tm_mday += j + m * 7; set &= ~(LAST|NEXT|THIS|ORDINAL); /*AHA*/ state &= ~(LAST|NEXT|THIS|ORDINAL); /*AHA*/ if (!(state & EXACT)) 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->tm_zone->dst; zone = tm->tm_zone->west; } zone += tmgoff(s, &t, dst); s = t; dst = 0; state |= ZONE; continue; case TM_NOISE: continue; } } } if (n < 1000) { if (!(state & ZONE) && (zp = tmzone(s, &t, type, &dst))) { s = t; zone = zp->west + dst; tm_info.date = zp; state |= ZONE; if (n < 0) continue; } else if (!type && (zp = tmtype(s, &t))) { s = t; type = zp->type; if (n < 0) continue; } state |= BREAK; } } else if (*s == '/') { if (!(state & (YEAR|MONTH)) && n >= 1969 && 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; set &= ~(LAST|NEXT|THIS); /*AHA*/ state &= ~(LAST|NEXT|THIS); /*AHA*/ goto clear_mday; } continue; } } if (n < 0 || w > 4) break; if (w == 4) { if ((state & YEAR) || n < 1969 || 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: message((-1, "AHA#%d DAY", __LINE__)); 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))) { message((-1, "AHA#%d day=%d dir=%d state=" FFMT, __LINE__, day, dir, FLAGS(state))); tmfix(tm); m = dir; if (state & MONTH) tm->tm_mday = 1; else if (m < 0) m++; tm = tmxtm(tm, tmxtime(tm, zone), tm->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)) { tmfix(tm); tm->tm_mday = tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year)); } if (state & WORK) { tm->tm_mday = (set & FINAL) ? (tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year))) : 1; tmfix(tm); message((-1, "AHA#%d WORK mday=%d wday=%d", __LINE__, tm->tm_mday, tm->tm_wday)); if (tm->tm_wday == 0 && (j = 1) || tm->tm_wday == 6 && (j = 2)) { if ((tm->tm_mday + j) > (tm_data.days[tm->tm_mon] + (tm->tm_mon == 1 && tmisleapyear(tm->tm_year)))) j -= 3; tm->tm_mday += j; } } now = tmxtime(tm, zone); if (tm->tm_year <= 70 && tmxsec(now) > 31536000) { now = 0; last = (char*)o; } if (e) *e = last; return now; }