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