diff options
Diffstat (limited to 'libedsio/partime.c')
-rwxr-xr-x | libedsio/partime.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/libedsio/partime.c b/libedsio/partime.c new file mode 100755 index 0000000..4cce654 --- /dev/null +++ b/libedsio/partime.c @@ -0,0 +1,742 @@ +/* Parse a string, yielding a struct partime that describes it. */ + +/* Copyright 1993, 1994, 1995, 1997 Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + + This file is part of RCS. + + RCS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + RCS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with RCS; see the file COPYING. + If not, write to the Free Software Foundation, + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + + */ + +#if has_conf_h +# include <conf.h> +#else +# if HAVE_CONFIG_H +# include <config.h> +# else +# ifndef __STDC__ +# define const +# endif +# endif +# if HAVE_LIMITS_H +# include <limits.h> +# endif +# ifndef LONG_MIN +# define LONG_MIN (-1-2147483647L) +# endif +# if STDC_HEADERS +# include <stdlib.h> +# endif +# include <time.h> +# ifdef __STDC__ +# define P(x) x +# else +# define P(x) () +# endif +#endif + +#include <ctype.h> +#if STDC_HEADERS +# define CTYPE_DOMAIN(c) 1 +#else +# define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177) +#endif +#define ISALNUM(c) (CTYPE_DOMAIN (c) && isalnum (c)) +#define ISALPHA(c) (CTYPE_DOMAIN (c) && isalpha (c)) +#define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c)) +#define ISUPPER(c) (CTYPE_DOMAIN (c) && isupper (c)) +#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) + +#include <partime.h> + +char const partimeId[] = + "$Id: partime.c 1.1 Sun, 28 Jan 2007 10:02:26 -0800 jmacd $"; + + +/* Lookup tables for names of months, weekdays, time zones. */ + +#define NAME_LENGTH_MAXIMUM 4 + +struct name_val + { + char name[NAME_LENGTH_MAXIMUM]; + int 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_ranged P ((char const *, int, int, int, 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 *)); + + +static struct name_val const month_names[] = +{ + {"jan", 0}, + {"feb", 1}, + {"mar", 2}, + {"apr", 3}, + {"may", 4}, + {"jun", 5}, + {"jul", 6}, + {"aug", 7}, + {"sep", 8}, + {"oct", 9}, + {"nov", 10}, + {"dec", 11}, + {"", TM_UNDEFINED} +}; + +static struct name_val const weekday_names[] = +{ + {"sun", 0}, + {"mon", 1}, + {"tue", 2}, + {"wed", 3}, + {"thu", 4}, + {"fri", 5}, + {"sat", 6}, + {"", 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) + +static struct name_val const zone_names[] = +{ + zs (-1000, "hst"), /* Hawaii */ + zd (-1000, "hast", "hadt"), /* Hawaii-Aleutian */ + zd (- 900, "akst", "akdt"), /* Alaska */ + zd (- 800, "pst" , "pdt" ), /* Pacific */ + zd (- 700, "mst" , "mdt" ), /* Mountain */ + zd (- 600, "cst" , "cdt" ), /* Central */ + zd (- 500, "est" , "edt" ), /* Eastern */ + zd (- 400, "ast" , "adt" ), /* Atlantic */ + zd (- 330, "nst" , "ndt" ), /* Newfoundland */ + zs ( 000, "utc" ), /* Coordinated Universal */ + zs ( 000, "uct" ), /* " */ + zs ( 000, "cut" ), /* " */ + zs ( 000, "ut"), /* Universal */ + zs ( 000, "z"), /* Zulu (required by ISO 8601) */ + zd ( 000, "gmt" , "bst" ), /* Greenwich Mean, British Summer */ + zd ( 000, "wet" , "west"), /* Western European */ + zd ( 100, "cet" , "cest"), /* Central European */ + zd ( 100, "met" , "mest"), /* Middle European (bug in old tz versions) */ + zd ( 100, "mez" , "mesz"), /* Mittel-Europaeische Zeit */ + zd ( 200, "eet" , "eest"), /* Eastern European */ + zs ( 530, "ist" ), /* India */ + zd ( 900, "jst" , "jdt" ), /* Japan */ + zd ( 900, "kst" , "kdt" ), /* Korea */ + zd ( 1200, "nzst", "nzdt"), /* New Zealand */ + {"lt", 1}, +#if 0 + /* The following names are duplicates or are not well attested. + 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 */ + zd (- 400, "wst" , "wdt" ), /* Western Brazil */ + zd (- 400, "cst" , "cdt" ), /* Chile */ + zd (- 200, "fst" , "fdt" ), /* Fernando de Noronha */ + zs ( 000, "wat" ), /* West African */ + zs ( 100, "cat" ), /* Central African */ + zs ( 200, "sat" ), /* South African */ + zd ( 200, "ist" , "idt" ), /* Israel */ + zs ( 300, "eat" ), /* East African */ + zd ( 300, "msk" , "msd" ), /* Moscow */ + zd ( 330, "ist" , "idt" ), /* Iran */ + zs ( 800, "hkt" ), /* Hong Kong */ + zs ( 800, "sgt" ), /* Singapore */ + zd ( 800, "cst" , "cdt" ), /* China */ + zd ( 800, "wst" , "wst" ), /* Western Australia */ + zd ( 930, "cst" , "cst" ), /* Central Australia */ + zs ( 1000, "gst" ), /* Guam */ + zd ( 1000, "est" , "est" ), /* Eastern Australia */ +#endif + {"", -1} +}; + +/* Look for a prefix of S in TABLE, returning val for first matching entry. */ +static int +lookup (s, table) + char const *s; + struct name_val const table[]; +{ + int j; + char buf[NAME_LENGTH_MAXIMUM]; + + for (j = 0; j < NAME_LENGTH_MAXIMUM; j++) + { + unsigned char c = *s++; + if (! ISALPHA (c)) + { + buf[j] = '\0'; + break; + } + buf[j] = ISUPPER (c) ? tolower (c) : c; + } + + for (;; table++) + for (j = 0; ; j++) + if (j == NAME_LENGTH_MAXIMUM || ! table[0].name[j]) + return table[0].val; + else if (buf[j] != table[0].name[j]) + break; +} + + +/* Set *T to ``undefined'' values. */ +static void +undefine (t) + struct partime *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 + = TM_UNDEFINED; + t->zone = TM_UNDEFINED_ZONE; +} + +/* Array of patterns to look for in a date 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[] = +{ + /* These traditional patterns must 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 */ + + /* 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", + + 0 +}; + +/* Parse an initial prefix of STR, setting *T accordingly. + 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. */ +static char const * +parse_prefix (str, t, pi) + char const *str; + struct partime *t; + int *pi; +{ + int i = *pi; + char const *pat; + unsigned char c; + + if (i < 0) + return 0; + + /* Remove initial noise. */ + while (! ISALNUM (c = *str) && c != '-' && c != '+') + { + if (! c) + { + undefine (t); + *pi = -1; + return str; + } + str++; + } + + /* Try a pattern until one succeeds. */ + while ((pat = patterns[i++]) != 0) + { + char const *s = str; + undefine (t); + do + { + if (! (c = *pat++)) + { + *pi = i; + return s; + } + } + while ((s = parse_pattern_letter (s, c, t)) != 0); + } + + return 0; +} + +/* Parse an initial prefix of S of length DIGITS; it must be a number. + Store the parsed number into *RES. + Return the first character after the prefix, or 0 if it wasn't parsed. */ +static char const * +parse_fixed (s, digits, res) + char const *s; + int digits, *res; +{ + int n = 0; + char const *lim = s + digits; + while (s < lim) + { + unsigned d = *s++ - '0'; + if (9 < d) + return 0; + 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. + Return the first character after the prefix, or 0 if it wasn't parsed. */ +static char const * +parse_ranged (s, digits, lo, hi, res) + char const *s; + int digits, lo, hi, *res; +{ + s = parse_fixed (s, digits, res); + return s && lo <= *res && *res <= hi ? s : 0; +} + +/* Parse an initial prefix of S of length DIGITS; + it must be a number in the range LO through HI + and it may be followed by a fraction to be computed using RESOLUTION. + Store the parsed number into *RES; store the fraction times RESOLUTION, + rounded to the nearest integer, into *FRES. + Return the first character after the prefix, or 0 if it wasn't parsed. */ +static char const * +parse_decimal (s, digits, lo, hi, resolution, res, fres) + char const *s; + int digits, lo, hi, resolution, *res, *fres; +{ + s = parse_fixed (s, digits, res); + if (s && lo <= *res && *res <= hi) + { + int f = 0; + if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1])) + { + char const *s1 = ++s; + int num10 = 0, denom10 = 10, product; + while (ISDIGIT (*++s)) + { + int d = denom10 * 10; + if (d / 10 != denom10) + return 0; /* overflow */ + denom10 = d; + } + s = parse_fixed (s1, (int) (s - s1), &num10); + product = num10 * resolution; + f = (product + (denom10 >> 1)) / denom10; + f -= f & (product % denom10 == denom10 >> 1); /* round to even */ + if (f < 0 || product/resolution != num10) + return 0; /* overflow */ + } + *fres = f; + return s; + } + return 0; +} + +/* Parse an initial prefix of S; it must denote a time zone. + Set *ZONE to the number of seconds east of GMT, + or to TM_LOCAL_ZONE if it is the local time zone. + Return the first character after the prefix, or 0 if it wasn't parsed. */ +char * +parzone (s, zone) + char const *s; + long *zone; +{ + char sign; + int hh, mm, ss; + int minutesEastOfUTC; + long offset, z; + + /* The formats are LT, n, n DST, nDST, no, o + where n is a time zone name + and o is a time zone offset of the form [-+]hh[:mm[:ss]]. */ + switch (*s) + { + case '-': + case '+': + z = 0; + break; + + default: + minutesEastOfUTC = lookup (s, zone_names); + if (minutesEastOfUTC == -1) + return 0; + + /* Don't bother to check rest of spelling. */ + while (ISALPHA ((unsigned char) *s)) + s++; + + /* Don't modify LT. */ + if (minutesEastOfUTC == 1) + { + *zone = TM_LOCAL_ZONE; + return (char *) s; + } + + z = minutesEastOfUTC * 60L; + + /* 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; + 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')) + { + s += 3; + trailing_dst: + *zone = z + 60*60; + return (char *) s; + } + + switch (*s) + { + case '-': + case '+': + break; + + default: + *zone = z; + return (char *) s; + } + + break; + } + + sign = *s++; + + if (! (s = parse_ranged (s, 2, 0, 23, &hh))) + return 0; + mm = ss = 0; + if (*s == ':') + s++; + if (ISDIGIT (*s)) + { + if (! (s = parse_ranged (s, 2, 0, 59, &mm))) + return 0; + if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1]) + && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss))) + return 0; + } + if (ISDIGIT (*s)) + return 0; + offset = (hh * 60 + mm) * 60L + ss; + *zone = z + (sign == '-' ? -offset : offset); + /* ?? Are fractions allowed here? If so, they're not implemented. */ + return (char *) s; +} + +/* Parse an initial prefix of S, matching the pattern whose code is C. + Set *T accordingly. + Return the first character after the prefix, or 0 if it wasn't parsed. */ +static char const * +parse_pattern_letter (s, c, t) + char const *s; + int c; + struct partime *t; +{ + switch (c) + { + case '$': /* The next character must be a non-digit. */ + if (ISDIGIT (*s)) + return 0; + break; + + case '-': + case '/': + case ':': + /* These characters stand for themselves. */ + if (*s++ != c) + return 0; + break; + + case '4': /* 4-digit year */ + s = parse_fixed (s, 4, &t->tm.tm_year); + break; + + case '=': /* optional '-' */ + s += *s == '-'; + break; + + case 'A': /* AM or PM */ + /* 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++) + { + case 'A': + case 'a': + if (t->tm.tm_hour == 12) + t->tm.tm_hour = 0; + break; + + case 'P': + case 'p': + if (t->tm.tm_hour != 12) + t->tm.tm_hour += 12; + break; + + default: + return 0; + } + switch (*s) + { + case 'M': + case 'm': + s++; + break; + } + if (ISALNUM ((unsigned char) *s)) + return 0; + break; + + case 'D': /* day of month [01-31] */ + s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday); + break; + + case 'd': /* day of year [001-366] */ + s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday); + t->tm.tm_yday--; + break; + + case 'E': /* extended 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] */ + { + int frac; + s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac); + t->tm.tm_min = frac / 60; + t->tm.tm_sec = frac % 60; + } + break; + + case 'm': /* 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"] */ + if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names))) + return 0; + /* Don't bother to check rest of spelling. */ + while (ISALPHA ((unsigned char) *s)) + 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; + break; + + case_R: + case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */ + s = parse_fixed (s, 2, &t->tm.tm_year); + t->ymodulus = 100; + break; + + case 's': /* second [00-60 followed by optional fraction] */ + { + int frac; + s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac); + t->tm.tm_sec += frac; + } + break; + + case 'T': /* 'T' or 't' */ + switch (*s++) + { + case 'T': + case 't': + break; + default: + return 0; + } + break; + + case 't': /* traditional hour [1-9 or 01-12] */ + s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12, + &t->tm.tm_hour); + break; + + case 'w': /* 'W' or 'w' only (stands for current week) */ + switch (*s++) + { + case 'W': + case 'w': + break; + default: + return 0; + } + break; + + case 'W': /* 'W' or 'w', followed by a week of year [00-53] */ + switch (*s++) + { + case 'W': + case 'w': + break; + default: + return 0; + } + s = parse_ranged (s, 2, 0, 53, &t->yweek); + break; + + case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */ + s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday); + t->tm.tm_wday--; + break; + + case 'x': /* weekday name [e.g. "Sun"] */ + if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names))) + return 0; + /* Don't bother to check rest of spelling. */ + while (ISALPHA ((unsigned char) *s)) + s++; + break; + + case 'y': /* either R or Y */ + if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2])) + 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); + } + break; + + case 'Z': /* time zone */ + s = parzone (s, &t->zone); + break; + + case '_': /* possibly empty sequence of non-alphanumerics */ + while (! ISALNUM ((unsigned char) *s) && *s) + s++; + break; + + default: /* bad pattern */ + return 0; + } + + return s; +} + +/* If there is no conflict, merge into *T the additional information in *U + and return 0. Otherwise do nothing and return -1. */ +static int +merge_partime (t, u) + struct partime *t; + struct partime const *u; +{ +# define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b)) + if (conflict (t->tm.tm_sec, u->tm.tm_sec) + || conflict (t->tm.tm_min, u->tm.tm_min) + || conflict (t->tm.tm_hour, u->tm.tm_hour) + || 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->ymodulus, u->ymodulus) + || conflict (t->yweek, u->yweek) + || (t->zone != u->zone + && t->zone != TM_UNDEFINED_ZONE + && u->zone != TM_UNDEFINED_ZONE)) + return -1; +# undef conflict +# define merge_(a,b) if (TM_DEFINED (b)) (a) = (b); + merge_ (t->tm.tm_sec, u->tm.tm_sec) + merge_ (t->tm.tm_min, u->tm.tm_min) + merge_ (t->tm.tm_hour, u->tm.tm_hour) + 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->ymodulus, u->ymodulus) + merge_ (t->yweek, u->yweek) +# undef merge_ + if (u->zone != TM_UNDEFINED_ZONE) + t->zone = u->zone; + return 0; +} + +/* Parse a date/time prefix of S, putting the parsed result into *T. + Return the first character after the prefix. + The prefix may contain no useful information; + in that case, *T will contain only undefined values. */ +char * +partime (s, t) + char const *s; + struct partime *t; +{ + struct partime p; + + undefine (t); + + while (*s) + { + int i = 0; + char const *s1; + + do + { + if (! (s1 = parse_prefix (s, &p, &i))) + return (char *) s; + } + while (merge_partime (t, &p) != 0); + + s = s1; + } + + return (char *) s; +} |