strptime.c revision 1.12
1/*	$OpenBSD: strptime.c,v 1.12 2008/06/26 05:42:05 ray Exp $ */
2/*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
3
4/*-
5 * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code was contributed to The NetBSD Foundation by Klaus Klein.
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/localedef.h>
33#include <ctype.h>
34#include <locale.h>
35#include <string.h>
36#include <time.h>
37#include <tzfile.h>
38
39#define	_ctloc(x)		(_CurrentTimeLocale->x)
40
41/*
42 * We do not implement alternate representations. However, we always
43 * check whether a given modifier is allowed for a certain conversion.
44 */
45#define _ALT_E			0x01
46#define _ALT_O			0x02
47#define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
48
49
50static	int _conv_num(const unsigned char **, int *, int, int);
51static	char *_strptime(const char *, const char *, struct tm *, int);
52
53
54char *
55strptime(const char *buf, const char *fmt, struct tm *tm)
56{
57	return(_strptime(buf, fmt, tm, 1));
58}
59
60static char *
61_strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
62{
63	unsigned char c;
64	const unsigned char *bp;
65	size_t len;
66	int alt_format, i;
67	static int century, relyear;
68
69	if (initialize) {
70		century = TM_YEAR_BASE;
71		relyear = -1;
72	}
73
74	bp = (unsigned char *)buf;
75	while ((c = *fmt) != '\0') {
76		/* Clear `alternate' modifier prior to new conversion. */
77		alt_format = 0;
78
79		/* Eat up white-space. */
80		if (isspace(c)) {
81			while (isspace(*bp))
82				bp++;
83
84			fmt++;
85			continue;
86		}
87
88		if ((c = *fmt++) != '%')
89			goto literal;
90
91
92again:		switch (c = *fmt++) {
93		case '%':	/* "%%" is converted to "%". */
94literal:
95		if (c != *bp++)
96			return (NULL);
97
98		break;
99
100		/*
101		 * "Alternative" modifiers. Just set the appropriate flag
102		 * and start over again.
103		 */
104		case 'E':	/* "%E?" alternative conversion modifier. */
105			_LEGAL_ALT(0);
106			alt_format |= _ALT_E;
107			goto again;
108
109		case 'O':	/* "%O?" alternative conversion modifier. */
110			_LEGAL_ALT(0);
111			alt_format |= _ALT_O;
112			goto again;
113
114		/*
115		 * "Complex" conversion rules, implemented through recursion.
116		 */
117		case 'c':	/* Date and time, using the locale's format. */
118			_LEGAL_ALT(_ALT_E);
119			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
120				return (NULL);
121			break;
122
123		case 'D':	/* The date as "%m/%d/%y". */
124			_LEGAL_ALT(0);
125			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
126				return (NULL);
127			break;
128
129		case 'R':	/* The time as "%H:%M". */
130			_LEGAL_ALT(0);
131			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
132				return (NULL);
133			break;
134
135		case 'r':	/* The time as "%I:%M:%S %p". */
136			_LEGAL_ALT(0);
137			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
138				return (NULL);
139			break;
140
141		case 'T':	/* The time as "%H:%M:%S". */
142			_LEGAL_ALT(0);
143			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
144				return (NULL);
145			break;
146
147		case 'X':	/* The time, using the locale's format. */
148			_LEGAL_ALT(_ALT_E);
149			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
150				return (NULL);
151			break;
152
153		case 'x':	/* The date, using the locale's format. */
154			_LEGAL_ALT(_ALT_E);
155			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
156				return (NULL);
157			break;
158
159		/*
160		 * "Elementary" conversion rules.
161		 */
162		case 'A':	/* The day of week, using the locale's form. */
163		case 'a':
164			_LEGAL_ALT(0);
165			for (i = 0; i < 7; i++) {
166				/* Full name. */
167				len = strlen(_ctloc(day[i]));
168				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
169					break;
170
171				/* Abbreviated name. */
172				len = strlen(_ctloc(abday[i]));
173				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
174					break;
175			}
176
177			/* Nothing matched. */
178			if (i == 7)
179				return (NULL);
180
181			tm->tm_wday = i;
182			bp += len;
183			break;
184
185		case 'B':	/* The month, using the locale's form. */
186		case 'b':
187		case 'h':
188			_LEGAL_ALT(0);
189			for (i = 0; i < 12; i++) {
190				/* Full name. */
191				len = strlen(_ctloc(mon[i]));
192				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
193					break;
194
195				/* Abbreviated name. */
196				len = strlen(_ctloc(abmon[i]));
197				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
198					break;
199			}
200
201			/* Nothing matched. */
202			if (i == 12)
203				return (NULL);
204
205			tm->tm_mon = i;
206			bp += len;
207			break;
208
209		case 'C':	/* The century number. */
210			_LEGAL_ALT(_ALT_E);
211			if (!(_conv_num(&bp, &i, 0, 99)))
212				return (NULL);
213
214			century = i * 100;
215			break;
216
217		case 'd':	/* The day of month. */
218		case 'e':
219			_LEGAL_ALT(_ALT_O);
220			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
221				return (NULL);
222			break;
223
224		case 'k':	/* The hour (24-hour clock representation). */
225			_LEGAL_ALT(0);
226			/* FALLTHROUGH */
227		case 'H':
228			_LEGAL_ALT(_ALT_O);
229			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
230				return (NULL);
231			break;
232
233		case 'l':	/* The hour (12-hour clock representation). */
234			_LEGAL_ALT(0);
235			/* FALLTHROUGH */
236		case 'I':
237			_LEGAL_ALT(_ALT_O);
238			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
239				return (NULL);
240			break;
241
242		case 'j':	/* The day of year. */
243			_LEGAL_ALT(0);
244			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
245				return (NULL);
246			tm->tm_yday--;
247			break;
248
249		case 'M':	/* The minute. */
250			_LEGAL_ALT(_ALT_O);
251			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
252				return (NULL);
253			break;
254
255		case 'm':	/* The month. */
256			_LEGAL_ALT(_ALT_O);
257			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
258				return (NULL);
259			tm->tm_mon--;
260			break;
261
262		case 'p':	/* The locale's equivalent of AM/PM. */
263			_LEGAL_ALT(0);
264			/* AM? */
265			len = strlen(_ctloc(am_pm[0]));
266			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
267				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
268					return (NULL);
269				else if (tm->tm_hour == 12)
270					tm->tm_hour = 0;
271
272				bp += len;
273				break;
274			}
275			/* PM? */
276			len = strlen(_ctloc(am_pm[1]));
277			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
278				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
279					return (NULL);
280				else if (tm->tm_hour < 12)
281					tm->tm_hour += 12;
282
283				bp += len;
284				break;
285			}
286
287			/* Nothing matched. */
288			return (NULL);
289
290		case 'S':	/* The seconds. */
291			_LEGAL_ALT(_ALT_O);
292			if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
293				return (NULL);
294			break;
295
296		case 'U':	/* The week of year, beginning on sunday. */
297		case 'W':	/* The week of year, beginning on monday. */
298			_LEGAL_ALT(_ALT_O);
299			/*
300			 * XXX This is bogus, as we can not assume any valid
301			 * information present in the tm structure at this
302			 * point to calculate a real value, so just check the
303			 * range for now.
304			 */
305			 if (!(_conv_num(&bp, &i, 0, 53)))
306				return (NULL);
307			 break;
308
309		case 'w':	/* The day of week, beginning on sunday. */
310			_LEGAL_ALT(_ALT_O);
311			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
312				return (NULL);
313			break;
314
315		case 'Y':	/* The year. */
316			_LEGAL_ALT(_ALT_E);
317			if (!(_conv_num(&bp, &i, 0, 9999)))
318				return (NULL);
319
320			relyear = -1;
321			tm->tm_year = i - TM_YEAR_BASE;
322			break;
323
324		case 'y':	/* The year within the century (2 digits). */
325			_LEGAL_ALT(_ALT_E | _ALT_O);
326			if (!(_conv_num(&bp, &relyear, 0, 99)))
327				return (NULL);
328			break;
329
330		/*
331		 * Miscellaneous conversions.
332		 */
333		case 'n':	/* Any kind of white-space. */
334		case 't':
335			_LEGAL_ALT(0);
336			while (isspace(*bp))
337				bp++;
338			break;
339
340
341		default:	/* Unknown/unsupported conversion. */
342			return (NULL);
343		}
344
345
346	}
347
348	/*
349	 * We need to evaluate the two digit year spec (%y)
350	 * last as we can get a century spec (%C) at any time.
351	 */
352	if (relyear != -1) {
353		if (century == TM_YEAR_BASE) {
354			if (relyear <= 68)
355				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
356			else
357				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
358		} else {
359			tm->tm_year = relyear + century - TM_YEAR_BASE;
360		}
361	}
362
363	return ((char *)bp);
364}
365
366
367static int
368_conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
369{
370	int result = 0;
371	int rulim = ulim;
372
373	if (**buf < '0' || **buf > '9')
374		return (0);
375
376	/* we use rulim to break out of the loop when we run out of digits */
377	do {
378		result *= 10;
379		result += *(*buf)++ - '0';
380		rulim /= 10;
381	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
382
383	if (result < llim || result > ulim)
384		return (0);
385
386	*dest = result;
387	return (1);
388}
389