1 /* Copyright (C) 2002, 2004, 2005, 2007 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17 
18 /* Minor modifications by acab@clamav.net */
19 
20 #define strncasecmp _strnicmp
21 #ifndef _LIBC
22 //# include <config.h>
23 #endif
24 
25 #include <time.h>
26 
27 #include <assert.h>
28 #include <ctype.h>
29 #ifdef _LIBC
30 #include <langinfo.h>
31 #endif
32 #include <limits.h>
33 #include <string.h>
34 //#include <stdbool.h>
35 
36 #ifdef _LIBC
37 #include "../locale/localeinfo.h"
38 #endif
39 
40 #ifndef _LIBC
41 enum ptime_locale_status { not,
42                            loc,
43                            raw };
44 #endif
45 
localtime_r(time_t const * t,struct tm * tp)46 struct tm *localtime_r(time_t const *t, struct tm *tp)
47 {
48     struct tm *copy = localtime(t);
49     if (!copy) return NULL;
50     memcpy(tp, copy, sizeof(*tp));
51     return tp;
52 }
53 
54 typedef int bool;
55 
56 #define match_char(ch1, ch2) \
57     if (ch1 != ch2) return NULL
58 #if defined _LIBC && defined __GNUC__ && __GNUC__ >= 2
59 #define match_string(cs1, s2) \
60     ({ size_t len = strlen (cs1);						      \
61      int result = __strncasecmp_l ((cs1), (s2), len, locale) == 0;	      \
62      if (result) (s2) += len;						      \
63      result; })
64 #else
65 /* Oh come on.  Get a reasonable compiler.  */
66 #define match_string(cs1, s2) \
67     (strncasecmp((cs1), (s2), strlen(cs1)) ? 0 : ((s2) += strlen(cs1), 1))
68 #endif
69 /* We intentionally do not use isdigit() for testing because this will
70    lead to problems with the wide character version.  */
71 #define get_number(from, to, n)                                            \
72     do {                                                                   \
73         int __n = n;                                                       \
74         val     = 0;                                                       \
75         while (*rp == ' ')                                                 \
76             ++rp;                                                          \
77         if (*rp < '0' || *rp > '9')                                        \
78             return NULL;                                                   \
79         do {                                                               \
80             val *= 10;                                                     \
81             val += *rp++ - '0';                                            \
82         } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \
83         if (val < from || val > to)                                        \
84             return NULL;                                                   \
85     } while (0)
86 #ifdef _NL_CURRENT
87 #define get_alt_number(from, to, n)                           \
88     ({                                                        \
89         __label__ do_normal;                                  \
90                                                               \
91         if (*decided != raw) {                                \
92             val = _nl_parse_alt_digit(&rp HELPER_LOCALE_ARG); \
93             if (val == -1 && *decided != loc) {               \
94                 *decided = loc;                               \
95                 goto do_normal;                               \
96             }                                                 \
97             if (val < from || val > to)                       \
98                 return NULL;                                  \
99         } else {                                              \
100         do_normal:                                            \
101             get_number(from, to, n);                          \
102         }                                                     \
103         0;                                                    \
104     })
105 #else
106 #define get_alt_number(from, to, n)                    \
107     /* We don't have the alternate representation.  */ \
108     get_number(from, to, n)
109 #endif
110 #define recursive(new_fmt)                                              \
111     (*(new_fmt) != '\0' && (rp = __strptime_internal(rp, (new_fmt), tm, \
112                                                      decided, era_cnt LOCALE_ARG)) != NULL)
113 
114 #ifdef _LIBC
115 /* This is defined in locale/C-time.c in the GNU libc.  */
116 extern const struct locale_data _nl_C_LC_TIME attribute_hidden;
117 
118 #define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX(DAY_1)].string)
119 #define ab_weekday_name \
120     (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX(ABDAY_1)].string)
121 #define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX(MON_1)].string)
122 #define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX(ABMON_1)].string)
123 #define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(D_T_FMT)].string)
124 #define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(D_FMT)].string)
125 #define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(AM_STR)].string)
126 #define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(PM_STR)].string)
127 #define HERE_T_FMT_AMPM \
128     (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(T_FMT_AMPM)].string)
129 #define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX(T_FMT)].string)
130 
131 #define strncasecmp(s1, s2, n) __strncasecmp(s1, s2, n)
132 #else
133 static char const weekday_name[][10] =
134     {
135         "Sunday", "Monday", "Tuesday", "Wednesday",
136         "Thursday", "Friday", "Saturday"};
137 static char const ab_weekday_name[][4] =
138     {
139         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
140 static char const month_name[][10] =
141     {
142         "January", "February", "March", "April", "May", "June",
143         "July", "August", "September", "October", "November", "December"};
144 static char const ab_month_name[][4] =
145     {
146         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
147         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
148 #define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
149 #define HERE_D_FMT "%m/%d/%y"
150 #define HERE_AM_STR "AM"
151 #define HERE_PM_STR "PM"
152 #define HERE_T_FMT_AMPM "%I:%M:%S %p"
153 #define HERE_T_FMT "%H:%M:%S"
154 
155 static const unsigned short int __mon_yday[2][13] =
156     {
157         /* Normal years.  */
158         {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
159         /* Leap years.  */
160         {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
161 #endif
162 
163 #if defined _LIBC
164 /* We use this code also for the extended locale handling where the
165    function gets as an additional argument the locale which has to be
166    used.  To access the values we have to redefine the _NL_CURRENT
167    macro.  */
168 #define strptime __strptime_l
169 #undef _NL_CURRENT
170 #define _NL_CURRENT(category, item) \
171     (current->values[_NL_ITEM_INDEX(item)].string)
172 #undef _NL_CURRENT_WORD
173 #define _NL_CURRENT_WORD(category, item) \
174     (current->values[_NL_ITEM_INDEX(item)].word)
175 #define LOCALE_PARAM , locale
176 #define LOCALE_ARG , locale
177 #define LOCALE_PARAM_PROTO , __locale_t locale
178 #define LOCALE_PARAM_DECL __locale_t locale;
179 #define HELPER_LOCALE_ARG , current
180 #define ISSPACE(Ch) __isspace_l(Ch, locale)
181 #else
182 #define LOCALE_PARAM
183 #define LOCALE_ARG
184 #define LOCALE_PARAM_DECL
185 #define LOCALE_PARAM_PROTO
186 #define HELPER_LOCALE_ARG
187 #define ISSPACE(Ch) isspace(Ch)
188 #endif
189 
190 #ifndef __isleap
191 /* Nonzero if YEAR is a leap year (every 4 years,
192    except every 100th isn't, and every 400th is).  */
193 #define __isleap(year) \
194     ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
195 #endif
196 
197 /* Compute the day of the week.  */
198 static void
day_of_the_week(struct tm * tm)199 day_of_the_week(struct tm *tm)
200 {
201     /* We know that January 1st 1970 was a Thursday (= 4).  Compute the
202      the difference between this data in the one on TM and so determine
203      the weekday.  */
204     int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
205     int wday      = (-473 + (365 * (tm->tm_year - 70)) + (corr_year / 4) - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0) + (((corr_year / 4) / 25) / 4) + __mon_yday[0][tm->tm_mon] + tm->tm_mday - 1);
206     tm->tm_wday   = ((wday % 7) + 7) % 7;
207 }
208 
209 /* Compute the day of the year.  */
210 static void
day_of_the_year(struct tm * tm)211 day_of_the_year(struct tm *tm)
212 {
213     tm->tm_yday = (__mon_yday[__isleap(1900 + tm->tm_year)][tm->tm_mon] + (tm->tm_mday - 1));
214 }
215 
216 #ifdef _LIBC
217 char *
218     internal_function
219 #else
220 static char *
221 #endif
222     __strptime_internal(rp, fmt, tm, decided, era_cnt LOCALE_PARAM)
223         const char *rp;
224 const char *fmt;
225 struct tm *tm;
226 enum ptime_locale_status *decided;
227 int era_cnt;
228 LOCALE_PARAM_DECL
229 {
230 #ifdef _LIBC
231     struct locale_data *const current = locale->__locales[LC_TIME];
232 #endif
233 
234     const char *rp_backup;
235     int cnt;
236     size_t val;
237     int have_I, is_pm;
238     int century, want_century;
239     int want_era;
240     int have_wday, want_xday;
241     int have_yday;
242     int have_mon, have_mday;
243     int have_uweek, have_wweek;
244     int week_no;
245     size_t num_eras;
246     struct era_entry *era;
247 
248     have_I = is_pm = 0;
249     century        = -1;
250     want_century   = 0;
251     want_era       = 0;
252     era            = NULL;
253     week_no        = 0;
254 
255     have_wday = want_xday = have_yday = have_mon = have_mday = have_uweek = 0;
256     have_wweek                                                            = 0;
257 
258     while (*fmt != '\0') {
259         /* A white space in the format string matches 0 more or white
260 	 space in the input string.  */
261         if (ISSPACE(*fmt)) {
262             while (ISSPACE(*rp))
263                 ++rp;
264             ++fmt;
265             continue;
266         }
267 
268         /* Any character but `%' must be matched by the same character
269 	 in the iput string.  */
270         if (*fmt != '%') {
271             match_char(*fmt++, *rp++);
272             continue;
273         }
274 
275         ++fmt;
276 #ifndef _NL_CURRENT
277         /* We need this for handling the `E' modifier.  */
278     start_over:
279 #endif
280 
281         /* Make back up of current processing pointer.  */
282         rp_backup = rp;
283 
284         switch (*fmt++) {
285             case '%':
286                 /* Match the `%' character itself.  */
287                 match_char('%', *rp++);
288                 break;
289             case 'a':
290             case 'A':
291                 /* Match day of week.  */
292                 for (cnt = 0; cnt < 7; ++cnt) {
293 #ifdef _NL_CURRENT
294                     if (*decided != raw) {
295                         if (match_string(_NL_CURRENT(LC_TIME, DAY_1 + cnt), rp)) {
296                             if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, DAY_1 + cnt),
297                                                         weekday_name[cnt]))
298                                 *decided = loc;
299                             break;
300                         }
301                         if (match_string(_NL_CURRENT(LC_TIME, ABDAY_1 + cnt), rp)) {
302                             if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, ABDAY_1 + cnt),
303                                                         ab_weekday_name[cnt]))
304                                 *decided = loc;
305                             break;
306                         }
307                     }
308 #endif
309                     if (*decided != loc && (match_string(weekday_name[cnt], rp) || match_string(ab_weekday_name[cnt], rp))) {
310                         *decided = raw;
311                         break;
312                     }
313                 }
314                 if (cnt == 7)
315                     /* Does not match a weekday name.  */
316                     return NULL;
317                 tm->tm_wday = cnt;
318                 have_wday   = 1;
319                 break;
320             case 'b':
321             case 'B':
322             case 'h':
323                 /* Match month name.  */
324                 for (cnt = 0; cnt < 12; ++cnt) {
325 #ifdef _NL_CURRENT
326                     if (*decided != raw) {
327                         if (match_string(_NL_CURRENT(LC_TIME, MON_1 + cnt), rp)) {
328                             if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, MON_1 + cnt),
329                                                         month_name[cnt]))
330                                 *decided = loc;
331                             break;
332                         }
333                         if (match_string(_NL_CURRENT(LC_TIME, ABMON_1 + cnt), rp)) {
334                             if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, ABMON_1 + cnt),
335                                                         ab_month_name[cnt]))
336                                 *decided = loc;
337                             break;
338                         }
339                     }
340 #endif
341                     if (match_string(month_name[cnt], rp) || match_string(ab_month_name[cnt], rp)) {
342                         *decided = raw;
343                         break;
344                     }
345                 }
346                 if (cnt == 12)
347                     /* Does not match a month name.  */
348                     return NULL;
349                 tm->tm_mon = cnt;
350                 want_xday  = 1;
351                 break;
352             case 'c':
353                 /* Match locale's date and time format.  */
354 #ifdef _NL_CURRENT
355                 if (*decided != raw) {
356                     if (!recursive(_NL_CURRENT(LC_TIME, D_T_FMT))) {
357                         if (*decided == loc)
358                             return NULL;
359                         else
360                             rp = rp_backup;
361                     } else {
362                         if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, D_T_FMT), HERE_D_T_FMT))
363                             *decided = loc;
364                         want_xday = 1;
365                         break;
366                     }
367                     *decided = raw;
368                 }
369 #endif
370                 if (!recursive(HERE_D_T_FMT))
371                     return NULL;
372                 want_xday = 1;
373                 break;
374             case 'C':
375                 /* Match century number.  */
376             match_century:
377                 get_number(0, 99, 2);
378                 century   = val;
379                 want_xday = 1;
380                 break;
381             case 'd':
382             case 'e':
383                 /* Match day of month.  */
384                 get_number(1, 31, 2);
385                 tm->tm_mday = val;
386                 have_mday   = 1;
387                 want_xday   = 1;
388                 break;
389             case 'F':
390                 if (!recursive("%Y-%m-%d"))
391                     return NULL;
392                 want_xday = 1;
393                 break;
394             case 'x':
395 #ifdef _NL_CURRENT
396                 if (*decided != raw) {
397                     if (!recursive(_NL_CURRENT(LC_TIME, D_FMT))) {
398                         if (*decided == loc)
399                             return NULL;
400                         else
401                             rp = rp_backup;
402                     } else {
403                         if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, D_FMT), HERE_D_FMT))
404                             *decided = loc;
405                         want_xday = 1;
406                         break;
407                     }
408                     *decided = raw;
409                 }
410 #endif
411                 /* Fall through.  */
412             case 'D':
413                 /* Match standard day format.  */
414                 if (!recursive(HERE_D_FMT))
415                     return NULL;
416                 want_xday = 1;
417                 break;
418             case 'k':
419             case 'H':
420                 /* Match hour in 24-hour clock.  */
421                 get_number(0, 23, 2);
422                 tm->tm_hour = val;
423                 have_I      = 0;
424                 break;
425             case 'l':
426                 /* Match hour in 12-hour clock.  GNU extension.  */
427             case 'I':
428                 /* Match hour in 12-hour clock.  */
429                 get_number(1, 12, 2);
430                 tm->tm_hour = val % 12;
431                 have_I      = 1;
432                 break;
433             case 'j':
434                 /* Match day number of year.  */
435                 get_number(1, 366, 3);
436                 tm->tm_yday = val - 1;
437                 have_yday   = 1;
438                 break;
439             case 'm':
440                 /* Match number of month.  */
441                 get_number(1, 12, 2);
442                 tm->tm_mon = val - 1;
443                 have_mon   = 1;
444                 want_xday  = 1;
445                 break;
446             case 'M':
447                 /* Match minute.  */
448                 get_number(0, 59, 2);
449                 tm->tm_min = val;
450                 break;
451             case 'n':
452             case 't':
453                 /* Match any white space.  */
454                 while (ISSPACE(*rp))
455                     ++rp;
456                 break;
457             case 'p':
458                 /* Match locale's equivalent of AM/PM.  */
459 #ifdef _NL_CURRENT
460                 if (*decided != raw) {
461                     if (match_string(_NL_CURRENT(LC_TIME, AM_STR), rp)) {
462                         if (strcmp(_NL_CURRENT(LC_TIME, AM_STR), HERE_AM_STR))
463                             *decided = loc;
464                         break;
465                     }
466                     if (match_string(_NL_CURRENT(LC_TIME, PM_STR), rp)) {
467                         if (strcmp(_NL_CURRENT(LC_TIME, PM_STR), HERE_PM_STR))
468                             *decided = loc;
469                         is_pm = 1;
470                         break;
471                     }
472                     *decided = raw;
473                 }
474 #endif
475                 if (!match_string(HERE_AM_STR, rp)) {
476                     if (match_string(HERE_PM_STR, rp))
477                         is_pm = 1;
478                     else
479                         return NULL;
480                 }
481                 break;
482             case 'r':
483 #ifdef _NL_CURRENT
484                 if (*decided != raw) {
485                     if (!recursive(_NL_CURRENT(LC_TIME, T_FMT_AMPM))) {
486                         if (*decided == loc)
487                             return NULL;
488                         else
489                             rp = rp_backup;
490                     } else {
491                         if (*decided == not&&strcmp(_NL_CURRENT(LC_TIME, T_FMT_AMPM),
492                                                     HERE_T_FMT_AMPM))
493                             *decided = loc;
494                         break;
495                     }
496                     *decided = raw;
497                 }
498 #endif
499                 if (!recursive(HERE_T_FMT_AMPM))
500                     return NULL;
501                 break;
502             case 'R':
503                 if (!recursive("%H:%M"))
504                     return NULL;
505                 break;
506             case 's': {
507                 /* The number of seconds may be very high so we cannot use
508 	       the `get_number' macro.  Instead read the number
509 	       character for character and construct the result while
510 	       doing this.  */
511                 time_t secs = 0;
512                 if (*rp < '0' || *rp > '9')
513                     /* We need at least one digit.  */
514                     return NULL;
515 
516                 do {
517                     secs *= 10;
518                     secs += *rp++ - '0';
519                 } while (*rp >= '0' && *rp <= '9');
520 
521                 if (localtime_r(&secs, tm) == NULL)
522                     /* Error in function.  */
523                     return NULL;
524             } break;
525             case 'S':
526                 get_number(0, 61, 2);
527                 tm->tm_sec = val;
528                 break;
529             case 'X':
530 #ifdef _NL_CURRENT
531                 if (*decided != raw) {
532                     if (!recursive(_NL_CURRENT(LC_TIME, T_FMT))) {
533                         if (*decided == loc)
534                             return NULL;
535                         else
536                             rp = rp_backup;
537                     } else {
538                         if (strcmp(_NL_CURRENT(LC_TIME, T_FMT), HERE_T_FMT))
539                             *decided = loc;
540                         break;
541                     }
542                     *decided = raw;
543                 }
544 #endif
545                 /* Fall through.  */
546             case 'T':
547                 if (!recursive(HERE_T_FMT))
548                     return NULL;
549                 break;
550             case 'u':
551                 get_number(1, 7, 1);
552                 tm->tm_wday = val % 7;
553                 have_wday   = 1;
554                 break;
555             case 'g':
556                 get_number(0, 99, 2);
557                 /* XXX This cannot determine any field in TM.  */
558                 break;
559             case 'G':
560                 if (*rp < '0' || *rp > '9')
561                     return NULL;
562                 /* XXX Ignore the number since we would need some more
563 	     information to compute a real date.  */
564                 do
565                     ++rp;
566                 while (*rp >= '0' && *rp <= '9');
567                 break;
568             case 'U':
569                 get_number(0, 53, 2);
570                 week_no    = val;
571                 have_uweek = 1;
572                 break;
573             case 'W':
574                 get_number(0, 53, 2);
575                 week_no    = val;
576                 have_wweek = 1;
577                 break;
578             case 'V':
579                 get_number(0, 53, 2);
580                 /* XXX This cannot determine any field in TM without some
581 	     information.  */
582                 break;
583             case 'w':
584                 /* Match number of weekday.  */
585                 get_number(0, 6, 1);
586                 tm->tm_wday = val;
587                 have_wday   = 1;
588                 break;
589             case 'y':
590             match_year_in_century:
591                 /* Match year within century.  */
592                 get_number(0, 99, 2);
593                 /* The "Year 2000: The Millennium Rollover" paper suggests that
594 	     values in the range 69-99 refer to the twentieth century.  */
595                 tm->tm_year = val >= 69 ? val : val + 100;
596                 /* Indicate that we want to use the century, if specified.  */
597                 want_century = 1;
598                 want_xday    = 1;
599                 break;
600             case 'Y':
601                 /* Match year including century number.  */
602                 get_number(0, 9999, 4);
603                 tm->tm_year  = val - 1900;
604                 want_century = 0;
605                 want_xday    = 1;
606                 break;
607             case 'Z':
608                 /* XXX How to handle this?  */
609                 break;
610             case 'z':
611                 /* We recognize two formats: if two digits are given, these
612 	     specify hours.  If fours digits are used, minutes are
613 	     also specified.  */
614                 {
615                     bool neg;
616                     int n;
617 
618                     val = 0;
619                     while (*rp == ' ')
620                         ++rp;
621                     if (*rp != '+' && *rp != '-')
622                         return NULL;
623                     neg = *rp++ == '-';
624                     n   = 0;
625                     while (n < 4 && *rp >= '0' && *rp <= '9') {
626                         val = val * 10 + *rp++ - '0';
627                         ++n;
628                     }
629                     if (n == 2)
630                         val *= 100;
631                     else if (n != 4)
632                         /* Only two or four digits recognized.  */
633                         return NULL;
634                     else {
635                         /* We have to convert the minutes into decimal.  */
636                         if (val % 100 >= 60)
637                             return NULL;
638                         val = (val / 100) * 100 + ((val % 100) * 50) / 30;
639                     }
640                     if (val > 1200)
641                         return NULL;
642 #if defined _LIBC || HAVE_TM_GMTOFF
643                     tm->tm_gmtoff = (val * 3600) / 100;
644                     if (neg)
645                         tm->tm_gmtoff = -tm->tm_gmtoff;
646 #endif
647                 }
648                 break;
649             case 'E':
650 #ifdef _NL_CURRENT
651                 switch (*fmt++) {
652                     case 'c':
653                         /* Match locale's alternate date and time format.  */
654                         if (*decided != raw) {
655                             const char *fmt = _NL_CURRENT(LC_TIME, ERA_D_T_FMT);
656 
657                             if (*fmt == '\0')
658                                 fmt = _NL_CURRENT(LC_TIME, D_T_FMT);
659 
660                             if (!recursive(fmt)) {
661                                 if (*decided == loc)
662                                     return NULL;
663                                 else
664                                     rp = rp_backup;
665                             } else {
666                                 if (strcmp(fmt, HERE_D_T_FMT))
667                                     *decided = loc;
668                                 want_xday = 1;
669                                 break;
670                             }
671                             *decided = raw;
672                         }
673                         /* The C locale has no era information, so use the
674 		 normal representation.  */
675                         if (!recursive(HERE_D_T_FMT))
676                             return NULL;
677                         want_xday = 1;
678                         break;
679                     case 'C':
680                         if (*decided != raw) {
681                             if (era_cnt >= 0) {
682                                 era = _nl_select_era_entry(era_cnt HELPER_LOCALE_ARG);
683                                 if (era != NULL && match_string(era->era_name, rp)) {
684                                     *decided = loc;
685                                     break;
686                                 } else
687                                     return NULL;
688                             }
689 
690                             num_eras = _NL_CURRENT_WORD(LC_TIME,
691                                                         _NL_TIME_ERA_NUM_ENTRIES);
692                             for (era_cnt = 0; era_cnt < (int)num_eras;
693                                  ++era_cnt, rp = rp_backup) {
694                                 era = _nl_select_era_entry(era_cnt
695                                                                HELPER_LOCALE_ARG);
696                                 if (era != NULL && match_string(era->era_name, rp)) {
697                                     *decided = loc;
698                                     break;
699                                 }
700                             }
701                             if (era_cnt != (int)num_eras)
702                                 break;
703 
704                             era_cnt = -1;
705                             if (*decided == loc)
706                                 return NULL;
707 
708                             *decided = raw;
709                         }
710                         /* The C locale has no era information, so use the
711 		 normal representation.  */
712                         goto match_century;
713                     case 'y':
714                         if (*decided != raw) {
715                             get_number(0, 9999, 4);
716                             tm->tm_year  = val;
717                             want_era     = 1;
718                             want_xday    = 1;
719                             want_century = 1;
720 
721                             if (era_cnt >= 0) {
722                                 assert(*decided == loc);
723 
724                                 era        = _nl_select_era_entry(era_cnt HELPER_LOCALE_ARG);
725                                 bool match = false;
726                                 if (era != NULL) {
727                                     int delta = ((tm->tm_year - era->offset) * era->absolute_direction);
728                                     match     = (delta >= 0 && delta < (((int64_t)era->stop_date[0] - (int64_t)era->start_date[0]) * era->absolute_direction));
729                                 }
730                                 if (!match)
731                                     return NULL;
732 
733                                 break;
734                             }
735 
736                             num_eras = _NL_CURRENT_WORD(LC_TIME,
737                                                         _NL_TIME_ERA_NUM_ENTRIES);
738                             for (era_cnt = 0; era_cnt < (int)num_eras; ++era_cnt) {
739                                 era = _nl_select_era_entry(era_cnt
740                                                                HELPER_LOCALE_ARG);
741                                 if (era != NULL) {
742                                     int delta = ((tm->tm_year - era->offset) * era->absolute_direction);
743                                     if (delta >= 0 && delta < (((int64_t)era->stop_date[0] - (int64_t)era->start_date[0]) * era->absolute_direction)) {
744                                         *decided = loc;
745                                         break;
746                                     }
747                                 }
748                             }
749                             if (era_cnt != (int)num_eras)
750                                 break;
751 
752                             era_cnt = -1;
753                             if (*decided == loc)
754                                 return NULL;
755 
756                             *decided = raw;
757                         }
758 
759                         goto match_year_in_century;
760                     case 'Y':
761                         if (*decided != raw) {
762                             num_eras = _NL_CURRENT_WORD(LC_TIME,
763                                                         _NL_TIME_ERA_NUM_ENTRIES);
764                             for (era_cnt = 0; era_cnt < (int)num_eras;
765                                  ++era_cnt, rp = rp_backup) {
766                                 era = _nl_select_era_entry(era_cnt HELPER_LOCALE_ARG);
767                                 if (era != NULL && recursive(era->era_format))
768                                     break;
769                             }
770                             if (era_cnt == (int)num_eras) {
771                                 era_cnt = -1;
772                                 if (*decided == loc)
773                                     return NULL;
774                                 else
775                                     rp = rp_backup;
776                             } else {
777                                 *decided = loc;
778                                 era_cnt  = -1;
779                                 break;
780                             }
781 
782                             *decided = raw;
783                         }
784                         get_number(0, 9999, 4);
785                         tm->tm_year  = val - 1900;
786                         want_century = 0;
787                         want_xday    = 1;
788                         break;
789                     case 'x':
790                         if (*decided != raw) {
791                             const char *fmt = _NL_CURRENT(LC_TIME, ERA_D_FMT);
792 
793                             if (*fmt == '\0')
794                                 fmt = _NL_CURRENT(LC_TIME, D_FMT);
795 
796                             if (!recursive(fmt)) {
797                                 if (*decided == loc)
798                                     return NULL;
799                                 else
800                                     rp = rp_backup;
801                             } else {
802                                 if (strcmp(fmt, HERE_D_FMT))
803                                     *decided = loc;
804                                 break;
805                             }
806                             *decided = raw;
807                         }
808                         if (!recursive(HERE_D_FMT))
809                             return NULL;
810                         break;
811                     case 'X':
812                         if (*decided != raw) {
813                             const char *fmt = _NL_CURRENT(LC_TIME, ERA_T_FMT);
814 
815                             if (*fmt == '\0')
816                                 fmt = _NL_CURRENT(LC_TIME, T_FMT);
817 
818                             if (!recursive(fmt)) {
819                                 if (*decided == loc)
820                                     return NULL;
821                                 else
822                                     rp = rp_backup;
823                             } else {
824                                 if (strcmp(fmt, HERE_T_FMT))
825                                     *decided = loc;
826                                 break;
827                             }
828                             *decided = raw;
829                         }
830                         if (!recursive(HERE_T_FMT))
831                             return NULL;
832                         break;
833                     default:
834                         return NULL;
835                 }
836                 break;
837 #else
838                 /* We have no information about the era format.  Just use
839 	     the normal format.  */
840                 if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y' && *fmt != 'x' && *fmt != 'X')
841                     /* This is an illegal format.  */
842                     return NULL;
843 
844                 goto start_over;
845 #endif
846             case 'O':
847                 switch (*fmt++) {
848                     case 'd':
849                     case 'e':
850                         /* Match day of month using alternate numeric symbols.  */
851                         get_alt_number(1, 31, 2);
852                         tm->tm_mday = val;
853                         have_mday   = 1;
854                         want_xday   = 1;
855                         break;
856                     case 'H':
857                         /* Match hour in 24-hour clock using alternate numeric
858 		 symbols.  */
859                         get_alt_number(0, 23, 2);
860                         tm->tm_hour = val;
861                         have_I      = 0;
862                         break;
863                     case 'I':
864                         /* Match hour in 12-hour clock using alternate numeric
865 		 symbols.  */
866                         get_alt_number(1, 12, 2);
867                         tm->tm_hour = val % 12;
868                         have_I      = 1;
869                         break;
870                     case 'm':
871                         /* Match month using alternate numeric symbols.  */
872                         get_alt_number(1, 12, 2);
873                         tm->tm_mon = val - 1;
874                         have_mon   = 1;
875                         want_xday  = 1;
876                         break;
877                     case 'M':
878                         /* Match minutes using alternate numeric symbols.  */
879                         get_alt_number(0, 59, 2);
880                         tm->tm_min = val;
881                         break;
882                     case 'S':
883                         /* Match seconds using alternate numeric symbols.  */
884                         get_alt_number(0, 61, 2);
885                         tm->tm_sec = val;
886                         break;
887                     case 'U':
888                         get_alt_number(0, 53, 2);
889                         week_no    = val;
890                         have_uweek = 1;
891                         break;
892                     case 'W':
893                         get_alt_number(0, 53, 2);
894                         week_no    = val;
895                         have_wweek = 1;
896                         break;
897                     case 'V':
898                         get_alt_number(0, 53, 2);
899                         /* XXX This cannot determine any field in TM without
900 		 further information.  */
901                         break;
902                     case 'w':
903                         /* Match number of weekday using alternate numeric symbols.  */
904                         get_alt_number(0, 6, 1);
905                         tm->tm_wday = val;
906                         have_wday   = 1;
907                         break;
908                     case 'y':
909                         /* Match year within century using alternate numeric symbols.  */
910                         get_alt_number(0, 99, 2);
911                         tm->tm_year = val >= 69 ? val : val + 100;
912                         want_xday   = 1;
913                         break;
914                     default:
915                         return NULL;
916                 }
917                 break;
918             default:
919                 return NULL;
920         }
921     }
922 
923     if (have_I && is_pm)
924         tm->tm_hour += 12;
925 
926     if (century != -1) {
927         if (want_century)
928             tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
929         else
930             /* Only the century, but not the year.  Strange, but so be it.  */
931             tm->tm_year = (century - 19) * 100;
932     }
933 
934     if (era_cnt != -1) {
935 #ifdef _NL_CURRENT
936         era = _nl_select_era_entry(era_cnt HELPER_LOCALE_ARG);
937         if (era == NULL)
938             return NULL;
939         if (want_era)
940             tm->tm_year = (era->start_date[0] + ((tm->tm_year - era->offset) * era->absolute_direction));
941         else
942             /* Era start year assumed.  */
943             tm->tm_year = era->start_date[0];
944 #endif
945     } else if (want_era) {
946         /* No era found but we have seen an E modifier.  Rectify some
947 	   values.  */
948         if (want_century && century == -1 && tm->tm_year < 69)
949             tm->tm_year += 100;
950     }
951 
952     if (want_xday && !have_wday) {
953         if (!(have_mon && have_mday) && have_yday) {
954             /* We don't have tm_mon and/or tm_mday, compute them.  */
955             int t_mon = 0;
956             while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
957                 t_mon++;
958             if (!have_mon)
959                 tm->tm_mon = t_mon - 1;
960             if (!have_mday)
961                 tm->tm_mday =
962                     (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
963         }
964         day_of_the_week(tm);
965     }
966 
967     if (want_xday && !have_yday)
968         day_of_the_year(tm);
969 
970     if ((have_uweek || have_wweek) && have_wday) {
971         int save_wday = tm->tm_wday;
972         int save_mday = tm->tm_mday;
973         int save_mon  = tm->tm_mon;
974         int w_offset  = have_uweek ? 0 : 1;
975 
976         tm->tm_mday = 1;
977         tm->tm_mon  = 0;
978         day_of_the_week(tm);
979         if (have_mday)
980             tm->tm_mday = save_mday;
981         if (have_mon)
982             tm->tm_mon = save_mon;
983 
984         if (!have_yday)
985             tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7 + (week_no - 1) * 7 + save_wday - w_offset);
986 
987         if (!have_mday || !have_mon) {
988             int t_mon = 0;
989             while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
990                 t_mon++;
991             if (!have_mon)
992                 tm->tm_mon = t_mon - 1;
993             if (!have_mday)
994                 tm->tm_mday =
995                     (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
996         }
997 
998         tm->tm_wday = save_wday;
999     }
1000 
1001     return (char *)rp;
1002 }
1003 
1004 char *
1005 strptime(buf, format, tm LOCALE_PARAM)
1006     const char *buf;
1007 const char *format;
1008 struct tm *tm;
1009 LOCALE_PARAM_DECL
1010 {
1011     enum ptime_locale_status decided;
1012 
1013 #ifdef _NL_CURRENT
1014     decided = not;
1015 #else
1016     decided = raw;
1017 #endif
1018     return __strptime_internal(buf, format, tm, &decided, -1 LOCALE_ARG);
1019 }
1020 
1021 #ifdef _LIBC
1022 weak_alias(__strptime_l, strptime_l)
1023 #endif
1024