xref: /openbsd-src/lib/libc/time/strptime.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code was contributed to The NetBSD Foundation by Klaus Klein.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *        This product includes software developed by the NetBSD
20  *        Foundation, Inc. and its contributors.
21  * 4. Neither the name of The NetBSD Foundation nor the names of its
22  *    contributors may be used to endorse or promote products derived
23  *    from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  */
37 
38 #if defined(LIBC_SCCS) && !defined(lint)
39 static char rcsid[] = "$OpenBSD: strptime.c,v 1.6 2001/01/08 15:23:20 d Exp $";
40 #endif /* LIBC_SCCS and not lint */
41 
42 #include <sys/localedef.h>
43 #include <ctype.h>
44 #include <locale.h>
45 #include <string.h>
46 #include <time.h>
47 #include <tzfile.h>
48 
49 #define	_ctloc(x)		__CONCAT(_CurrentTimeLocale->,x)
50 
51 /*
52  * We do not implement alternate representations. However, we always
53  * check whether a given modifier is allowed for a certain conversion.
54  */
55 #define _ALT_E			0x01
56 #define _ALT_O			0x02
57 #define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
58 
59 
60 static	int _conv_num __P((const char **, int *, int, int));
61 static	char *_strptime __P((const char *, const char *, struct tm *, int));
62 
63 
64 char *
65 strptime(buf, fmt, tm)
66 	const char *buf, *fmt;
67 	struct tm *tm;
68 {
69 	return(_strptime(buf, fmt, tm, 1));
70 }
71 
72 static char *
73 _strptime(buf, fmt, tm, initialize)
74 	const char *buf, *fmt;
75 	struct tm *tm;
76 	int initialize;
77 {
78 	char c;
79 	const char *bp;
80 	int alt_format, i, len;
81 	static int century, relyear;
82 
83 	if (initialize) {
84 		century = TM_YEAR_BASE;
85 		relyear = -1;
86 	}
87 
88 	bp = buf;
89 	while ((c = *fmt) != '\0') {
90 		/* Clear `alternate' modifier prior to new conversion. */
91 		alt_format = 0;
92 
93 		/* Eat up white-space. */
94 		if (isspace(c)) {
95 			while (isspace(*bp))
96 				bp++;
97 
98 			fmt++;
99 			continue;
100 		}
101 
102 		if ((c = *fmt++) != '%')
103 			goto literal;
104 
105 
106 again:		switch (c = *fmt++) {
107 		case '%':	/* "%%" is converted to "%". */
108 literal:
109 		if (c != *bp++)
110 			return (NULL);
111 
112 		break;
113 
114 		/*
115 		 * "Alternative" modifiers. Just set the appropriate flag
116 		 * and start over again.
117 		 */
118 		case 'E':	/* "%E?" alternative conversion modifier. */
119 			_LEGAL_ALT(0);
120 			alt_format |= _ALT_E;
121 			goto again;
122 
123 		case 'O':	/* "%O?" alternative conversion modifier. */
124 			_LEGAL_ALT(0);
125 			alt_format |= _ALT_O;
126 			goto again;
127 
128 		/*
129 		 * "Complex" conversion rules, implemented through recursion.
130 		 */
131 		case 'c':	/* Date and time, using the locale's format. */
132 			_LEGAL_ALT(_ALT_E);
133 			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
134 				return (NULL);
135 			break;
136 
137 		case 'D':	/* The date as "%m/%d/%y". */
138 			_LEGAL_ALT(0);
139 			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
140 				return (NULL);
141 			break;
142 
143 		case 'R':	/* The time as "%H:%M". */
144 			_LEGAL_ALT(0);
145 			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
146 				return (NULL);
147 			break;
148 
149 		case 'r':	/* The time as "%I:%M:%S %p". */
150 			_LEGAL_ALT(0);
151 			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
152 				return (NULL);
153 			break;
154 
155 		case 'T':	/* The time as "%H:%M:%S". */
156 			_LEGAL_ALT(0);
157 			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
158 				return (NULL);
159 			break;
160 
161 		case 'X':	/* The time, using the locale's format. */
162 			_LEGAL_ALT(_ALT_E);
163 			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
164 				return (NULL);
165 			break;
166 
167 		case 'x':	/* The date, using the locale's format. */
168 			_LEGAL_ALT(_ALT_E);
169 			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
170 				return (NULL);
171 			break;
172 
173 		/*
174 		 * "Elementary" conversion rules.
175 		 */
176 		case 'A':	/* The day of week, using the locale's form. */
177 		case 'a':
178 			_LEGAL_ALT(0);
179 			for (i = 0; i < 7; i++) {
180 				/* Full name. */
181 				len = strlen(_ctloc(day[i]));
182 				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
183 					break;
184 
185 				/* Abbreviated name. */
186 				len = strlen(_ctloc(abday[i]));
187 				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
188 					break;
189 			}
190 
191 			/* Nothing matched. */
192 			if (i == 7)
193 				return (NULL);
194 
195 			tm->tm_wday = i;
196 			bp += len;
197 			break;
198 
199 		case 'B':	/* The month, using the locale's form. */
200 		case 'b':
201 		case 'h':
202 			_LEGAL_ALT(0);
203 			for (i = 0; i < 12; i++) {
204 				/* Full name. */
205 				len = strlen(_ctloc(mon[i]));
206 				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
207 					break;
208 
209 				/* Abbreviated name. */
210 				len = strlen(_ctloc(abmon[i]));
211 				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
212 					break;
213 			}
214 
215 			/* Nothing matched. */
216 			if (i == 12)
217 				return (NULL);
218 
219 			tm->tm_mon = i;
220 			bp += len;
221 			break;
222 
223 		case 'C':	/* The century number. */
224 			_LEGAL_ALT(_ALT_E);
225 			if (!(_conv_num(&bp, &i, 0, 99)))
226 				return (NULL);
227 
228 			century = i * 100;
229 			break;
230 
231 		case 'd':	/* The day of month. */
232 		case 'e':
233 			_LEGAL_ALT(_ALT_O);
234 			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
235 				return (NULL);
236 			break;
237 
238 		case 'k':	/* The hour (24-hour clock representation). */
239 			_LEGAL_ALT(0);
240 			/* FALLTHROUGH */
241 		case 'H':
242 			_LEGAL_ALT(_ALT_O);
243 			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
244 				return (NULL);
245 			break;
246 
247 		case 'l':	/* The hour (12-hour clock representation). */
248 			_LEGAL_ALT(0);
249 			/* FALLTHROUGH */
250 		case 'I':
251 			_LEGAL_ALT(_ALT_O);
252 			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
253 				return (NULL);
254 			break;
255 
256 		case 'j':	/* The day of year. */
257 			_LEGAL_ALT(0);
258 			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
259 				return (NULL);
260 			tm->tm_yday--;
261 			break;
262 
263 		case 'M':	/* The minute. */
264 			_LEGAL_ALT(_ALT_O);
265 			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
266 				return (NULL);
267 			break;
268 
269 		case 'm':	/* The month. */
270 			_LEGAL_ALT(_ALT_O);
271 			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
272 				return (NULL);
273 			tm->tm_mon--;
274 			break;
275 
276 		case 'p':	/* The locale's equivalent of AM/PM. */
277 			_LEGAL_ALT(0);
278 			/* AM? */
279 			len = strlen(_ctloc(am_pm[0]));
280 			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
281 				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
282 					return (NULL);
283 				else if (tm->tm_hour == 12)
284 					tm->tm_hour = 0;
285 
286 				bp += len;
287 				break;
288 			}
289 			/* PM? */
290 			len = strlen(_ctloc(am_pm[1]));
291 			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
292 				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
293 					return (NULL);
294 				else if (tm->tm_hour < 12)
295 					tm->tm_hour += 12;
296 
297 				bp += len;
298 				break;
299 			}
300 
301 			/* Nothing matched. */
302 			return (NULL);
303 
304 		case 'S':	/* The seconds. */
305 			_LEGAL_ALT(_ALT_O);
306 			if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
307 				return (NULL);
308 			break;
309 
310 		case 'U':	/* The week of year, beginning on sunday. */
311 		case 'W':	/* The week of year, beginning on monday. */
312 			_LEGAL_ALT(_ALT_O);
313 			/*
314 			 * XXX This is bogus, as we can not assume any valid
315 			 * information present in the tm structure at this
316 			 * point to calculate a real value, so just check the
317 			 * range for now.
318 			 */
319 			 if (!(_conv_num(&bp, &i, 0, 53)))
320 				return (NULL);
321 			 break;
322 
323 		case 'w':	/* The day of week, beginning on sunday. */
324 			_LEGAL_ALT(_ALT_O);
325 			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
326 				return (NULL);
327 			break;
328 
329 		case 'Y':	/* The year. */
330 			_LEGAL_ALT(_ALT_E);
331 			if (!(_conv_num(&bp, &i, 0, INT_MAX)))
332 				return (NULL);
333 
334 			relyear = -1;
335 			tm->tm_year = i - TM_YEAR_BASE;
336 			break;
337 
338 		case 'y':	/* The year within the century (2 digits). */
339 			_LEGAL_ALT(_ALT_E | _ALT_O);
340 			if (!(_conv_num(&bp, &relyear, 0, 99)))
341 				return (NULL);
342 			break;
343 
344 		/*
345 		 * Miscellaneous conversions.
346 		 */
347 		case 'n':	/* Any kind of white-space. */
348 		case 't':
349 			_LEGAL_ALT(0);
350 			while (isspace(*bp))
351 				bp++;
352 			break;
353 
354 
355 		default:	/* Unknown/unsupported conversion. */
356 			return (NULL);
357 		}
358 
359 
360 	}
361 
362 	/*
363 	 * We need to evaluate the two digit year spec (%y)
364 	 * last as we can get a century spec (%C) at any time.
365 	 */
366 	if (relyear != -1) {
367 		if (century == TM_YEAR_BASE) {
368 			if (relyear <= 68)
369 				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
370 			else
371 				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
372 		} else {
373 			tm->tm_year = relyear + century - TM_YEAR_BASE;
374 		}
375 	}
376 
377 	return ((char *)bp);
378 }
379 
380 
381 static int
382 _conv_num(buf, dest, llim, ulim)
383 	const char **buf;
384 	int *dest;
385 	int llim, ulim;
386 {
387 	*dest = 0;
388 
389 	if (**buf < '0' || **buf > '9')
390 		return (0);
391 
392 	do {
393 		*dest *= 10;
394 		*dest += *(*buf)++ - '0';
395 	} while ((*dest * 10 <= ulim) && **buf >= '0' && **buf <= '9');
396 
397 	if (*dest < llim || *dest > ulim || isdigit(**buf))
398 		return (0);
399 
400 	return (1);
401 }
402