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