diff options
Diffstat (limited to 'partime.c')
-rw-r--r-- | partime.c | 372 |
1 files changed, 293 insertions, 79 deletions
diff --git a/partime.c b/partime.c index ce8710989b30..17328437dd7e 100644 --- a/partime.c +++ b/partime.c @@ -1,6 +1,6 @@ /* Parse a string, yielding a struct partime that describes it. */ -/* Copyright 1993, 1994, 1995, 1997 Paul Eggert +/* Copyright (C) 1993, 1994, 1995, 1997, 2002 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. @@ -42,6 +42,9 @@ # ifndef LONG_MIN # define LONG_MIN (-1-2147483647L) # endif +# if HAVE_STDDEF_H +# include <stddef.h> +# endif # if STDC_HEADERS # include <stdlib.h> # endif @@ -53,6 +56,10 @@ # endif #endif +#ifndef offsetof +#define offsetof(aggregate, member) ((size_t) &((aggregate *) 0)->member) +#endif + #include <ctype.h> #if STDC_HEADERS # define CTYPE_DOMAIN(c) 1 @@ -67,8 +74,8 @@ #include <partime.h> -char const partimeId[] = - "$Id: partime.c,v 5.16 1997/05/19 06:33:53 eggert Exp $"; +char const partime_id[] = + "$Id: partime.c,v 1.2 2002/02/18 07:42:58 eggert Exp $"; /* Lookup tables for names of months, weekdays, time zones. */ @@ -85,8 +92,9 @@ struct name_val static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *)); static char const *parse_fixed P ((char const *, int, int *)); static char const *parse_pattern_letter P ((char const *, int, struct partime *)); -static char const *parse_prefix P ((char const *, struct partime *, int *)); +static char const *parse_prefix P ((char const *, char const **, struct partime *)); static char const *parse_ranged P ((char const *, int, int, int, int *)); +static char const *parse_varying P ((char const *, int *)); static int lookup P ((char const *, struct name_val const[])); static int merge_partime P ((struct partime *, struct partime const *)); static void undefine P ((struct partime *)); @@ -121,10 +129,39 @@ static struct name_val const weekday_names[] = {"", TM_UNDEFINED} }; -#define hr60nonnegative(t) ((t)/100 * 60 + (t)%100) -#define hr60(t) ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t)) -#define zs(t,s) {s, hr60(t)} -#define zd(t,s,d) zs(t, s), zs((t)+100, d) +#define RELATIVE_CONS(member, multiplier) \ + (offsetof (struct tm, member) + (multiplier) * sizeof (struct tm)) +#define RELATIVE_OFFSET(c) ((c) % sizeof (struct tm)) +#define RELATIVE_MULTIPLIER(c) ((c) / sizeof (struct tm)) +static struct name_val const relative_units[] = +{ + {"year", RELATIVE_CONS (tm_year, 1) }, + {"mont", RELATIVE_CONS (tm_mon , 1) }, + {"fort", RELATIVE_CONS (tm_mday, 14) }, + {"week", RELATIVE_CONS (tm_mday, 7) }, + {"day" , RELATIVE_CONS (tm_mday, 1) }, + {"hour", RELATIVE_CONS (tm_hour, 1) }, + {"min" , RELATIVE_CONS (tm_min , 1) }, + {"sec" , RELATIVE_CONS (tm_sec , 1) }, + {"", TM_UNDEFINED} +}; + +static struct name_val const ago[] = +{ + {"ago", 0}, + {"", TM_UNDEFINED} +}; + +static struct name_val const dst_names[] = +{ + {"dst", 1}, + {"", 0} +}; + +#define hr60nonnegative(t) ((t)/100 * 60 + (t)%100) +#define hr60(t) ((t) < 0 ? - hr60nonnegative (-(t)) : hr60nonnegative (t)) +#define zs(t, s) {s, hr60 (t)} +#define zd(t, s, d) zs (t, s), zs ((t) + 100, d) static struct name_val const zone_names[] = { @@ -155,7 +192,8 @@ static struct name_val const zone_names[] = {"lt", 1}, #if 0 /* The following names are duplicates or are not well attested. - There are lots more where these came from. */ + It's not worth keeping a complete list, since alphabetic time zone names + are deprecated and there are lots more where these came from. */ zs (-1100, "sst" ), /* Samoan */ zd (- 900, "yst" , "ydt" ), /* Yukon - name is no longer used */ zd (- 500, "ast" , "adt" ), /* Acre */ @@ -191,13 +229,15 @@ lookup (s, table) for (j = 0; j < NAME_LENGTH_MAXIMUM; j++) { - unsigned char c = *s++; + unsigned char c = *s; if (! ISALPHA (c)) { buf[j] = '\0'; break; } buf[j] = ISUPPER (c) ? tolower (c) : c; + s++; + s += *s == '.'; } for (;; table++) @@ -216,54 +256,109 @@ undefine (t) { t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday - = t->ymodulus = t->yweek + = t->wday_ordinal = t->ymodulus = t->yweek = TM_UNDEFINED; + t->tmr.tm_sec = t->tmr.tm_min = t->tmr.tm_hour = + t->tmr.tm_mday = t->tmr.tm_mon = t->tmr.tm_year = 0; t->zone = TM_UNDEFINED_ZONE; } -/* Array of patterns to look for in a date string. +/* Patterns to look for in a time string. Order is important: we look for the first matching pattern whose values do not contradict values that we already know about. See `parse_pattern_letter' below for the meaning of the pattern codes. */ -static char const *const patterns[] = +static char const time_patterns[] = { - /* These traditional patterns must come first, + /* Traditional patterns come first, to prevent an ISO 8601 format from misinterpreting their prefixes. */ - "E_n_y", "x", /* RFC 822 */ - "E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */ - "y/N/D$", /* traditional RCS */ + + /* RFC 822, extended */ + 'E', '_', 'N', '_', 'y', '$', 0, + 'x', 0, + + /* traditional */ + '4', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0, + 'R', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0, + 'E', '_', 'N', 0, + 'N', '_', 'E', '_', 'y', ';', 0, + 'N', '_', 'E', ';', 0, + 'N', 0, + 't', ':', 'm', ':', 's', '_', 'A', 0, + 't', ':', 'm', '_', 'A', 0, + 't', '_', 'A', 0, + + /* traditional get_date */ + 'i', '_', 'x', 0, + 'Y', '/', 'n', '/', 'E', ';', 0, + 'n', '/', 'E', '/', 'y', ';', 0, + 'n', '/', 'E', ';', 0, + 'u', 0, /* ISO 8601:1988 formats, generalized a bit. */ - "y-N-D$", "4ND$", "Y-N$", - "RND$", "-R=N$", "-R$", "--N=D$", "N=DT", - "--N$", "---D$", "DT", - "Y-d$", "4d$", "R=d$", "-d$", "dT", - "y-W-X", "yWX", "y=W", - "-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W", - "-w-X", "w-XT", "---X$", "XT", "4$", - "T", - "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$", - "Y", "Z", + 'y', '-', 'M', '-', 'D', '$', 0, + '4', 'M', 'D', '$', 0, + 'Y', '-', 'M', '$', 0, + 'R', 'M', 'D', '$', 0, + '-', 'R', '=', 'M', '$', 0, + '-', 'R', '$', 0, + '-', '-', 'M', '=', 'D', '$', 0, + 'M', '=', 'D', 'T', 0, + '-', '-', 'M', '$', 0, + '-', '-', '-', 'D', '$', 0, + 'D', 'T', 0, + 'Y', '-', 'd', '$', 0, + '4', 'd', '$', 0, + 'R', '=', 'd', '$', 0, + '-', 'd', '$', 0, + 'd', 'T', 0, + 'y', '-', 'W', '-', 'X', 0, + 'y', 'W', 'X', 0, + 'y', '=', 'W', 0, + '-', 'r', '-', 'W', '-', 'X', 0, + 'r', '-', 'W', '-', 'X', 'T', 0, + '-', 'r', 'W', 'X', 0, + 'r', 'W', 'X', 'T', 0, + '-', 'W', '=', 'X', 0, + 'W', '=', 'X', 'T', 0, + '-', 'W', 0, + '-', 'w', '-', 'X', 0, + 'w', '-', 'X', 'T', 0, + '-', '-', '-', 'X', '$', 0, + 'X', 'T', 0, + '4', '$', 0, + 'T', 0, + 'h', ':', 'm', ':', 's', '$', 0, + 'h', 'm', 's', '$', 0, + 'h', ':', 'L', '$', 0, + 'h', 'L', '$', 0, + 'H', '$', 0, + '-', 'm', ':', 's', '$', 0, + '-', 'm', 's', '$', 0, + '-', 'L', '$', 0, + '-', '-', 's', '$', 0, + 'Y', 0, + 'Z', 0, 0 }; -/* Parse an initial prefix of STR, setting *T accordingly. +/* Parse an initial prefix of STR according to *PATTERNS, setting *T. Return the first character after the prefix, or 0 if it couldn't be parsed. - Start with pattern *PI; if success, set *PI to the next pattern to try. - Set *PI to -1 if we know there are no more patterns to try; - if *PI is initially negative, give up immediately. */ + *PATTERNS is a character array containing one pattern string after another; + it is terminated by an empty string. + If success, set *PATTERNS to the next pattern to try. + Set *PATTERNS to 0 if we know there are no more patterns to try; + if *PATTERNS is initially 0, give up immediately. */ static char const * -parse_prefix (str, t, pi) +parse_prefix (str, patterns, t) char const *str; + char const **patterns; struct partime *t; - int *pi; { - int i = *pi; - char const *pat; + char const *pat = *patterns; unsigned char c; - if (i < 0) + if (! pat) return 0; /* Remove initial noise. */ @@ -272,26 +367,31 @@ parse_prefix (str, t, pi) if (! c) { undefine (t); - *pi = -1; + *patterns = 0; return str; } + str++; } /* Try a pattern until one succeeds. */ - while ((pat = patterns[i++]) != 0) + while (*pat) { char const *s = str; undefine (t); + do { if (! (c = *pat++)) { - *pi = i; + *patterns = pat; return s; } } while ((s = parse_pattern_letter (s, c, t)) != 0); + + while (*pat++) + continue; } return 0; @@ -318,6 +418,27 @@ parse_fixed (s, digits, res) return s; } +/* Parse a possibly empty initial prefix of S. + Store the parsed number into *RES. + Return the first character after the prefix. */ +static char const * +parse_varying (s, res) + char const *s; + int *res; +{ + int n = 0; + for (;;) + { + unsigned d = *s - '0'; + if (9 < d) + break; + s++; + n = 10 * n + d; + } + *res = n; + return s; +} + /* Parse an initial prefix of S of length DIGITS; it must be a number in the range LO through HI. Store the parsed number into *RES. @@ -379,9 +500,11 @@ parzone (s, zone) char const *s; long *zone; { + char const *s1; char sign; int hh, mm, ss; - int minutesEastOfUTC; + int minutes_east_of_UTC; + int trailing_DST; long offset, z; /* The formats are LT, n, n DST, nDST, no, o @@ -395,40 +518,52 @@ parzone (s, zone) break; default: - minutesEastOfUTC = lookup (s, zone_names); - if (minutesEastOfUTC == -1) + minutes_east_of_UTC = lookup (s, zone_names); + if (minutes_east_of_UTC == -1) return 0; - /* Don't bother to check rest of spelling. */ + /* Don't bother to check rest of spelling, + but look for an embedded "DST". */ + trailing_DST = 0; while (ISALPHA ((unsigned char) *s)) - s++; + { + if ((*s == 'D' || *s == 'd') && lookup (s, dst_names)) + trailing_DST = 1; + s++; + s += *s == '.'; + } /* Don't modify LT. */ - if (minutesEastOfUTC == 1) + if (minutes_east_of_UTC == 1) { *zone = TM_LOCAL_ZONE; return (char *) s; } - z = minutesEastOfUTC * 60L; + z = minutes_east_of_UTC * 60L; + s1 = s; - /* Look for trailing " DST". */ - if ((s[-1] == 'T' || s[-1] == 't') - && (s[-2] == 'S' || s[-2] == 's') - && (s[-3] == 'D' || s[-3] == 'd')) - goto trailing_dst; + /* Look for trailing "DST" or " DST". */ while (ISSPACE ((unsigned char) *s)) s++; - if ((s[0] == 'D' || s[0] == 'd') - && (s[1] == 'S' || s[1] == 's') - && (s[2] == 'T' || s[2] == 't')) + if (lookup (s, dst_names)) + { + while (ISALPHA ((unsigned char) *s)) + { + s++; + s += *s == '.'; + } + trailing_DST = 1; + } + + if (trailing_DST) { - s += 3; - trailing_dst: *zone = z + 60*60; return (char *) s; } + s = s1; + switch (*s) { case '-': @@ -475,6 +610,8 @@ parse_pattern_letter (s, c, t) int c; struct partime *t; { + char const *s0 = s; + switch (c) { case '$': /* The next character must be a non-digit. */ @@ -494,15 +631,20 @@ parse_pattern_letter (s, c, t) s = parse_fixed (s, 4, &t->tm.tm_year); break; + case ';': /* The next character must be a non-digit, and cannot be ':'. */ + if (ISDIGIT (*s) || *s == ':') + return 0; + break; + case '=': /* optional '-' */ s += *s == '-'; break; case 'A': /* AM or PM */ - /* This matches the regular expression [AaPp][Mm]?. + /* This matches the regular expression [AaPp]\.?([Mm]\.?)?. It must not be followed by a letter or digit; otherwise it would match prefixes of strings like "PST". */ - switch (*s++) + switch (*s) { case 'A': case 'a': @@ -519,11 +661,14 @@ parse_pattern_letter (s, c, t) default: return 0; } + s++; + s += *s == '.'; switch (*s) { case 'M': case 'm': s++; + s += *s == '.'; break; } if (ISALNUM ((unsigned char) *s)) @@ -539,12 +684,16 @@ parse_pattern_letter (s, c, t) t->tm.tm_yday--; break; - case 'E': /* extended day of month [1-9, 01-31] */ + case 'E': /* traditional day of month [1-9, 01-31] */ s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 31, &t->tm.tm_mday); break; - case 'h': /* hour [00-23 followed by optional fraction] */ + case 'h': /* hour [00-23] */ + s = parse_ranged (s, 2, 0, 23, &t->tm.tm_hour); + break; + + case 'H': /* hour [00-23 followed by optional fraction] */ { int frac; s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac); @@ -553,11 +702,34 @@ parse_pattern_letter (s, c, t) } break; - case 'm': /* minute [00-59 followed by optional fraction] */ + case 'i': /* ordinal day number, e.g. "3rd" */ + s = parse_varying (s, &t->wday_ordinal); + if (s == s0) + return 0; + while (ISALPHA ((unsigned char) *s)) + s++; + break; + + case 'L': /* minute [00-59 followed by optional fraction] */ s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec); break; - case 'n': /* month name [e.g. "Jan"] */ + case 'm': /* minute [00-59] */ + s = parse_ranged (s, 2, 0, 59, &t->tm.tm_min); + break; + + case 'M': /* month [01-12] */ + s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon); + t->tm.tm_mon--; + break; + + case 'n': /* traditional month [1-9, 01-12] */ + s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12, + &t->tm.tm_mon); + t->tm.tm_mon--; + break; + + case 'N': /* month name [e.g. "Jan"] */ if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names))) return 0; /* Don't bother to check rest of spelling. */ @@ -565,11 +737,6 @@ parse_pattern_letter (s, c, t) s++; break; - case 'N': /* month [01-12] */ - s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon); - t->tm.tm_mon--; - break; - case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */ s = parse_fixed (s, 1, &t->tm.tm_year); t->ymodulus = 10; @@ -605,6 +772,50 @@ parse_pattern_letter (s, c, t) &t->tm.tm_hour); break; + case 'u': /* relative unit */ + { + int i; + int n; + int negative = 0; + switch (*s) + { + case '-': negative = 1; + /* Fall through. */ + case '+': s++; + } + if (ISDIGIT (*s)) + s = parse_varying (s, &n); + else if (s == s0) + n = 1; + else + return 0; + if (negative) + n = -n; + while (! ISALNUM ((unsigned char) *s) && *s) + s++; + i = lookup (s, relative_units); + if (!TM_DEFINED (i)) + return 0; + * (int *) ((char *) &t->tmr + RELATIVE_OFFSET (i)) + += n * RELATIVE_MULTIPLIER (i); + while (ISALPHA ((unsigned char) *s)) + s++; + while (! ISALNUM ((unsigned char) *s) && *s) + s++; + if (TM_DEFINED (lookup (s, ago))) + { + t->tmr.tm_sec = - t->tmr.tm_sec; + t->tmr.tm_min = - t->tmr.tm_min; + t->tmr.tm_hour = - t->tmr.tm_hour; + t->tmr.tm_mday = - t->tmr.tm_mday; + t->tmr.tm_mon = - t->tmr.tm_mon; + t->tmr.tm_year = - t->tmr.tm_year; + while (ISALPHA ((unsigned char) *s)) + s++; + } + break; + } + case 'w': /* 'W' or 'w' only (stands for current week) */ switch (*s++) { @@ -646,14 +857,9 @@ parse_pattern_letter (s, c, t) goto case_R; /* fall into */ case 'Y': /* year in full [4 or more digits] */ - { - int len = 0; - while (ISDIGIT (s[len])) - len++; - if (len < 4) - return 0; - s = parse_fixed (s, len, &t->tm.tm_year); - } + s = parse_varying (s, &t->tm.tm_year); + if (s - s0 < 4) + return 0; break; case 'Z': /* time zone */ @@ -686,7 +892,8 @@ merge_partime (t, u) || conflict (t->tm.tm_mday, u->tm.tm_mday) || conflict (t->tm.tm_mon, u->tm.tm_mon) || conflict (t->tm.tm_year, u->tm.tm_year) - || conflict (t->tm.tm_wday, u->tm.tm_yday) + || conflict (t->tm.tm_wday, u->tm.tm_wday) + || conflict (t->tm.tm_yday, u->tm.tm_yday) || conflict (t->ymodulus, u->ymodulus) || conflict (t->yweek, u->yweek) || (t->zone != u->zone @@ -701,10 +908,17 @@ merge_partime (t, u) merge_ (t->tm.tm_mday, u->tm.tm_mday) merge_ (t->tm.tm_mon, u->tm.tm_mon) merge_ (t->tm.tm_year, u->tm.tm_year) - merge_ (t->tm.tm_wday, u->tm.tm_yday) + merge_ (t->tm.tm_wday, u->tm.tm_wday) + merge_ (t->tm.tm_yday, u->tm.tm_yday) merge_ (t->ymodulus, u->ymodulus) merge_ (t->yweek, u->yweek) # undef merge_ + t->tmr.tm_sec += u->tmr.tm_sec; + t->tmr.tm_min += u->tmr.tm_min; + t->tmr.tm_hour += u->tmr.tm_hour; + t->tmr.tm_mday += u->tmr.tm_mday; + t->tmr.tm_mon += u->tmr.tm_mon; + t->tmr.tm_year += u->tmr.tm_year; if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone; return 0; @@ -725,12 +939,12 @@ partime (s, t) while (*s) { - int i = 0; + char const *patterns = time_patterns; char const *s1; do { - if (! (s1 = parse_prefix (s, &p, &i))) + if (! (s1 = parse_prefix (s, &patterns, &p))) return (char *) s; } while (merge_partime (t, &p) != 0); |