]> git.saurik.com Git - apple/libc.git/blob - stdtime/FreeBSD/strptime.c
2cac16184cf92bd22cd63fa425331b7fe07b2af3
[apple/libc.git] / stdtime / FreeBSD / strptime.c
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 * Redistribution and use in source and binary forms, with or without
26 * modification, are permitted provided that the following conditions
27 * are met:
28 * 1. Redistributions of source code must retain the above copyright
29 * notice, this list of conditions and the following disclaimer.
30 * 2. Redistributions in binary form must reproduce the above copyright
31 * notice, this list of conditions and the following disclaimer
32 * in the documentation and/or other materials provided with the
33 * distribution.
34 * 3. All advertising materials mentioning features or use of this
35 * software must display the following acknowledgement:
36 * This product includes software developed by Powerdog Industries.
37 * 4. The name of Powerdog Industries may not be used to endorse or
38 * promote products derived from this software without specific prior
39 * written permission.
40 *
41 * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
42 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
44 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
45 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
46 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
47 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
48 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
49 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
50 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
51 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 */
53
54 #include <sys/cdefs.h>
55 #ifndef lint
56 #ifndef NOID
57 static char copyright[] __unused =
58 "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved.";
59 static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27";
60 #endif /* !defined NOID */
61 #endif /* not lint */
62 __FBSDID("$FreeBSD: src/lib/libc/stdtime/strptime.c,v 1.37 2009/09/02 04:56:30 ache Exp $");
63
64 #include "xlocale_private.h"
65
66 #include "namespace.h"
67 #include <time.h>
68 #include <ctype.h>
69 #include <errno.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <pthread.h>
74 #include <stdint.h>
75 #include <limits.h>
76 #include "un-namespace.h"
77 #include "libc_private.h"
78 #include "timelocal.h"
79
80 time_t _mktime(struct tm *, const char *);
81
82 #define asizeof(a) (sizeof (a) / sizeof ((a)[0]))
83
84 enum {CONVERT_NONE, CONVERT_GMT, CONVERT_ZONE};
85
86 #define _strptime(b,f,t,c,l) _strptime0(b,f,t,c,l,-1,0,-1,-1,'U')
87
88 #define WEEK_U 'U'
89 #define WEEK_V 'V'
90 #define WEEK_W 'W'
91
92 static int
93 calcweeknum(struct tm *tm, int weeknum, int wday, int year, int kind)
94 {
95 struct tm t;
96 int off;
97
98 bzero(&t, sizeof(t));
99 t.tm_mday = kind == WEEK_V ? 4 : 1;
100 t.tm_hour = 12; /* avoid any DST effects */
101 t.tm_year = year;
102 if (mktime(&t) == (time_t)-1) return 0;
103 off = t.tm_wday;
104
105 bzero(&t, sizeof(t));
106 if (kind != WEEK_U) {
107 off = (off + 6) % 7;
108 wday = (wday + 6) % 7;
109 }
110 if (kind == WEEK_V) {
111 t.tm_mday = 7 * weeknum + wday - off - 3;
112 } else {
113 if(off == 0) off = 7;
114 t.tm_mday = 7 * weeknum + wday - off + 1;
115 }
116 t.tm_hour = 12; /* avoid any DST effects */
117 t.tm_year = year;
118 if (mktime(&t) == (time_t)-1) return 0;
119
120 tm->tm_mday = t.tm_mday;
121 tm->tm_mon = t.tm_mon;
122 tm->tm_yday = t.tm_yday;
123 return 1;
124 }
125
126 static char *
127 _strptime0(const char *buf, const char *fmt, struct tm *tm, int *convp, locale_t loc, int year, int yday, int wday, int weeknum, int weekkind)
128 {
129 char c;
130 const char *ptr;
131 int i,
132 len;
133 int Ealternative, Oalternative;
134 struct lc_time_T *tptr = __get_current_time_locale(loc);
135
136 ptr = fmt;
137 while (*ptr != 0) {
138 if (*buf == 0) {
139 fmt = ptr;
140 while (isspace_l((unsigned char)*ptr, loc)) {
141 ptr++;
142 }
143 return ((*ptr)==0) ? (char *)fmt : 0; /* trailing whitespace is ok */
144 }
145
146 c = *ptr++;
147
148 if (c != '%') {
149 if (isspace_l((unsigned char)c, loc))
150 while (*buf != 0 && isspace_l((unsigned char)*buf, loc))
151 buf++;
152 else if (c != *buf++)
153 return 0;
154 continue;
155 }
156
157 Ealternative = 0;
158 Oalternative = 0;
159 label:
160 c = *ptr++;
161 switch (c) {
162 case 0:
163 case '%':
164 if (*buf++ != '%')
165 return 0;
166 break;
167
168 case '+':
169 buf = _strptime(buf, tptr->date_fmt, tm, convp, loc);
170 if (buf == 0)
171 return 0;
172 break;
173
174 case 'C':
175 if (!isdigit_l((unsigned char)*buf, loc))
176 return 0;
177
178 /* XXX This will break for 3-digit centuries. */
179 len = 2;
180 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
181 i *= 10;
182 i += *buf - '0';
183 len--;
184 }
185 if (i < 19)
186 return 0;
187
188 if (year != -1)
189 tm->tm_year = (year % 100) + i * 100 - 1900;
190 else
191 tm->tm_year = i * 100 - 1900;
192 year = tm->tm_year;
193 break;
194
195 case 'c':
196 buf = _strptime(buf, tptr->c_fmt, tm, convp, loc);
197 if (buf == 0)
198 return 0;
199 break;
200
201 case 'D':
202 buf = _strptime(buf, "%m/%d/%y", tm, convp, loc);
203 if (buf == 0)
204 return 0;
205 break;
206
207 case 'E':
208 if (Ealternative || Oalternative)
209 break;
210 Ealternative++;
211 goto label;
212
213 case 'O':
214 if (Ealternative || Oalternative)
215 break;
216 Oalternative++;
217 goto label;
218
219 case 'F':
220 buf = _strptime(buf, "%Y-%m-%d", tm, convp, loc);
221 if (buf == 0)
222 return 0;
223 break;
224
225 case 'R':
226 buf = _strptime(buf, "%H:%M", tm, convp, loc);
227 if (buf == 0)
228 return 0;
229 break;
230
231 case 'r':
232 buf = _strptime(buf, tptr->ampm_fmt, tm, convp, loc);
233 if (buf == 0)
234 return 0;
235 break;
236
237 case 'n':
238 case 't':
239 if (!isspace((unsigned char)*buf))
240 return 0;
241 while (isspace((unsigned char)*buf))
242 buf++;
243 break;
244
245 case 'T':
246 buf = _strptime(buf, "%H:%M:%S", tm, convp, loc);
247 if (buf == 0)
248 return 0;
249 break;
250
251 case 'X':
252 buf = _strptime(buf, tptr->X_fmt, tm, convp, loc);
253 if (buf == 0)
254 return 0;
255 break;
256
257 case 'x':
258 buf = _strptime(buf, tptr->x_fmt, tm, convp, loc);
259 if (buf == 0)
260 return 0;
261 break;
262
263 case 'j':
264 if (!isdigit_l((unsigned char)*buf, loc))
265 return 0;
266
267 len = 3;
268 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
269 i *= 10;
270 i += *buf - '0';
271 len--;
272 }
273 if (i < 1 || i > 366)
274 return 0;
275
276 tm->tm_yday = yday = i - 1;
277 break;
278
279 case 'M':
280 case 'S':
281 if (*buf == 0 || isspace_l((unsigned char)*buf, loc))
282 break;
283
284 if (!isdigit_l((unsigned char)*buf, loc))
285 return 0;
286
287 len = 2;
288 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
289 i *= 10;
290 i += *buf - '0';
291 len--;
292 }
293
294 if (c == 'M') {
295 if (i > 59)
296 return 0;
297 tm->tm_min = i;
298 } else {
299 if (i > 60)
300 return 0;
301 tm->tm_sec = i;
302 }
303
304 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
305 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
306 ptr++;
307 break;
308
309 case 'H':
310 case 'I':
311 case 'k':
312 case 'l':
313 /*
314 * Of these, %l is the only specifier explicitly
315 * documented as not being zero-padded. However,
316 * there is no harm in allowing zero-padding.
317 *
318 * XXX The %l specifier may gobble one too many
319 * digits if used incorrectly.
320 */
321 if (!isdigit_l((unsigned char)*buf, loc))
322 return 0;
323
324 len = 2;
325 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
326 i *= 10;
327 i += *buf - '0';
328 len--;
329 }
330 if (c == 'H' || c == 'k') {
331 if (i > 23)
332 return 0;
333 } else if (i > 12)
334 return 0;
335
336 tm->tm_hour = i;
337
338 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
339 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
340 ptr++;
341 break;
342
343 case 'p':
344 /*
345 * XXX This is bogus if parsed before hour-related
346 * specifiers.
347 */
348 len = strlen(tptr->am);
349 if (strncasecmp_l(buf, tptr->am, len, loc) == 0) {
350 if (tm->tm_hour > 12)
351 return 0;
352 if (tm->tm_hour == 12)
353 tm->tm_hour = 0;
354 buf += len;
355 break;
356 }
357
358 len = strlen(tptr->pm);
359 if (strncasecmp_l(buf, tptr->pm, len, loc) == 0) {
360 if (tm->tm_hour > 12)
361 return 0;
362 if (tm->tm_hour != 12)
363 tm->tm_hour += 12;
364 buf += len;
365 break;
366 }
367
368 return 0;
369
370 case 'A':
371 case 'a':
372 for (i = 0; i < asizeof(tptr->weekday); i++) {
373 len = strlen(tptr->weekday[i]);
374 if (strncasecmp_l(buf, tptr->weekday[i],
375 len, loc) == 0)
376 break;
377 len = strlen(tptr->wday[i]);
378 if (strncasecmp_l(buf, tptr->wday[i],
379 len, loc) == 0)
380 break;
381 }
382 if (i == asizeof(tptr->weekday))
383 return 0;
384
385 tm->tm_wday = wday = i;
386 buf += len;
387 break;
388
389 case 'U': /* Sunday week */
390 case 'V': /* ISO 8601 week */
391 case 'W': /* Monday week */
392 if (!isdigit_l((unsigned char)*buf, loc))
393 return 0;
394
395 len = 2;
396 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
397 i *= 10;
398 i += *buf - '0';
399 len--;
400 }
401 if (i > 53)
402 return 0;
403 if (c == 'V' && i < 1)
404 return 0;
405
406 weeknum = i;
407 weekkind = c;
408
409 /* Calculate mon/mday/yday if we have enough data */
410 if ((year != -1) && (wday != -1)) {
411 if (!calcweeknum(tm, weeknum, wday, year, weekkind)) return 0;
412 }
413 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
414 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
415 ptr++;
416 break;
417
418 case 'u': /* [1,7] */
419 case 'w': /* [0,6] */
420 if (!isdigit_l((unsigned char)*buf, loc))
421 return 0;
422
423 i = *buf - '0';
424 if (i > 6 + (c == 'u'))
425 return 0;
426 if (i == 7)
427 i = 0;
428 tm->tm_wday = wday = i;
429
430 /* Calculate mon/mday/yday if we have enough data */
431 if ((year != -1) && (weeknum != -1)) {
432 if (!calcweeknum(tm, weeknum, wday, year, weekkind)) return 0;
433 }
434
435 buf++;
436 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
437 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
438 ptr++;
439 break;
440
441 case 'd':
442 case 'e':
443 /*
444 * The %e specifier is explicitly documented as not
445 * being zero-padded but there is no harm in allowing
446 * such padding.
447 *
448 * XXX The %e specifier may gobble one too many
449 * digits if used incorrectly.
450 */
451 /* Leading space is ok if date is single digit */
452 len = 2;
453 if (isspace_l((unsigned char)buf[0], loc) &&
454 isdigit_l((unsigned char)buf[1], loc) &&
455 !isdigit_l((unsigned char)buf[2], loc)) {
456 len = 1;
457 buf++;
458 }
459 if (!isdigit_l((unsigned char)*buf, loc))
460 return 0;
461
462 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
463 i *= 10;
464 i += *buf - '0';
465 len--;
466 }
467 if (i > 31)
468 return 0;
469
470 tm->tm_mday = i;
471
472 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
473 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
474 ptr++;
475 break;
476
477 case 'B':
478 case 'b':
479 case 'h':
480 for (i = 0; i < asizeof(tptr->month); i++) {
481 if (Oalternative) {
482 if (c == 'B') {
483 len = strlen(tptr->alt_month[i]);
484 if (strncasecmp_l(buf,
485 tptr->alt_month[i],
486 len, loc) == 0)
487 break;
488 }
489 } else {
490 len = strlen(tptr->month[i]);
491 if (strncasecmp_l(buf, tptr->month[i],
492 len, loc) == 0)
493 break;
494 len = strlen(tptr->mon[i]);
495 if (strncasecmp_l(buf, tptr->mon[i],
496 len, loc) == 0)
497 break;
498 }
499 }
500 if (i == asizeof(tptr->month))
501 return 0;
502
503 tm->tm_mon = i;
504 buf += len;
505 break;
506
507 case 'm':
508 if (!isdigit_l((unsigned char)*buf, loc))
509 return 0;
510
511 len = 2;
512 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
513 i *= 10;
514 i += *buf - '0';
515 len--;
516 }
517 if (i < 1 || i > 12)
518 return 0;
519
520 tm->tm_mon = i - 1;
521
522 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
523 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
524 ptr++;
525 break;
526
527 case 's':
528 {
529 char *cp;
530 int sverrno;
531 long n;
532 time_t t;
533
534 sverrno = errno;
535 errno = 0;
536 n = strtol_l(buf, &cp, 10, loc);
537 if (errno == ERANGE || (long)(t = n) != n) {
538 errno = sverrno;
539 return 0;
540 }
541 errno = sverrno;
542 buf = cp;
543 gmtime_r(&t, tm);
544 *convp = CONVERT_GMT;
545 }
546 break;
547
548 case 'Y':
549 case 'y':
550 if (*buf == 0 || isspace_l((unsigned char)*buf, loc))
551 break;
552
553 if (!isdigit_l((unsigned char)*buf, loc))
554 return 0;
555
556 #if __DARWIN_UNIX03
557 if (c == 'Y') {
558 int savei = 0;
559 const char *savebuf = buf;
560 int64_t i64 = 0;
561 int overflow = 0;
562
563 for (len = 0; *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
564 i64 *= 10;
565 i64 += *buf - '0';
566 if (++len <= 4) {
567 savei = i64;
568 savebuf = buf + 1;
569 }
570 if (i64 > INT_MAX) {
571 overflow++;
572 break;
573 }
574 }
575 /*
576 * Conformance requires %Y to be more then 4
577 * digits. However, there are several cases
578 * where %Y is immediately followed by other
579 * digits values. So we do the conformance
580 * case first (as many digits as possible),
581 * and if we fail, we backup and try just 4
582 * digits for %Y.
583 */
584 if (len > 4 && !overflow) {
585 struct tm savetm = *tm;
586 int saveconv = *convp;
587 const char *saveptr = ptr;
588 char *ret;
589
590 if (i64 < 1900)
591 return 0;
592
593 tm->tm_year = i64 - 1900;
594
595 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
596 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
597 ptr++;
598 ret = _strptime0(buf, ptr, tm, convp, loc, tm->tm_year, yday, wday, weeknum, weekkind);
599 if (ret) return ret;
600 /* Failed, so try 4-digit year */
601 *tm = savetm;
602 *convp = saveconv;
603 ptr = saveptr;
604 }
605 buf = savebuf;
606 i = savei;
607 } else {
608 len = 2;
609 #else /* !__DARWIN_UNIX03 */
610 len = (c == 'Y') ? 4 : 2;
611 #endif /* __DARWIN_UNIX03 */
612 for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, loc); buf++) {
613 i *= 10;
614 i += *buf - '0';
615 len--;
616 }
617 #if __DARWIN_UNIX03
618 }
619 #endif /* __DARWIN_UNIX03 */
620 if (c == 'Y')
621 i -= 1900;
622 if (c == 'y' && i < 69)
623 i += 100;
624 if (i < 0)
625 return 0;
626
627 tm->tm_year = year = i;
628
629 /* Calculate mon/mday/yday if we have enough data */
630 if ((weeknum != -1) && (wday != -1)) {
631 if (!calcweeknum(tm, weeknum, wday, year, weekkind)) return 0;
632 }
633
634 if (*buf != 0 && isspace_l((unsigned char)*buf, loc))
635 while (*ptr != 0 && !isspace_l((unsigned char)*ptr, loc) && *ptr != '%')
636 ptr++;
637 break;
638
639 case 'Z':
640 {
641 const char *cp;
642 size_t tzlen, len;
643
644 for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) {/*empty*/}
645 len = cp - buf;
646 if (len == 3 && strncmp(buf, "GMT", 3) == 0) {
647 *convp = CONVERT_GMT;
648 buf += len;
649 break;
650 }
651 tzset();
652 tzlen = strlen(tzname[0]);
653 if (len == tzlen && strncmp(buf, tzname[0], tzlen) == 0) {
654 tm->tm_isdst = 0;
655 buf += len;
656 break;
657 }
658 tzlen = strlen(tzname[1]);
659 if (len == tzlen && strncmp(buf, tzname[1], tzlen) == 0) {
660 tm->tm_isdst = 1;
661 buf += len;
662 break;
663 }
664 return 0;
665 }
666
667 case 'z':
668 {
669 int sign = 1;
670
671 if (*buf != '+') {
672 if (*buf == '-')
673 sign = -1;
674 else
675 return 0;
676 }
677
678 buf++;
679 i = 0;
680 for (len = 4; len > 0; len--) {
681 if (isdigit_l((unsigned char)*buf, loc)) {
682 i *= 10;
683 i += *buf - '0';
684 buf++;
685 } else
686 return 0;
687 }
688
689 tm->tm_hour -= sign * (i / 100);
690 tm->tm_min -= sign * (i % 100);
691 *convp = CONVERT_GMT;
692 }
693 break;
694 }
695 }
696 return (char *)buf;
697 }
698
699
700 char *
701 strptime(const char * __restrict buf, const char * __restrict fmt,
702 struct tm * __restrict tm)
703 {
704 return strptime_l(buf, fmt, tm, __current_locale());
705 }
706
707 extern time_t timeoff(struct tm *, long);
708
709 char *
710 strptime_l(const char * __restrict buf, const char * __restrict fmt,
711 struct tm * __restrict tm, locale_t loc)
712 {
713 char *ret;
714 int conv;
715
716 NORMALIZE_LOCALE(loc);
717 conv = CONVERT_NONE;
718 tm->tm_zone = NULL;
719 ret = _strptime(buf, fmt, tm, &conv, loc);
720 if (ret) {
721 time_t t;
722
723 switch(conv) {
724 case CONVERT_GMT:
725 t = timegm(tm);
726 localtime_r(&t, tm);
727 break;
728 case CONVERT_ZONE:
729 {
730 long offset = tm->tm_gmtoff;
731 tm->tm_gmtoff = 0;
732 t = timeoff(tm, offset);
733 localtime_r(&t, tm);
734 break;
735 }
736 }
737 }
738
739 return (ret);
740 }