111894Speter/* Parse a string, yielding a struct partime that describes it.  */
29Sjkh
311894Speter/* Copyright 1993, 1994, 1995 Paul Eggert
411894Speter   Distributed under license by the Free Software Foundation, Inc.
59Sjkh
611894SpeterThis file is part of RCS.
79Sjkh
811894SpeterRCS is free software; you can redistribute it and/or modify
911894Speterit under the terms of the GNU General Public License as published by
1011894Speterthe Free Software Foundation; either version 2, or (at your option)
1111894Speterany later version.
129Sjkh
1311894SpeterRCS is distributed in the hope that it will be useful,
1411894Speterbut WITHOUT ANY WARRANTY; without even the implied warranty of
1511894SpeterMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1611894SpeterGNU General Public License for more details.
179Sjkh
1811894SpeterYou should have received a copy of the GNU General Public License
1911894Speteralong with RCS; see the file COPYING.
2011894SpeterIf not, write to the Free Software Foundation,
2111894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
229Sjkh
2311894SpeterReport problems and direct all questions to:
249Sjkh
2511894Speter    rcs-bugs@cs.purdue.edu
269Sjkh
2711894Speter*/
289Sjkh
2911894Speter#if has_conf_h
3011894Speter#	include "conf.h"
3111894Speter#else
3211894Speter#	ifdef __STDC__
3311894Speter#		define P(x) x
3411894Speter#	else
3511894Speter#		define const
3611894Speter#		define P(x) ()
3711894Speter#	endif
3811894Speter#	include <limits.h>
3911894Speter#	include <time.h>
4011894Speter#endif
419Sjkh
4211894Speter#include <ctype.h>
4311894Speter#undef isdigit
4411894Speter#define isdigit(c) (((unsigned)(c)-'0') <= 9) /* faster than stock */
459Sjkh
4611894Speter#include "partime.h"
479Sjkh
4811894Speterchar const partimeId[]
4950472Speter  = "$FreeBSD$";
509Sjkh
519Sjkh
5211894Speter/* Lookup tables for names of months, weekdays, time zones.  */
539Sjkh
5411894Speter#define NAME_LENGTH_MAXIMUM 4
559Sjkh
5611894Speterstruct name_val {
5711894Speter	char name[NAME_LENGTH_MAXIMUM];
5811894Speter	int val;
599Sjkh};
609Sjkh
6111894Speter
6211894Speterstatic char const *parse_decimal P((char const*,int,int,int,int,int*,int*));
6311894Speterstatic char const *parse_fixed P((char const*,int,int*));
6411894Speterstatic char const *parse_pattern_letter P((char const*,int,struct partime*));
6511894Speterstatic char const *parse_prefix P((char const*,struct partime*,int*));
6611894Speterstatic char const *parse_ranged P((char const*,int,int,int,int*));
6711894Speterstatic int lookup P((char const*,struct name_val const[]));
6811894Speterstatic int merge_partime P((struct partime*, struct partime const*));
6911894Speterstatic void undefine P((struct partime*));
7011894Speter
7111894Speter
7211894Speterstatic struct name_val const month_names[] = {
7311894Speter	{"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5},
7411894Speter	{"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11},
7511894Speter	{"", TM_UNDEFINED}
769Sjkh};
779Sjkh
7811894Speterstatic struct name_val const weekday_names[] = {
7911894Speter	{"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6},
8011894Speter	{"", TM_UNDEFINED}
8111894Speter};
829Sjkh
8311894Speter#define hr60nonnegative(t)  ((t)/100 * 60  +  (t)%100)
8411894Speter#define hr60(t)  ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
8511894Speter#define zs(t,s)  {s, hr60(t)}
8611894Speter#define zd(t,s,d)  zs(t, s),  zs((t)+100, d)
8711894Speter
8811894Speterstatic struct name_val const zone_names[] = {
8911894Speter	zs(-1000, "hst"),		/* Hawaii */
9011894Speter	zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */
9111894Speter	zd(- 900,"akst","akdt"),/* Alaska */
9211894Speter	zd(- 800, "pst", "pdt"),/* Pacific */
9311894Speter	zd(- 700, "mst", "mdt"),/* Mountain */
9411894Speter	zd(- 600, "cst", "cdt"),/* Central */
9511894Speter	zd(- 500, "est", "edt"),/* Eastern */
9611894Speter	zd(- 400, "ast", "adt"),/* Atlantic */
9711894Speter	zd(- 330, "nst", "ndt"),/* Newfoundland */
9811894Speter	zs(  000, "utc"),		/* Coordinated Universal */
9911894Speter	zs(  000, "cut"),		/* " */
10011894Speter	zs(  000,  "ut"),		/* Universal */
10111894Speter	zs(  000,   "z"),		/* Zulu (required by ISO 8601) */
10211894Speter	zd(  000, "gmt", "bst"),/* Greenwich Mean, British Summer */
10311894Speter	zs(  000, "wet"),		/* Western Europe */
10411894Speter	zs(  100, "met"),		/* Middle Europe */
10511894Speter	zs(  100, "cet"),		/* Central Europe */
10611894Speter	zs(  200, "eet"),		/* Eastern Europe */
10711894Speter	zs(  530, "ist"),		/* India */
10811894Speter	zd(  900, "jst", "jdt"),/* Japan */
10911894Speter	zd(  900, "kst", "kdt"),/* Korea */
11011894Speter	zd( 1200,"nzst","nzdt"),/* New Zealand */
11111894Speter	{ "lt", 1 },
11211894Speter#if 0
11311894Speter	/* The following names are duplicates or are not well attested.  */
11411894Speter	zs(-1100, "sst"),		/* Samoa */
11511894Speter	zs(-1000, "tht"),		/* Tahiti */
11611894Speter	zs(- 930, "mqt"),		/* Marquesas */
11711894Speter	zs(- 900, "gbt"),		/* Gambier */
11811894Speter	zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */
11911894Speter	zs(- 830, "pit"),		/* Pitcairn */
12011894Speter	zd(- 500, "cst", "cdt"),/* Cuba */
12111894Speter	zd(- 500, "ast", "adt"),/* Acre */
12211894Speter	zd(- 400, "wst", "wdt"),/* Western Brazil */
12311894Speter	zd(- 400, "ast", "adt"),/* Andes */
12411894Speter	zd(- 400, "cst", "cdt"),/* Chile */
12511894Speter	zs(- 300, "wgt"),		/* Western Greenland */
12611894Speter	zd(- 300, "est", "edt"),/* Eastern South America */
12711894Speter	zs(- 300, "mgt"),		/* Middle Greenland */
12811894Speter	zd(- 200, "fst", "fdt"),/* Fernando de Noronha */
12911894Speter	zs(- 100, "egt"),		/* Eastern Greenland */
13011894Speter	zs(- 100, "aat"),		/* Atlantic Africa */
13111894Speter	zs(- 100, "act"),		/* Azores and Canaries */
13211894Speter	zs(  000, "wat"),		/* West Africa */
13311894Speter	zs(  100, "cat"),		/* Central Africa */
13411894Speter	zd(  100, "mez","mesz"),/* Mittel-Europaeische Zeit */
13511894Speter	zs(  200, "sat"),		/* South Africa */
13611894Speter	zd(  200, "ist", "idt"),/* Israel */
13711894Speter	zs(  300, "eat"),		/* East Africa */
13811894Speter	zd(  300, "ast", "adt"),/* Arabia */
13911894Speter	zd(  300, "msk", "msd"),/* Moscow */
14011894Speter	zd(  330, "ist", "idt"),/* Iran */
14111894Speter	zs(  400, "gst"),		/* Gulf */
14211894Speter	zs(  400, "smt"),		/* Seychelles & Mascarene */
14311894Speter	zd(  400, "esk", "esd"),/* Yekaterinburg */
14411894Speter	zd(  400, "bsk", "bsd"),/* Baku */
14511894Speter	zs(  430, "aft"),		/* Afghanistan */
14611894Speter	zd(  500, "osk", "osd"),/* Omsk */
14711894Speter	zs(  500, "pkt"),		/* Pakistan */
14811894Speter	zd(  500, "tsk", "tsd"),/* Tashkent */
14911894Speter	zs(  545, "npt"),		/* Nepal */
15011894Speter	zs(  600, "bgt"),		/* Bangladesh */
15111894Speter	zd(  600, "nsk", "nsd"),/* Novosibirsk */
15211894Speter	zs(  630, "bmt"),		/* Burma */
15311894Speter	zs(  630, "cct"),		/* Cocos */
15411894Speter	zs(  700, "ict"),		/* Indochina */
15511894Speter	zs(  700, "jvt"),		/* Java */
15611894Speter	zd(  700, "isk", "isd"),/* Irkutsk */
15711894Speter	zs(  800, "hkt"),		/* Hong Kong */
15811894Speter	zs(  800, "pst"),		/* Philippines */
15911894Speter	zs(  800, "sgt"),		/* Singapore */
16011894Speter	zd(  800, "cst", "cdt"),/* China */
16111894Speter	zd(  800, "ust", "udt"),/* Ulan Bator */
16211894Speter	zd(  800, "wst", "wst"),/* Western Australia */
16311894Speter	zd(  800, "ysk", "ysd"),/* Yakutsk */
16411894Speter	zs(  900, "blt"),		/* Belau */
16511894Speter	zs(  900, "mlt"),		/* Moluccas */
16611894Speter	zd(  900, "vsk", "vsd"),/* Vladivostok */
16711894Speter	zd(  930, "cst", "cst"),/* Central Australia */
16811894Speter	zs( 1000, "gst"),		/* Guam */
16911894Speter	zd( 1000, "gsk", "gsd"),/* Magadan */
17011894Speter	zd( 1000, "est", "est"),/* Eastern Australia */
17111894Speter	zd( 1100,"lhst","lhst"),/* Lord Howe */
17211894Speter	zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */
17311894Speter	zs( 1100,"ncst"),		/* New Caledonia */
17411894Speter	zs( 1130,"nrft"),		/* Norfolk */
17511894Speter	zd( 1200, "ask", "asd"),/* Anadyr */
17611894Speter	zs( 1245,"nz-chat"),	/* Chatham */
17711894Speter	zs( 1300, "tgt"),		/* Tongatapu */
17811894Speter#endif
17911894Speter	{"", -1}
18011894Speter};
18111894Speter
1829Sjkh	static int
18311894Speterlookup (s, table)
18411894Speter	char const *s;
18511894Speter	struct name_val const table[];
18611894Speter/* Look for a prefix of S in TABLE, returning val for first matching entry.  */
1879Sjkh{
18811894Speter	int j;
18911894Speter	char buf[NAME_LENGTH_MAXIMUM];
19011894Speter
19111894Speter	for (j = 0;  j < NAME_LENGTH_MAXIMUM;  j++) {
19211894Speter		unsigned char c = *s++;
19311894Speter		buf[j] = isupper (c) ? tolower (c) : c;
19411894Speter		if (!isalpha (c))
19511894Speter			break;
1969Sjkh	}
19711894Speter	for (;  table[0].name[0];  table++)
19811894Speter		for (j = 0;  buf[j] == table[0].name[j];  )
19911894Speter			if (++j == NAME_LENGTH_MAXIMUM  ||  !table[0].name[j])
20011894Speter				goto done;
20111894Speter  done:
20211894Speter	return table[0].val;
20311894Speter}
20411894Speter
20511894Speter
20611894Speter	static void
20711894Speterundefine (t) struct partime *t;
20811894Speter/* Set *T to ``undefined'' values.  */
20911894Speter{
21011894Speter	t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
21111894Speter		= t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
21211894Speter		= t->ymodulus = t->yweek
21311894Speter		= TM_UNDEFINED;
21411894Speter	t->zone = TM_UNDEFINED_ZONE;
21511894Speter}
21611894Speter
21711894Speter/*
21811894Speter* Array of patterns to look for in a date string.
21911894Speter* Order is important: we look for the first matching pattern
22011894Speter* whose values do not contradict values that we already know about.
22111894Speter* See `parse_pattern_letter' below for the meaning of the pattern codes.
22211894Speter*/
22311894Speterstatic char const * const patterns[] = {
22411894Speter	/*
22511894Speter	* These traditional patterns must come first,
22611894Speter	* to prevent an ISO 8601 format from misinterpreting their prefixes.
22711894Speter	*/
22811894Speter	"E_n_y", "x", /* RFC 822 */
22911894Speter	"E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
23011894Speter	"y/N/D$", /* traditional RCS */
23111894Speter
23211894Speter	/* ISO 8601:1988 formats, generalized a bit.  */
23311894Speter	"y-N-D$", "4ND$", "Y-N$",
23411894Speter	"RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
23511894Speter	"--N$", "---D$", "DT",
23611894Speter	"Y-d$", "4d$", "R=d$", "-d$", "dT",
23711894Speter	"y-W-X", "yWX", "y=W",
23811894Speter	"-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
23911894Speter	"-w-X", "w-XT", "---X$", "XT", "4$",
24011894Speter	"T",
24111894Speter	"h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
24211894Speter	"Y", "Z",
24311894Speter
24411894Speter	0
24511894Speter};
24611894Speter
24711894Speter	static char const *
24811894Speterparse_prefix (str, t, pi) char const *str; struct partime *t; int *pi;
24911894Speter/*
25011894Speter* Parse an initial prefix of STR, setting *T accordingly.
25111894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
25211894Speter* Start with pattern *PI; if success, set *PI to the next pattern to try.
25311894Speter* Set *PI to -1 if we know there are no more patterns to try;
25411894Speter* if *PI is initially negative, give up immediately.
25511894Speter*/
25611894Speter{
25711894Speter	int i = *pi;
25811894Speter	char const *pat;
25911894Speter	unsigned char c;
26011894Speter
26111894Speter	if (i < 0)
26211894Speter		return 0;
26311894Speter
26411894Speter	/* Remove initial noise.  */
26511894Speter	while (!isalnum (c = *str)  &&  c != '-'  &&  c != '+') {
26611894Speter		if (!c) {
26711894Speter			undefine (t);
26811894Speter			*pi = -1;
26911894Speter			return str;
27011894Speter		}
27111894Speter		str++;
27211894Speter	}
27311894Speter
27411894Speter	/* Try a pattern until one succeeds.  */
27511894Speter	while ((pat = patterns[i++]) != 0) {
27611894Speter		char const *s = str;
27711894Speter		undefine (t);
27811894Speter		do {
27911894Speter			if (!(c = *pat++)) {
28011894Speter				*pi = i;
28111894Speter				return s;
28211894Speter			}
28311894Speter		} while ((s = parse_pattern_letter (s, c, t)) != 0);
28411894Speter	}
28511894Speter
2869Sjkh	return 0;
2879Sjkh}
2889Sjkh
28911894Speter	static char const *
29011894Speterparse_fixed (s, digits, res) char const *s; int digits, *res;
29111894Speter/*
29211894Speter* Parse an initial prefix of S of length DIGITS; it must be a number.
29311894Speter* Store the parsed number into *RES.
29411894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
29511894Speter*/
2969Sjkh{
29711894Speter	int n = 0;
29811894Speter	char const *lim = s + digits;
29911894Speter	while (s < lim) {
30011894Speter		unsigned d = *s++ - '0';
30111894Speter		if (9 < d)
30211894Speter			return 0;
30311894Speter		n = 10*n + d;
30411894Speter	}
30511894Speter	*res = n;
30611894Speter	return s;
30711894Speter}
3089Sjkh
30911894Speter	static char const *
31011894Speterparse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res;
31111894Speter/*
31211894Speter* Parse an initial prefix of S of length DIGITS;
31311894Speter* it must be a number in the range LO through HI.
31411894Speter* Store the parsed number into *RES.
31511894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
31611894Speter*/
31711894Speter{
31811894Speter	s = parse_fixed (s, digits, res);
31911894Speter	return  s && lo<=*res && *res<=hi  ?  s  :  0;
32011894Speter}
3219Sjkh
32211894Speter	static char const *
32311894Speterparse_decimal (s, digits, lo, hi, resolution, res, fres)
32411894Speter	char const *s;
32511894Speter	int digits, lo, hi, resolution, *res, *fres;
32611894Speter/*
32711894Speter* Parse an initial prefix of S of length DIGITS;
32811894Speter* it must be a number in the range LO through HI
32911894Speter* and it may be followed by a fraction that is to be computed using RESOLUTION.
33011894Speter* Store the parsed number into *RES; store the fraction times RESOLUTION,
33111894Speter* rounded to the nearest integer, into *FRES.
33211894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
33311894Speter*/
33411894Speter{
33511894Speter	s = parse_fixed (s, digits, res);
33611894Speter	if (s && lo<=*res && *res<=hi) {
33711894Speter		int f = 0;
33811894Speter		if ((s[0]==',' || s[0]=='.')  &&  isdigit ((unsigned char) s[1])) {
33911894Speter			char const *s1 = ++s;
34011894Speter			int num10 = 0, denom10 = 10, product;
34111894Speter			while (isdigit ((unsigned char) *++s))
34211894Speter				denom10 *= 10;
34311894Speter			s = parse_fixed (s1, s - s1, &num10);
34411894Speter			product = num10*resolution;
34511894Speter			f = (product + (denom10>>1)) / denom10;
34611894Speter			f -= f & (product%denom10 == denom10>>1); /* round to even */
34711894Speter			if (f < 0  ||  product/resolution != num10)
34811894Speter				return 0; /* overflow */
34911894Speter		}
35011894Speter		*fres = f;
35111894Speter		return s;
35211894Speter	}
35311894Speter	return 0;
35411894Speter}
35511894Speter
35611894Speter	char *
35711894Speterparzone (s, zone) char const *s; long *zone;
35811894Speter/*
35911894Speter* Parse an initial prefix of S; it must denote a time zone.
36011894Speter* Set *ZONE to the number of seconds east of GMT,
36111894Speter* or to TM_LOCAL_ZONE if it is the local time zone.
36211894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
36311894Speter*/
36411894Speter{
36511894Speter	char sign;
36611894Speter	int hh, mm, ss;
36711894Speter	int minutesEastOfUTC;
36811894Speter	long offset, z;
36911894Speter
37011894Speter	/*
37111894Speter	* The formats are LT, n, n DST, nDST, no, o
37211894Speter	* where n is a time zone name
37311894Speter	* and o is a time zone offset of the form [-+]hh[:mm[:ss]].
37411894Speter	*/
37511894Speter	switch (*s) {
37611894Speter		case '-': case '+':
37711894Speter			z = 0;
3789Sjkh			break;
37911894Speter
38011894Speter		default:
38111894Speter			minutesEastOfUTC = lookup (s, zone_names);
38211894Speter			if (minutesEastOfUTC == -1)
3839Sjkh				return 0;
38411894Speter
38511894Speter			/* Don't bother to check rest of spelling.  */
38611894Speter			while (isalpha ((unsigned char) *s))
38711894Speter				s++;
38811894Speter
38911894Speter			/* Don't modify LT.  */
39011894Speter			if (minutesEastOfUTC == 1) {
39111894Speter				*zone = TM_LOCAL_ZONE;
39211894Speter				return (char *) s;
39311894Speter			}
39411894Speter
39511894Speter			z = minutesEastOfUTC * 60L;
39611894Speter
39711894Speter			/* Look for trailing " DST".  */
3989Sjkh			if (
39911894Speter				(s[-1]=='T' || s[-1]=='t') &&
40011894Speter				(s[-2]=='S' || s[-2]=='s') &&
40111894Speter				(s[-3]=='D' || s[-3]=='t')
40211894Speter			)
40311894Speter				goto trailing_dst;
40411894Speter			while (isspace ((unsigned char) *s))
40511894Speter				s++;
40611894Speter			if (
40711894Speter				(s[0]=='D' || s[0]=='d') &&
40811894Speter				(s[1]=='S' || s[1]=='s') &&
40911894Speter				(s[2]=='T' || s[2]=='t')
4109Sjkh			) {
41111894Speter				s += 3;
41211894Speter			  trailing_dst:
41311894Speter				*zone = z + 60*60;
41411894Speter				return (char *) s;
4159Sjkh			}
4169Sjkh
41711894Speter			switch (*s) {
41811894Speter				case '-': case '+': break;
41911894Speter				default: return (char *) s;
42011894Speter			}
42111894Speter	}
42211894Speter	sign = *s++;
42311894Speter
42411894Speter	if (!(s = parse_ranged (s, 2, 0, 23, &hh)))
42511894Speter		return 0;
42611894Speter	mm = ss = 0;
42711894Speter	if (*s == ':')
42811894Speter		s++;
42911894Speter	if (isdigit ((unsigned char) *s)) {
43011894Speter		if (!(s = parse_ranged (s, 2, 0, 59, &mm)))
4319Sjkh			return 0;
43211894Speter		if (*s==':' && s[-3]==':' && isdigit ((unsigned char) s[1])) {
43311894Speter			if (!(s = parse_ranged (s + 1, 2, 0, 59, &ss)))
43411894Speter				return 0;
43511894Speter		}
4369Sjkh	}
43711894Speter	if (isdigit ((unsigned char) *s))
43811894Speter		return 0;
43911894Speter	offset = (hh*60 + mm)*60L + ss;
44011894Speter	*zone = z + (sign=='-' ? -offset : offset);
44111894Speter	/*
44211894Speter	* ?? Are fractions allowed here?
44311894Speter	* If so, they're not implemented.
44411894Speter	*/
44511894Speter	return (char *) s;
44611894Speter}
4479Sjkh
44811894Speter	static char const *
44911894Speterparse_pattern_letter (s, c, t) char const *s; int c; struct partime *t;
45011894Speter/*
45111894Speter* Parse an initial prefix of S, matching the pattern whose code is C.
45211894Speter* Set *T accordingly.
45311894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed.
45411894Speter*/
45511894Speter{
45611894Speter	switch (c) {
45711894Speter		case '$': /* The next character must be a non-digit.  */
45811894Speter			if (isdigit ((unsigned char) *s))
45911894Speter				return 0;
46011894Speter			break;
4619Sjkh
46211894Speter		case '-': case '/': case ':':
46311894Speter			/* These characters stand for themselves.  */
46411894Speter			if (*s++ != c)
46511894Speter				return 0;
46611894Speter			break;
4679Sjkh
46811894Speter		case '4': /* 4-digit year */
46911894Speter			s = parse_fixed (s, 4, &t->tm.tm_year);
47011894Speter			break;
4719Sjkh
47211894Speter		case '=': /* optional '-' */
47311894Speter			s  +=  *s == '-';
47411894Speter			break;
4759Sjkh
47611894Speter		case 'A': /* AM or PM */
47711894Speter			/*
47811894Speter			* This matches the regular expression [AaPp][Mm]?.
47911894Speter			* It must not be followed by a letter or digit;
48011894Speter			* otherwise it would match prefixes of strings like "PST".
48111894Speter			*/
48211894Speter			switch (*s++) {
48311894Speter				case 'A': case 'a':
48411894Speter					if (t->tm.tm_hour == 12)
48511894Speter						t->tm.tm_hour = 0;
48611894Speter					break;
4879Sjkh
48811894Speter				case 'P': case 'p':
48911894Speter					if (t->tm.tm_hour != 12)
49011894Speter						t->tm.tm_hour += 12;
49111894Speter					break;
4929Sjkh
49311894Speter				default: return 0;
49411894Speter			}
49511894Speter			switch (*s) {
49611894Speter				case 'M': case 'm': s++; break;
49711894Speter			}
49811894Speter			if (isalnum (*s))
49911894Speter				return 0;
50011894Speter			break;
5019Sjkh
50211894Speter		case 'D': /* day of month [01-31] */
50311894Speter			s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
50411894Speter			break;
5059Sjkh
50611894Speter		case 'd': /* day of year [001-366] */
50711894Speter			s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
50811894Speter			t->tm.tm_yday--;
50911894Speter			break;
5109Sjkh
51111894Speter		case 'E': /* extended day of month [1-9, 01-31] */
51211894Speter			s = parse_ranged (s, (
51311894Speter				isdigit ((unsigned char) s[0]) &&
51411894Speter				isdigit ((unsigned char) s[1])
51511894Speter			) + 1, 1, 31, &t->tm.tm_mday);
51611894Speter			break;
5179Sjkh
51811894Speter		case 'h': /* hour [00-23 followed by optional fraction] */
51911894Speter			{
52011894Speter				int frac;
52111894Speter				s = parse_decimal (s, 2, 0, 23, 60*60, &t->tm.tm_hour, &frac);
52211894Speter				t->tm.tm_min = frac / 60;
52311894Speter				t->tm.tm_sec = frac % 60;
52411894Speter			}
52511894Speter			break;
5269Sjkh
52711894Speter		case 'm': /* minute [00-59 followed by optional fraction] */
52811894Speter			s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
52911894Speter			break;
5309Sjkh
53111894Speter		case 'n': /* month name [e.g. "Jan"] */
53211894Speter			if (!TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
53311894Speter				return 0;
53411894Speter			/* Don't bother to check rest of spelling.  */
53511894Speter			while (isalpha ((unsigned char) *s))
53611894Speter				s++;
53711894Speter			break;
5389Sjkh
53911894Speter		case 'N': /* month [01-12] */
54011894Speter			s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
54111894Speter			t->tm.tm_mon--;
54211894Speter			break;
5439Sjkh
54411894Speter		case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
54511894Speter			s = parse_fixed (s, 1, &t->tm.tm_year);
54611894Speter			t->ymodulus = 10;
54711894Speter			break;
5489Sjkh
54911894Speter		case_R:
55011894Speter		case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
55111894Speter			s = parse_fixed (s, 2, &t->tm.tm_year);
55211894Speter			t->ymodulus = 100;
55311894Speter			break;
55411894Speter
55511894Speter		case 's': /* second [00-60 followed by optional fraction] */
55611894Speter			{
55711894Speter				int frac;
55811894Speter				s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
55911894Speter				t->tm.tm_sec += frac;
56011894Speter			}
56111894Speter			break;
56211894Speter
56311894Speter		case 'T': /* 'T' or 't' */
56411894Speter			switch (*s++) {
56511894Speter				case 'T': case 't': break;
56611894Speter				default: return 0;
56711894Speter			}
56811894Speter			break;
56911894Speter
57011894Speter		case 't': /* traditional hour [1-9 or 01-12] */
57111894Speter			s = parse_ranged (s, (
57211894Speter				isdigit ((unsigned char) s[0]) && isdigit ((unsigned char) s[1])
57311894Speter			) + 1, 1, 12, &t->tm.tm_hour);
57411894Speter			break;
57511894Speter
57611894Speter		case 'w': /* 'W' or 'w' only (stands for current week) */
57711894Speter			switch (*s++) {
57811894Speter				case 'W': case 'w': break;
57911894Speter				default: return 0;
58011894Speter			}
58111894Speter			break;
58211894Speter
58311894Speter		case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
58411894Speter			switch (*s++) {
58511894Speter				case 'W': case 'w': break;
58611894Speter				default: return 0;
58711894Speter			}
58811894Speter			s = parse_ranged (s, 2, 0, 53, &t->yweek);
58911894Speter			break;
59011894Speter
59111894Speter		case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
59211894Speter			s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
59311894Speter			t->tm.tm_wday--;
59411894Speter			break;
59511894Speter
59611894Speter		case 'x': /* weekday name [e.g. "Sun"] */
59711894Speter			if (!TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
5989Sjkh				return 0;
59911894Speter			/* Don't bother to check rest of spelling.  */
60011894Speter			while (isalpha ((unsigned char) *s))
60111894Speter				s++;
60211894Speter			break;
60311894Speter
60411894Speter		case 'y': /* either R or Y */
60511894Speter			if (
60611894Speter				isdigit ((unsigned char) s[0]) &&
60711894Speter				isdigit ((unsigned char) s[1]) &&
60811894Speter				!isdigit ((unsigned char) s[2])
60911894Speter			)
61011894Speter				goto case_R;
61111894Speter			/* fall into */
61211894Speter		case 'Y': /* year in full [4 or more digits] */
61311894Speter			{
61411894Speter				int len = 0;
61511894Speter				while (isdigit ((unsigned char) s[len]))
61611894Speter					len++;
61711894Speter				if (len < 4)
61811894Speter					return 0;
61911894Speter				s = parse_fixed (s, len, &t->tm.tm_year);
6209Sjkh			}
62111894Speter			break;
6229Sjkh
62311894Speter		case 'Z': /* time zone */
62411894Speter			s = parzone (s, &t->zone);
62511894Speter			break;
6269Sjkh
62711894Speter		case '_': /* possibly empty sequence of non-alphanumerics */
62811894Speter			while (!isalnum (*s)  &&  *s)
62911894Speter				s++;
63011894Speter			break;
63111894Speter
63211894Speter		default: /* bad pattern */
63311894Speter			return 0;
63411894Speter	}
63511894Speter	return s;
6369Sjkh}
6379Sjkh
6389Sjkh	static int
63911894Spetermerge_partime (t, u) struct partime *t; struct partime const *u;
64011894Speter/*
64111894Speter* If there is no conflict, merge into *T the additional information in *U
64211894Speter* and return 0.  Otherwise do nothing and return -1.
64311894Speter*/
6449Sjkh{
64511894Speter#	define conflict(a,b) ((a) != (b)  &&  TM_DEFINED (a)  &&  TM_DEFINED (b))
64611894Speter	if (
64711894Speter		conflict (t->tm.tm_sec, u->tm.tm_sec) ||
64811894Speter		conflict (t->tm.tm_min, u->tm.tm_min) ||
64911894Speter		conflict (t->tm.tm_hour, u->tm.tm_hour) ||
65011894Speter		conflict (t->tm.tm_mday, u->tm.tm_mday) ||
65111894Speter		conflict (t->tm.tm_mon, u->tm.tm_mon) ||
65211894Speter		conflict (t->tm.tm_year, u->tm.tm_year) ||
65311894Speter		conflict (t->tm.tm_wday, u->tm.tm_yday) ||
65411894Speter		conflict (t->ymodulus, u->ymodulus) ||
65511894Speter		conflict (t->yweek, u->yweek) ||
65611894Speter		(
65711894Speter			t->zone != u->zone &&
65811894Speter			t->zone != TM_UNDEFINED_ZONE &&
65911894Speter			u->zone != TM_UNDEFINED_ZONE
66011894Speter		)
66111894Speter	)
66211894Speter		return -1;
66311894Speter#	undef conflict
66411894Speter#	define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
66511894Speter	merge_ (t->tm.tm_sec, u->tm.tm_sec)
66611894Speter	merge_ (t->tm.tm_min, u->tm.tm_min)
66711894Speter	merge_ (t->tm.tm_hour, u->tm.tm_hour)
66811894Speter	merge_ (t->tm.tm_mday, u->tm.tm_mday)
66911894Speter	merge_ (t->tm.tm_mon, u->tm.tm_mon)
67011894Speter	merge_ (t->tm.tm_year, u->tm.tm_year)
67111894Speter	merge_ (t->tm.tm_wday, u->tm.tm_yday)
67211894Speter	merge_ (t->ymodulus, u->ymodulus)
67311894Speter	merge_ (t->yweek, u->yweek)
67411894Speter#	undef merge_
67511894Speter	if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone;
67611894Speter	return 0;
6779Sjkh}
6789Sjkh
67911894Speter	char *
68011894Speterpartime (s, t) char const *s; struct partime *t;
68111894Speter/*
68211894Speter* Parse a date/time prefix of S, putting the parsed result into *T.
68311894Speter* Return the first character after the prefix.
68411894Speter* The prefix may contain no useful information;
68511894Speter* in that case, *T will contain only undefined values.
68611894Speter*/
6879Sjkh{
68811894Speter	struct partime p;
6899Sjkh
69011894Speter	undefine (t);
69111894Speter	while (*s) {
69211894Speter		int i = 0;
69311894Speter		char const *s1;
69411894Speter		do {
69511894Speter			if (!(s1 = parse_prefix (s, &p, &i)))
69611894Speter				return (char *) s;
69711894Speter		} while (merge_partime (t, &p) != 0);
69811894Speter		s = s1;
69911894Speter	}
70011894Speter	return (char *) s;
7019Sjkh}
702