1/*	$NetBSD: strptime.c,v 1.67 2024/06/07 13:53:23 riastradh Exp $	*/
2
3/*-
4 * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code was contributed to The NetBSD Foundation by Klaus Klein.
8 * Heavily optimised by David Laight
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#if defined(LIBC_SCCS) && !defined(lint)
34__RCSID("$NetBSD: strptime.c,v 1.67 2024/06/07 13:53:23 riastradh Exp $");
35#endif
36
37#include "namespace.h"
38#include <sys/localedef.h>
39#include <sys/types.h>
40#include <ctype.h>
41#include <locale.h>
42#include <string.h>
43#include <time.h>
44#include <tzfile.h>
45#include "private.h"
46#include "setlocale_local.h"
47
48#ifdef __weak_alias
49__weak_alias(strptime,_strptime)
50__weak_alias(strptime_l, _strptime_l)
51#endif
52
53static const u_char *conv_num(const unsigned char *, int *, uint, uint);
54static const u_char *find_string(const u_char *, int *, const char * const *,
55	const char * const *, int);
56
57#define _TIME_LOCALE(loc) \
58    ((_TimeLocale *)((loc)->part_impl[LC_TIME]))
59
60/*
61 * We do not implement alternate representations. However, we always
62 * check whether a given modifier is allowed for a certain conversion.
63 */
64#define ALT_E			0x01
65#define ALT_O			0x02
66#define LEGAL_ALT(x)		{ if (alt_format & ~(x)) return NULL; }
67
68#define S_YEAR			(1 << 0)
69#define S_MON			(1 << 1)
70#define S_YDAY			(1 << 2)
71#define S_MDAY			(1 << 3)
72#define S_WDAY			(1 << 4)
73#define S_HOUR			(1 << 5)
74
75#define HAVE_MDAY(s)		(s & S_MDAY)
76#define HAVE_MON(s)		(s & S_MON)
77#define HAVE_WDAY(s)		(s & S_WDAY)
78#define HAVE_YDAY(s)		(s & S_YDAY)
79#define HAVE_YEAR(s)		(s & S_YEAR)
80#define HAVE_HOUR(s)		(s & S_HOUR)
81
82static char utc[] = { "UTC" };
83/* RFC-822/RFC-2822 */
84static const char * const nast[5] = {
85       "EST",    "CST",    "MST",    "PST",    "\0\0\0"
86};
87static const char * const nadt[5] = {
88       "EDT",    "CDT",    "MDT",    "PDT",    "\0\0\0"
89};
90
91/*
92 * Table to determine the ordinal date for the start of a month.
93 * Ref: http://en.wikipedia.org/wiki/ISO_week_date
94 */
95static const int start_of_month[2][13] = {
96	/* non-leap year */
97	{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
98	/* leap year */
99	{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
100};
101
102/*
103 * Calculate the week day of the first day of a year. Valid for
104 * the Gregorian calendar, which began Sept 14, 1752 in the UK
105 * and its colonies. Ref:
106 * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
107 */
108
109static int
110first_wday_of(int yr)
111{
112	return ((2 * (3 - (yr / 100) % 4)) + (yr % 100) + ((yr % 100) /  4) +
113	    (isleap(yr) ? 6 : 0) + 1) % 7;
114}
115
116#define delim(p)	((p) == '\0' || isspace((unsigned char)(p)))
117
118static int
119fromzone(const unsigned char **bp, struct tm *tm, int mandatory)
120{
121	timezone_t tz;
122	char buf[512], *p;
123	const unsigned char *rp;
124
125	for (p = buf, rp = *bp; !delim(*rp) && p < &buf[sizeof(buf) - 1]; rp++)
126		*p++ = *rp;
127	*p = '\0';
128
129	if (mandatory)
130		*bp = rp;
131	if (!isalnum((unsigned char)*buf))
132		return 0;
133	tz = tzalloc(buf);
134	if (tz == NULL)
135		return 0;
136
137	*bp = rp;
138	tm->tm_isdst = 0;	/* XXX */
139#ifdef TM_GMTOFF
140	tm->TM_GMTOFF = tzgetgmtoff(tz, tm->tm_isdst);
141#endif
142#ifdef TM_ZONE
143	// Can't use tzgetname() here because we are going to free()
144	tm->TM_ZONE = NULL; /* XXX */
145#endif
146	tzfree(tz);
147	return 1;
148}
149
150char *
151strptime(const char *buf, const char *fmt, struct tm *tm)
152{
153	return strptime_l(buf, fmt, tm, _current_locale());
154}
155
156char *
157strptime_l(const char *buf, const char *fmt, struct tm *tm, locale_t loc)
158{
159	unsigned char c;
160	const unsigned char *bp, *ep, *zname;
161	int alt_format, i, split_year = 0, neg = 0, state = 0,
162	    day_offset = -1, week_offset = 0, offs, mandatory;
163	const char *new_fmt;
164
165	bp = (const u_char *)buf;
166
167	while (bp != NULL && (c = *fmt++) != '\0') {
168		/* Clear `alternate' modifier prior to new conversion. */
169		alt_format = 0;
170		i = 0;
171
172		/* Eat up white-space. */
173		if (isspace(c)) {
174			while (isspace(*bp))
175				bp++;
176			continue;
177		}
178
179		if (c != '%')
180			goto literal;
181
182
183again:		switch (c = *fmt++) {
184		case '%':	/* "%%" is converted to "%". */
185literal:
186			if (c != *bp++)
187				return NULL;
188			LEGAL_ALT(0);
189			continue;
190
191		/*
192		 * "Alternative" modifiers. Just set the appropriate flag
193		 * and start over again.
194		 */
195		case 'E':	/* "%E?" alternative conversion modifier. */
196			LEGAL_ALT(0);
197			alt_format |= ALT_E;
198			goto again;
199
200		case 'O':	/* "%O?" alternative conversion modifier. */
201			LEGAL_ALT(0);
202			alt_format |= ALT_O;
203			goto again;
204
205		/*
206		 * "Complex" conversion rules, implemented through recursion.
207		 */
208		case 'c':	/* Date and time, using the locale's format. */
209			new_fmt = _TIME_LOCALE(loc)->d_t_fmt;
210			state |= S_WDAY | S_MON | S_MDAY | S_YEAR;
211			goto recurse;
212
213		case 'D':	/* The date as "%m/%d/%y". */
214			new_fmt = "%m/%d/%y";
215			LEGAL_ALT(0);
216			state |= S_MON | S_MDAY | S_YEAR;
217			goto recurse;
218
219		case 'F':	/* The date as "%Y-%m-%d". */
220			new_fmt = "%Y-%m-%d";
221			LEGAL_ALT(0);
222			state |= S_MON | S_MDAY | S_YEAR;
223			goto recurse;
224
225		case 'R':	/* The time as "%H:%M". */
226			new_fmt = "%H:%M";
227			LEGAL_ALT(0);
228			goto recurse;
229
230		case 'r':	/* The time in 12-hour clock representation. */
231			new_fmt = _TIME_LOCALE(loc)->t_fmt_ampm;
232			LEGAL_ALT(0);
233			goto recurse;
234
235		case 'T':	/* The time as "%H:%M:%S". */
236			new_fmt = "%H:%M:%S";
237			LEGAL_ALT(0);
238			goto recurse;
239
240		case 'X':	/* The time, using the locale's format. */
241			new_fmt = _TIME_LOCALE(loc)->t_fmt;
242			goto recurse;
243
244		case 'x':	/* The date, using the locale's format. */
245			new_fmt = _TIME_LOCALE(loc)->d_fmt;
246			state |= S_MON | S_MDAY | S_YEAR;
247		    recurse:
248			bp = (const u_char *)strptime((const char *)bp,
249							    new_fmt, tm);
250			LEGAL_ALT(ALT_E);
251			continue;
252
253		/*
254		 * "Elementary" conversion rules.
255		 */
256		case 'A':	/* The day of week, using the locale's form. */
257		case 'a':
258			bp = find_string(bp, &tm->tm_wday,
259			    _TIME_LOCALE(loc)->day, _TIME_LOCALE(loc)->abday, 7);
260			LEGAL_ALT(0);
261			state |= S_WDAY;
262			continue;
263
264		case 'B':	/* The month, using the locale's form. */
265		case 'b':
266		case 'h':
267			bp = find_string(bp, &tm->tm_mon,
268			    _TIME_LOCALE(loc)->mon, _TIME_LOCALE(loc)->abmon,
269			    12);
270			LEGAL_ALT(0);
271			state |= S_MON;
272			continue;
273
274		case 'C':	/* The century number. */
275			i = 20;
276			bp = conv_num(bp, &i, 0, 99);
277
278			i = i * 100 - TM_YEAR_BASE;
279			if (split_year)
280				i += tm->tm_year % 100;
281			split_year = 1;
282			tm->tm_year = i;
283			LEGAL_ALT(ALT_E);
284			state |= S_YEAR;
285			continue;
286
287		case 'd':	/* The day of month. */
288		case 'e':
289			bp = conv_num(bp, &tm->tm_mday, 1, 31);
290			LEGAL_ALT(ALT_O);
291			state |= S_MDAY;
292			continue;
293
294		case 'k':	/* The hour (24-hour clock representation). */
295			LEGAL_ALT(0);
296			/* FALLTHROUGH */
297		case 'H':
298			bp = conv_num(bp, &tm->tm_hour, 0, 23);
299			LEGAL_ALT(ALT_O);
300			state |= S_HOUR;
301			continue;
302
303		case 'l':	/* The hour (12-hour clock representation). */
304			LEGAL_ALT(0);
305			/* FALLTHROUGH */
306		case 'I':
307			bp = conv_num(bp, &tm->tm_hour, 1, 12);
308			if (tm->tm_hour == 12)
309				tm->tm_hour = 0;
310			LEGAL_ALT(ALT_O);
311			state |= S_HOUR;
312			continue;
313
314		case 'j':	/* The day of year. */
315			i = 1;
316			bp = conv_num(bp, &i, 1, 366);
317			tm->tm_yday = i - 1;
318			LEGAL_ALT(0);
319			state |= S_YDAY;
320			continue;
321
322		case 'M':	/* The minute. */
323			bp = conv_num(bp, &tm->tm_min, 0, 59);
324			LEGAL_ALT(ALT_O);
325			continue;
326
327		case 'm':	/* The month. */
328			i = 1;
329			bp = conv_num(bp, &i, 1, 12);
330			tm->tm_mon = i - 1;
331			LEGAL_ALT(ALT_O);
332			state |= S_MON;
333			continue;
334
335		case 'p':	/* The locale's equivalent of AM/PM. */
336			bp = find_string(bp, &i, _TIME_LOCALE(loc)->am_pm,
337			    NULL, 2);
338			if (HAVE_HOUR(state) && tm->tm_hour > 11)
339				return NULL;
340			tm->tm_hour += i * 12;
341			LEGAL_ALT(0);
342			continue;
343
344		case 'S':	/* The seconds. */
345			bp = conv_num(bp, &tm->tm_sec, 0, 61);
346			LEGAL_ALT(ALT_O);
347			continue;
348
349		case 's': {	/* seconds since the epoch */
350			const time_t TIME_MAX = __type_max(time_t);
351			time_t sse, d;
352
353			if (*bp < '0' || *bp > '9') {
354				bp = NULL;
355				continue;
356			}
357
358			sse = *bp++ - '0';
359			while (*bp >= '0' && *bp <= '9') {
360				d = *bp++ - '0';
361				if (sse > TIME_MAX/10) {
362					bp = NULL;
363					break;
364				}
365				sse *= 10;
366				if (sse > TIME_MAX - d) {
367					bp = NULL;
368					break;
369				}
370				sse += d;
371			}
372			if (bp == NULL)
373				continue;
374
375			if (localtime_r(&sse, tm) == NULL)
376				bp = NULL;
377			else
378				state |= S_YDAY | S_WDAY |
379				    S_MON | S_MDAY | S_YEAR;
380			continue;
381		}
382
383		case 'U':	/* The week of year, beginning on sunday. */
384		case 'W':	/* The week of year, beginning on monday. */
385			/*
386			 * This is bogus, as we can not assume any valid
387			 * information present in the tm structure at this
388			 * point to calculate a real value, so save the
389			 * week for now in case it can be used later.
390			 */
391			bp = conv_num(bp, &i, 0, 53);
392			LEGAL_ALT(ALT_O);
393			if (c == 'U')
394				day_offset = TM_SUNDAY;
395			else
396				day_offset = TM_MONDAY;
397			week_offset = i;
398			continue;
399
400		case 'w':	/* The day of week, beginning on sunday. */
401			bp = conv_num(bp, &tm->tm_wday, 0, 6);
402			LEGAL_ALT(ALT_O);
403			state |= S_WDAY;
404			continue;
405
406		case 'u':	/* The day of week, monday = 1. */
407			bp = conv_num(bp, &i, 1, 7);
408			tm->tm_wday = i % 7;
409			LEGAL_ALT(ALT_O);
410			state |= S_WDAY;
411			continue;
412
413		case 'g':	/* The year corresponding to the ISO week
414				 * number but without the century.
415				 */
416			bp = conv_num(bp, &i, 0, 99);
417			continue;
418
419		case 'G':	/* The year corresponding to the ISO week
420				 * number with century.
421				 */
422			do
423				bp++;
424			while (isdigit(*bp));
425			continue;
426
427		case 'V':	/* The ISO 8601:1988 week number as decimal */
428			bp = conv_num(bp, &i, 1, 53);
429			continue;
430
431		case 'Y':	/* The year. */
432			i = TM_YEAR_BASE;	/* just for data sanity... */
433			bp = conv_num(bp, &i, 0, 9999);
434			tm->tm_year = i - TM_YEAR_BASE;
435			LEGAL_ALT(ALT_E);
436			state |= S_YEAR;
437			continue;
438
439		case 'y':	/* The year within 100 years of the epoch. */
440			/* LEGAL_ALT(ALT_E | ALT_O); */
441			bp = conv_num(bp, &i, 0, 99);
442
443			if (split_year)
444				/* preserve century */
445				i += (tm->tm_year / 100) * 100;
446			else {
447				split_year = 1;
448				if (i <= 68)
449					i = i + 2000 - TM_YEAR_BASE;
450				else
451					i = i + 1900 - TM_YEAR_BASE;
452			}
453			tm->tm_year = i;
454			state |= S_YEAR;
455			continue;
456
457		case 'Z':
458		case 'z':
459			tzset();
460			mandatory = c == 'z';
461			/*
462			 * We recognize all ISO 8601 formats:
463			 * Z	= Zulu time/UTC
464			 * [+-]hhmm
465			 * [+-]hh:mm
466			 * [+-]hh
467			 * We recognize all RFC-822/RFC-2822 formats:
468			 * UT|GMT
469			 *          North American : UTC offsets
470			 * E[DS]T = Eastern : -4 | -5
471			 * C[DS]T = Central : -5 | -6
472			 * M[DS]T = Mountain: -6 | -7
473			 * P[DS]T = Pacific : -7 | -8
474			 *          Nautical/Military
475			 * [A-IL-M] = -1 ... -9 (J not used)
476			 * [N-Y]  = +1 ... +12
477			 * Note: J maybe used to denote non-nautical
478			 *       local time
479			 */
480			if (mandatory)
481				while (isspace(*bp))
482					bp++;
483
484			zname = bp;
485			switch (*bp++) {
486			case 'G':
487				if (*bp++ != 'M')
488					goto namedzone;
489				/*FALLTHROUGH*/
490			case 'U':
491				if (*bp++ != 'T')
492					goto namedzone;
493				else if (!delim(*bp) && *bp++ != 'C')
494					goto namedzone;
495				/*FALLTHROUGH*/
496			case 'Z':
497				if (!delim(*bp))
498					goto namedzone;
499				tm->tm_isdst = 0;
500#ifdef TM_GMTOFF
501				tm->TM_GMTOFF = 0;
502#endif
503#ifdef TM_ZONE
504				tm->TM_ZONE = utc;
505#endif
506				continue;
507			case '+':
508				neg = 0;
509				break;
510			case '-':
511				neg = 1;
512				break;
513			default:
514namedzone:
515				bp = zname;
516
517				/* Nautical / Military style */
518				if (delim(bp[1]) &&
519				    ((*bp >= 'A' && *bp <= 'I') ||
520				     (*bp >= 'L' && *bp <= 'Y'))) {
521#ifdef TM_GMTOFF
522					/* Argh! No 'J'! */
523					if (*bp >= 'A' && *bp <= 'I')
524						tm->TM_GMTOFF =
525						    (int)*bp - ('A' - 1);
526					else if (*bp >= 'L' && *bp <= 'M')
527						tm->TM_GMTOFF = (int)*bp - 'A';
528					else if (*bp >= 'N' && *bp <= 'Y')
529						tm->TM_GMTOFF = 'M' - (int)*bp;
530					tm->TM_GMTOFF *= SECSPERHOUR;
531#endif
532#ifdef TM_ZONE
533					tm->TM_ZONE = NULL; /* XXX */
534#endif
535					bp++;
536					continue;
537				}
538				/* 'J' is local time */
539				if (delim(bp[1]) && *bp == 'J') {
540#ifdef TM_GMTOFF
541					tm->TM_GMTOFF = -timezone;
542#endif
543#ifdef TM_ZONE
544					tm->TM_ZONE = NULL; /* XXX */
545#endif
546					bp++;
547					continue;
548				}
549
550				/*
551				 * From our 3 letter hard-coded table
552				 * XXX: Can be removed, handled by tzload()
553				 */
554				if (delim(bp[0]) || delim(bp[1]) ||
555				    delim(bp[2]) || !delim(bp[3]))
556					goto loadzone;
557				ep = find_string(bp, &i, nast, NULL, 4);
558				if (ep != NULL) {
559#ifdef TM_GMTOFF
560					tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR;
561#endif
562#ifdef TM_ZONE
563					tm->TM_ZONE = __UNCONST(nast[i]);
564#endif
565					bp = ep;
566					continue;
567				}
568				ep = find_string(bp, &i, nadt, NULL, 4);
569				if (ep != NULL) {
570					tm->tm_isdst = 1;
571#ifdef TM_GMTOFF
572					tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR;
573#endif
574#ifdef TM_ZONE
575					tm->TM_ZONE = __UNCONST(nadt[i]);
576#endif
577					bp = ep;
578					continue;
579				}
580				/*
581				 * Our current timezone
582				 */
583				ep = find_string(bp, &i,
584					       	 (const char * const *)tzname,
585					       	  NULL, 2);
586				if (ep != NULL) {
587					tm->tm_isdst = i;
588#ifdef TM_GMTOFF
589					tm->TM_GMTOFF = -timezone;
590#endif
591#ifdef TM_ZONE
592					tm->TM_ZONE = tzname[i];
593#endif
594					bp = ep;
595					continue;
596				}
597loadzone:
598				/*
599				 * The hard way, load the zone!
600				 */
601				if (fromzone(&bp, tm, mandatory))
602					continue;
603				goto out;
604			}
605			offs = 0;
606			for (i = 0; i < 4; ) {
607				if (isdigit(*bp)) {
608					offs = offs * 10 + (*bp++ - '0');
609					i++;
610					continue;
611				}
612				if (i == 2 && *bp == ':') {
613					bp++;
614					continue;
615				}
616				break;
617			}
618			if (isdigit(*bp))
619				goto out;
620			switch (i) {
621			case 2:
622				offs *= SECSPERHOUR;
623				break;
624			case 4:
625				i = offs % 100;
626				offs /= 100;
627				if (i >= SECSPERMIN)
628					goto out;
629				/* Convert minutes into decimal */
630				offs = offs * SECSPERHOUR + i * SECSPERMIN;
631				break;
632			default:
633			out:
634				if (mandatory)
635					return NULL;
636				bp = zname;
637				continue;
638			}
639			/* ISO 8601 & RFC 3339 limit to 23:59 max */
640			if (offs >= (HOURSPERDAY * SECSPERHOUR))
641				goto out;
642			if (neg)
643				offs = -offs;
644			tm->tm_isdst = 0;	/* XXX */
645#ifdef TM_GMTOFF
646			tm->TM_GMTOFF = offs;
647#endif
648#ifdef TM_ZONE
649			tm->TM_ZONE = NULL;	/* XXX */
650#endif
651			continue;
652
653		/*
654		 * Miscellaneous conversions.
655		 */
656		case 'n':	/* Any kind of white-space. */
657		case 't':
658			while (isspace(*bp))
659				bp++;
660			LEGAL_ALT(0);
661			continue;
662
663
664		default:	/* Unknown/unsupported conversion. */
665			return NULL;
666		}
667	}
668
669	if (!HAVE_YDAY(state) && HAVE_YEAR(state)) {
670		if (HAVE_MON(state) && HAVE_MDAY(state)) {
671			/* calculate day of year (ordinal date) */
672			tm->tm_yday =  start_of_month[isleap_sum(tm->tm_year,
673			    TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
674			state |= S_YDAY;
675		} else if (day_offset != -1) {
676			/*
677			 * Set the date to the first Sunday (or Monday)
678			 * of the specified week of the year.
679			 */
680			if (!HAVE_WDAY(state)) {
681				tm->tm_wday = day_offset;
682				state |= S_WDAY;
683			}
684			tm->tm_yday = (7 -
685			    first_wday_of(tm->tm_year + TM_YEAR_BASE) +
686			    day_offset) % 7 + (week_offset - 1) * 7 +
687			    tm->tm_wday  - day_offset;
688			state |= S_YDAY;
689		}
690	}
691
692	if (HAVE_YDAY(state) && HAVE_YEAR(state)) {
693		int isleap;
694
695		if (!HAVE_MON(state)) {
696			/* calculate month of day of year */
697			i = 0;
698			isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE);
699			while (tm->tm_yday >= start_of_month[isleap][i])
700				i++;
701			if (i > 12) {
702				i = 1;
703				tm->tm_yday -= start_of_month[isleap][12];
704				tm->tm_year++;
705			}
706			tm->tm_mon = i - 1;
707			state |= S_MON;
708		}
709
710		if (!HAVE_MDAY(state)) {
711			/* calculate day of month */
712			isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE);
713			tm->tm_mday = tm->tm_yday -
714			    start_of_month[isleap][tm->tm_mon] + 1;
715			state |= S_MDAY;
716		}
717
718		if (!HAVE_WDAY(state)) {
719			/* calculate day of week */
720			i = 0;
721			week_offset = first_wday_of(tm->tm_year);
722			while (i++ <= tm->tm_yday) {
723				if (week_offset++ >= 6)
724					week_offset = 0;
725			}
726			tm->tm_wday = week_offset;
727			state |= S_WDAY;
728		}
729	}
730
731	return __UNCONST(bp);
732}
733
734
735static const u_char *
736conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim)
737{
738	uint result = 0;
739	unsigned char ch;
740
741	/* The limit also determines the number of valid digits. */
742	uint rulim = ulim;
743
744	ch = *buf;
745	if (ch < '0' || ch > '9')
746		return NULL;
747
748	do {
749		result *= 10;
750		result += ch - '0';
751		rulim /= 10;
752		ch = *++buf;
753	} while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9');
754
755	if (result < llim || result > ulim)
756		return NULL;
757
758	*dest = result;
759	return buf;
760}
761
762static const u_char *
763find_string(const u_char *bp, int *tgt, const char * const *n1,
764		const char * const *n2, int c)
765{
766	int i;
767	size_t len;
768
769	/* check full name - then abbreviated ones */
770	for (; n1 != NULL; n1 = n2, n2 = NULL) {
771		for (i = 0; i < c; i++, n1++) {
772			len = strlen(*n1);
773			if (strncasecmp(*n1, (const char *)bp, len) == 0) {
774				*tgt = i;
775				return bp + len;
776			}
777		}
778	}
779
780	/* Nothing matched */
781	return NULL;
782}
783