xref: /dragonfly/gnu/usr.bin/rcs/lib/partime.c (revision 36a3d1d6)
1 /* Parse a string, yielding a struct partime that describes it.  */
2 
3 /* Copyright 1993, 1994, 1995 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 /*
30  * $FreeBSD: src/gnu/usr.bin/rcs/lib/partime.c,v 1.6 1999/08/27 23:36:44 peter Exp $
31  * $DragonFly: src/gnu/usr.bin/rcs/lib/partime.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
32  */
33 
34 #if has_conf_h
35 #	include "conf.h"
36 #else
37 #	ifdef __STDC__
38 #		define P(x) x
39 #	else
40 #		define const
41 #		define P(x) ()
42 #	endif
43 #	include <limits.h>
44 #	include <time.h>
45 #endif
46 
47 #include <ctype.h>
48 #undef isdigit
49 #define isdigit(c) (((unsigned)(c)-'0') <= 9) /* faster than stock */
50 
51 #include "partime.h"
52 
53 /* Lookup tables for names of months, weekdays, time zones.  */
54 
55 #define NAME_LENGTH_MAXIMUM 4
56 
57 struct name_val {
58 	char name[NAME_LENGTH_MAXIMUM];
59 	int val;
60 };
61 
62 
63 static char const *parse_decimal P((char const*,int,int,int,int,int*,int*));
64 static char const *parse_fixed P((char const*,int,int*));
65 static char const *parse_pattern_letter P((char const*,int,struct partime*));
66 static char const *parse_prefix P((char const*,struct partime*,int*));
67 static char const *parse_ranged P((char const*,int,int,int,int*));
68 static int lookup P((char const*,struct name_val const[]));
69 static int merge_partime P((struct partime*, struct partime const*));
70 static void undefine P((struct partime*));
71 
72 
73 static struct name_val const month_names[] = {
74 	{"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5},
75 	{"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11},
76 	{"", TM_UNDEFINED}
77 };
78 
79 static struct name_val const weekday_names[] = {
80 	{"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6},
81 	{"", TM_UNDEFINED}
82 };
83 
84 #define hr60nonnegative(t)  ((t)/100 * 60  +  (t)%100)
85 #define hr60(t)  ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
86 #define zs(t,s)  {s, hr60(t)}
87 #define zd(t,s,d)  zs(t, s),  zs((t)+100, d)
88 
89 static struct name_val const zone_names[] = {
90 	zs(-1000, "hst"),		/* Hawaii */
91 	zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */
92 	zd(- 900,"akst","akdt"),/* Alaska */
93 	zd(- 800, "pst", "pdt"),/* Pacific */
94 	zd(- 700, "mst", "mdt"),/* Mountain */
95 	zd(- 600, "cst", "cdt"),/* Central */
96 	zd(- 500, "est", "edt"),/* Eastern */
97 	zd(- 400, "ast", "adt"),/* Atlantic */
98 	zd(- 330, "nst", "ndt"),/* Newfoundland */
99 	zs(  000, "utc"),		/* Coordinated Universal */
100 	zs(  000, "cut"),		/* " */
101 	zs(  000,  "ut"),		/* Universal */
102 	zs(  000,   "z"),		/* Zulu (required by ISO 8601) */
103 	zd(  000, "gmt", "bst"),/* Greenwich Mean, British Summer */
104 	zs(  000, "wet"),		/* Western Europe */
105 	zs(  100, "met"),		/* Middle Europe */
106 	zs(  100, "cet"),		/* Central Europe */
107 	zs(  200, "eet"),		/* Eastern Europe */
108 	zs(  530, "ist"),		/* India */
109 	zd(  900, "jst", "jdt"),/* Japan */
110 	zd(  900, "kst", "kdt"),/* Korea */
111 	zd( 1200,"nzst","nzdt"),/* New Zealand */
112 	{ "lt", 1 },
113 #if 0
114 	/* The following names are duplicates or are not well attested.  */
115 	zs(-1100, "sst"),		/* Samoa */
116 	zs(-1000, "tht"),		/* Tahiti */
117 	zs(- 930, "mqt"),		/* Marquesas */
118 	zs(- 900, "gbt"),		/* Gambier */
119 	zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */
120 	zs(- 830, "pit"),		/* Pitcairn */
121 	zd(- 500, "cst", "cdt"),/* Cuba */
122 	zd(- 500, "ast", "adt"),/* Acre */
123 	zd(- 400, "wst", "wdt"),/* Western Brazil */
124 	zd(- 400, "ast", "adt"),/* Andes */
125 	zd(- 400, "cst", "cdt"),/* Chile */
126 	zs(- 300, "wgt"),		/* Western Greenland */
127 	zd(- 300, "est", "edt"),/* Eastern South America */
128 	zs(- 300, "mgt"),		/* Middle Greenland */
129 	zd(- 200, "fst", "fdt"),/* Fernando de Noronha */
130 	zs(- 100, "egt"),		/* Eastern Greenland */
131 	zs(- 100, "aat"),		/* Atlantic Africa */
132 	zs(- 100, "act"),		/* Azores and Canaries */
133 	zs(  000, "wat"),		/* West Africa */
134 	zs(  100, "cat"),		/* Central Africa */
135 	zd(  100, "mez","mesz"),/* Mittel-Europaeische Zeit */
136 	zs(  200, "sat"),		/* South Africa */
137 	zd(  200, "ist", "idt"),/* Israel */
138 	zs(  300, "eat"),		/* East Africa */
139 	zd(  300, "ast", "adt"),/* Arabia */
140 	zd(  300, "msk", "msd"),/* Moscow */
141 	zd(  330, "ist", "idt"),/* Iran */
142 	zs(  400, "gst"),		/* Gulf */
143 	zs(  400, "smt"),		/* Seychelles & Mascarene */
144 	zd(  400, "esk", "esd"),/* Yekaterinburg */
145 	zd(  400, "bsk", "bsd"),/* Baku */
146 	zs(  430, "aft"),		/* Afghanistan */
147 	zd(  500, "osk", "osd"),/* Omsk */
148 	zs(  500, "pkt"),		/* Pakistan */
149 	zd(  500, "tsk", "tsd"),/* Tashkent */
150 	zs(  545, "npt"),		/* Nepal */
151 	zs(  600, "bgt"),		/* Bangladesh */
152 	zd(  600, "nsk", "nsd"),/* Novosibirsk */
153 	zs(  630, "bmt"),		/* Burma */
154 	zs(  630, "cct"),		/* Cocos */
155 	zs(  700, "ict"),		/* Indochina */
156 	zs(  700, "jvt"),		/* Java */
157 	zd(  700, "isk", "isd"),/* Irkutsk */
158 	zs(  800, "hkt"),		/* Hong Kong */
159 	zs(  800, "pst"),		/* Philippines */
160 	zs(  800, "sgt"),		/* Singapore */
161 	zd(  800, "cst", "cdt"),/* China */
162 	zd(  800, "ust", "udt"),/* Ulan Bator */
163 	zd(  800, "wst", "wst"),/* Western Australia */
164 	zd(  800, "ysk", "ysd"),/* Yakutsk */
165 	zs(  900, "blt"),		/* Belau */
166 	zs(  900, "mlt"),		/* Moluccas */
167 	zd(  900, "vsk", "vsd"),/* Vladivostok */
168 	zd(  930, "cst", "cst"),/* Central Australia */
169 	zs( 1000, "gst"),		/* Guam */
170 	zd( 1000, "gsk", "gsd"),/* Magadan */
171 	zd( 1000, "est", "est"),/* Eastern Australia */
172 	zd( 1100,"lhst","lhst"),/* Lord Howe */
173 	zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */
174 	zs( 1100,"ncst"),		/* New Caledonia */
175 	zs( 1130,"nrft"),		/* Norfolk */
176 	zd( 1200, "ask", "asd"),/* Anadyr */
177 	zs( 1245,"nz-chat"),	/* Chatham */
178 	zs( 1300, "tgt"),		/* Tongatapu */
179 #endif
180 	{"", -1}
181 };
182 
183 	static int
184 lookup (s, table)
185 	char const *s;
186 	struct name_val const table[];
187 /* Look for a prefix of S in TABLE, returning val for first matching entry.  */
188 {
189 	int j;
190 	char buf[NAME_LENGTH_MAXIMUM];
191 
192 	for (j = 0;  j < NAME_LENGTH_MAXIMUM;  j++) {
193 		unsigned char c = *s++;
194 		buf[j] = isupper (c) ? tolower (c) : c;
195 		if (!isalpha (c))
196 			break;
197 	}
198 	for (;  table[0].name[0];  table++)
199 		for (j = 0;  buf[j] == table[0].name[j];  )
200 			if (++j == NAME_LENGTH_MAXIMUM  ||  !table[0].name[j])
201 				goto done;
202   done:
203 	return table[0].val;
204 }
205 
206 
207 	static void
208 undefine (t) struct partime *t;
209 /* Set *T to ``undefined'' values.  */
210 {
211 	t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
212 		= t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
213 		= t->ymodulus = t->yweek
214 		= TM_UNDEFINED;
215 	t->zone = TM_UNDEFINED_ZONE;
216 }
217 
218 /*
219 * Array of patterns to look for in a date string.
220 * Order is important: we look for the first matching pattern
221 * whose values do not contradict values that we already know about.
222 * See `parse_pattern_letter' below for the meaning of the pattern codes.
223 */
224 static char const * const patterns[] = {
225 	/*
226 	* These traditional patterns must come first,
227 	* to prevent an ISO 8601 format from misinterpreting their prefixes.
228 	*/
229 	"E_n_y", "x", /* RFC 822 */
230 	"E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
231 	"y/N/D$", /* traditional RCS */
232 
233 	/* ISO 8601:1988 formats, generalized a bit.  */
234 	"y-N-D$", "4ND$", "Y-N$",
235 	"RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
236 	"--N$", "---D$", "DT",
237 	"Y-d$", "4d$", "R=d$", "-d$", "dT",
238 	"y-W-X", "yWX", "y=W",
239 	"-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
240 	"-w-X", "w-XT", "---X$", "XT", "4$",
241 	"T",
242 	"h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
243 	"Y", "Z",
244 
245 	0
246 };
247 
248 	static char const *
249 parse_prefix (str, t, pi) char const *str; struct partime *t; int *pi;
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 */
257 {
258 	int i = *pi;
259 	char const *pat;
260 	unsigned char c;
261 
262 	if (i < 0)
263 		return 0;
264 
265 	/* Remove initial noise.  */
266 	while (!isalnum (c = *str)  &&  c != '-'  &&  c != '+') {
267 		if (!c) {
268 			undefine (t);
269 			*pi = -1;
270 			return str;
271 		}
272 		str++;
273 	}
274 
275 	/* Try a pattern until one succeeds.  */
276 	while ((pat = patterns[i++]) != 0) {
277 		char const *s = str;
278 		undefine (t);
279 		do {
280 			if (!(c = *pat++)) {
281 				*pi = i;
282 				return s;
283 			}
284 		} while ((s = parse_pattern_letter (s, c, t)) != 0);
285 	}
286 
287 	return 0;
288 }
289 
290 	static char const *
291 parse_fixed (s, digits, res) char const *s; int digits, *res;
292 /*
293 * Parse an initial prefix of S of length DIGITS; it must be a number.
294 * Store the parsed number into *RES.
295 * Return the first character after the prefix, or 0 if it couldn't be parsed.
296 */
297 {
298 	int n = 0;
299 	char const *lim = s + digits;
300 	while (s < lim) {
301 		unsigned d = *s++ - '0';
302 		if (9 < d)
303 			return 0;
304 		n = 10*n + d;
305 	}
306 	*res = n;
307 	return s;
308 }
309 
310 	static char const *
311 parse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res;
312 /*
313 * Parse an initial prefix of S of length DIGITS;
314 * it must be a number in the range LO through HI.
315 * Store the parsed number into *RES.
316 * Return the first character after the prefix, or 0 if it couldn't be parsed.
317 */
318 {
319 	s = parse_fixed (s, digits, res);
320 	return  s && lo<=*res && *res<=hi  ?  s  :  0;
321 }
322 
323 	static char const *
324 parse_decimal (s, digits, lo, hi, resolution, res, fres)
325 	char const *s;
326 	int digits, lo, hi, resolution, *res, *fres;
327 /*
328 * Parse an initial prefix of S of length DIGITS;
329 * it must be a number in the range LO through HI
330 * and it may be followed by a fraction that is to be computed using RESOLUTION.
331 * Store the parsed number into *RES; store the fraction times RESOLUTION,
332 * rounded to the nearest integer, into *FRES.
333 * Return the first character after the prefix, or 0 if it couldn't be parsed.
334 */
335 {
336 	s = parse_fixed (s, digits, res);
337 	if (s && lo<=*res && *res<=hi) {
338 		int f = 0;
339 		if ((s[0]==',' || s[0]=='.')  &&  isdigit ((unsigned char) s[1])) {
340 			char const *s1 = ++s;
341 			int num10 = 0, denom10 = 10, product;
342 			while (isdigit ((unsigned char) *++s))
343 				denom10 *= 10;
344 			s = parse_fixed (s1, s - s1, &num10);
345 			product = num10*resolution;
346 			f = (product + (denom10>>1)) / denom10;
347 			f -= f & (product%denom10 == denom10>>1); /* round to even */
348 			if (f < 0  ||  product/resolution != num10)
349 				return 0; /* overflow */
350 		}
351 		*fres = f;
352 		return s;
353 	}
354 	return 0;
355 }
356 
357 	char *
358 parzone (s, zone) char const *s; long *zone;
359 /*
360 * Parse an initial prefix of S; it must denote a time zone.
361 * Set *ZONE to the number of seconds east of GMT,
362 * or to TM_LOCAL_ZONE if it is the local time zone.
363 * Return the first character after the prefix, or 0 if it couldn't be parsed.
364 */
365 {
366 	char sign;
367 	int hh, mm, ss;
368 	int minutesEastOfUTC;
369 	long offset, z;
370 
371 	/*
372 	* The formats are LT, n, n DST, nDST, no, o
373 	* where n is a time zone name
374 	* and o is a time zone offset of the form [-+]hh[:mm[:ss]].
375 	*/
376 	switch (*s) {
377 		case '-': case '+':
378 			z = 0;
379 			break;
380 
381 		default:
382 			minutesEastOfUTC = lookup (s, zone_names);
383 			if (minutesEastOfUTC == -1)
384 				return 0;
385 
386 			/* Don't bother to check rest of spelling.  */
387 			while (isalpha ((unsigned char) *s))
388 				s++;
389 
390 			/* Don't modify LT.  */
391 			if (minutesEastOfUTC == 1) {
392 				*zone = TM_LOCAL_ZONE;
393 				return (char *) s;
394 			}
395 
396 			z = minutesEastOfUTC * 60L;
397 
398 			/* Look for trailing " DST".  */
399 			if (
400 				(s[-1]=='T' || s[-1]=='t') &&
401 				(s[-2]=='S' || s[-2]=='s') &&
402 				(s[-3]=='D' || s[-3]=='t')
403 			)
404 				goto trailing_dst;
405 			while (isspace ((unsigned char) *s))
406 				s++;
407 			if (
408 				(s[0]=='D' || s[0]=='d') &&
409 				(s[1]=='S' || s[1]=='s') &&
410 				(s[2]=='T' || s[2]=='t')
411 			) {
412 				s += 3;
413 			  trailing_dst:
414 				*zone = z + 60*60;
415 				return (char *) s;
416 			}
417 
418 			switch (*s) {
419 				case '-': case '+': break;
420 				default: return (char *) s;
421 			}
422 	}
423 	sign = *s++;
424 
425 	if (!(s = parse_ranged (s, 2, 0, 23, &hh)))
426 		return 0;
427 	mm = ss = 0;
428 	if (*s == ':')
429 		s++;
430 	if (isdigit ((unsigned char) *s)) {
431 		if (!(s = parse_ranged (s, 2, 0, 59, &mm)))
432 			return 0;
433 		if (*s==':' && s[-3]==':' && isdigit ((unsigned char) s[1])) {
434 			if (!(s = parse_ranged (s + 1, 2, 0, 59, &ss)))
435 				return 0;
436 		}
437 	}
438 	if (isdigit ((unsigned char) *s))
439 		return 0;
440 	offset = (hh*60 + mm)*60L + ss;
441 	*zone = z + (sign=='-' ? -offset : offset);
442 	/*
443 	* ?? Are fractions allowed here?
444 	* If so, they're not implemented.
445 	*/
446 	return (char *) s;
447 }
448 
449 	static char const *
450 parse_pattern_letter (s, c, t) char const *s; int c; struct partime *t;
451 /*
452 * Parse an initial prefix of S, matching the pattern whose code is C.
453 * Set *T accordingly.
454 * Return the first character after the prefix, or 0 if it couldn't be parsed.
455 */
456 {
457 	switch (c) {
458 		case '$': /* The next character must be a non-digit.  */
459 			if (isdigit ((unsigned char) *s))
460 				return 0;
461 			break;
462 
463 		case '-': case '/': case ':':
464 			/* These characters stand for themselves.  */
465 			if (*s++ != c)
466 				return 0;
467 			break;
468 
469 		case '4': /* 4-digit year */
470 			s = parse_fixed (s, 4, &t->tm.tm_year);
471 			break;
472 
473 		case '=': /* optional '-' */
474 			s  +=  *s == '-';
475 			break;
476 
477 		case 'A': /* AM or PM */
478 			/*
479 			* This matches the regular expression [AaPp][Mm]?.
480 			* It must not be followed by a letter or digit;
481 			* otherwise it would match prefixes of strings like "PST".
482 			*/
483 			switch (*s++) {
484 				case 'A': case 'a':
485 					if (t->tm.tm_hour == 12)
486 						t->tm.tm_hour = 0;
487 					break;
488 
489 				case 'P': case 'p':
490 					if (t->tm.tm_hour != 12)
491 						t->tm.tm_hour += 12;
492 					break;
493 
494 				default: return 0;
495 			}
496 			switch (*s) {
497 				case 'M': case 'm': s++; break;
498 			}
499 			if (isalnum (*s))
500 				return 0;
501 			break;
502 
503 		case 'D': /* day of month [01-31] */
504 			s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
505 			break;
506 
507 		case 'd': /* day of year [001-366] */
508 			s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
509 			t->tm.tm_yday--;
510 			break;
511 
512 		case 'E': /* extended day of month [1-9, 01-31] */
513 			s = parse_ranged (s, (
514 				isdigit ((unsigned char) s[0]) &&
515 				isdigit ((unsigned char) s[1])
516 			) + 1, 1, 31, &t->tm.tm_mday);
517 			break;
518 
519 		case 'h': /* hour [00-23 followed by optional fraction] */
520 			{
521 				int frac;
522 				s = parse_decimal (s, 2, 0, 23, 60*60, &t->tm.tm_hour, &frac);
523 				t->tm.tm_min = frac / 60;
524 				t->tm.tm_sec = frac % 60;
525 			}
526 			break;
527 
528 		case 'm': /* minute [00-59 followed by optional fraction] */
529 			s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
530 			break;
531 
532 		case 'n': /* month name [e.g. "Jan"] */
533 			if (!TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
534 				return 0;
535 			/* Don't bother to check rest of spelling.  */
536 			while (isalpha ((unsigned char) *s))
537 				s++;
538 			break;
539 
540 		case 'N': /* month [01-12] */
541 			s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
542 			t->tm.tm_mon--;
543 			break;
544 
545 		case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
546 			s = parse_fixed (s, 1, &t->tm.tm_year);
547 			t->ymodulus = 10;
548 			break;
549 
550 		case_R:
551 		case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
552 			s = parse_fixed (s, 2, &t->tm.tm_year);
553 			t->ymodulus = 100;
554 			break;
555 
556 		case 's': /* second [00-60 followed by optional fraction] */
557 			{
558 				int frac;
559 				s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
560 				t->tm.tm_sec += frac;
561 			}
562 			break;
563 
564 		case 'T': /* 'T' or 't' */
565 			switch (*s++) {
566 				case 'T': case 't': break;
567 				default: return 0;
568 			}
569 			break;
570 
571 		case 't': /* traditional hour [1-9 or 01-12] */
572 			s = parse_ranged (s, (
573 				isdigit ((unsigned char) s[0]) && isdigit ((unsigned char) s[1])
574 			) + 1, 1, 12, &t->tm.tm_hour);
575 			break;
576 
577 		case 'w': /* 'W' or 'w' only (stands for current week) */
578 			switch (*s++) {
579 				case 'W': case 'w': break;
580 				default: return 0;
581 			}
582 			break;
583 
584 		case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
585 			switch (*s++) {
586 				case 'W': case 'w': break;
587 				default: return 0;
588 			}
589 			s = parse_ranged (s, 2, 0, 53, &t->yweek);
590 			break;
591 
592 		case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
593 			s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
594 			t->tm.tm_wday--;
595 			break;
596 
597 		case 'x': /* weekday name [e.g. "Sun"] */
598 			if (!TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
599 				return 0;
600 			/* Don't bother to check rest of spelling.  */
601 			while (isalpha ((unsigned char) *s))
602 				s++;
603 			break;
604 
605 		case 'y': /* either R or Y */
606 			if (
607 				isdigit ((unsigned char) s[0]) &&
608 				isdigit ((unsigned char) s[1]) &&
609 				!isdigit ((unsigned char) s[2])
610 			)
611 				goto case_R;
612 			/* fall into */
613 		case 'Y': /* year in full [4 or more digits] */
614 			{
615 				int len = 0;
616 				while (isdigit ((unsigned char) s[len]))
617 					len++;
618 				if (len < 4)
619 					return 0;
620 				s = parse_fixed (s, len, &t->tm.tm_year);
621 			}
622 			break;
623 
624 		case 'Z': /* time zone */
625 			s = parzone (s, &t->zone);
626 			break;
627 
628 		case '_': /* possibly empty sequence of non-alphanumerics */
629 			while (!isalnum (*s)  &&  *s)
630 				s++;
631 			break;
632 
633 		default: /* bad pattern */
634 			return 0;
635 	}
636 	return s;
637 }
638 
639 	static int
640 merge_partime (t, u) struct partime *t; struct partime const *u;
641 /*
642 * If there is no conflict, merge into *T the additional information in *U
643 * and return 0.  Otherwise do nothing and return -1.
644 */
645 {
646 #	define conflict(a,b) ((a) != (b)  &&  TM_DEFINED (a)  &&  TM_DEFINED (b))
647 	if (
648 		conflict (t->tm.tm_sec, u->tm.tm_sec) ||
649 		conflict (t->tm.tm_min, u->tm.tm_min) ||
650 		conflict (t->tm.tm_hour, u->tm.tm_hour) ||
651 		conflict (t->tm.tm_mday, u->tm.tm_mday) ||
652 		conflict (t->tm.tm_mon, u->tm.tm_mon) ||
653 		conflict (t->tm.tm_year, u->tm.tm_year) ||
654 		conflict (t->tm.tm_wday, u->tm.tm_yday) ||
655 		conflict (t->ymodulus, u->ymodulus) ||
656 		conflict (t->yweek, u->yweek) ||
657 		(
658 			t->zone != u->zone &&
659 			t->zone != TM_UNDEFINED_ZONE &&
660 			u->zone != TM_UNDEFINED_ZONE
661 		)
662 	)
663 		return -1;
664 #	undef conflict
665 #	define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
666 	merge_ (t->tm.tm_sec, u->tm.tm_sec)
667 	merge_ (t->tm.tm_min, u->tm.tm_min)
668 	merge_ (t->tm.tm_hour, u->tm.tm_hour)
669 	merge_ (t->tm.tm_mday, u->tm.tm_mday)
670 	merge_ (t->tm.tm_mon, u->tm.tm_mon)
671 	merge_ (t->tm.tm_year, u->tm.tm_year)
672 	merge_ (t->tm.tm_wday, u->tm.tm_yday)
673 	merge_ (t->ymodulus, u->ymodulus)
674 	merge_ (t->yweek, u->yweek)
675 #	undef merge_
676 	if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone;
677 	return 0;
678 }
679 
680 	char *
681 partime (s, t) char const *s; struct partime *t;
682 /*
683 * Parse a date/time prefix of S, putting the parsed result into *T.
684 * Return the first character after the prefix.
685 * The prefix may contain no useful information;
686 * in that case, *T will contain only undefined values.
687 */
688 {
689 	struct partime p;
690 
691 	undefine (t);
692 	while (*s) {
693 		int i = 0;
694 		char const *s1;
695 		do {
696 			if (!(s1 = parse_prefix (s, &p, &i)))
697 				return (char *) s;
698 		} while (merge_partime (t, &p) != 0);
699 		s = s1;
700 	}
701 	return (char *) s;
702 }
703