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