1 /* Parse a string, yielding a struct partime that describes it. */
3 /* Copyright 1993, 1994, 1995, 1997 Paul Eggert
4 Distributed under license by the Free Software Foundation, Inc.
6 This file is part of RCS.
8 RCS is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
13 RCS is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with RCS; see the file COPYING.
20 If not, write to the Free Software Foundation,
21 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 Report problems and direct all questions to:
25 rcs-bugs@cs.purdue.edu
43 # define LONG_MIN (-1-2147483647L)
58 # define CTYPE_DOMAIN(c) 1
60 # define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177)
62 #define ISALNUM(c) (CTYPE_DOMAIN (c) && isalnum (c))
63 #define ISALPHA(c) (CTYPE_DOMAIN (c) && isalpha (c))
64 #define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c))
65 #define ISUPPER(c) (CTYPE_DOMAIN (c) && isupper (c))
66 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
70 char const partimeId[] =
71 "$Id: partime.c,v 5.16 1997/05/19 06:33:53 eggert Exp $";
74 /* Lookup tables for names of months, weekdays, time zones. */
76 #define NAME_LENGTH_MAXIMUM 4
80 char name[NAME_LENGTH_MAXIMUM];
85 static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *));
86 static char const *parse_fixed P ((char const *, int, int *));
87 static char const *parse_pattern_letter P ((char const *, int, struct partime *));
88 static char const *parse_prefix P ((char const *, struct partime *, int *));
89 static char const *parse_ranged P ((char const *, int, int, int, int *));
90 static int lookup P ((char const *, struct name_val const[]));
91 static int merge_partime P ((struct partime *, struct partime const *));
92 static void undefine P ((struct partime *));
95 static struct name_val const month_names[] =
112 static struct name_val const weekday_names[] =
124 #define hr60nonnegative(t) ((t)/100 * 60 + (t)%100)
125 #define hr60(t) ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
126 #define zs(t,s) {s, hr60(t)}
127 #define zd(t,s,d) zs(t, s), zs((t)+100, d)
129 static struct name_val const zone_names[] =
131 zs (-1000, "hst"), /* Hawaii */
132 zd (-1000, "hast", "hadt"), /* Hawaii-Aleutian */
133 zd (- 900, "akst", "akdt"), /* Alaska */
134 zd (- 800, "pst" , "pdt" ), /* Pacific */
135 zd (- 700, "mst" , "mdt" ), /* Mountain */
136 zd (- 600, "cst" , "cdt" ), /* Central */
137 zd (- 500, "est" , "edt" ), /* Eastern */
138 zd (- 400, "ast" , "adt" ), /* Atlantic */
139 zd (- 330, "nst" , "ndt" ), /* Newfoundland */
140 zs ( 000, "utc" ), /* Coordinated Universal */
141 zs ( 000, "uct" ), /* " */
142 zs ( 000, "cut" ), /* " */
143 zs ( 000, "ut"), /* Universal */
144 zs ( 000, "z"), /* Zulu (required by ISO 8601) */
145 zd ( 000, "gmt" , "bst" ), /* Greenwich Mean, British Summer */
146 zd ( 000, "wet" , "west"), /* Western European */
147 zd ( 100, "cet" , "cest"), /* Central European */
148 zd ( 100, "met" , "mest"), /* Middle European (bug in old tz versions) */
149 zd ( 100, "mez" , "mesz"), /* Mittel-Europaeische Zeit */
150 zd ( 200, "eet" , "eest"), /* Eastern European */
151 zs ( 530, "ist" ), /* India */
152 zd ( 900, "jst" , "jdt" ), /* Japan */
153 zd ( 900, "kst" , "kdt" ), /* Korea */
154 zd ( 1200, "nzst", "nzdt"), /* New Zealand */
157 /* The following names are duplicates or are not well attested.
158 There are lots more where these came from. */
159 zs (-1100, "sst" ), /* Samoan */
160 zd (- 900, "yst" , "ydt" ), /* Yukon - name is no longer used */
161 zd (- 500, "ast" , "adt" ), /* Acre */
162 zd (- 400, "wst" , "wdt" ), /* Western Brazil */
163 zd (- 400, "cst" , "cdt" ), /* Chile */
164 zd (- 200, "fst" , "fdt" ), /* Fernando de Noronha */
165 zs ( 000, "wat" ), /* West African */
166 zs ( 100, "cat" ), /* Central African */
167 zs ( 200, "sat" ), /* South African */
168 zd ( 200, "ist" , "idt" ), /* Israel */
169 zs ( 300, "eat" ), /* East African */
170 zd ( 300, "msk" , "msd" ), /* Moscow */
171 zd ( 330, "ist" , "idt" ), /* Iran */
172 zs ( 800, "hkt" ), /* Hong Kong */
173 zs ( 800, "sgt" ), /* Singapore */
174 zd ( 800, "cst" , "cdt" ), /* China */
175 zd ( 800, "wst" , "wst" ), /* Western Australia */
176 zd ( 930, "cst" , "cst" ), /* Central Australia */
177 zs ( 1000, "gst" ), /* Guam */
178 zd ( 1000, "est" , "est" ), /* Eastern Australia */
183 /* Look for a prefix of S in TABLE, returning val for first matching entry. */
187 struct name_val const table[];
190 char buf[NAME_LENGTH_MAXIMUM];
192 for (j = 0; j < NAME_LENGTH_MAXIMUM; j++)
194 unsigned char c = *s++;
200 buf[j] = ISUPPER (c) ? tolower (c) : c;
205 if (j == NAME_LENGTH_MAXIMUM || ! table[0].name[j])
207 else if (buf[j] != table[0].name[j])
212 /* Set *T to ``undefined'' values. */
217 t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
218 = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
219 = t->ymodulus = t->yweek
221 t->zone = TM_UNDEFINED_ZONE;
224 /* Array of patterns to look for in a date string.
225 Order is important: we look for the first matching pattern
226 whose values do not contradict values that we already know about.
227 See `parse_pattern_letter' below for the meaning of the pattern codes. */
228 static char const *const patterns[] =
230 /* These traditional patterns must come first,
231 to prevent an ISO 8601 format from misinterpreting their prefixes. */
232 "E_n_y", "x", /* RFC 822 */
233 "E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
234 "y/N/D$", /* traditional RCS */
236 /* ISO 8601:1988 formats, generalized a bit. */
237 "y-N-D$", "4ND$", "Y-N$",
238 "RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
239 "--N$", "---D$", "DT",
240 "Y-d$", "4d$", "R=d$", "-d$", "dT",
241 "y-W-X", "yWX", "y=W",
242 "-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
243 "-w-X", "w-XT", "---X$", "XT", "4$",
245 "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
251 /* Parse an initial prefix of STR, setting *T accordingly.
252 Return the first character after the prefix, or 0 if it couldn't be parsed.
253 Start with pattern *PI; if success, set *PI to the next pattern to try.
254 Set *PI to -1 if we know there are no more patterns to try;
255 if *PI is initially negative, give up immediately. */
257 parse_prefix (str, t, pi)
269 /* Remove initial noise. */
270 while (! ISALNUM (c = *str) && c != '-' && c != '+')
281 /* Try a pattern until one succeeds. */
282 while ((pat = patterns[i++]) != 0)
294 while ((s = parse_pattern_letter (s, c, t)) != 0);
300 /* Parse an initial prefix of S of length DIGITS; it must be a number.
301 Store the parsed number into *RES.
302 Return the first character after the prefix, or 0 if it wasn't parsed. */
304 parse_fixed (s, digits, res)
309 char const *lim = s + digits;
312 unsigned d = *s++ - '0';
321 /* Parse an initial prefix of S of length DIGITS;
322 it must be a number in the range LO through HI.
323 Store the parsed number into *RES.
324 Return the first character after the prefix, or 0 if it wasn't parsed. */
326 parse_ranged (s, digits, lo, hi, res)
328 int digits, lo, hi, *res;
330 s = parse_fixed (s, digits, res);
331 return s && lo <= *res && *res <= hi ? s : 0;
334 /* Parse an initial prefix of S of length DIGITS;
335 it must be a number in the range LO through HI
336 and it may be followed by a fraction to be computed using RESOLUTION.
337 Store the parsed number into *RES; store the fraction times RESOLUTION,
338 rounded to the nearest integer, into *FRES.
339 Return the first character after the prefix, or 0 if it wasn't parsed. */
341 parse_decimal (s, digits, lo, hi, resolution, res, fres)
343 int digits, lo, hi, resolution, *res, *fres;
345 s = parse_fixed (s, digits, res);
346 if (s && lo <= *res && *res <= hi)
349 if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1]))
351 char const *s1 = ++s;
352 int num10 = 0, denom10 = 10, product;
353 while (ISDIGIT (*++s))
355 int d = denom10 * 10;
356 if (d / 10 != denom10)
357 return 0; /* overflow */
360 s = parse_fixed (s1, (int) (s - s1), &num10);
361 product = num10 * resolution;
362 f = (product + (denom10 >> 1)) / denom10;
363 f -= f & (product % denom10 == denom10 >> 1); /* round to even */
364 if (f < 0 || product/resolution != num10)
365 return 0; /* overflow */
373 /* Parse an initial prefix of S; it must denote a time zone.
374 Set *ZONE to the number of seconds east of GMT,
375 or to TM_LOCAL_ZONE if it is the local time zone.
376 Return the first character after the prefix, or 0 if it wasn't parsed. */
384 int minutesEastOfUTC;
387 /* The formats are LT, n, n DST, nDST, no, o
388 where n is a time zone name
389 and o is a time zone offset of the form [-+]hh[:mm[:ss]]. */
398 minutesEastOfUTC = lookup (s, zone_names);
399 if (minutesEastOfUTC == -1)
402 /* Don't bother to check rest of spelling. */
403 while (ISALPHA ((unsigned char) *s))
406 /* Don't modify LT. */
407 if (minutesEastOfUTC == 1)
409 *zone = TM_LOCAL_ZONE;
413 z = minutesEastOfUTC * 60L;
415 /* Look for trailing " DST". */
416 if ((s[-1] == 'T' || s[-1] == 't')
417 && (s[-2] == 'S' || s[-2] == 's')
418 && (s[-3] == 'D' || s[-3] == 'd'))
420 while (ISSPACE ((unsigned char) *s))
422 if ((s[0] == 'D' || s[0] == 'd')
423 && (s[1] == 'S' || s[1] == 's')
424 && (s[2] == 'T' || s[2] == 't'))
448 if (! (s = parse_ranged (s, 2, 0, 23, &hh)))
455 if (! (s = parse_ranged (s, 2, 0, 59, &mm)))
457 if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1])
458 && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss)))
463 offset = (hh * 60 + mm) * 60L + ss;
464 *zone = z + (sign == '-' ? -offset : offset);
465 /* ?? Are fractions allowed here? If so, they're not implemented. */
469 /* Parse an initial prefix of S, matching the pattern whose code is C.
471 Return the first character after the prefix, or 0 if it wasn't parsed. */
473 parse_pattern_letter (s, c, t)
480 case '$': /* The next character must be a non-digit. */
488 /* These characters stand for themselves. */
493 case '4': /* 4-digit year */
494 s = parse_fixed (s, 4, &t->tm.tm_year);
497 case '=': /* optional '-' */
501 case 'A': /* AM or PM */
502 /* This matches the regular expression [AaPp][Mm]?.
503 It must not be followed by a letter or digit;
504 otherwise it would match prefixes of strings like "PST". */
509 if (t->tm.tm_hour == 12)
515 if (t->tm.tm_hour != 12)
529 if (ISALNUM ((unsigned char) *s))
533 case 'D': /* day of month [01-31] */
534 s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
537 case 'd': /* day of year [001-366] */
538 s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
542 case 'E': /* extended day of month [1-9, 01-31] */
543 s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 31,
547 case 'h': /* hour [00-23 followed by optional fraction] */
550 s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac);
551 t->tm.tm_min = frac / 60;
552 t->tm.tm_sec = frac % 60;
556 case 'm': /* minute [00-59 followed by optional fraction] */
557 s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
560 case 'n': /* month name [e.g. "Jan"] */
561 if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
563 /* Don't bother to check rest of spelling. */
564 while (ISALPHA ((unsigned char) *s))
568 case 'N': /* month [01-12] */
569 s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
573 case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
574 s = parse_fixed (s, 1, &t->tm.tm_year);
579 case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
580 s = parse_fixed (s, 2, &t->tm.tm_year);
584 case 's': /* second [00-60 followed by optional fraction] */
587 s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
588 t->tm.tm_sec += frac;
592 case 'T': /* 'T' or 't' */
603 case 't': /* traditional hour [1-9 or 01-12] */
604 s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
608 case 'w': /* 'W' or 'w' only (stands for current week) */
619 case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
628 s = parse_ranged (s, 2, 0, 53, &t->yweek);
631 case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
632 s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
636 case 'x': /* weekday name [e.g. "Sun"] */
637 if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
639 /* Don't bother to check rest of spelling. */
640 while (ISALPHA ((unsigned char) *s))
644 case 'y': /* either R or Y */
645 if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2]))
648 case 'Y': /* year in full [4 or more digits] */
651 while (ISDIGIT (s[len]))
655 s = parse_fixed (s, len, &t->tm.tm_year);
659 case 'Z': /* time zone */
660 s = parzone (s, &t->zone);
663 case '_': /* possibly empty sequence of non-alphanumerics */
664 while (! ISALNUM ((unsigned char) *s) && *s)
668 default: /* bad pattern */
675 /* If there is no conflict, merge into *T the additional information in *U
676 and return 0. Otherwise do nothing and return -1. */
680 struct partime const *u;
682 # define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b))
683 if (conflict (t->tm.tm_sec, u->tm.tm_sec)
684 || conflict (t->tm.tm_min, u->tm.tm_min)
685 || conflict (t->tm.tm_hour, u->tm.tm_hour)
686 || conflict (t->tm.tm_mday, u->tm.tm_mday)
687 || conflict (t->tm.tm_mon, u->tm.tm_mon)
688 || conflict (t->tm.tm_year, u->tm.tm_year)
689 || conflict (t->tm.tm_wday, u->tm.tm_yday)
690 || conflict (t->ymodulus, u->ymodulus)
691 || conflict (t->yweek, u->yweek)
692 || (t->zone != u->zone
693 && t->zone != TM_UNDEFINED_ZONE
694 && u->zone != TM_UNDEFINED_ZONE))
697 # define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
698 merge_ (t->tm.tm_sec, u->tm.tm_sec)
699 merge_ (t->tm.tm_min, u->tm.tm_min)
700 merge_ (t->tm.tm_hour, u->tm.tm_hour)
701 merge_ (t->tm.tm_mday, u->tm.tm_mday)
702 merge_ (t->tm.tm_mon, u->tm.tm_mon)
703 merge_ (t->tm.tm_year, u->tm.tm_year)
704 merge_ (t->tm.tm_wday, u->tm.tm_yday)
705 merge_ (t->ymodulus, u->ymodulus)
706 merge_ (t->yweek, u->yweek)
708 if (u->zone != TM_UNDEFINED_ZONE)
713 /* Parse a date/time prefix of S, putting the parsed result into *T.
714 Return the first character after the prefix.
715 The prefix may contain no useful information;
716 in that case, *T will contain only undefined values. */
733 if (! (s1 = parse_prefix (s, &p, &i)))
736 while (merge_partime (t, &p) != 0);