]>
git.saurik.com Git - apple/libc.git/blob - stdtime/FreeBSD/strptime.c
2 * Copyright (c) 2014 Gary Mills
3 * Copyright 2011, Nexenta Systems, Inc. All rights reserved.
4 * Copyright (c) 1994 Powerdog Industries. All rights reserved.
6 * Copyright (c) 2011 The FreeBSD Foundation
8 * Portions of this software were developed by David Chisnall
9 * under sponsorship from the FreeBSD Foundation.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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
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.
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.
38 #include <sys/cdefs.h>
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 */
46 __FBSDID("$FreeBSD$");
48 #include "xlocale_private.h"
50 #include "namespace.h"
58 #include "un-namespace.h"
59 #include "libc_private.h"
60 #include "timelocal.h"
63 time_t _mktime(struct tm
*, const char *);
65 #define asizeof(a) (sizeof(a) / sizeof((a)[0]))
67 enum {CONVERT_NONE
, CONVERT_GMT
, CONVERT_ZONE
};
68 enum week_kind
{ WEEK_U
= 'U', WEEK_V
= 'V', WEEK_W
= 'W'};
70 #define _strptime(b,f,t,c,l) _strptime0(b,f,t,c,l,FLAG_NONE,0,WEEK_U)
72 #define FLAG_NONE 0x01
73 #define FLAG_YEAR 0x02
74 #define FLAG_MONTH 0x04
75 #define FLAG_YDAY 0x08
76 #define FLAG_MDAY 0x10
77 #define FLAG_WDAY 0x20
78 #define FLAG_WEEK 0x40
79 #define FLAG_CENTURY 0x100
80 #define FLAG_YEAR_IN_CENTURY 0x200
83 * Calculate the week day of the first day of a year. Valid for
84 * the Gregorian calendar, which began Sept 14, 1752 in the UK
85 * and its colonies. Ref:
86 * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
90 first_wday_of(int year
)
92 return (((2 * (3 - (year
/ 100) % 4)) + (year
% 100) +
93 ((year
% 100) / 4) + (isleap(year
) ? 6 : 0) + 1) % 7);
96 static inline bool is_plus(char c
) {
100 static inline bool is_minus(char c
) {
104 static inline bool is_zero(char c
) {
109 _strptime0(const char *buf
, const char *fmt
, struct tm
*tm
, int *convp
, locale_t locale
, int flags
, int week_number
, enum week_kind week_kind
)
116 int Ealternative
, Oalternative
;
117 const struct lc_time_T
*tptr
= __get_current_time_locale(locale
);
118 static int start_of_month
[2][13] = {
119 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
120 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
129 if (isspace_l((unsigned char)c
, locale
))
131 isspace_l((unsigned char)*buf
, locale
))
133 else if (c
!= *buf
++)
143 // Leading '0' is to be ignored.
145 } else if (is_plus(c
)) {
146 // POSIX sats leading '+' should be ignored, but FreeBSD interprets
147 // "%+" to mean locale-specific date format. Try to handle both by
148 // checking the next character.
150 if (next
!= '\0' && next
!= '%' && !isspace_l(next
, locale
)) {
151 // Use POSIX interpretation.
156 if (isdigit_l(c
, locale
)) {
158 field_width
= c
- '0';
159 while (*ptr
!= '\0' && isdigit_l(*ptr
, locale
)) {
161 field_width
+= *ptr
++ - '0';
172 buf
= _strptime(buf
, tptr
->date_fmt
, tm
, convp
, locale
);
175 flags
|= FLAG_WDAY
| FLAG_MONTH
| FLAG_MDAY
| FLAG_YEAR
;
179 if (!isdigit_l((unsigned char)*buf
, locale
) && !is_plus(*buf
) && !is_minus(*buf
))
182 /* XXX This will break for 3-digit centuries. */
184 len
= field_width
? field_width
: 2;
188 } else if (is_minus(*buf
)) {
193 for (i
= 0; len
&& *buf
!= 0 &&
194 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
204 if (flags
& FLAG_YEAR_IN_CENTURY
) {
205 tm
->tm_year
= i
* 100 + (tm
->tm_year
% 100) - TM_YEAR_BASE
;
206 flags
&= ~FLAG_YEAR_IN_CENTURY
;
208 tm
->tm_year
= i
* 100 - TM_YEAR_BASE
;
210 flags
|= FLAG_CENTURY
;
216 buf
= _strptime(buf
, tptr
->c_fmt
, tm
, convp
, locale
);
219 flags
|= FLAG_WDAY
| FLAG_MONTH
| FLAG_MDAY
| FLAG_YEAR
;
220 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
224 buf
= _strptime(buf
, "%m/%d/%y", tm
, convp
, locale
);
227 flags
|= FLAG_MONTH
| FLAG_MDAY
| FLAG_YEAR
;
228 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
232 if (Ealternative
|| Oalternative
)
235 if (*ptr
== '%') return (NULL
);
239 if (Ealternative
|| Oalternative
)
242 if (*ptr
== '%') return (NULL
);
246 buf
= _strptime(buf
, "%Y-%m-%d", tm
, convp
, locale
);
249 flags
|= FLAG_MONTH
| FLAG_MDAY
| FLAG_YEAR
;
250 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
254 buf
= _strptime(buf
, "%H:%M", tm
, convp
, locale
);
260 buf
= _strptime(buf
, tptr
->ampm_fmt
, tm
, convp
, locale
);
266 buf
= _strptime(buf
, "%H:%M:%S", tm
, convp
, locale
);
272 buf
= _strptime(buf
, tptr
->X_fmt
, tm
, convp
, locale
);
278 buf
= _strptime(buf
, tptr
->x_fmt
, tm
, convp
, locale
);
281 flags
|= FLAG_MONTH
| FLAG_MDAY
| FLAG_YEAR
;
282 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
286 if (!isdigit_l((unsigned char)*buf
, locale
))
289 len
= field_width
? field_width
: 3;
290 for (i
= 0; len
&& *buf
!= 0 &&
291 isdigit_l((unsigned char)*buf
, locale
); buf
++){
296 if (i
< 1 || i
> 366)
307 isspace_l((unsigned char)*buf
, locale
))
310 if (!isdigit_l((unsigned char)*buf
, locale
))
313 len
= field_width
? field_width
: 2;
314 for (i
= 0; len
&& *buf
!= 0 &&
315 isdigit_l((unsigned char)*buf
, locale
); buf
++){
338 * Of these, %l is the only specifier explicitly
339 * documented as not being zero-padded. However,
340 * there is no harm in allowing zero-padding.
342 * XXX The %l specifier may gobble one too many
343 * digits if used incorrectly.
345 if (!isdigit_l((unsigned char)*buf
, locale
))
348 len
= field_width
? field_width
: 2;
349 for (i
= 0; len
&& *buf
!= 0 &&
350 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
355 if (c
== 'H' || c
== 'k') {
367 * XXX This is bogus if parsed before hour-related
370 if (tm
->tm_hour
> 12)
373 len
= (int)strlen(tptr
->am
);
374 if (strncasecmp_l(buf
, tptr
->am
, len
, locale
) == 0) {
375 if (tm
->tm_hour
== 12)
381 len
= (int)strlen(tptr
->pm
);
382 if (strncasecmp_l(buf
, tptr
->pm
, len
, locale
) == 0) {
383 if (tm
->tm_hour
!= 12)
393 for (i
= 0; i
< asizeof(tptr
->weekday
); i
++) {
394 len
= (int)strlen(tptr
->weekday
[i
]);
395 if (strncasecmp_l(buf
, tptr
->weekday
[i
],
398 len
= (int)strlen(tptr
->wday
[i
]);
399 if (strncasecmp_l(buf
, tptr
->wday
[i
],
403 if (i
== asizeof(tptr
->weekday
))
411 case 'U': /* Sunday week */
412 case 'V': /* ISO 8601 week */
413 case 'W': /* Monday week */
414 if (!isdigit_l((unsigned char)*buf
, locale
))
417 len
= field_width
? field_width
: 2;
418 for (i
= 0; len
&& *buf
!= 0 &&
419 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
426 if (c
== WEEK_V
&& i
< 1)
435 case 'u': /* [1,7] */
436 case 'w': /* [0,6] */
437 if (!isdigit_l((unsigned char)*buf
, locale
))
441 if (i
< 0 || i
> 7 || (c
== 'u' && i
< 1) ||
453 * With %e format, our strftime(3) adds a blank space
454 * before single digits.
457 isspace_l((unsigned char)*buf
, locale
))
462 * The %e specifier was once explicitly documented as
463 * not being zero-padded but was later changed to
464 * equivalent to %d. There is no harm in allowing
467 * XXX The %e specifier may gobble one too many
468 * digits if used incorrectly.
470 /* Leading space is ok if date is single digit */
471 len
= field_width
? field_width
: 2;
472 if (isspace_l((unsigned char)buf
[0], locale
) &&
473 isdigit_l((unsigned char)buf
[1], locale
) &&
474 !isdigit_l((unsigned char)buf
[2], locale
)) {
478 if (!isdigit_l((unsigned char)*buf
, locale
))
481 for (i
= 0; len
&& *buf
!= 0 &&
482 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
498 for (i
= 0; i
< asizeof(tptr
->month
); i
++) {
501 len
= (int)strlen(tptr
->alt_month
[i
]);
502 if (strncasecmp_l(buf
,
508 len
= (int)strlen(tptr
->month
[i
]);
509 if (strncasecmp_l(buf
, tptr
->month
[i
],
515 * Try the abbreviated month name if the full name
516 * wasn't found and Oalternative was not requested.
518 if (i
== asizeof(tptr
->month
) && !Oalternative
) {
519 for (i
= 0; i
< asizeof(tptr
->month
); i
++) {
520 len
= (int)strlen(tptr
->mon
[i
]);
521 if (strncasecmp_l(buf
, tptr
->mon
[i
],
526 if (i
== asizeof(tptr
->month
))
536 if (!isdigit_l((unsigned char)*buf
, locale
))
539 len
= field_width
? field_width
: 2;
540 for (i
= 0; len
&& *buf
!= 0 &&
541 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
563 n
= strtol_l(buf
, &cp
, 10, locale
);
564 if (errno
== ERANGE
|| (long)(t
= n
) != n
) {
570 if (gmtime_r(&t
, tm
) == NULL
)
572 *convp
= CONVERT_GMT
;
573 flags
|= FLAG_YDAY
| FLAG_WDAY
| FLAG_MONTH
|
574 FLAG_MDAY
| FLAG_YEAR
;
575 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
582 isspace_l((unsigned char)*buf
, locale
))
585 if (!isdigit_l((unsigned char)*buf
, locale
) && !is_plus(*buf
)
589 len
= field_width
? field_width
: ((c
== 'Y') ? 4 : 2);
594 } else if (is_minus(*buf
)) {
599 for (i
= 0; len
&& *buf
!= 0 &&
600 isdigit_l((unsigned char)*buf
, locale
); buf
++) {
615 } else if (c
== 'y' && flags
& FLAG_CENTURY
) {
616 i
= tm
->tm_year
+ (i
% 100);
617 flags
&= ~FLAG_CENTURY
;
618 } else if (c
== 'y'){
619 if (i
< 69) i
+= 100;
620 flags
|= FLAG_YEAR_IN_CENTURY
;
626 flags
&= ~(FLAG_CENTURY
| FLAG_YEAR_IN_CENTURY
);
636 for (cp
= buf
; *cp
&&
637 isupper_l((unsigned char)*cp
, locale
); ++cp
) {
640 len
= field_width
? field_width
: cp
- buf
;
641 if (len
== 3 && strncmp(buf
, "GMT", 3) == 0) {
642 *convp
= CONVERT_GMT
;
648 tzlen
= strlen(tzname
[0]);
649 if (len
== tzlen
&& strncmp(buf
, tzname
[0], tzlen
) == 0) {
654 tzlen
= strlen(tzname
[1]);
655 if (len
== tzlen
&& strncmp(buf
, tzname
[1], tzlen
) == 0) {
667 if ((buf
[0] != '+' && buf
[0] != '-')
668 || !isdigit_l((unsigned char)buf
[1], locale
)
669 || !isdigit_l((unsigned char)buf
[2], locale
)
670 || !isdigit_l((unsigned char)buf
[3], locale
)
671 || !isdigit_l((unsigned char)buf
[4], locale
))
673 sscanf(buf
, "%c%2d%2d", &sign
, &hr
, &min
);
674 *convp
= CONVERT_ZONE
;
675 tm
->tm_gmtoff
= 60 * (60 * hr
+ min
);
677 tm
->tm_gmtoff
= -tm
->tm_gmtoff
;
684 if (!isspace((unsigned char)*buf
))
686 while (isspace_l((unsigned char)*buf
, locale
))
695 if (!(flags
& FLAG_YDAY
) && (flags
& FLAG_YEAR
)) {
696 if ((flags
& (FLAG_MONTH
| FLAG_MDAY
)) ==
697 (FLAG_MONTH
| FLAG_MDAY
)) {
698 tm
->tm_yday
= start_of_month
[isleap(tm
->tm_year
+
699 TM_YEAR_BASE
)][tm
->tm_mon
] + (tm
->tm_mday
- 1);
701 } else if (flags
& FLAG_WEEK
){
702 int day_offset
= week_kind
== WEEK_U
? TM_SUNDAY
: TM_MONDAY
;
703 int fwo
= first_wday_of(tm
->tm_year
+ TM_YEAR_BASE
);
705 /* No incomplete week (week 0). */
706 if (week_number
== 0 && fwo
== day_offset
)
709 if (!(flags
& FLAG_WDAY
)) {
711 * Set the date to the first Sunday (or Monday)
712 * of the specified week of the year.
714 tm
->tm_wday
= day_offset
;
719 * Start our yday at the first day of the relevant week type.
721 int tmpyday
= (7 - fwo
+ day_offset
) % 7;
724 * ISO Weeks start counting from the first week with at least
725 * four days. If our first week had that, subtract off a week.
727 if (week_kind
== WEEK_V
&& fwo
> TM_MONDAY
&& fwo
<= TM_THURSDAY
) {
730 /* Advance the relevant number of weeks */
731 tmpyday
+= (week_number
- 1) * 7;
732 /* And go to the right day of week */
733 tmpyday
+= (tm
->tm_wday
- day_offset
+ 7) % 7;
735 /* Impossible yday for incomplete week (week 0). */
737 if (flags
& FLAG_WDAY
)
741 tm
->tm_yday
= tmpyday
;
746 if ((flags
& (FLAG_YEAR
| FLAG_YDAY
)) == (FLAG_YEAR
| FLAG_YDAY
)) {
747 if (!(flags
& FLAG_MONTH
)) {
749 while (tm
->tm_yday
>=
750 start_of_month
[isleap(tm
->tm_year
+
756 start_of_month
[isleap(tm
->tm_year
+
763 if (!(flags
& FLAG_MDAY
)) {
764 tm
->tm_mday
= tm
->tm_yday
-
765 start_of_month
[isleap(tm
->tm_year
+ TM_YEAR_BASE
)]
769 if (!(flags
& FLAG_WDAY
)) {
770 wday_offset
= first_wday_of(tm
->tm_year
+ TM_YEAR_BASE
);
771 tm
->tm_wday
= (wday_offset
+ tm
->tm_yday
) % 7;
776 return ((char *)buf
);
781 strptime(const char * __restrict buf
, const char * __restrict fmt
,
782 struct tm
* __restrict tm
)
784 return strptime_l(buf
, fmt
, tm
, __current_locale());
787 extern time_t timeoff(struct tm
*, long);
790 strptime_l(const char * __restrict buf
, const char * __restrict fmt
,
791 struct tm
* __restrict tm
, locale_t loc
)
796 NORMALIZE_LOCALE(loc
);
799 ret
= _strptime(buf
, fmt
, tm
, &conv
, loc
);
810 long offset
= tm
->tm_gmtoff
;
812 t
= timeoff(tm
, offset
);