1214979Sdes/*	$OpenBSD: strptime.c,v 1.12 2008/06/26 05:42:05 ray Exp $ */
2214979Sdes/*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
3214979Sdes
4214979Sdes/*-
5214979Sdes * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
6214979Sdes * All rights reserved.
7214979Sdes *
8214979Sdes * This code was contributed to The NetBSD Foundation by Klaus Klein.
9214979Sdes *
10214979Sdes * Redistribution and use in source and binary forms, with or without
11214979Sdes * modification, are permitted provided that the following conditions
12214979Sdes * are met:
13214979Sdes * 1. Redistributions of source code must retain the above copyright
14214979Sdes *    notice, this list of conditions and the following disclaimer.
15214979Sdes * 2. Redistributions in binary form must reproduce the above copyright
16214979Sdes *    notice, this list of conditions and the following disclaimer in the
17214979Sdes *    documentation and/or other materials provided with the distribution.
18214979Sdes *
19214979Sdes * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20214979Sdes * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21214979Sdes * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22214979Sdes * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23214979Sdes * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24214979Sdes * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25214979Sdes * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26214979Sdes * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27214979Sdes * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28214979Sdes * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29214979Sdes * POSSIBILITY OF SUCH DAMAGE.
30214979Sdes */
31214979Sdes
32214979Sdes/* OPENBSD ORIGINAL: lib/libc/time/strptime.c */
33214979Sdes
34214979Sdes#include "includes.h"
35214979Sdes
36214979Sdes#ifndef HAVE_STRPTIME
37214979Sdes
38214979Sdes#define TM_YEAR_BASE 1900	/* from tzfile.h */
39214979Sdes
40214979Sdes#include <ctype.h>
41214979Sdes#include <locale.h>
42214979Sdes#include <string.h>
43214979Sdes#include <time.h>
44214979Sdes
45214979Sdes/* #define	_ctloc(x)		(_CurrentTimeLocale->x) */
46214979Sdes
47214979Sdes/*
48214979Sdes * We do not implement alternate representations. However, we always
49214979Sdes * check whether a given modifier is allowed for a certain conversion.
50214979Sdes */
51214979Sdes#define _ALT_E			0x01
52214979Sdes#define _ALT_O			0x02
53214979Sdes#define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
54214979Sdes
55214979Sdes
56214979Sdesstatic	int _conv_num(const unsigned char **, int *, int, int);
57214979Sdesstatic	char *_strptime(const char *, const char *, struct tm *, int);
58214979Sdes
59214979Sdes
60214979Sdeschar *
61214979Sdesstrptime(const char *buf, const char *fmt, struct tm *tm)
62214979Sdes{
63214979Sdes	return(_strptime(buf, fmt, tm, 1));
64214979Sdes}
65214979Sdes
66214979Sdesstatic char *
67214979Sdes_strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
68214979Sdes{
69214979Sdes	unsigned char c;
70214979Sdes	const unsigned char *bp;
71214979Sdes	size_t len;
72214979Sdes	int alt_format, i;
73214979Sdes	static int century, relyear;
74214979Sdes
75214979Sdes	if (initialize) {
76214979Sdes		century = TM_YEAR_BASE;
77214979Sdes		relyear = -1;
78214979Sdes	}
79214979Sdes
80214979Sdes	bp = (unsigned char *)buf;
81214979Sdes	while ((c = *fmt) != '\0') {
82214979Sdes		/* Clear `alternate' modifier prior to new conversion. */
83214979Sdes		alt_format = 0;
84214979Sdes
85214979Sdes		/* Eat up white-space. */
86214979Sdes		if (isspace(c)) {
87214979Sdes			while (isspace(*bp))
88214979Sdes				bp++;
89214979Sdes
90214979Sdes			fmt++;
91214979Sdes			continue;
92214979Sdes		}
93214979Sdes
94214979Sdes		if ((c = *fmt++) != '%')
95214979Sdes			goto literal;
96214979Sdes
97214979Sdes
98214979Sdesagain:		switch (c = *fmt++) {
99214979Sdes		case '%':	/* "%%" is converted to "%". */
100214979Sdesliteral:
101214979Sdes		if (c != *bp++)
102214979Sdes			return (NULL);
103214979Sdes
104214979Sdes		break;
105214979Sdes
106214979Sdes		/*
107214979Sdes		 * "Alternative" modifiers. Just set the appropriate flag
108214979Sdes		 * and start over again.
109214979Sdes		 */
110214979Sdes		case 'E':	/* "%E?" alternative conversion modifier. */
111214979Sdes			_LEGAL_ALT(0);
112214979Sdes			alt_format |= _ALT_E;
113214979Sdes			goto again;
114214979Sdes
115214979Sdes		case 'O':	/* "%O?" alternative conversion modifier. */
116214979Sdes			_LEGAL_ALT(0);
117214979Sdes			alt_format |= _ALT_O;
118214979Sdes			goto again;
119214979Sdes
120214979Sdes		/*
121214979Sdes		 * "Complex" conversion rules, implemented through recursion.
122214979Sdes		 */
123214979Sdes#if 0
124214979Sdes		case 'c':	/* Date and time, using the locale's format. */
125214979Sdes			_LEGAL_ALT(_ALT_E);
126214979Sdes			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
127214979Sdes				return (NULL);
128214979Sdes			break;
129214979Sdes#endif
130214979Sdes		case 'D':	/* The date as "%m/%d/%y". */
131214979Sdes			_LEGAL_ALT(0);
132214979Sdes			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
133214979Sdes				return (NULL);
134214979Sdes			break;
135214979Sdes
136214979Sdes		case 'R':	/* The time as "%H:%M". */
137214979Sdes			_LEGAL_ALT(0);
138214979Sdes			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
139214979Sdes				return (NULL);
140214979Sdes			break;
141214979Sdes
142214979Sdes		case 'r':	/* The time as "%I:%M:%S %p". */
143214979Sdes			_LEGAL_ALT(0);
144214979Sdes			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
145214979Sdes				return (NULL);
146214979Sdes			break;
147214979Sdes
148214979Sdes		case 'T':	/* The time as "%H:%M:%S". */
149214979Sdes			_LEGAL_ALT(0);
150214979Sdes			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
151214979Sdes				return (NULL);
152214979Sdes			break;
153214979Sdes#if 0
154214979Sdes		case 'X':	/* The time, using the locale's format. */
155214979Sdes			_LEGAL_ALT(_ALT_E);
156214979Sdes			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
157214979Sdes				return (NULL);
158214979Sdes			break;
159214979Sdes
160214979Sdes		case 'x':	/* The date, using the locale's format. */
161214979Sdes			_LEGAL_ALT(_ALT_E);
162214979Sdes			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
163214979Sdes				return (NULL);
164214979Sdes			break;
165214979Sdes#endif
166214979Sdes		/*
167214979Sdes		 * "Elementary" conversion rules.
168214979Sdes		 */
169214979Sdes#if 0
170214979Sdes		case 'A':	/* The day of week, using the locale's form. */
171214979Sdes		case 'a':
172214979Sdes			_LEGAL_ALT(0);
173214979Sdes			for (i = 0; i < 7; i++) {
174214979Sdes				/* Full name. */
175214979Sdes				len = strlen(_ctloc(day[i]));
176214979Sdes				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
177214979Sdes					break;
178214979Sdes
179214979Sdes				/* Abbreviated name. */
180214979Sdes				len = strlen(_ctloc(abday[i]));
181214979Sdes				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
182214979Sdes					break;
183214979Sdes			}
184214979Sdes
185214979Sdes			/* Nothing matched. */
186214979Sdes			if (i == 7)
187214979Sdes				return (NULL);
188214979Sdes
189214979Sdes			tm->tm_wday = i;
190214979Sdes			bp += len;
191214979Sdes			break;
192214979Sdes
193214979Sdes		case 'B':	/* The month, using the locale's form. */
194214979Sdes		case 'b':
195214979Sdes		case 'h':
196214979Sdes			_LEGAL_ALT(0);
197214979Sdes			for (i = 0; i < 12; i++) {
198214979Sdes				/* Full name. */
199214979Sdes				len = strlen(_ctloc(mon[i]));
200214979Sdes				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
201214979Sdes					break;
202214979Sdes
203214979Sdes				/* Abbreviated name. */
204214979Sdes				len = strlen(_ctloc(abmon[i]));
205214979Sdes				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
206214979Sdes					break;
207214979Sdes			}
208214979Sdes
209214979Sdes			/* Nothing matched. */
210214979Sdes			if (i == 12)
211214979Sdes				return (NULL);
212214979Sdes
213214979Sdes			tm->tm_mon = i;
214214979Sdes			bp += len;
215214979Sdes			break;
216214979Sdes#endif
217214979Sdes
218214979Sdes		case 'C':	/* The century number. */
219214979Sdes			_LEGAL_ALT(_ALT_E);
220214979Sdes			if (!(_conv_num(&bp, &i, 0, 99)))
221214979Sdes				return (NULL);
222214979Sdes
223214979Sdes			century = i * 100;
224214979Sdes			break;
225214979Sdes
226214979Sdes		case 'd':	/* The day of month. */
227214979Sdes		case 'e':
228214979Sdes			_LEGAL_ALT(_ALT_O);
229214979Sdes			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
230214979Sdes				return (NULL);
231214979Sdes			break;
232214979Sdes
233214979Sdes		case 'k':	/* The hour (24-hour clock representation). */
234214979Sdes			_LEGAL_ALT(0);
235214979Sdes			/* FALLTHROUGH */
236214979Sdes		case 'H':
237214979Sdes			_LEGAL_ALT(_ALT_O);
238214979Sdes			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
239214979Sdes				return (NULL);
240214979Sdes			break;
241214979Sdes
242214979Sdes		case 'l':	/* The hour (12-hour clock representation). */
243214979Sdes			_LEGAL_ALT(0);
244214979Sdes			/* FALLTHROUGH */
245214979Sdes		case 'I':
246214979Sdes			_LEGAL_ALT(_ALT_O);
247214979Sdes			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
248214979Sdes				return (NULL);
249214979Sdes			break;
250214979Sdes
251214979Sdes		case 'j':	/* The day of year. */
252214979Sdes			_LEGAL_ALT(0);
253214979Sdes			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
254214979Sdes				return (NULL);
255214979Sdes			tm->tm_yday--;
256214979Sdes			break;
257214979Sdes
258214979Sdes		case 'M':	/* The minute. */
259214979Sdes			_LEGAL_ALT(_ALT_O);
260214979Sdes			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
261214979Sdes				return (NULL);
262214979Sdes			break;
263214979Sdes
264214979Sdes		case 'm':	/* The month. */
265214979Sdes			_LEGAL_ALT(_ALT_O);
266214979Sdes			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
267214979Sdes				return (NULL);
268214979Sdes			tm->tm_mon--;
269214979Sdes			break;
270214979Sdes
271214979Sdes#if 0
272214979Sdes		case 'p':	/* The locale's equivalent of AM/PM. */
273214979Sdes			_LEGAL_ALT(0);
274214979Sdes			/* AM? */
275214979Sdes			len = strlen(_ctloc(am_pm[0]));
276214979Sdes			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
277214979Sdes				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
278214979Sdes					return (NULL);
279214979Sdes				else if (tm->tm_hour == 12)
280214979Sdes					tm->tm_hour = 0;
281214979Sdes
282214979Sdes				bp += len;
283214979Sdes				break;
284214979Sdes			}
285214979Sdes			/* PM? */
286214979Sdes			len = strlen(_ctloc(am_pm[1]));
287214979Sdes			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
288214979Sdes				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
289214979Sdes					return (NULL);
290214979Sdes				else if (tm->tm_hour < 12)
291214979Sdes					tm->tm_hour += 12;
292214979Sdes
293214979Sdes				bp += len;
294214979Sdes				break;
295214979Sdes			}
296214979Sdes
297214979Sdes			/* Nothing matched. */
298214979Sdes			return (NULL);
299214979Sdes#endif
300214979Sdes		case 'S':	/* The seconds. */
301214979Sdes			_LEGAL_ALT(_ALT_O);
302214979Sdes			if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
303214979Sdes				return (NULL);
304214979Sdes			break;
305214979Sdes
306214979Sdes		case 'U':	/* The week of year, beginning on sunday. */
307214979Sdes		case 'W':	/* The week of year, beginning on monday. */
308214979Sdes			_LEGAL_ALT(_ALT_O);
309214979Sdes			/*
310214979Sdes			 * XXX This is bogus, as we can not assume any valid
311214979Sdes			 * information present in the tm structure at this
312214979Sdes			 * point to calculate a real value, so just check the
313214979Sdes			 * range for now.
314214979Sdes			 */
315214979Sdes			 if (!(_conv_num(&bp, &i, 0, 53)))
316214979Sdes				return (NULL);
317214979Sdes			 break;
318214979Sdes
319214979Sdes		case 'w':	/* The day of week, beginning on sunday. */
320214979Sdes			_LEGAL_ALT(_ALT_O);
321214979Sdes			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
322214979Sdes				return (NULL);
323214979Sdes			break;
324214979Sdes
325214979Sdes		case 'Y':	/* The year. */
326214979Sdes			_LEGAL_ALT(_ALT_E);
327214979Sdes			if (!(_conv_num(&bp, &i, 0, 9999)))
328214979Sdes				return (NULL);
329214979Sdes
330214979Sdes			relyear = -1;
331214979Sdes			tm->tm_year = i - TM_YEAR_BASE;
332214979Sdes			break;
333214979Sdes
334214979Sdes		case 'y':	/* The year within the century (2 digits). */
335214979Sdes			_LEGAL_ALT(_ALT_E | _ALT_O);
336214979Sdes			if (!(_conv_num(&bp, &relyear, 0, 99)))
337214979Sdes				return (NULL);
338214979Sdes			break;
339214979Sdes
340214979Sdes		/*
341214979Sdes		 * Miscellaneous conversions.
342214979Sdes		 */
343214979Sdes		case 'n':	/* Any kind of white-space. */
344214979Sdes		case 't':
345214979Sdes			_LEGAL_ALT(0);
346214979Sdes			while (isspace(*bp))
347214979Sdes				bp++;
348214979Sdes			break;
349214979Sdes
350214979Sdes
351214979Sdes		default:	/* Unknown/unsupported conversion. */
352214979Sdes			return (NULL);
353214979Sdes		}
354214979Sdes
355214979Sdes
356214979Sdes	}
357214979Sdes
358214979Sdes	/*
359214979Sdes	 * We need to evaluate the two digit year spec (%y)
360214979Sdes	 * last as we can get a century spec (%C) at any time.
361214979Sdes	 */
362214979Sdes	if (relyear != -1) {
363214979Sdes		if (century == TM_YEAR_BASE) {
364214979Sdes			if (relyear <= 68)
365214979Sdes				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
366214979Sdes			else
367214979Sdes				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
368214979Sdes		} else {
369214979Sdes			tm->tm_year = relyear + century - TM_YEAR_BASE;
370214979Sdes		}
371214979Sdes	}
372214979Sdes
373214979Sdes	return ((char *)bp);
374214979Sdes}
375214979Sdes
376214979Sdes
377214979Sdesstatic int
378214979Sdes_conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
379214979Sdes{
380214979Sdes	int result = 0;
381214979Sdes	int rulim = ulim;
382214979Sdes
383214979Sdes	if (**buf < '0' || **buf > '9')
384214979Sdes		return (0);
385214979Sdes
386214979Sdes	/* we use rulim to break out of the loop when we run out of digits */
387214979Sdes	do {
388214979Sdes		result *= 10;
389214979Sdes		result += *(*buf)++ - '0';
390214979Sdes		rulim /= 10;
391214979Sdes	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
392214979Sdes
393214979Sdes	if (result < llim || result > ulim)
394214979Sdes		return (0);
395214979Sdes
396214979Sdes	*dest = result;
397214979Sdes	return (1);
398214979Sdes}
399214979Sdes
400214979Sdes#endif /* HAVE_STRPTIME */
401214979Sdes
402