xref: /freebsd/lib/libc/stdtime/strptime.c (revision 3c87aa1d)
1 /*
2  * Powerdog Industries kindly requests feedback from anyone modifying
3  * this function:
4  *
5  * Date: Thu, 05 Jun 1997 23:17:17 -0400
6  * From: Kevin Ruddy <kevin.ruddy@powerdog.com>
7  * To: James FitzGibbon <james@nexis.net>
8  * Subject: Re: Use of your strptime(3) code (fwd)
9  *
10  * The reason for the "no mod" clause was so that modifications would
11  * come back and we could integrate them and reissue so that a wider
12  * audience could use it (thereby spreading the wealth).  This has
13  * made it possible to get strptime to work on many operating systems.
14  * I'm not sure why that's "plain unacceptable" to the FreeBSD team.
15  *
16  * Anyway, you can change it to "with or without modification" as
17  * you see fit.  Enjoy.
18  *
19  * Kevin Ruddy
20  * Powerdog Industries, Inc.
21  */
22 /*
23  * Copyright (c) 1994 Powerdog Industries.  All rights reserved.
24  *
25  * Copyright (c) 2011 The FreeBSD Foundation
26  * All rights reserved.
27  * Portions of this software were developed by David Chisnall
28  * under sponsorship from the FreeBSD Foundation.
29  *
30  * Redistribution and use in source and binary forms, with or without
31  * modification, are permitted provided that the following conditions
32  * are met:
33  * 1. Redistributions of source code must retain the above copyright
34  *    notice, this list of conditions and the following disclaimer.
35  * 2. Redistributions in binary form must reproduce the above copyright
36  *    notice, this list of conditions and the following disclaimer
37  *    in the documentation and/or other materials provided with the
38  *    distribution.
39  * 3. All advertising materials mentioning features or use of this
40  *    software must display the following acknowledgement:
41  *      This product includes software developed by Powerdog Industries.
42  * 4. The name of Powerdog Industries may not be used to endorse or
43  *    promote products derived from this software without specific prior
44  *    written permission.
45  *
46  * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
47  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
49  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
50  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
51  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
52  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
53  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
54  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
55  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
56  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57  */
58 
59 #include <sys/cdefs.h>
60 #ifndef lint
61 #ifndef NOID
62 static char copyright[] __unused =
63 "@(#) Copyright (c) 1994 Powerdog Industries.  All rights reserved.";
64 static char sccsid[] __unused = "@(#)strptime.c	0.1 (Powerdog) 94/03/27";
65 #endif /* !defined NOID */
66 #endif /* not lint */
67 __FBSDID("$FreeBSD$");
68 
69 #include "namespace.h"
70 #include <time.h>
71 #include <ctype.h>
72 #include <errno.h>
73 #include <stdlib.h>
74 #include <string.h>
75 #include <pthread.h>
76 #include "un-namespace.h"
77 #include "libc_private.h"
78 #include "timelocal.h"
79 
80 static char * _strptime(const char *, const char *, struct tm *, int *, locale_t);
81 
82 #define asizeof(a)	(sizeof (a) / sizeof ((a)[0]))
83 
84 static char *
85 _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
86 		locale_t locale)
87 {
88 	char	c;
89 	const char *ptr;
90 	int	i,
91 		len;
92 	int Ealternative, Oalternative;
93 	struct lc_time_T *tptr = __get_current_time_locale(locale);
94 
95 	ptr = fmt;
96 	while (*ptr != 0) {
97 		if (*buf == 0)
98 			break;
99 
100 		c = *ptr++;
101 
102 		if (c != '%') {
103 			if (isspace_l((unsigned char)c, locale))
104 				while (*buf != 0 &&
105 				       isspace_l((unsigned char)*buf, locale))
106 					buf++;
107 			else if (c != *buf++)
108 				return 0;
109 			continue;
110 		}
111 
112 		Ealternative = 0;
113 		Oalternative = 0;
114 label:
115 		c = *ptr++;
116 		switch (c) {
117 		case 0:
118 		case '%':
119 			if (*buf++ != '%')
120 				return 0;
121 			break;
122 
123 		case '+':
124 			buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale);
125 			if (buf == 0)
126 				return 0;
127 			break;
128 
129 		case 'C':
130 			if (!isdigit_l((unsigned char)*buf, locale))
131 				return 0;
132 
133 			/* XXX This will break for 3-digit centuries. */
134 			len = 2;
135 			for (i = 0; len && *buf != 0 &&
136 			     isdigit_l((unsigned char)*buf, locale); buf++) {
137 				i *= 10;
138 				i += *buf - '0';
139 				len--;
140 			}
141 			if (i < 19)
142 				return 0;
143 
144 			tm->tm_year = i * 100 - 1900;
145 			break;
146 
147 		case 'c':
148 			buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale);
149 			if (buf == 0)
150 				return 0;
151 			break;
152 
153 		case 'D':
154 			buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale);
155 			if (buf == 0)
156 				return 0;
157 			break;
158 
159 		case 'E':
160 			if (Ealternative || Oalternative)
161 				break;
162 			Ealternative++;
163 			goto label;
164 
165 		case 'O':
166 			if (Ealternative || Oalternative)
167 				break;
168 			Oalternative++;
169 			goto label;
170 
171 		case 'F':
172 			buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale);
173 			if (buf == 0)
174 				return 0;
175 			break;
176 
177 		case 'R':
178 			buf = _strptime(buf, "%H:%M", tm, GMTp, locale);
179 			if (buf == 0)
180 				return 0;
181 			break;
182 
183 		case 'r':
184 			buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale);
185 			if (buf == 0)
186 				return 0;
187 			break;
188 
189 		case 'T':
190 			buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale);
191 			if (buf == 0)
192 				return 0;
193 			break;
194 
195 		case 'X':
196 			buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale);
197 			if (buf == 0)
198 				return 0;
199 			break;
200 
201 		case 'x':
202 			buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale);
203 			if (buf == 0)
204 				return 0;
205 			break;
206 
207 		case 'j':
208 			if (!isdigit_l((unsigned char)*buf, locale))
209 				return 0;
210 
211 			len = 3;
212 			for (i = 0; len && *buf != 0 &&
213 			     isdigit_l((unsigned char)*buf, locale); buf++){
214 				i *= 10;
215 				i += *buf - '0';
216 				len--;
217 			}
218 			if (i < 1 || i > 366)
219 				return 0;
220 
221 			tm->tm_yday = i - 1;
222 			break;
223 
224 		case 'M':
225 		case 'S':
226 			if (*buf == 0 ||
227 				isspace_l((unsigned char)*buf, locale))
228 				break;
229 
230 			if (!isdigit_l((unsigned char)*buf, locale))
231 				return 0;
232 
233 			len = 2;
234 			for (i = 0; len && *buf != 0 &&
235 				isdigit_l((unsigned char)*buf, locale); buf++){
236 				i *= 10;
237 				i += *buf - '0';
238 				len--;
239 			}
240 
241 			if (c == 'M') {
242 				if (i > 59)
243 					return 0;
244 				tm->tm_min = i;
245 			} else {
246 				if (i > 60)
247 					return 0;
248 				tm->tm_sec = i;
249 			}
250 
251 			if (*buf != 0 &&
252 				isspace_l((unsigned char)*buf, locale))
253 				while (*ptr != 0 &&
254 				       !isspace_l((unsigned char)*ptr, locale))
255 					ptr++;
256 			break;
257 
258 		case 'H':
259 		case 'I':
260 		case 'k':
261 		case 'l':
262 			/*
263 			 * Of these, %l is the only specifier explicitly
264 			 * documented as not being zero-padded.  However,
265 			 * there is no harm in allowing zero-padding.
266 			 *
267 			 * XXX The %l specifier may gobble one too many
268 			 * digits if used incorrectly.
269 			 */
270 			if (!isdigit_l((unsigned char)*buf, locale))
271 				return 0;
272 
273 			len = 2;
274 			for (i = 0; len && *buf != 0 &&
275 			     isdigit_l((unsigned char)*buf, locale); buf++) {
276 				i *= 10;
277 				i += *buf - '0';
278 				len--;
279 			}
280 			if (c == 'H' || c == 'k') {
281 				if (i > 23)
282 					return 0;
283 			} else if (i > 12)
284 				return 0;
285 
286 			tm->tm_hour = i;
287 
288 			if (*buf != 0 &&
289 			    isspace_l((unsigned char)*buf, locale))
290 				while (*ptr != 0 &&
291 				       !isspace_l((unsigned char)*ptr, locale))
292 					ptr++;
293 			break;
294 
295 		case 'p':
296 			/*
297 			 * XXX This is bogus if parsed before hour-related
298 			 * specifiers.
299 			 */
300 			len = strlen(tptr->am);
301 			if (strncasecmp_l(buf, tptr->am, len, locale) == 0) {
302 				if (tm->tm_hour > 12)
303 					return 0;
304 				if (tm->tm_hour == 12)
305 					tm->tm_hour = 0;
306 				buf += len;
307 				break;
308 			}
309 
310 			len = strlen(tptr->pm);
311 			if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) {
312 				if (tm->tm_hour > 12)
313 					return 0;
314 				if (tm->tm_hour != 12)
315 					tm->tm_hour += 12;
316 				buf += len;
317 				break;
318 			}
319 
320 			return 0;
321 
322 		case 'A':
323 		case 'a':
324 			for (i = 0; i < asizeof(tptr->weekday); i++) {
325 				len = strlen(tptr->weekday[i]);
326 				if (strncasecmp_l(buf, tptr->weekday[i],
327 						len, locale) == 0)
328 					break;
329 				len = strlen(tptr->wday[i]);
330 				if (strncasecmp_l(buf, tptr->wday[i],
331 						len, locale) == 0)
332 					break;
333 			}
334 			if (i == asizeof(tptr->weekday))
335 				return 0;
336 
337 			tm->tm_wday = i;
338 			buf += len;
339 			break;
340 
341 		case 'U':
342 		case 'W':
343 			/*
344 			 * XXX This is bogus, as we can not assume any valid
345 			 * information present in the tm structure at this
346 			 * point to calculate a real value, so just check the
347 			 * range for now.
348 			 */
349 			if (!isdigit_l((unsigned char)*buf, locale))
350 				return 0;
351 
352 			len = 2;
353 			for (i = 0; len && *buf != 0 &&
354 			     isdigit_l((unsigned char)*buf, locale); buf++) {
355 				i *= 10;
356 				i += *buf - '0';
357 				len--;
358 			}
359 			if (i > 53)
360 				return 0;
361 
362 			if (*buf != 0 &&
363 			    isspace_l((unsigned char)*buf, locale))
364 				while (*ptr != 0 &&
365 				       !isspace_l((unsigned char)*ptr, locale))
366 					ptr++;
367 			break;
368 
369 		case 'w':
370 			if (!isdigit_l((unsigned char)*buf, locale))
371 				return 0;
372 
373 			i = *buf - '0';
374 			if (i > 6)
375 				return 0;
376 
377 			tm->tm_wday = i;
378 
379 			if (*buf != 0 &&
380 			    isspace_l((unsigned char)*buf, locale))
381 				while (*ptr != 0 &&
382 				       !isspace_l((unsigned char)*ptr, locale))
383 					ptr++;
384 			break;
385 
386 		case 'd':
387 		case 'e':
388 			/*
389 			 * The %e specifier is explicitly documented as not
390 			 * being zero-padded but there is no harm in allowing
391 			 * such padding.
392 			 *
393 			 * XXX The %e specifier may gobble one too many
394 			 * digits if used incorrectly.
395 			 */
396 			if (!isdigit_l((unsigned char)*buf, locale))
397 				return 0;
398 
399 			len = 2;
400 			for (i = 0; len && *buf != 0 &&
401 			     isdigit_l((unsigned char)*buf, locale); buf++) {
402 				i *= 10;
403 				i += *buf - '0';
404 				len--;
405 			}
406 			if (i > 31)
407 				return 0;
408 
409 			tm->tm_mday = i;
410 
411 			if (*buf != 0 &&
412 			    isspace_l((unsigned char)*buf, locale))
413 				while (*ptr != 0 &&
414 				       !isspace_l((unsigned char)*ptr, locale))
415 					ptr++;
416 			break;
417 
418 		case 'B':
419 		case 'b':
420 		case 'h':
421 			for (i = 0; i < asizeof(tptr->month); i++) {
422 				if (Oalternative) {
423 					if (c == 'B') {
424 						len = strlen(tptr->alt_month[i]);
425 						if (strncasecmp_l(buf,
426 								tptr->alt_month[i],
427 								len, locale) == 0)
428 							break;
429 					}
430 				} else {
431 					len = strlen(tptr->month[i]);
432 					if (strncasecmp_l(buf, tptr->month[i],
433 							len, locale) == 0)
434 						break;
435 				}
436 			}
437 			/*
438 			 * Try the abbreviated month name if the full name
439 			 * wasn't found and Oalternative was not requested.
440 			 */
441 			if (i == asizeof(tptr->month) && !Oalternative) {
442 				for (i = 0; i < asizeof(tptr->month); i++) {
443 					len = strlen(tptr->mon[i]);
444 					if (strncasecmp_l(buf, tptr->mon[i],
445 							len, locale) == 0)
446 						break;
447 				}
448 			}
449 			if (i == asizeof(tptr->month))
450 				return 0;
451 
452 			tm->tm_mon = i;
453 			buf += len;
454 			break;
455 
456 		case 'm':
457 			if (!isdigit_l((unsigned char)*buf, locale))
458 				return 0;
459 
460 			len = 2;
461 			for (i = 0; len && *buf != 0 &&
462 			     isdigit_l((unsigned char)*buf, locale); buf++) {
463 				i *= 10;
464 				i += *buf - '0';
465 				len--;
466 			}
467 			if (i < 1 || i > 12)
468 				return 0;
469 
470 			tm->tm_mon = i - 1;
471 
472 			if (*buf != 0 &&
473 			    isspace_l((unsigned char)*buf, locale))
474 				while (*ptr != 0 &&
475 				       !isspace_l((unsigned char)*ptr, locale))
476 					ptr++;
477 			break;
478 
479 		case 's':
480 			{
481 			char *cp;
482 			int sverrno;
483 			long n;
484 			time_t t;
485 
486 			sverrno = errno;
487 			errno = 0;
488 			n = strtol_l(buf, &cp, 10, locale);
489 			if (errno == ERANGE || (long)(t = n) != n) {
490 				errno = sverrno;
491 				return 0;
492 			}
493 			errno = sverrno;
494 			buf = cp;
495 			gmtime_r(&t, tm);
496 			*GMTp = 1;
497 			}
498 			break;
499 
500 		case 'Y':
501 		case 'y':
502 			if (*buf == 0 ||
503 			    isspace_l((unsigned char)*buf, locale))
504 				break;
505 
506 			if (!isdigit_l((unsigned char)*buf, locale))
507 				return 0;
508 
509 			len = (c == 'Y') ? 4 : 2;
510 			for (i = 0; len && *buf != 0 &&
511 			     isdigit_l((unsigned char)*buf, locale); buf++) {
512 				i *= 10;
513 				i += *buf - '0';
514 				len--;
515 			}
516 			if (c == 'Y')
517 				i -= 1900;
518 			if (c == 'y' && i < 69)
519 				i += 100;
520 			if (i < 0)
521 				return 0;
522 
523 			tm->tm_year = i;
524 
525 			if (*buf != 0 &&
526 			    isspace_l((unsigned char)*buf, locale))
527 				while (*ptr != 0 &&
528 				       !isspace_l((unsigned char)*ptr, locale))
529 					ptr++;
530 			break;
531 
532 		case 'Z':
533 			{
534 			const char *cp;
535 			char *zonestr;
536 
537 			for (cp = buf; *cp &&
538 			     isupper_l((unsigned char)*cp, locale); ++cp) {
539 				/*empty*/}
540 			if (cp - buf) {
541 				zonestr = alloca(cp - buf + 1);
542 				strncpy(zonestr, buf, cp - buf);
543 				zonestr[cp - buf] = '\0';
544 				tzset();
545 				if (0 == strcmp(zonestr, "GMT")) {
546 				    *GMTp = 1;
547 				} else if (0 == strcmp(zonestr, tzname[0])) {
548 				    tm->tm_isdst = 0;
549 				} else if (0 == strcmp(zonestr, tzname[1])) {
550 				    tm->tm_isdst = 1;
551 				} else {
552 				    return 0;
553 				}
554 				buf += cp - buf;
555 			}
556 			}
557 			break;
558 
559 		case 'z':
560 			{
561 			int sign = 1;
562 
563 			if (*buf != '+') {
564 				if (*buf == '-')
565 					sign = -1;
566 				else
567 					return 0;
568 			}
569 
570 			buf++;
571 			i = 0;
572 			for (len = 4; len > 0; len--) {
573 				if (isdigit_l((unsigned char)*buf, locale)) {
574 					i *= 10;
575 					i += *buf - '0';
576 					buf++;
577 				} else
578 					return 0;
579 			}
580 
581 			tm->tm_hour -= sign * (i / 100);
582 			tm->tm_min  -= sign * (i % 100);
583 			*GMTp = 1;
584 			}
585 			break;
586 		}
587 	}
588 	return (char *)buf;
589 }
590 
591 
592 char *
593 strptime_l(const char * __restrict buf, const char * __restrict fmt,
594     struct tm * __restrict tm, locale_t loc)
595 {
596 	char *ret;
597 	int gmt;
598 	FIX_LOCALE(loc);
599 
600 	gmt = 0;
601 	ret = _strptime(buf, fmt, tm, &gmt, loc);
602 	if (ret && gmt) {
603 		time_t t = timegm(tm);
604 		localtime_r(&t, tm);
605 	}
606 
607 	return (ret);
608 }
609 char *
610 strptime(const char * __restrict buf, const char * __restrict fmt,
611     struct tm * __restrict tm)
612 {
613 	return strptime_l(buf, fmt, tm, __get_locale());
614 }
615