Home | History | Annotate | Line # | Download | only in src
      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 (at) 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
    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
    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 *
    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 *
    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 *
    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 *
    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 *
    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 *
    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
    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 *
    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