1/* Parse a string, yielding a struct partime that describes it.  */
2
3/* Copyright 1993, 1994, 1995, 1997 Paul Eggert
4   Distributed under license by the Free Software Foundation, Inc.
5
6   This file is part of RCS.
7
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)
11   any later version.
12
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.
17
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.
22
23   Report problems and direct all questions to:
24
25	rcs-bugs@cs.purdue.edu
26
27 */
28
29#if has_conf_h
30# include <conf.h>
31#else
32# if HAVE_CONFIG_H
33#  include <config.h>
34# else
35#  ifndef __STDC__
36#   define const
37#  endif
38# endif
39# if HAVE_LIMITS_H
40#  include <limits.h>
41# endif
42# ifndef LONG_MIN
43# define LONG_MIN (-1-2147483647L)
44# endif
45# if HAVE_STDDEF_H
46#  include <stddef.h>
47# endif
48# if STDC_HEADERS
49#  include <stdlib.h>
50# endif
51# include <time.h>
52# ifdef __STDC__
53#  define P(x) x
54# else
55#  define P(x) ()
56# endif
57#endif
58
59#ifndef offsetof
60#define offsetof(aggregate, member) ((size_t) &((aggregate *) 0)->member)
61#endif
62
63#include <ctype.h>
64#if STDC_HEADERS
65# define CTYPE_DOMAIN(c) 1
66#else
67# define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177)
68#endif
69#define ISALNUM(c)	(CTYPE_DOMAIN (c) && isalnum (c))
70#define ISALPHA(c)	(CTYPE_DOMAIN (c) && isalpha (c))
71#define ISSPACE(c)	(CTYPE_DOMAIN (c) && isspace (c))
72#define ISUPPER(c)	(CTYPE_DOMAIN (c) && isupper (c))
73#define ISDIGIT(c)	((unsigned) (c) - '0' <= 9)
74
75#include <partime.h>
76
77char const partime_id[] =
78  "$Id: partime.c 8008 2004-06-16 21:22:10Z korli $";
79
80
81/* Lookup tables for names of months, weekdays, time zones.  */
82
83#define NAME_LENGTH_MAXIMUM 4
84
85struct name_val
86  {
87    char name[NAME_LENGTH_MAXIMUM];
88    int val;
89  };
90
91
92static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *));
93static char const *parse_fixed P ((char const *, int, int *));
94static char const *parse_pattern_letter P ((char const *, int, struct partime *));
95static char const *parse_prefix P ((char const *, char const **, struct partime *));
96static char const *parse_ranged P ((char const *, int, int, int, int *));
97static char const *parse_varying P ((char const *, int *));
98static int lookup P ((char const *, struct name_val const[]));
99static int merge_partime P ((struct partime *, struct partime const *));
100static void undefine P ((struct partime *));
101
102
103static struct name_val const month_names[] =
104{
105  {"jan", 0},
106  {"feb", 1},
107  {"mar", 2},
108  {"apr", 3},
109  {"may", 4},
110  {"jun", 5},
111  {"jul", 6},
112  {"aug", 7},
113  {"sep", 8},
114  {"oct", 9},
115  {"nov", 10},
116  {"dec", 11},
117  {"", TM_UNDEFINED}
118};
119
120static struct name_val const weekday_names[] =
121{
122  {"sun", 0},
123  {"mon", 1},
124  {"tue", 2},
125  {"wed", 3},
126  {"thu", 4},
127  {"fri", 5},
128  {"sat", 6},
129  {"", TM_UNDEFINED}
130};
131
132#define RELATIVE_CONS(member, multiplier)	\
133	(offsetof (struct tm, member) + (multiplier) * sizeof (struct tm))
134#define RELATIVE_OFFSET(c)	((c) % sizeof (struct tm))
135#define RELATIVE_MULTIPLIER(c)	((c) / sizeof (struct tm))
136static struct name_val const relative_units[] =
137{
138  {"year", RELATIVE_CONS (tm_year,  1) },
139  {"mont", RELATIVE_CONS (tm_mon ,  1) },
140  {"fort", RELATIVE_CONS (tm_mday, 14) },
141  {"week", RELATIVE_CONS (tm_mday,  7) },
142  {"day" , RELATIVE_CONS (tm_mday,  1) },
143  {"hour", RELATIVE_CONS (tm_hour,  1) },
144  {"min" , RELATIVE_CONS (tm_min ,  1) },
145  {"sec" , RELATIVE_CONS (tm_sec ,  1) },
146  {"", TM_UNDEFINED}
147};
148
149static struct name_val const ago[] =
150{
151  {"ago", 0},
152  {"", TM_UNDEFINED}
153};
154
155static struct name_val const dst_names[] =
156{
157  {"dst", 1},
158  {"", 0}
159};
160
161#define hr60nonnegative(t)	((t)/100 * 60  +  (t)%100)
162#define hr60(t)	((t) < 0 ? - hr60nonnegative (-(t)) : hr60nonnegative (t))
163#define zs(t, s)	{s, hr60 (t)}
164#define zd(t, s, d)	zs (t, s),  zs ((t) + 100, d)
165
166static struct name_val const zone_names[] =
167{
168  zs (-1000, "hst"),		/* Hawaii */
169  zd (-1000, "hast", "hadt"),	/* Hawaii-Aleutian */
170  zd (- 900, "akst", "akdt"),	/* Alaska */
171  zd (- 800, "pst" , "pdt" ),	/* Pacific */
172  zd (- 700, "mst" , "mdt" ),	/* Mountain */
173  zd (- 600, "cst" , "cdt" ),	/* Central */
174  zd (- 500, "est" , "edt" ),	/* Eastern */
175  zd (- 400, "ast" , "adt" ),	/* Atlantic */
176  zd (- 330, "nst" , "ndt" ),	/* Newfoundland */
177  zs (  000, "utc" ),		/* Coordinated Universal */
178  zs (  000, "uct" ),		/* " */
179  zs (  000, "cut" ),		/* " */
180  zs (  000, "ut"),		/* Universal */
181  zs (  000, "z"),		/* Zulu (required by ISO 8601) */
182  zd (  000, "gmt" , "bst" ),	/* Greenwich Mean, British Summer */
183  zd (  000, "wet" , "west"),	/* Western European */
184  zd (  100, "cet" , "cest"),	/* Central European */
185  zd (  100, "met" , "mest"),	/* Middle European (bug in old tz versions) */
186  zd (  100, "mez" , "mesz"),	/* Mittel-Europaeische Zeit */
187  zd (  200, "eet" , "eest"),	/* Eastern European */
188  zs (  530, "ist" ),		/* India */
189  zd (  900, "jst" , "jdt" ),	/* Japan */
190  zd (  900, "kst" , "kdt" ),	/* Korea */
191  zd ( 1200, "nzst", "nzdt"),	/* New Zealand */
192  {"lt", 1},
193#if 0
194  /* The following names are duplicates or are not well attested.
195     It's not worth keeping a complete list, since alphabetic time zone names
196     are deprecated and there are lots more where these came from.  */
197  zs (-1100, "sst" ),		/* Samoan */
198  zd (- 900, "yst" , "ydt" ),	/* Yukon - name is no longer used */
199  zd (- 500, "ast" , "adt" ),	/* Acre */
200  zd (- 400, "wst" , "wdt" ),	/* Western Brazil */
201  zd (- 400, "cst" , "cdt" ),	/* Chile */
202  zd (- 200, "fst" , "fdt" ),	/* Fernando de Noronha */
203  zs (  000, "wat" ),		/* West African */
204  zs (  100, "cat" ),		/* Central African */
205  zs (  200, "sat" ),		/* South African */
206  zd (  200, "ist" , "idt" ),	/* Israel */
207  zs (  300, "eat" ),		/* East African */
208  zd (  300, "msk" , "msd" ),	/* Moscow */
209  zd (  330, "ist" , "idt" ),	/* Iran */
210  zs (  800, "hkt" ),		/* Hong Kong */
211  zs (  800, "sgt" ),		/* Singapore */
212  zd (  800, "cst" , "cdt" ),	/* China */
213  zd (  800, "wst" , "wst" ),	/* Western Australia */
214  zd (  930, "cst" , "cst" ),	/* Central Australia */
215  zs ( 1000, "gst" ),		/* Guam */
216  zd ( 1000, "est" , "est" ),	/* Eastern Australia */
217#endif
218  {"", -1}
219};
220
221/* Look for a prefix of S in TABLE, returning val for first matching entry.  */
222static int
223lookup (s, table)
224     char const *s;
225     struct name_val const table[];
226{
227  int j;
228  char buf[NAME_LENGTH_MAXIMUM];
229
230  for (j = 0; j < NAME_LENGTH_MAXIMUM; j++)
231    {
232      unsigned char c = *s;
233      if (! ISALPHA (c))
234	{
235	  buf[j] = '\0';
236	  break;
237	}
238      buf[j] = ISUPPER (c) ? tolower (c) : c;
239      s++;
240      s += *s == '.';
241    }
242
243  for (;; table++)
244    for (j = 0; ; j++)
245      if (j == NAME_LENGTH_MAXIMUM  ||  ! table[0].name[j])
246	return table[0].val;
247      else if (buf[j] != table[0].name[j])
248	break;
249}
250
251
252/* Set *T to ``undefined'' values.  */
253static void
254undefine (t)
255     struct partime *t;
256{
257  t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
258    = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
259    = t->wday_ordinal = t->ymodulus = t->yweek
260    = TM_UNDEFINED;
261  t->tmr.tm_sec = t->tmr.tm_min = t->tmr.tm_hour =
262    t->tmr.tm_mday = t->tmr.tm_mon = t->tmr.tm_year = 0;
263  t->zone = TM_UNDEFINED_ZONE;
264}
265
266/* Patterns to look for in a time string.
267   Order is important: we look for the first matching pattern
268   whose values do not contradict values that we already know about.
269   See `parse_pattern_letter' below for the meaning of the pattern codes.  */
270static char const time_patterns[] =
271{
272  /* Traditional patterns come first,
273     to prevent an ISO 8601 format from misinterpreting their prefixes.  */
274
275  /* RFC 822, extended */
276  'E', '_', 'N', '_', 'y', '$', 0,
277  'x', 0,
278
279  /* traditional */
280  '4', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0,
281  'R', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0,
282  'E', '_', 'N', 0,
283  'N', '_', 'E', '_', 'y', ';', 0,
284  'N', '_', 'E', ';', 0,
285  'N', 0,
286  't', ':', 'm', ':', 's', '_', 'A', 0,
287  't', ':', 'm', '_', 'A', 0,
288  't', '_', 'A', 0,
289
290  /* traditional get_date */
291  'i', '_', 'x', 0,
292  'Y', '/', 'n', '/', 'E', ';', 0,
293  'n', '/', 'E', '/', 'y', ';', 0,
294  'n', '/', 'E', ';', 0,
295  'u', 0,
296
297  /* ISO 8601:1988 formats, generalized a bit.  */
298  'y', '-', 'M', '-', 'D', '$', 0,
299  '4', 'M', 'D', '$', 0,
300  'Y', '-', 'M', '$', 0,
301  'R', 'M', 'D', '$', 0,
302  '-', 'R', '=', 'M', '$', 0,
303  '-', 'R', '$', 0,
304  '-', '-', 'M', '=', 'D', '$', 0,
305  'M', '=', 'D', 'T', 0,
306  '-', '-', 'M', '$', 0,
307  '-', '-', '-', 'D', '$', 0,
308  'D', 'T', 0,
309  'Y', '-', 'd', '$', 0,
310  '4', 'd', '$', 0,
311  'R', '=', 'd', '$', 0,
312  '-', 'd', '$', 0,
313  'd', 'T', 0,
314  'y', '-', 'W', '-', 'X', 0,
315  'y', 'W', 'X', 0,
316  'y', '=', 'W', 0,
317  '-', 'r', '-', 'W', '-', 'X', 0,
318  'r', '-', 'W', '-', 'X', 'T', 0,
319  '-', 'r', 'W', 'X', 0,
320  'r', 'W', 'X', 'T', 0,
321  '-', 'W', '=', 'X', 0,
322  'W', '=', 'X', 'T', 0,
323  '-', 'W', 0,
324  '-', 'w', '-', 'X', 0,
325  'w', '-', 'X', 'T', 0,
326  '-', '-', '-', 'X', '$', 0,
327  'X', 'T', 0,
328  '4', '$', 0,
329  'T', 0,
330  'h', ':', 'm', ':', 's', '$', 0,
331  'h', 'm', 's', '$', 0,
332  'h', ':', 'L', '$', 0,
333  'h', 'L', '$', 0,
334  'H', '$', 0,
335  '-', 'm', ':', 's', '$', 0,
336  '-', 'm', 's', '$', 0,
337  '-', 'L', '$', 0,
338  '-', '-', 's', '$', 0,
339  'Y', 0,
340  'Z', 0,
341
342  0
343};
344
345/* Parse an initial prefix of STR according to *PATTERNS, setting *T.
346   Return the first character after the prefix, or 0 if it couldn't be parsed.
347   *PATTERNS is a character array containing one pattern string after another;
348   it is terminated by an empty string.
349   If success, set *PATTERNS to the next pattern to try.
350   Set *PATTERNS to 0 if we know there are no more patterns to try;
351   if *PATTERNS is initially 0, give up immediately.  */
352static char const *
353parse_prefix (str, patterns, t)
354     char const *str;
355     char const **patterns;
356     struct partime *t;
357{
358  char const *pat = *patterns;
359  unsigned char c;
360
361  if (! pat)
362    return 0;
363
364  /* Remove initial noise.  */
365  while (! ISALNUM (c = *str) && c != '-' && c != '+')
366    {
367      if (! c)
368	{
369	  undefine (t);
370	  *patterns = 0;
371	  return str;
372	}
373
374      str++;
375    }
376
377  /* Try a pattern until one succeeds.  */
378  while (*pat)
379    {
380      char const *s = str;
381      undefine (t);
382
383      do
384	{
385	  if (! (c = *pat++))
386	    {
387	      *patterns = pat;
388	      return s;
389	    }
390	}
391      while ((s = parse_pattern_letter (s, c, t)) != 0);
392
393      while (*pat++)
394	continue;
395    }
396
397  return 0;
398}
399
400/* Parse an initial prefix of S of length DIGITS; it must be a number.
401   Store the parsed number into *RES.
402   Return the first character after the prefix, or 0 if it wasn't parsed.  */
403static char const *
404parse_fixed (s, digits, res)
405     char const *s;
406     int digits, *res;
407{
408  int n = 0;
409  char const *lim = s + digits;
410  while (s < lim)
411    {
412      unsigned d = *s++ - '0';
413      if (9 < d)
414	return 0;
415      n = 10 * n + d;
416    }
417  *res = n;
418  return s;
419}
420
421/* Parse a possibly empty initial prefix of S.
422   Store the parsed number into *RES.
423   Return the first character after the prefix.  */
424static char const *
425parse_varying (s, res)
426     char const *s;
427     int *res;
428{
429  int n = 0;
430  for (;;)
431    {
432      unsigned d = *s - '0';
433      if (9 < d)
434	break;
435      s++;
436      n = 10 * n + d;
437    }
438  *res = n;
439  return s;
440}
441
442/* Parse an initial prefix of S of length DIGITS;
443   it must be a number in the range LO through HI.
444   Store the parsed number into *RES.
445   Return the first character after the prefix, or 0 if it wasn't parsed.  */
446static char const *
447parse_ranged (s, digits, lo, hi, res)
448     char const *s;
449     int digits, lo, hi, *res;
450{
451  s = parse_fixed (s, digits, res);
452  return s && lo <= *res && *res <= hi ? s : 0;
453}
454
455/* Parse an initial prefix of S of length DIGITS;
456   it must be a number in the range LO through HI
457   and it may be followed by a fraction to be computed using RESOLUTION.
458   Store the parsed number into *RES; store the fraction times RESOLUTION,
459   rounded to the nearest integer, into *FRES.
460   Return the first character after the prefix, or 0 if it wasn't parsed.  */
461static char const *
462parse_decimal (s, digits, lo, hi, resolution, res, fres)
463     char const *s;
464     int digits, lo, hi, resolution, *res, *fres;
465{
466  s = parse_fixed (s, digits, res);
467  if (s && lo <= *res && *res <= hi)
468    {
469      int f = 0;
470      if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1]))
471	{
472	  char const *s1 = ++s;
473	  int num10 = 0, denom10 = 10, product;
474	  while (ISDIGIT (*++s))
475	    {
476	      int d = denom10 * 10;
477	      if (d / 10  !=  denom10)
478		return 0; /* overflow */
479	      denom10 = d;
480	    }
481	  s = parse_fixed (s1, (int) (s - s1), &num10);
482	  product = num10 * resolution;
483	  f = (product + (denom10 >> 1)) / denom10;
484	  f -= f & (product % denom10  ==  denom10 >> 1); /* round to even */
485	  if (f < 0  ||  product/resolution != num10)
486	    return 0; /* overflow */
487	}
488      *fres = f;
489      return s;
490    }
491  return 0;
492}
493
494/* Parse an initial prefix of S; it must denote a time zone.
495   Set *ZONE to the number of seconds east of GMT,
496   or to TM_LOCAL_ZONE if it is the local time zone.
497   Return the first character after the prefix, or 0 if it wasn't parsed.  */
498char *
499parzone (s, zone)
500     char const *s;
501     long *zone;
502{
503  char const *s1;
504  char sign;
505  int hh, mm, ss;
506  int minutes_east_of_UTC;
507  int trailing_DST;
508  long offset, z;
509
510  /* The formats are LT, n, n DST, nDST, no, o
511     where n is a time zone name
512     and o is a time zone offset of the form [-+]hh[:mm[:ss]].  */
513  switch (*s)
514    {
515    case '-':
516    case '+':
517      z = 0;
518      break;
519
520    default:
521      minutes_east_of_UTC = lookup (s, zone_names);
522      if (minutes_east_of_UTC == -1)
523	return 0;
524
525      /* Don't bother to check rest of spelling,
526	 but look for an embedded "DST".  */
527      trailing_DST = 0;
528      while (ISALPHA ((unsigned char) *s))
529	{
530	  if ((*s == 'D' || *s == 'd') && lookup (s, dst_names))
531	    trailing_DST = 1;
532	  s++;
533	  s += *s == '.';
534	}
535
536      /* Don't modify LT.  */
537      if (minutes_east_of_UTC == 1)
538	{
539	  *zone = TM_LOCAL_ZONE;
540	  return (char *) s;
541	}
542
543      z = minutes_east_of_UTC * 60L;
544      s1 = s;
545
546      /* Look for trailing "DST" or " DST".  */
547      while (ISSPACE ((unsigned char) *s))
548	s++;
549      if (lookup (s, dst_names))
550	{
551	  while (ISALPHA ((unsigned char) *s))
552	    {
553	      s++;
554	      s += *s == '.';
555	    }
556	  trailing_DST = 1;
557	}
558
559      if (trailing_DST)
560	{
561	  *zone = z + 60*60;
562	  return (char *) s;
563	}
564
565      s = s1;
566
567      switch (*s)
568	{
569	case '-':
570	case '+':
571	  break;
572
573	default:
574	  *zone = z;
575	  return (char *) s;
576	}
577
578      break;
579    }
580
581  sign = *s++;
582
583  if (! (s = parse_ranged (s, 2, 0, 23, &hh)))
584    return 0;
585  mm = ss = 0;
586  if (*s == ':')
587    s++;
588  if (ISDIGIT (*s))
589    {
590      if (! (s = parse_ranged (s, 2, 0, 59, &mm)))
591	return 0;
592      if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1])
593	  && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss)))
594	return 0;
595    }
596  if (ISDIGIT (*s))
597    return 0;
598  offset = (hh * 60 + mm) * 60L + ss;
599  *zone = z + (sign == '-' ? -offset : offset);
600  /* ?? Are fractions allowed here?  If so, they're not implemented.  */
601  return (char *) s;
602}
603
604/* Parse an initial prefix of S, matching the pattern whose code is C.
605   Set *T accordingly.
606   Return the first character after the prefix, or 0 if it wasn't parsed.  */
607static char const *
608parse_pattern_letter (s, c, t)
609     char const *s;
610     int c;
611     struct partime *t;
612{
613  char const *s0 = s;
614
615  switch (c)
616    {
617    case '$': /* The next character must be a non-digit.  */
618      if (ISDIGIT (*s))
619	return 0;
620      break;
621
622    case '-':
623    case '/':
624    case ':':
625      /* These characters stand for themselves.  */
626      if (*s++ != c)
627	return 0;
628      break;
629
630    case '4': /* 4-digit year */
631      s = parse_fixed (s, 4, &t->tm.tm_year);
632      break;
633
634    case ';': /* The next character must be a non-digit, and cannot be ':'.  */
635      if (ISDIGIT (*s) || *s == ':')
636	return 0;
637      break;
638
639    case '=': /* optional '-' */
640      s += *s == '-';
641      break;
642
643    case 'A': /* AM or PM */
644      /* This matches the regular expression [AaPp]\.?([Mm]\.?)?.
645         It must not be followed by a letter or digit;
646         otherwise it would match prefixes of strings like "PST".  */
647      switch (*s)
648	{
649	case 'A':
650	case 'a':
651	  if (t->tm.tm_hour == 12)
652	    t->tm.tm_hour = 0;
653	  break;
654
655	case 'P':
656	case 'p':
657	  if (t->tm.tm_hour != 12)
658	    t->tm.tm_hour += 12;
659	  break;
660
661	default:
662	  return 0;
663	}
664      s++;
665      s += *s == '.';
666      switch (*s)
667	{
668	case 'M':
669	case 'm':
670	  s++;
671	  s += *s == '.';
672	  break;
673	}
674      if (ISALNUM ((unsigned char) *s))
675	return 0;
676      break;
677
678    case 'D': /* day of month [01-31] */
679      s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
680      break;
681
682    case 'd': /* day of year [001-366] */
683      s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
684      t->tm.tm_yday--;
685      break;
686
687    case 'E': /* traditional day of month [1-9, 01-31] */
688      s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 31,
689			&t->tm.tm_mday);
690      break;
691
692    case 'h': /* hour [00-23] */
693      s = parse_ranged (s, 2, 0, 23, &t->tm.tm_hour);
694      break;
695
696    case 'H': /* hour [00-23 followed by optional fraction] */
697      {
698	int frac;
699	s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac);
700	t->tm.tm_min = frac / 60;
701	t->tm.tm_sec = frac % 60;
702      }
703      break;
704
705    case 'i': /* ordinal day number, e.g. "3rd" */
706      s = parse_varying (s, &t->wday_ordinal);
707      if (s == s0)
708	return 0;
709      while (ISALPHA ((unsigned char) *s))
710	s++;
711      break;
712
713    case 'L': /* minute [00-59 followed by optional fraction] */
714      s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
715      break;
716
717    case 'm': /* minute [00-59] */
718      s = parse_ranged (s, 2, 0, 59, &t->tm.tm_min);
719      break;
720
721    case 'M': /* month [01-12] */
722      s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
723      t->tm.tm_mon--;
724      break;
725
726    case 'n': /* traditional month [1-9, 01-12] */
727      s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
728			&t->tm.tm_mon);
729      t->tm.tm_mon--;
730      break;
731
732    case 'N': /* month name [e.g. "Jan"] */
733      if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
734	return 0;
735      /* Don't bother to check rest of spelling.  */
736      while (ISALPHA ((unsigned char) *s))
737	s++;
738      break;
739
740    case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
741      s = parse_fixed (s, 1, &t->tm.tm_year);
742      t->ymodulus = 10;
743      break;
744
745    case_R:
746    case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
747      s = parse_fixed (s, 2, &t->tm.tm_year);
748      t->ymodulus = 100;
749      break;
750
751    case 's': /* second [00-60 followed by optional fraction] */
752      {
753	int frac;
754	s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
755	t->tm.tm_sec += frac;
756      }
757      break;
758
759    case 'T': /* 'T' or 't' */
760      switch (*s++)
761	{
762	case 'T':
763	case 't':
764	  break;
765	default:
766	  return 0;
767	}
768      break;
769
770    case 't': /* traditional hour [1-9 or 01-12] */
771      s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
772			&t->tm.tm_hour);
773      break;
774
775    case 'u': /* relative unit */
776      {
777	int i;
778	int n;
779	int negative = 0;
780	switch (*s)
781	  {
782	    case '-': negative = 1;
783	    /* Fall through.  */
784	    case '+': s++;
785	  }
786	if (ISDIGIT (*s))
787	  s = parse_varying (s, &n);
788	else if (s == s0)
789	  n = 1;
790	else
791	  return 0;
792	if (negative)
793	  n = -n;
794	while (!ISALNUM ((unsigned char) *s))
795	  s++;
796	i = lookup (s, relative_units);
797	if (!TM_DEFINED (i))
798	  return 0;
799	* (int *) ((char *) &t->tmr + RELATIVE_OFFSET (i))
800	  += n * RELATIVE_MULTIPLIER (i);
801	while (ISALPHA ((unsigned char) *s))
802	  s++;
803	while (! ISALNUM ((unsigned char) *s) && *s)
804	  s++;
805	if (TM_DEFINED (lookup (s, ago)))
806	  {
807	    t->tmr.tm_sec  = - t->tmr.tm_sec;
808	    t->tmr.tm_min  = - t->tmr.tm_min;
809	    t->tmr.tm_hour = - t->tmr.tm_hour;
810	    t->tmr.tm_mday = - t->tmr.tm_mday;
811	    t->tmr.tm_mon  = - t->tmr.tm_mon;
812	    t->tmr.tm_year = - t->tmr.tm_year;
813	    while (ISALPHA ((unsigned char) *s))
814	      s++;
815	  }
816	break;
817      }
818
819    case 'w': /* 'W' or 'w' only (stands for current week) */
820      switch (*s++)
821	{
822	case 'W':
823	case 'w':
824	  break;
825	default:
826	  return 0;
827	}
828      break;
829
830    case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
831      switch (*s++)
832	{
833	case 'W':
834	case 'w':
835	  break;
836	default:
837	  return 0;
838	}
839      s = parse_ranged (s, 2, 0, 53, &t->yweek);
840      break;
841
842    case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
843      s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
844      t->tm.tm_wday--;
845      break;
846
847    case 'x': /* weekday name [e.g. "Sun"] */
848      if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
849	return 0;
850      /* Don't bother to check rest of spelling.  */
851      while (ISALPHA ((unsigned char) *s))
852	s++;
853      break;
854
855    case 'y': /* either R or Y */
856      if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2]))
857	goto case_R;
858      /* fall into */
859    case 'Y': /* year in full [4 or more digits] */
860      s = parse_varying (s, &t->tm.tm_year);
861      if (s - s0 < 4)
862	return 0;
863      break;
864
865    case 'Z': /* time zone */
866      s = parzone (s, &t->zone);
867      break;
868
869    case '_': /* possibly empty sequence of non-alphanumerics */
870      while (! ISALNUM ((unsigned char) *s) && *s)
871	s++;
872      break;
873
874    default: /* bad pattern */
875      return 0;
876    }
877
878  return s;
879}
880
881/* If there is no conflict, merge into *T the additional information in *U
882   and return 0.  Otherwise do nothing and return -1.  */
883static int
884merge_partime (t, u)
885     struct partime *t;
886     struct partime const *u;
887{
888# define conflict(a,b) ((a) != (b)  &&  TM_DEFINED (a)  &&  TM_DEFINED (b))
889  if (conflict (t->tm.tm_sec, u->tm.tm_sec)
890      || conflict (t->tm.tm_min, u->tm.tm_min)
891      || conflict (t->tm.tm_hour, u->tm.tm_hour)
892      || conflict (t->tm.tm_mday, u->tm.tm_mday)
893      || conflict (t->tm.tm_mon, u->tm.tm_mon)
894      || conflict (t->tm.tm_year, u->tm.tm_year)
895      || conflict (t->tm.tm_wday, u->tm.tm_wday)
896      || conflict (t->tm.tm_yday, u->tm.tm_yday)
897      || conflict (t->ymodulus, u->ymodulus)
898      || conflict (t->yweek, u->yweek)
899      || (t->zone != u->zone
900	  && t->zone != TM_UNDEFINED_ZONE
901	  && u->zone != TM_UNDEFINED_ZONE))
902    return -1;
903# undef conflict
904# define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
905  merge_ (t->tm.tm_sec, u->tm.tm_sec)
906  merge_ (t->tm.tm_min, u->tm.tm_min)
907  merge_ (t->tm.tm_hour, u->tm.tm_hour)
908  merge_ (t->tm.tm_mday, u->tm.tm_mday)
909  merge_ (t->tm.tm_mon, u->tm.tm_mon)
910  merge_ (t->tm.tm_year, u->tm.tm_year)
911  merge_ (t->tm.tm_wday, u->tm.tm_wday)
912  merge_ (t->tm.tm_yday, u->tm.tm_yday)
913  merge_ (t->ymodulus, u->ymodulus)
914  merge_ (t->yweek, u->yweek)
915# undef merge_
916  t->tmr.tm_sec += u->tmr.tm_sec;
917  t->tmr.tm_min += u->tmr.tm_min;
918  t->tmr.tm_hour += u->tmr.tm_hour;
919  t->tmr.tm_mday += u->tmr.tm_mday;
920  t->tmr.tm_mon += u->tmr.tm_mon;
921  t->tmr.tm_year += u->tmr.tm_year;
922  if (u->zone != TM_UNDEFINED_ZONE)
923    t->zone = u->zone;
924  return 0;
925}
926
927/* Parse a date/time prefix of S, putting the parsed result into *T.
928   Return the first character after the prefix.
929   The prefix may contain no useful information;
930   in that case, *T will contain only undefined values.  */
931char *
932partime (s, t)
933     char const *s;
934     struct partime *t;
935{
936  struct partime p;
937
938  undefine (t);
939
940  while (*s)
941    {
942      char const *patterns = time_patterns;
943      char const *s1;
944
945      do
946	{
947	  if (! (s1 = parse_prefix (s, &patterns, &p)))
948	    return (char *) s;
949	}
950      while (merge_partime (t, &p) != 0);
951
952      s = s1;
953    }
954
955  return (char *) s;
956}
957