xref: /openbsd/lib/libc/time/strptime.c (revision 404b540a)
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 
50 static	int _conv_num(const unsigned char **, int *, int, int);
51 static	char *_strptime(const char *, const char *, struct tm *, int);
52 
53 
54 char *
55 strptime(const char *buf, const char *fmt, struct tm *tm)
56 {
57 	return(_strptime(buf, fmt, tm, 1));
58 }
59 
60 static 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 
92 again:		switch (c = *fmt++) {
93 		case '%':	/* "%%" is converted to "%". */
94 literal:
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 
367 static 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