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