(strptime_internal): In %y format, regard years >= 69 as of twentieth
[kopensolaris-gnu/glibc.git] / time / strptime.c
1 /* Convert a string representation of time to a time value.
2    Copyright (C) 1996, 1997 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 /* XXX This version of the implementation is not really complete.
22    Some of the fields cannot add information alone.  But if seeing
23    some of them in the same format (such as year, week and weekday)
24    this is enough information for determining the date.  */
25
26 #ifdef HAVE_CONFIG_H
27 # include <config.h>
28 #endif
29
30 #include <ctype.h>
31 #include <langinfo.h>
32 #include <limits.h>
33 #include <string.h>
34 #include <time.h>
35
36 #ifdef _LIBC
37 # include "../locale/localeinfo.h"
38 #endif
39
40
41 #ifndef __P
42 # if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
43 #  define __P(args) args
44 # else
45 #  define __P(args) ()
46 # endif  /* GCC.  */
47 #endif  /* Not __P.  */
48
49 #if ! HAVE_LOCALTIME_R && ! defined (localtime_r)
50 #ifdef _LIBC
51 #define localtime_r __localtime_r
52 #else
53 /* Approximate localtime_r as best we can in its absence.  */
54 #define localtime_r my_localtime_r
55 static struct tm *localtime_r __P ((const time_t *, struct tm *));
56 static struct tm *
57 localtime_r (t, tp)
58      const time_t *t;
59      struct tm *tp;
60 {
61   struct tm *l = localtime (t);
62   if (! l)
63     return 0;
64   *tp = *l;
65   return tp;
66 }
67 #endif /* ! _LIBC */
68 #endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */
69
70
71 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
72 #if defined __GNUC__ && __GNUC__ >= 2
73 # define match_string(cs1, s2) \
74   ({ size_t len = strlen (cs1);                                               \
75      int result = strncasecmp ((cs1), (s2), len) == 0;                        \
76      if (result) (s2) += len;                                                 \
77      result; })
78 #else
79 /* Oh come on.  Get a reasonable compiler.  */
80 # define match_string(cs1, s2) \
81   (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
82 #endif
83 /* We intentionally do not use isdigit() for testing because this will
84    lead to problems with the wide character version.  */
85 #define get_number(from, to) \
86   do {                                                                        \
87     val = 0;                                                                  \
88     if (*rp < '0' || *rp > '9')                                               \
89       return NULL;                                                            \
90     do {                                                                      \
91       val *= 10;                                                              \
92       val += *rp++ - '0';                                                     \
93     } while (val * 10 <= to && *rp >= '0' && *rp <= '9');                     \
94     if (val < from || val > to)                                               \
95       return NULL;                                                            \
96   } while (0)
97 #ifdef _NL_CURRENT
98 # define get_alt_number(from, to) \
99   do {                                                                        \
100     if (*decided != raw)                                                      \
101       {                                                                       \
102         const char *alts = _NL_CURRENT (LC_TIME, ALT_DIGITS);                 \
103         val = 0;                                                              \
104         while (*alts != '\0')                                                 \
105           {                                                                   \
106             size_t len = strlen (alts);                                       \
107             if (strncasecmp (alts, rp, len) == 0)                             \
108               break;                                                          \
109             alts = strchr (alts, '\0') + 1;                                   \
110             ++val;                                                            \
111           }                                                                   \
112         if (*alts == '\0')                                                    \
113           {                                                                   \
114             if (*decided == loc && val != 0)                                  \
115               return NULL;                                                    \
116           }                                                                   \
117         else                                                                  \
118           {                                                                   \
119             *decided = loc;                                                   \
120             break;                                                            \
121           }                                                                   \
122       }                                                                       \
123     get_number (from, to);                                                    \
124   } while (0)
125 #else
126 # define get_alt_number(from, to) \
127   /* We don't have the alternate representation.  */                          \
128   get_number(from, to)
129 #endif
130 #define recursive(new_fmt) \
131   (*(new_fmt) != '\0'                                                         \
132    && (rp = strptime_internal (rp, (new_fmt), tm, decided)) != NULL)
133
134
135 #ifdef _LIBC
136 /* This is defined in locale/C-time.c in the GNU libc.  */
137 extern const struct locale_data _nl_C_LC_TIME;
138
139 # define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string)
140 # define ab_weekday_name \
141   (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string)
142 # define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string)
143 # define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string)
144 # define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
145 # define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
146 # define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string)
147 # define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string)
148 # define HERE_T_FMT_AMPM \
149   (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string)
150 # define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string)
151 #else
152 static char const weekday_name[][10] =
153   {
154     "Sunday", "Monday", "Tuesday", "Wednesday",
155     "Thursday", "Friday", "Saturday"
156   };
157 static char const ab_weekday_name[][4] =
158   {
159     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
160   };
161 static char const month_name[][10] =
162   {
163     "January", "February", "March", "April", "May", "June",
164     "July", "August", "September", "October", "November", "December"
165   };
166 static char const ab_month_name[][4] =
167   {
168     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
169     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
170   };
171 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
172 # define HERE_D_FMT "%m/%d/%y"
173 # define HERE_AM_STR "AM"
174 # define HERE_PM_STR "PM"
175 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
176 # define HERE_T_FMT "%H:%M:%S"
177 #endif
178
179 /* Status of lookup: do we use the locale data or the raw data?  */
180 enum locale_status { not, loc, raw };
181
182 static char *
183 #ifdef _LIBC
184 internal_function
185 #endif
186 strptime_internal __P ((const char *buf, const char *format, struct tm *tm,
187                         enum locale_status *decided));
188
189 static char *
190 #ifdef _LIBC
191 internal_function
192 #endif
193 strptime_internal (buf, format, tm, decided)
194      const char *buf;
195      const char *format;
196      struct tm *tm;
197      enum locale_status *decided;
198 {
199   const char *rp;
200   const char *fmt;
201   int cnt;
202   size_t val;
203   int have_I, is_pm;
204
205   rp = buf;
206   fmt = format;
207   have_I = is_pm = 0;
208
209   while (*fmt != '\0')
210     {
211       /* A white space in the format string matches 0 more or white
212          space in the input string.  */
213       if (isspace (*fmt))
214         {
215           while (isspace (*rp))
216             ++rp;
217           ++fmt;
218           continue;
219         }
220
221       /* Any character but `%' must be matched by the same character
222          in the iput string.  */
223       if (*fmt != '%')
224         {
225           match_char (*fmt++, *rp++);
226           continue;
227         }
228
229       ++fmt;
230 #ifndef _NL_CURRENT
231       /* We need this for handling the `E' modifier.  */
232     start_over:
233 #endif
234       switch (*fmt++)
235         {
236         case '%':
237           /* Match the `%' character itself.  */
238           match_char ('%', *rp++);
239           break;
240         case 'a':
241         case 'A':
242           /* Match day of week.  */
243           for (cnt = 0; cnt < 7; ++cnt)
244             {
245 #ifdef _NL_CURRENT
246               if (*decided !=raw)
247                 {
248                   if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
249                     {
250                       if (*decided == not
251                           && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt),
252                                      weekday_name[cnt]))
253                         *decided = loc;
254                       break;
255                     }
256                   if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp))
257                     {
258                       if (*decided == not
259                           && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt),
260                                      ab_weekday_name[cnt]))
261                         *decided = loc;
262                       break;
263                     }
264                 }
265 #endif
266               if (*decided != loc
267                   && (match_string (weekday_name[cnt], rp)
268                       || match_string (ab_weekday_name[cnt], rp)))
269                 {
270                   *decided = raw;
271                   break;
272                 }
273             }
274           if (cnt == 7)
275             /* Does not match a weekday name.  */
276             return NULL;
277           tm->tm_wday = cnt;
278           break;
279         case 'b':
280         case 'B':
281         case 'h':
282           /* Match month name.  */
283           for (cnt = 0; cnt < 12; ++cnt)
284             {
285 #ifdef _NL_CURRENT
286               if (*decided !=raw)
287                 {
288                   if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
289                     {
290                       if (*decided == not
291                           && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt),
292                                      month_name[cnt]))
293                         *decided = loc;
294                       break;
295                     }
296                   if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp))
297                     {
298                       if (*decided == not
299                           && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt),
300                                      ab_month_name[cnt]))
301                         *decided = loc;
302                       break;
303                     }
304                 }
305 #endif
306               if (match_string (month_name[cnt], rp)
307                   || match_string (ab_month_name[cnt], rp))
308                 {
309                   *decided = raw;
310                   break;
311                 }
312             }
313           if (cnt == 12)
314             /* Does not match a month name.  */
315             return NULL;
316           tm->tm_mon = cnt;
317           break;
318         case 'c':
319           /* Match locale's date and time format.  */
320 #ifdef _NL_CURRENT
321           if (*decided != raw)
322             {
323               if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT)))
324                 {
325                   if (*decided == loc)
326                     return NULL;
327                 }
328               else
329                 {
330                   if (*decided == not &&
331                       strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT))
332                     *decided = loc;
333                   break;
334                 }
335               *decided = raw;
336             }
337 #endif
338           if (!recursive (HERE_D_T_FMT))
339             return NULL;
340           break;
341         case 'C':
342           /* Match century number.  */
343           get_number (0, 99);
344           /* We don't need the number.  */
345           break;
346         case 'd':
347         case 'e':
348           /* Match day of month.  */
349           get_number (1, 31);
350           tm->tm_mday = val;
351           break;
352         case 'x':
353 #ifdef _NL_CURRENT
354           if (*decided != raw)
355             {
356               if (!recursive (_NL_CURRENT (LC_TIME, D_FMT)))
357                 {
358                   if (*decided == loc)
359                     return NULL;
360                 }
361               else
362                 {
363                   if (decided == not
364                       && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT))
365                     *decided = loc;
366                   break;
367                 }
368               *decided = raw;
369             }
370 #endif
371           /* Fall through.  */
372         case 'D':
373           /* Match standard day format.  */
374           if (!recursive (HERE_D_FMT))
375             return NULL;
376           break;
377         case 'H':
378           /* Match hour in 24-hour clock.  */
379           get_number (0, 23);
380           tm->tm_hour = val;
381           have_I = 0;
382           break;
383         case 'I':
384           /* Match hour in 12-hour clock.  */
385           get_number (1, 12);
386           tm->tm_hour = val % 12;
387           have_I = 1;
388           break;
389         case 'j':
390           /* Match day number of year.  */
391           get_number (1, 366);
392           tm->tm_yday = val - 1;
393           break;
394         case 'm':
395           /* Match number of month.  */
396           get_number (1, 12);
397           tm->tm_mon = val - 1;
398           break;
399         case 'M':
400           /* Match minute.  */
401           get_number (0, 59);
402           tm->tm_min = val;
403           break;
404         case 'n':
405         case 't':
406           /* Match any white space.  */
407           while (isspace (*rp))
408             ++rp;
409           break;
410         case 'p':
411           /* Match locale's equivalent of AM/PM.  */
412 #ifdef _NL_CURRENT
413           if (*decided != raw)
414             {
415               if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp))
416                 {
417                   if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR))
418                     *decided = loc;
419                   break;
420                 }
421               if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp))
422                 {
423                   if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR))
424                     *decided = loc;
425                   is_pm = 1;
426                   break;
427                 }
428               *decided = raw;
429             }
430 #endif
431           if (!match_string (HERE_AM_STR, rp))
432             if (match_string (HERE_PM_STR, rp))
433               is_pm = 1;
434             else
435               return NULL;
436           break;
437         case 'r':
438 #ifdef _NL_CURRENT
439           if (*decided != raw)
440             {
441               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM)))
442                 {
443                   if (*decided == loc)
444                     return NULL;
445                 }
446               else
447                 {
448                   if (*decided == not &&
449                       strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM),
450                               HERE_T_FMT_AMPM))
451                     *decided = loc;
452                   break;
453                 }
454               *decided = raw;
455             }
456 #endif
457           if (!recursive (HERE_T_FMT_AMPM))
458             return NULL;
459           break;
460         case 'R':
461           if (!recursive ("%H:%M"))
462             return NULL;
463           break;
464         case 's':
465           {
466             /* The number of seconds may be very high so we cannot use
467                the `get_number' macro.  Instead read the number
468                character for character and construct the result while
469                doing this.  */
470             time_t secs;
471             if (*rp < '0' || *rp > '9')
472               /* We need at least one digit.  */
473               return NULL;
474
475             do
476               {
477                 secs *= 10;
478                 secs += *rp++ - '0';
479               }
480             while (*rp >= '0' && *rp <= '9');
481
482             if (localtime_r (&secs, tm) == NULL)
483               /* Error in function.  */
484               return NULL;
485           }
486           break;
487         case 'S':
488           get_number (0, 61);
489           tm->tm_sec = val;
490           break;
491         case 'X':
492 #ifdef _NL_CURRENT
493           if (*decided != raw)
494             {
495               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT)))
496                 {
497                   if (*decided == loc)
498                     return NULL;
499                 }
500               else
501                 {
502                   if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT))
503                     *decided = loc;
504                   break;
505                 }
506               *decided = raw;
507             }
508 #endif
509           /* Fall through.  */
510         case 'T':
511           if (!recursive (HERE_T_FMT))
512             return NULL;
513           break;
514         case 'u':
515           get_number (1, 7);
516           tm->tm_wday = val % 7;
517           break;
518         case 'g':
519           get_number (0, 99);
520           /* XXX This cannot determine any field in TM.  */
521           break;
522         case 'G':
523           if (*rp < '0' || *rp > '9')
524             return NULL;
525           /* XXX Ignore the number since we would need some more
526              information to compute a real date.  */
527           do
528             ++rp;
529           while (*rp >= '0' && *rp <= '9');
530           break;
531         case 'U':
532         case 'V':
533         case 'W':
534           get_number (0, 53);
535           /* XXX This cannot determine any field in TM without some
536              information.  */
537           break;
538         case 'w':
539           /* Match number of weekday.  */
540           get_number (0, 6);
541           tm->tm_wday = val;
542           break;
543         case 'y':
544           /* Match year within century.  */
545           get_number (0, 99);
546           /* The "Year 2000 :The Millennium Rollover" paper suggests that
547              values in the range 69-99 refer to the twentieth century.  */
548           tm->tm_year = val >= 69 ? val : val + 100;
549           break;
550         case 'Y':
551           /* Match year including century number.  */
552           if (sizeof (time_t) > 4)
553             get_number (0, 9999);
554           else
555             get_number (0, 2036);
556           tm->tm_year = val - 1900;
557           break;
558         case 'Z':
559           /* XXX How to handle this?  */
560           break;
561         case 'E':
562 #ifdef _NL_CURRENT
563           switch (*fmt++)
564             {
565             case 'c':
566               /* Match locale's alternate date and time format.  */
567               if (*decided != raw)
568                 {
569                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
570
571                   if (*fmt == '\0')
572                     fmt = _NL_CURRENT (LC_TIME, D_T_FMT);
573
574                   if (!recursive (fmt))
575                     {
576                       if (*decided == loc)
577                         return NULL;
578                     }
579                   else
580                     {
581                       if (strcmp (fmt, HERE_D_T_FMT))
582                         *decided = loc;
583                       break;
584                     }
585                   *decided = raw;
586                 }
587               /* The C locale has no era information, so use the
588                  normal representation.  */
589               if (!recursive (HERE_D_T_FMT))
590                 return NULL;
591               break;
592             case 'C':
593             case 'y':
594             case 'Y':
595               /* Match name of base year in locale's alternate
596                  representation.  */
597               /* XXX This is currently not implemented.  It should
598                  use the value _NL_CURRENT (LC_TIME, ERA).  */
599               break;
600             case 'x':
601               if (*decided != raw)
602                 {
603                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
604
605                   if (*fmt == '\0')
606                     fmt = _NL_CURRENT (LC_TIME, D_FMT);
607
608                   if (!recursive (fmt))
609                     {
610                       if (*decided == loc)
611                         return NULL;
612                     }
613                   else
614                     {
615                       if (strcmp (fmt, HERE_D_FMT))
616                         *decided = loc;
617                       break;
618                     }
619                   *decided = raw;
620                 }
621               if (!recursive (HERE_D_FMT))
622                 return NULL;
623               break;
624             case 'X':
625               if (*decided != raw)
626                 {
627                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
628
629                   if (*fmt == '\0')
630                     fmt = _NL_CURRENT (LC_TIME, T_FMT);
631
632                   if (!recursive (fmt))
633                     {
634                       if (*decided == loc)
635                         return NULL;
636                     }
637                   else
638                     {
639                       if (strcmp (fmt, HERE_T_FMT))
640                         *decided = loc;
641                       break;
642                     }
643                   *decided = raw;
644                 }
645               if (!recursive (HERE_T_FMT))
646                 return NULL;
647               break;
648             default:
649               return NULL;
650             }
651           break;
652 #else
653           /* We have no information about the era format.  Just use
654              the normal format.  */
655           if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
656               && *fmt != 'x' && *fmt != 'X')
657             /* This is an illegal format.  */
658             return NULL;
659
660           goto start_over;
661 #endif
662         case 'O':
663           switch (*fmt++)
664             {
665             case 'd':
666             case 'e':
667               /* Match day of month using alternate numeric symbols.  */
668               get_alt_number (1, 31);
669               tm->tm_mday = val;
670               break;
671             case 'H':
672               /* Match hour in 24-hour clock using alternate numeric
673                  symbols.  */
674               get_alt_number (0, 23);
675               tm->tm_hour = val;
676               have_I = 0;
677               break;
678             case 'I':
679               /* Match hour in 12-hour clock using alternate numeric
680                  symbols.  */
681               get_alt_number (1, 12);
682               tm->tm_hour = val - 1;
683               have_I = 1;
684               break;
685             case 'm':
686               /* Match month using alternate numeric symbols.  */
687               get_alt_number (1, 12);
688               tm->tm_mon = val - 1;
689               break;
690             case 'M':
691               /* Match minutes using alternate numeric symbols.  */
692               get_alt_number (0, 59);
693               tm->tm_min = val;
694               break;
695             case 'S':
696               /* Match seconds using alternate numeric symbols.  */
697               get_alt_number (0, 61);
698               tm->tm_sec = val;
699               break;
700             case 'U':
701             case 'V':
702             case 'W':
703               get_alt_number (0, 53);
704               /* XXX This cannot determine any field in TM without
705                  further information.  */
706               break;
707             case 'w':
708               /* Match number of weekday using alternate numeric symbols.  */
709               get_alt_number (0, 6);
710               tm->tm_wday = val;
711               break;
712             case 'y':
713               /* Match year within century using alternate numeric symbols.  */
714               get_alt_number (0, 99);
715               break;
716             default:
717               return NULL;
718             }
719           break;
720         default:
721           return NULL;
722         }
723     }
724
725   if (have_I && is_pm)
726     tm->tm_hour += 12;
727
728   return (char *) rp;
729 }
730
731
732 char *
733 strptime (buf, format, tm)
734      const char *buf;
735      const char *format;
736      struct tm *tm;
737 {
738   enum locale_status decided;
739 #ifdef _NL_CURRENT
740   decided = not;
741 #else
742   decided = raw;
743 #endif
744   return strptime_internal (buf, format, tm, &decided);
745 }