1 /* Copyright (C) 2002, 2004-2005, 2007, 2009-2024 Free Software Foundation,
3 This file is part of the GNU C Library.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 This file is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
27 # include <langinfo.h>
35 # include "../locale/localeinfo.h"
39 enum ptime_locale_status
{ not, loc
, raw
};
44 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
45 #if defined _LIBC && defined __GNUC__ && __GNUC__ >= 2
46 # define match_string(cs1, s2) \
47 ({ size_t len = strlen (cs1); \
48 int result = __strncasecmp_l ((cs1), (s2), len, locale) == 0; \
49 if (result) (s2) += len; \
52 /* Oh come on. Get a reasonable compiler. */
53 # define match_string(cs1, s2) \
54 (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
56 /* We intentionally do not use isdigit() for testing because this will
57 lead to problems with the wide character version. */
58 #define get_number(from, to, n) \
64 if (*rp < '0' || *rp > '9') \
69 } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \
70 if (val < from || val > to) \
74 # define get_alt_number(from, to, n) \
76 __label__ do_normal; \
78 if (*decided != raw) \
80 val = _nl_parse_alt_digit (&rp HELPER_LOCALE_ARG); \
81 if (val == -1 && *decided != loc) \
86 if (val < from || val > to) \
92 get_number (from, to, n); \
97 # define get_alt_number(from, to, n) \
98 /* We don't have the alternate representation. */ \
99 get_number(from, to, n)
101 #define recursive(new_fmt) \
102 (*(new_fmt) != '\0' \
103 && (rp = __strptime_internal (rp, (new_fmt), tm, \
104 decided, era_cnt LOCALE_ARG)) != NULL)
108 /* This is defined in locale/C-time.c in the GNU libc. */
109 extern const struct locale_data _nl_C_LC_TIME attribute_hidden
;
111 # define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string)
112 # define ab_weekday_name \
113 (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string)
114 # define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string)
115 # define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string)
116 # define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
117 # define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string)
118 # define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string)
119 # define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string)
120 # define HERE_T_FMT_AMPM \
121 (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string)
122 # define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string)
124 # define strncasecmp(s1, s2, n) __strncasecmp (s1, s2, n)
126 static char const weekday_name
[][10] =
128 "Sunday", "Monday", "Tuesday", "Wednesday",
129 "Thursday", "Friday", "Saturday"
131 static char const ab_weekday_name
[][4] =
133 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
135 static char const month_name
[][10] =
137 "January", "February", "March", "April", "May", "June",
138 "July", "August", "September", "October", "November", "December"
140 static char const ab_month_name
[][4] =
142 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
143 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
145 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
146 # define HERE_D_FMT "%m/%d/%y"
147 # define HERE_AM_STR "AM"
148 # define HERE_PM_STR "PM"
149 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
150 # define HERE_T_FMT "%H:%M:%S"
152 static const unsigned short int __mon_yday
[2][13] =
155 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
157 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
162 /* We use this code also for the extended locale handling where the
163 function gets as an additional argument the locale which has to be
164 used. To access the values we have to redefine the _NL_CURRENT
166 # define strptime __strptime_l
168 # define _NL_CURRENT(category, item) \
169 (current->values[_NL_ITEM_INDEX (item)].string)
170 # undef _NL_CURRENT_WORD
171 # define _NL_CURRENT_WORD(category, item) \
172 (current->values[_NL_ITEM_INDEX (item)].word)
173 # define LOCALE_PARAM , locale
174 # define LOCALE_ARG , locale
175 # define LOCALE_PARAM_PROTO , __locale_t locale
176 # define LOCALE_PARAM_DECL __locale_t locale;
177 # define HELPER_LOCALE_ARG , current
178 # define ISSPACE(Ch) __isspace_l (Ch, locale)
180 # define LOCALE_PARAM
182 # define LOCALE_PARAM_DECL
183 # define LOCALE_PARAM_PROTO
184 # define HELPER_LOCALE_ARG
185 # define ISSPACE(Ch) isspace (Ch)
192 /* Nonzero if YEAR is a leap year (every 4 years,
193 except every 100th isn't, and every 400th is). */
194 # define __isleap(year) \
195 ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
198 /* Compute the day of the week. */
200 day_of_the_week (struct tm
*tm
)
202 /* We know that January 1st 1970 was a Thursday (= 4). Compute the
203 difference between this data in the one on TM and so determine
205 int corr_year
= 1900 + tm
->tm_year
- (tm
->tm_mon
< 2);
206 int corr_quad
= corr_year
/ 4;
208 + (365 * (tm
->tm_year
- 70))
210 - ((corr_quad
+ (corr_quad
< 0)) / 25 - (corr_quad
< 0))
211 + ((corr_quad
/ 25) / 4)
212 + __mon_yday
[0][tm
->tm_mon
]
214 tm
->tm_wday
= ((wday
% 7) + 7) % 7;
217 /* Compute the day of the year. */
219 day_of_the_year (struct tm
*tm
)
221 tm
->tm_yday
= (__mon_yday
[__isleap (1900 + tm
->tm_year
)][tm
->tm_mon
]
222 + (tm
->tm_mday
- 1));
232 __strptime_internal (rp
, fmt
, tm
, decided
, era_cnt LOCALE_PARAM
)
236 enum ptime_locale_status
*decided
;
241 struct locale_data
*const current
= locale
->__locales
[LC_TIME
];
247 int century
, want_century
;
249 int have_wday
, want_xday
;
251 int have_mon
, have_mday
;
252 int have_uweek
, have_wweek
;
256 struct era_entry
*era
= NULL
;
257 const char *rp_backup
;
266 have_wday
= want_xday
= have_yday
= have_mon
= have_mday
= have_uweek
= 0;
271 /* A white space in the format string matches 0 more or white
272 space in the input string. */
275 while (ISSPACE (*rp
))
281 /* Any character but '%' must be matched by the same character
282 in the input string. */
285 match_char (*fmt
++, *rp
++);
291 /* We need this for handling the 'E' modifier. */
294 /* Make back up of current processing pointer. */
301 /* Match the '%' character itself. */
302 match_char ('%', *rp
++);
306 /* Match day of week. */
307 for (cnt
= 0; cnt
< 7; ++cnt
)
312 if (match_string (_NL_CURRENT (LC_TIME
, DAY_1
+ cnt
), rp
))
315 && strcmp (_NL_CURRENT (LC_TIME
, DAY_1
+ cnt
),
320 if (match_string (_NL_CURRENT (LC_TIME
, ABDAY_1
+ cnt
), rp
))
323 && strcmp (_NL_CURRENT (LC_TIME
, ABDAY_1
+ cnt
),
324 ab_weekday_name
[cnt
]))
331 && (match_string (weekday_name
[cnt
], rp
)
332 || match_string (ab_weekday_name
[cnt
], rp
)))
339 /* Does not match a weekday name. */
347 /* Match month name. */
348 for (cnt
= 0; cnt
< 12; ++cnt
)
353 if (match_string (_NL_CURRENT (LC_TIME
, MON_1
+ cnt
), rp
))
356 && strcmp (_NL_CURRENT (LC_TIME
, MON_1
+ cnt
),
361 if (match_string (_NL_CURRENT (LC_TIME
, ABMON_1
+ cnt
), rp
))
364 && strcmp (_NL_CURRENT (LC_TIME
, ABMON_1
+ cnt
),
371 if (match_string (month_name
[cnt
], rp
)
372 || match_string (ab_month_name
[cnt
], rp
))
379 /* Does not match a month name. */
385 /* Match locale's date and time format. */
389 if (!recursive (_NL_CURRENT (LC_TIME
, D_T_FMT
)))
398 if (*decided
== not &&
399 strcmp (_NL_CURRENT (LC_TIME
, D_T_FMT
), HERE_D_T_FMT
))
407 if (!recursive (HERE_D_T_FMT
))
412 /* Match century number. */
416 get_number (0, 99, 2);
422 /* Match day of month. */
423 get_number (1, 31, 2);
429 if (!recursive ("%Y-%m-%d"))
437 if (!recursive (_NL_CURRENT (LC_TIME
, D_FMT
)))
447 && strcmp (_NL_CURRENT (LC_TIME
, D_FMT
), HERE_D_FMT
))
457 /* Match standard day format. */
458 if (!recursive (HERE_D_FMT
))
464 /* Match hour in 24-hour clock. */
465 get_number (0, 23, 2);
470 /* Match hour in 12-hour clock. GNU extension. */
472 /* Match hour in 12-hour clock. */
473 get_number (1, 12, 2);
474 tm
->tm_hour
= val
% 12;
478 /* Match day number of year. */
479 get_number (1, 366, 3);
480 tm
->tm_yday
= val
- 1;
484 /* Match number of month. */
485 get_number (1, 12, 2);
486 tm
->tm_mon
= val
- 1;
492 get_number (0, 59, 2);
497 /* Match any white space. */
498 while (ISSPACE (*rp
))
502 /* Match locale's equivalent of AM/PM. */
506 if (match_string (_NL_CURRENT (LC_TIME
, AM_STR
), rp
))
508 if (strcmp (_NL_CURRENT (LC_TIME
, AM_STR
), HERE_AM_STR
))
512 if (match_string (_NL_CURRENT (LC_TIME
, PM_STR
), rp
))
514 if (strcmp (_NL_CURRENT (LC_TIME
, PM_STR
), HERE_PM_STR
))
522 if (!match_string (HERE_AM_STR
, rp
))
524 if (match_string (HERE_PM_STR
, rp
))
531 /* Match quarter of year. GNU extension. */
532 get_number (1, 4, 1);
533 tm
->tm_mon
= (val
- 1) * 3;
543 if (!recursive (_NL_CURRENT (LC_TIME
, T_FMT_AMPM
)))
552 if (*decided
== not &&
553 strcmp (_NL_CURRENT (LC_TIME
, T_FMT_AMPM
),
561 if (!recursive (HERE_T_FMT_AMPM
))
565 if (!recursive ("%H:%M"))
570 /* The number of seconds may be very high so we cannot use
571 the 'get_number' macro. Instead read the number
572 character for character and construct the result while
575 if (*rp
< '0' || *rp
> '9')
576 /* We need at least one digit. */
584 while (*rp
>= '0' && *rp
<= '9');
586 if (localtime_r (&secs
, tm
) == NULL
)
587 /* Error in function. */
592 get_number (0, 61, 2);
599 if (!recursive (_NL_CURRENT (LC_TIME
, T_FMT
)))
608 if (strcmp (_NL_CURRENT (LC_TIME
, T_FMT
), HERE_T_FMT
))
617 if (!recursive (HERE_T_FMT
))
621 get_number (1, 7, 1);
622 tm
->tm_wday
= val
% 7;
626 get_number (0, 99, 2);
627 /* XXX This cannot determine any field in TM. */
630 if (*rp
< '0' || *rp
> '9')
632 /* XXX Ignore the number since we would need some more
633 information to compute a real date. */
636 while (*rp
>= '0' && *rp
<= '9');
639 get_number (0, 53, 2);
644 get_number (0, 53, 2);
649 get_number (0, 53, 2);
650 /* XXX This cannot determine any field in TM without some
654 /* Match number of weekday. */
655 get_number (0, 6, 1);
661 match_year_in_century
:
663 /* Match year within century. */
664 get_number (0, 99, 2);
665 /* The "Year 2000: The Millennium Rollover" paper suggests that
666 values in the range 69-99 refer to the twentieth century. */
667 tm
->tm_year
= val
>= 69 ? val
: val
+ 100;
668 /* Indicate that we want to use the century, if specified. */
673 /* Match year including century number. */
674 get_number (0, 9999, 4);
675 tm
->tm_year
= val
- 1900;
680 /* XXX How to handle this? */
683 /* We recognize two formats: if two digits are given, these
684 specify hours. If fours digits are used, minutes are
693 if (*rp
!= '+' && *rp
!= '-')
697 while (n
< 4 && *rp
>= '0' && *rp
<= '9')
699 val
= val
* 10 + *rp
++ - '0';
705 /* Only two or four digits recognized. */
709 /* We have to convert the minutes into decimal. */
712 val
= (val
/ 100) * 100 + ((val
% 100) * 50) / 30;
716 #if defined _LIBC || HAVE_STRUCT_TM_TM_GMTOFF
717 tm
->tm_gmtoff
= (val
* 3600) / 100;
719 tm
->tm_gmtoff
= -tm
->tm_gmtoff
;
728 /* Match locale's alternate date and time format. */
731 const char *fmt
= _NL_CURRENT (LC_TIME
, ERA_D_T_FMT
);
734 fmt
= _NL_CURRENT (LC_TIME
, D_T_FMT
);
736 if (!recursive (fmt
))
745 if (strcmp (fmt
, HERE_D_T_FMT
))
752 /* The C locale has no era information, so use the
753 normal representation. */
754 if (!recursive (HERE_D_T_FMT
))
763 era
= _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG
);
764 if (era
!= NULL
&& match_string (era
->era_name
, rp
))
773 num_eras
= _NL_CURRENT_WORD (LC_TIME
,
774 _NL_TIME_ERA_NUM_ENTRIES
);
775 for (era_cnt
= 0; era_cnt
< (int) num_eras
;
776 ++era_cnt
, rp
= rp_backup
)
778 era
= _nl_select_era_entry (era_cnt
780 if (era
!= NULL
&& match_string (era
->era_name
, rp
))
786 if (era_cnt
!= (int) num_eras
)
795 /* The C locale has no era information, so use the
796 normal representation. */
801 get_number(0, 9999, 4);
809 assert (*decided
== loc
);
811 era
= _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG
);
815 int delta
= ((tm
->tm_year
- era
->offset
)
816 * era
->absolute_direction
);
818 && delta
< (((int64_t) era
->stop_date
[0]
819 - (int64_t) era
->start_date
[0])
820 * era
->absolute_direction
));
828 num_eras
= _NL_CURRENT_WORD (LC_TIME
,
829 _NL_TIME_ERA_NUM_ENTRIES
);
830 for (era_cnt
= 0; era_cnt
< (int) num_eras
; ++era_cnt
)
832 era
= _nl_select_era_entry (era_cnt
836 int delta
= ((tm
->tm_year
- era
->offset
)
837 * era
->absolute_direction
);
839 && delta
< (((int64_t) era
->stop_date
[0]
840 - (int64_t) era
->start_date
[0])
841 * era
->absolute_direction
))
848 if (era_cnt
!= (int) num_eras
)
858 goto match_year_in_century
;
862 num_eras
= _NL_CURRENT_WORD (LC_TIME
,
863 _NL_TIME_ERA_NUM_ENTRIES
);
864 for (era_cnt
= 0; era_cnt
< (int) num_eras
;
865 ++era_cnt
, rp
= rp_backup
)
867 era
= _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG
);
868 if (era
!= NULL
&& recursive (era
->era_format
))
871 if (era_cnt
== (int) num_eras
)
888 get_number (0, 9999, 4);
889 tm
->tm_year
= val
- 1900;
896 const char *fmt
= _NL_CURRENT (LC_TIME
, ERA_D_FMT
);
899 fmt
= _NL_CURRENT (LC_TIME
, D_FMT
);
901 if (!recursive (fmt
))
910 if (strcmp (fmt
, HERE_D_FMT
))
916 if (!recursive (HERE_D_FMT
))
922 const char *fmt
= _NL_CURRENT (LC_TIME
, ERA_T_FMT
);
925 fmt
= _NL_CURRENT (LC_TIME
, T_FMT
);
927 if (!recursive (fmt
))
936 if (strcmp (fmt
, HERE_T_FMT
))
942 if (!recursive (HERE_T_FMT
))
950 /* We have no information about the era format. Just use
951 the normal format. */
952 if (*fmt
!= 'c' && *fmt
!= 'C' && *fmt
!= 'y' && *fmt
!= 'Y'
953 && *fmt
!= 'x' && *fmt
!= 'X')
954 /* This is an illegal format. */
964 /* Match day of month using alternate numeric symbols. */
965 get_alt_number (1, 31, 2);
971 /* Match hour in 24-hour clock using alternate numeric
973 get_alt_number (0, 23, 2);
978 /* Match hour in 12-hour clock using alternate numeric
980 get_alt_number (1, 12, 2);
981 tm
->tm_hour
= val
% 12;
985 /* Match month using alternate numeric symbols. */
986 get_alt_number (1, 12, 2);
987 tm
->tm_mon
= val
- 1;
992 /* Match minutes using alternate numeric symbols. */
993 get_alt_number (0, 59, 2);
997 /* Match quarter using alternate numeric symbols. */
998 get_alt_number (1, 4, 1);
999 tm
->tm_mon
= (val
- 1) * 3;
1006 /* Match seconds using alternate numeric symbols. */
1007 get_alt_number (0, 61, 2);
1011 get_alt_number (0, 53, 2);
1016 get_alt_number (0, 53, 2);
1021 get_alt_number (0, 53, 2);
1022 /* XXX This cannot determine any field in TM without
1023 further information. */
1026 /* Match number of weekday using alternate numeric symbols. */
1027 get_alt_number (0, 6, 1);
1032 /* Match year within century using alternate numeric symbols. */
1033 get_alt_number (0, 99, 2);
1034 tm
->tm_year
= val
>= 69 ? val
: val
+ 100;
1046 if (have_I
&& is_pm
)
1052 tm
->tm_year
= tm
->tm_year
% 100 + (century
- 19) * 100;
1054 /* Only the century, but not the year. Strange, but so be it. */
1055 tm
->tm_year
= (century
- 19) * 100;
1061 era
= _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG
);
1065 tm
->tm_year
= (era
->start_date
[0]
1066 + ((tm
->tm_year
- era
->offset
)
1067 * era
->absolute_direction
));
1069 /* Era start year assumed. */
1070 tm
->tm_year
= era
->start_date
[0];
1076 /* No era found but we have seen an E modifier. Rectify some
1078 if (want_century
&& century
== -1 && tm
->tm_year
< 69)
1082 if (want_xday
&& !have_wday
)
1084 if ( !(have_mon
&& have_mday
) && have_yday
)
1086 /* We don't have tm_mon and/or tm_mday, compute them. */
1088 while (__mon_yday
[__isleap(1900 + tm
->tm_year
)][t_mon
] <= tm
->tm_yday
)
1091 tm
->tm_mon
= t_mon
- 1;
1095 - __mon_yday
[__isleap(1900 + tm
->tm_year
)][t_mon
- 1] + 1);
1097 day_of_the_week (tm
);
1100 if (want_xday
&& !have_yday
)
1101 day_of_the_year (tm
);
1103 if ((have_uweek
|| have_wweek
) && have_wday
)
1105 int saved_wday
= tm
->tm_wday
;
1106 int saved_mday
= tm
->tm_mday
;
1107 int saved_mon
= tm
->tm_mon
;
1108 int w_offset
= have_uweek
? 0 : 1;
1112 day_of_the_week (tm
);
1114 tm
->tm_mday
= saved_mday
;
1116 tm
->tm_mon
= saved_mon
;
1119 tm
->tm_yday
= ((7 - (tm
->tm_wday
- w_offset
)) % 7
1121 + saved_wday
- w_offset
);
1123 if (!have_mday
|| !have_mon
)
1126 while (__mon_yday
[__isleap(1900 + tm
->tm_year
)][t_mon
]
1130 tm
->tm_mon
= t_mon
- 1;
1134 - __mon_yday
[__isleap(1900 + tm
->tm_year
)][t_mon
- 1] + 1);
1137 tm
->tm_wday
= saved_wday
;
1145 strptime (buf
, format
, tm LOCALE_PARAM
)
1146 const char *restrict buf
;
1147 const char *restrict format
;
1148 struct tm
*restrict tm
;
1151 enum ptime_locale_status decided
;
1158 return __strptime_internal (buf
, format
, tm
, &decided
, -1 LOCALE_ARG
);
1162 weak_alias (__strptime_l
, strptime_l
)