xref: /plan9/sys/src/ape/cmd/patch/partime.c (revision 0b459c2cb92b7c9d88818e9a2f72e678e5bc4553)
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 STDC_HEADERS
46 #  include <stdlib.h>
47 # endif
48 # include <time.h>
49 # ifdef __STDC__
50 #  define P(x) x
51 # else
52 #  define P(x) ()
53 # endif
54 #endif
55 
56 #include <ctype.h>
57 #if STDC_HEADERS
58 # define CTYPE_DOMAIN(c) 1
59 #else
60 # define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177)
61 #endif
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)
67 
68 #include <partime.h>
69 
70 char const partimeId[] =
71   "$Id: partime.c,v 5.16 1997/05/19 06:33:53 eggert Exp $";
72 
73 
74 /* Lookup tables for names of months, weekdays, time zones.  */
75 
76 #define NAME_LENGTH_MAXIMUM 4
77 
78 struct name_val
79   {
80     char name[NAME_LENGTH_MAXIMUM];
81     int val;
82   };
83 
84 
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 *));
93 
94 
95 static struct name_val const month_names[] =
96 {
97   {"jan", 0},
98   {"feb", 1},
99   {"mar", 2},
100   {"apr", 3},
101   {"may", 4},
102   {"jun", 5},
103   {"jul", 6},
104   {"aug", 7},
105   {"sep", 8},
106   {"oct", 9},
107   {"nov", 10},
108   {"dec", 11},
109   {"", TM_UNDEFINED}
110 };
111 
112 static struct name_val const weekday_names[] =
113 {
114   {"sun", 0},
115   {"mon", 1},
116   {"tue", 2},
117   {"wed", 3},
118   {"thu", 4},
119   {"fri", 5},
120   {"sat", 6},
121   {"", TM_UNDEFINED}
122 };
123 
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)
128 
129 static struct name_val const zone_names[] =
130 {
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 */
155   {"lt", 1},
156 #if 0
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 */
179 #endif
180   {"", -1}
181 };
182 
183 /* Look for a prefix of S in TABLE, returning val for first matching entry.  */
184 static int
lookup(s,table)185 lookup (s, table)
186      char const *s;
187      struct name_val const table[];
188 {
189   int j;
190   char buf[NAME_LENGTH_MAXIMUM];
191 
192   for (j = 0; j < NAME_LENGTH_MAXIMUM; j++)
193     {
194       unsigned char c = *s++;
195       if (! ISALPHA (c))
196 	{
197 	  buf[j] = '\0';
198 	  break;
199 	}
200       buf[j] = ISUPPER (c) ? tolower (c) : c;
201     }
202 
203   for (;; table++)
204     for (j = 0; ; j++)
205       if (j == NAME_LENGTH_MAXIMUM  ||  ! table[0].name[j])
206 	return table[0].val;
207       else if (buf[j] != table[0].name[j])
208 	break;
209 }
210 
211 
212 /* Set *T to ``undefined'' values.  */
213 static void
undefine(t)214 undefine (t)
215      struct partime *t;
216 {
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
220     = TM_UNDEFINED;
221   t->zone = TM_UNDEFINED_ZONE;
222 }
223 
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[] =
229 {
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 */
235 
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$",
244   "T",
245   "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
246   "Y", "Z",
247 
248   0
249 };
250 
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.  */
256 static char const *
parse_prefix(str,t,pi)257 parse_prefix (str, t, pi)
258      char const *str;
259      struct partime *t;
260      int *pi;
261 {
262   int i = *pi;
263   char const *pat;
264   unsigned char c;
265 
266   if (i < 0)
267     return 0;
268 
269   /* Remove initial noise.  */
270   while (! ISALNUM (c = *str) && c != '-' && c != '+')
271     {
272       if (! c)
273 	{
274 	  undefine (t);
275 	  *pi = -1;
276 	  return str;
277 	}
278       str++;
279     }
280 
281   /* Try a pattern until one succeeds.  */
282   while ((pat = patterns[i++]) != 0)
283     {
284       char const *s = str;
285       undefine (t);
286       do
287 	{
288 	  if (! (c = *pat++))
289 	    {
290 	      *pi = i;
291 	      return s;
292 	    }
293 	}
294       while ((s = parse_pattern_letter (s, c, t)) != 0);
295     }
296 
297   return 0;
298 }
299 
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.  */
303 static char const *
parse_fixed(s,digits,res)304 parse_fixed (s, digits, res)
305      char const *s;
306      int digits, *res;
307 {
308   int n = 0;
309   char const *lim = s + digits;
310   while (s < lim)
311     {
312       unsigned d = *s++ - '0';
313       if (9 < d)
314 	return 0;
315       n = 10 * n + d;
316     }
317   *res = n;
318   return s;
319 }
320 
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.  */
325 static char const *
parse_ranged(s,digits,lo,hi,res)326 parse_ranged (s, digits, lo, hi, res)
327      char const *s;
328      int digits, lo, hi, *res;
329 {
330   s = parse_fixed (s, digits, res);
331   return s && lo <= *res && *res <= hi ? s : 0;
332 }
333 
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.  */
340 static char const *
parse_decimal(s,digits,lo,hi,resolution,res,fres)341 parse_decimal (s, digits, lo, hi, resolution, res, fres)
342      char const *s;
343      int digits, lo, hi, resolution, *res, *fres;
344 {
345   s = parse_fixed (s, digits, res);
346   if (s && lo <= *res && *res <= hi)
347     {
348       int f = 0;
349       if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1]))
350 	{
351 	  char const *s1 = ++s;
352 	  int num10 = 0, denom10 = 10, product;
353 	  while (ISDIGIT (*++s))
354 	    {
355 	      int d = denom10 * 10;
356 	      if (d / 10  !=  denom10)
357 		return 0; /* overflow */
358 	      denom10 = d;
359 	    }
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 */
366 	}
367       *fres = f;
368       return s;
369     }
370   return 0;
371 }
372 
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.  */
377 char *
parzone(s,zone)378 parzone (s, zone)
379      char const *s;
380      long *zone;
381 {
382   char sign;
383   int hh, mm, ss;
384   int minutesEastOfUTC;
385   long offset, z;
386 
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]].  */
390   switch (*s)
391     {
392     case '-':
393     case '+':
394       z = 0;
395       break;
396 
397     default:
398       minutesEastOfUTC = lookup (s, zone_names);
399       if (minutesEastOfUTC == -1)
400 	return 0;
401 
402       /* Don't bother to check rest of spelling.  */
403       while (ISALPHA ((unsigned char) *s))
404 	s++;
405 
406       /* Don't modify LT.  */
407       if (minutesEastOfUTC == 1)
408 	{
409 	  *zone = TM_LOCAL_ZONE;
410 	  return (char *) s;
411 	}
412 
413       z = minutesEastOfUTC * 60L;
414 
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'))
419 	goto trailing_dst;
420       while (ISSPACE ((unsigned char) *s))
421 	s++;
422       if ((s[0] == 'D' || s[0] == 'd')
423 	  && (s[1] == 'S' || s[1] == 's')
424 	  && (s[2] == 'T' || s[2] == 't'))
425 	{
426 	  s += 3;
427 	trailing_dst:
428 	  *zone = z + 60*60;
429 	  return (char *) s;
430 	}
431 
432       switch (*s)
433 	{
434 	case '-':
435 	case '+':
436 	  break;
437 
438 	default:
439 	  *zone = z;
440 	  return (char *) s;
441 	}
442 
443       break;
444     }
445 
446   sign = *s++;
447 
448   if (! (s = parse_ranged (s, 2, 0, 23, &hh)))
449     return 0;
450   mm = ss = 0;
451   if (*s == ':')
452     s++;
453   if (ISDIGIT (*s))
454     {
455       if (! (s = parse_ranged (s, 2, 0, 59, &mm)))
456 	return 0;
457       if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1])
458 	  && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss)))
459 	return 0;
460     }
461   if (ISDIGIT (*s))
462     return 0;
463   offset = (hh * 60 + mm) * 60L + ss;
464   *zone = z + (sign == '-' ? -offset : offset);
465   /* ?? Are fractions allowed here?  If so, they're not implemented.  */
466   return (char *) s;
467 }
468 
469 /* Parse an initial prefix of S, matching the pattern whose code is C.
470    Set *T accordingly.
471    Return the first character after the prefix, or 0 if it wasn't parsed.  */
472 static char const *
parse_pattern_letter(s,c,t)473 parse_pattern_letter (s, c, t)
474      char const *s;
475      int c;
476      struct partime *t;
477 {
478   switch (c)
479     {
480     case '$': /* The next character must be a non-digit.  */
481       if (ISDIGIT (*s))
482 	return 0;
483       break;
484 
485     case '-':
486     case '/':
487     case ':':
488       /* These characters stand for themselves.  */
489       if (*s++ != c)
490 	return 0;
491       break;
492 
493     case '4': /* 4-digit year */
494       s = parse_fixed (s, 4, &t->tm.tm_year);
495       break;
496 
497     case '=': /* optional '-' */
498       s += *s == '-';
499       break;
500 
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".  */
505       switch (*s++)
506 	{
507 	case 'A':
508 	case 'a':
509 	  if (t->tm.tm_hour == 12)
510 	    t->tm.tm_hour = 0;
511 	  break;
512 
513 	case 'P':
514 	case 'p':
515 	  if (t->tm.tm_hour != 12)
516 	    t->tm.tm_hour += 12;
517 	  break;
518 
519 	default:
520 	  return 0;
521 	}
522       switch (*s)
523 	{
524 	case 'M':
525 	case 'm':
526 	  s++;
527 	  break;
528 	}
529       if (ISALNUM ((unsigned char) *s))
530 	return 0;
531       break;
532 
533     case 'D': /* day of month [01-31] */
534       s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
535       break;
536 
537     case 'd': /* day of year [001-366] */
538       s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
539       t->tm.tm_yday--;
540       break;
541 
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,
544 			&t->tm.tm_mday);
545       break;
546 
547     case 'h': /* hour [00-23 followed by optional fraction] */
548       {
549 	int frac;
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;
553       }
554       break;
555 
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);
558       break;
559 
560     case 'n': /* month name [e.g. "Jan"] */
561       if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
562 	return 0;
563       /* Don't bother to check rest of spelling.  */
564       while (ISALPHA ((unsigned char) *s))
565 	s++;
566       break;
567 
568     case 'N': /* month [01-12] */
569       s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
570       t->tm.tm_mon--;
571       break;
572 
573     case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
574       s = parse_fixed (s, 1, &t->tm.tm_year);
575       t->ymodulus = 10;
576       break;
577 
578     case_R:
579     case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
580       s = parse_fixed (s, 2, &t->tm.tm_year);
581       t->ymodulus = 100;
582       break;
583 
584     case 's': /* second [00-60 followed by optional fraction] */
585       {
586 	int frac;
587 	s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
588 	t->tm.tm_sec += frac;
589       }
590       break;
591 
592     case 'T': /* 'T' or 't' */
593       switch (*s++)
594 	{
595 	case 'T':
596 	case 't':
597 	  break;
598 	default:
599 	  return 0;
600 	}
601       break;
602 
603     case 't': /* traditional hour [1-9 or 01-12] */
604       s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
605 			&t->tm.tm_hour);
606       break;
607 
608     case 'w': /* 'W' or 'w' only (stands for current week) */
609       switch (*s++)
610 	{
611 	case 'W':
612 	case 'w':
613 	  break;
614 	default:
615 	  return 0;
616 	}
617       break;
618 
619     case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
620       switch (*s++)
621 	{
622 	case 'W':
623 	case 'w':
624 	  break;
625 	default:
626 	  return 0;
627 	}
628       s = parse_ranged (s, 2, 0, 53, &t->yweek);
629       break;
630 
631     case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
632       s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
633       t->tm.tm_wday--;
634       break;
635 
636     case 'x': /* weekday name [e.g. "Sun"] */
637       if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
638 	return 0;
639       /* Don't bother to check rest of spelling.  */
640       while (ISALPHA ((unsigned char) *s))
641 	s++;
642       break;
643 
644     case 'y': /* either R or Y */
645       if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2]))
646 	goto case_R;
647       /* fall into */
648     case 'Y': /* year in full [4 or more digits] */
649       {
650 	int len = 0;
651 	while (ISDIGIT (s[len]))
652 	  len++;
653 	if (len < 4)
654 	  return 0;
655 	s = parse_fixed (s, len, &t->tm.tm_year);
656       }
657       break;
658 
659     case 'Z': /* time zone */
660       s = parzone (s, &t->zone);
661       break;
662 
663     case '_': /* possibly empty sequence of non-alphanumerics */
664       while (! ISALNUM ((unsigned char) *s) && *s)
665 	s++;
666       break;
667 
668     default: /* bad pattern */
669       return 0;
670     }
671 
672   return s;
673 }
674 
675 /* If there is no conflict, merge into *T the additional information in *U
676    and return 0.  Otherwise do nothing and return -1.  */
677 static int
merge_partime(t,u)678 merge_partime (t, u)
679      struct partime *t;
680      struct partime const *u;
681 {
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))
695     return -1;
696 # undef conflict
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)
707 # undef merge_
708   if (u->zone != TM_UNDEFINED_ZONE)
709     t->zone = u->zone;
710   return 0;
711 }
712 
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.  */
717 char *
partime(s,t)718 partime (s, t)
719      char const *s;
720      struct partime *t;
721 {
722   struct partime p;
723 
724   undefine (t);
725 
726   while (*s)
727     {
728       int i = 0;
729       char const *s1;
730 
731       do
732 	{
733 	  if (! (s1 = parse_prefix (s, &p, &i)))
734 	    return (char *) s;
735 	}
736       while (merge_partime (t, &p) != 0);
737 
738       s = s1;
739     }
740 
741   return (char *) s;
742 }
743