(strptime_internal, case 'Y'): Restrict year number to four digits and
[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 strptime_internal __P ((const char *buf, const char *format, struct tm *tm,
184                         enum locale_status *decided));
185
186 static char *
187 strptime_internal (buf, format, tm, decided)
188      const char *buf;
189      const char *format;
190      struct tm *tm;
191      enum locale_status *decided;
192 {
193   const char *rp;
194   const char *fmt;
195   int cnt;
196   size_t val;
197   int have_I, is_pm;
198
199   rp = buf;
200   fmt = format;
201   have_I = is_pm = 0;
202
203   while (*fmt != '\0')
204     {
205       /* A white space in the format string matches 0 more or white
206          space in the input string.  */
207       if (isspace (*fmt))
208         {
209           while (isspace (*rp))
210             ++rp;
211           ++fmt;
212           continue;
213         }
214
215       /* Any character but `%' must be matched by the same character
216          in the iput string.  */
217       if (*fmt != '%')
218         {
219           match_char (*fmt++, *rp++);
220           continue;
221         }
222
223       ++fmt;
224 #ifndef _NL_CURRENT
225       /* We need this for handling the `E' modifier.  */
226     start_over:
227 #endif
228       switch (*fmt++)
229         {
230         case '%':
231           /* Match the `%' character itself.  */
232           match_char ('%', *rp++);
233           break;
234         case 'a':
235         case 'A':
236           /* Match day of week.  */
237           for (cnt = 0; cnt < 7; ++cnt)
238             {
239 #ifdef _NL_CURRENT
240               if (*decided !=raw)
241                 {
242                   if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
243                     {
244                       if (*decided == not
245                           && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt),
246                                      weekday_name[cnt]))
247                         *decided = loc;
248                       break;
249                     }
250                   if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp))
251                     {
252                       if (*decided == not
253                           && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt),
254                                      ab_weekday_name[cnt]))
255                         *decided = loc;
256                       break;
257                     }
258                 }
259 #endif
260               if (*decided != loc
261                   && (match_string (weekday_name[cnt], rp)
262                       || match_string (ab_weekday_name[cnt], rp)))
263                 {
264                   *decided = raw;
265                   break;
266                 }
267             }
268           if (cnt == 7)
269             /* Does not match a weekday name.  */
270             return NULL;
271           tm->tm_wday = cnt;
272           break;
273         case 'b':
274         case 'B':
275         case 'h':
276           /* Match month name.  */
277           for (cnt = 0; cnt < 12; ++cnt)
278             {
279 #ifdef _NL_CURRENT
280               if (*decided !=raw)
281                 {
282                   if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
283                     {
284                       if (*decided == not
285                           && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt),
286                                      month_name[cnt]))
287                         *decided = loc;
288                       break;
289                     }
290                   if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp))
291                     {
292                       if (*decided == not
293                           && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt),
294                                      ab_month_name[cnt]))
295                         *decided = loc;
296                       break;
297                     }
298                 }
299 #endif
300               if (match_string (month_name[cnt], rp)
301                   || match_string (ab_month_name[cnt], rp))
302                 {
303                   *decided = raw;
304                   break;
305                 }
306             }
307           if (cnt == 12)
308             /* Does not match a month name.  */
309             return NULL;
310           tm->tm_mon = cnt;
311           break;
312         case 'c':
313           /* Match locale's date and time format.  */
314 #ifdef _NL_CURRENT
315           if (*decided != raw)
316             {
317               if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT)))
318                 {
319                   if (*decided == loc)
320                     return NULL;
321                 }
322               else
323                 {
324                   if (*decided == not &&
325                       strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT))
326                     *decided = loc;
327                   break;
328                 }
329               *decided = raw;
330             }
331 #endif
332           if (!recursive (HERE_D_T_FMT))
333             return NULL;
334           break;
335         case 'C':
336           /* Match century number.  */
337           get_number (0, 99);
338           /* We don't need the number.  */
339           break;
340         case 'd':
341         case 'e':
342           /* Match day of month.  */
343           get_number (1, 31);
344           tm->tm_mday = val;
345           break;
346         case 'x':
347 #ifdef _NL_CURRENT
348           if (*decided != raw)
349             {
350               if (!recursive (_NL_CURRENT (LC_TIME, D_FMT)))
351                 {
352                   if (*decided == loc)
353                     return NULL;
354                 }
355               else
356                 {
357                   if (decided == not
358                       && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT))
359                     *decided = loc;
360                   break;
361                 }
362               *decided = raw;
363             }
364 #endif
365           /* Fall through.  */
366         case 'D':
367           /* Match standard day format.  */
368           if (!recursive (HERE_D_FMT))
369             return NULL;
370           break;
371         case 'H':
372           /* Match hour in 24-hour clock.  */
373           get_number (0, 23);
374           tm->tm_hour = val;
375           have_I = 0;
376           break;
377         case 'I':
378           /* Match hour in 12-hour clock.  */
379           get_number (1, 12);
380           tm->tm_hour = val % 12;
381           have_I = 1;
382           break;
383         case 'j':
384           /* Match day number of year.  */
385           get_number (1, 366);
386           tm->tm_yday = val - 1;
387           break;
388         case 'm':
389           /* Match number of month.  */
390           get_number (1, 12);
391           tm->tm_mon = val - 1;
392           break;
393         case 'M':
394           /* Match minute.  */
395           get_number (0, 59);
396           tm->tm_min = val;
397           break;
398         case 'n':
399         case 't':
400           /* Match any white space.  */
401           while (isspace (*rp))
402             ++rp;
403           break;
404         case 'p':
405           /* Match locale's equivalent of AM/PM.  */
406 #ifdef _NL_CURRENT
407           if (*decided != raw)
408             {
409               if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp))
410                 {
411                   if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR))
412                     *decided = loc;
413                   break;
414                 }
415               if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp))
416                 {
417                   if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR))
418                     *decided = loc;
419                   is_pm = 1;
420                   break;
421                 }
422               *decided = raw;
423             }
424 #endif
425           if (!match_string (HERE_AM_STR, rp))
426             if (match_string (HERE_PM_STR, rp))
427               is_pm = 1;
428             else
429               return NULL;
430           break;
431         case 'r':
432 #ifdef _NL_CURRENT
433           if (*decided != raw)
434             {
435               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM)))
436                 {
437                   if (*decided == loc)
438                     return NULL;
439                 }
440               else
441                 {
442                   if (*decided == not &&
443                       strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM),
444                               HERE_T_FMT_AMPM))
445                     *decided = loc;
446                   break;
447                 }
448               *decided = raw;
449             }
450 #endif
451           if (!recursive (HERE_T_FMT_AMPM))
452             return NULL;
453           break;
454         case 'R':
455           if (!recursive ("%H:%M"))
456             return NULL;
457           break;
458         case 's':
459           {
460             /* The number of seconds may be very high so we cannot use
461                the `get_number' macro.  Instead read the number
462                character for character and construct the result while
463                doing this.  */
464             time_t secs;
465             if (*rp < '0' || *rp > '9')
466               /* We need at least one digit.  */
467               return NULL;
468
469             do
470               {
471                 secs *= 10;
472                 secs += *rp++ - '0';
473               }
474             while (*rp >= '0' && *rp <= '9');
475
476             if (localtime_r (&secs, tm) == NULL)
477               /* Error in function.  */
478               return NULL;
479           }
480           break;
481         case 'S':
482           get_number (0, 61);
483           tm->tm_sec = val;
484           break;
485         case 'X':
486 #ifdef _NL_CURRENT
487           if (*decided != raw)
488             {
489               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT)))
490                 {
491                   if (*decided == loc)
492                     return NULL;
493                 }
494               else
495                 {
496                   if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT))
497                     *decided = loc;
498                   break;
499                 }
500               *decided = raw;
501             }
502 #endif
503           /* Fall through.  */
504         case 'T':
505           if (!recursive (HERE_T_FMT))
506             return NULL;
507           break;
508         case 'u':
509           get_number (1, 7);
510           tm->tm_wday = val % 7;
511           break;
512         case 'g':
513           get_number (0, 99);
514           /* XXX This cannot determine any field in TM.  */
515           break;
516         case 'G':
517           if (*rp < '0' || *rp > '9')
518             return NULL;
519           /* XXX Ignore the number since we would need some more
520              information to compute a real date.  */
521           do
522             ++rp;
523           while (*rp >= '0' && *rp <= '9');
524           break;
525         case 'U':
526         case 'V':
527         case 'W':
528           get_number (0, 53);
529           /* XXX This cannot determine any field in TM without some
530              information.  */
531           break;
532         case 'w':
533           /* Match number of weekday.  */
534           get_number (0, 6);
535           tm->tm_wday = val;
536           break;
537         case 'y':
538           /* Match year within century.  */
539           get_number (0, 99);
540           tm->tm_year = val >= 50 ? val : val + 100;
541           break;
542         case 'Y':
543           /* Match year including century number.  */
544           if (sizeof (time_t) > 4)
545             get_number (0, 9999);
546           else
547             get_number (0, 2036);
548           tm->tm_year = val - 1900;
549           break;
550         case 'Z':
551           /* XXX How to handle this?  */
552           break;
553         case 'E':
554 #ifdef _NL_CURRENT
555           switch (*fmt++)
556             {
557             case 'c':
558               /* Match locale's alternate date and time format.  */
559               if (*decided != raw)
560                 {
561                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
562
563                   if (*fmt == '\0')
564                     fmt = _NL_CURRENT (LC_TIME, D_T_FMT);
565
566                   if (!recursive (fmt))
567                     {
568                       if (*decided == loc)
569                         return NULL;
570                     }
571                   else
572                     {
573                       if (strcmp (fmt, HERE_D_T_FMT))
574                         *decided = loc;
575                       break;
576                     }
577                   *decided = raw;
578                 }
579               /* The C locale has no era information, so use the
580                  normal representation.  */
581               if (!recursive (HERE_D_T_FMT))
582                 return NULL;
583               break;
584             case 'C':
585             case 'y':
586             case 'Y':
587               /* Match name of base year in locale's alternate
588                  representation.  */
589               /* XXX This is currently not implemented.  It should
590                  use the value _NL_CURRENT (LC_TIME, ERA).  */
591               break;
592             case 'x':
593               if (*decided != raw)
594                 {
595                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
596
597                   if (*fmt == '\0')
598                     fmt = _NL_CURRENT (LC_TIME, D_FMT);
599
600                   if (!recursive (fmt))
601                     {
602                       if (*decided == loc)
603                         return NULL;
604                     }
605                   else
606                     {
607                       if (strcmp (fmt, HERE_D_FMT))
608                         *decided = loc;
609                       break;
610                     }
611                   *decided = raw;
612                 }
613               if (!recursive (HERE_D_FMT))
614                 return NULL;
615               break;
616             case 'X':
617               if (*decided != raw)
618                 {
619                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
620
621                   if (*fmt == '\0')
622                     fmt = _NL_CURRENT (LC_TIME, T_FMT);
623
624                   if (!recursive (fmt))
625                     {
626                       if (*decided == loc)
627                         return NULL;
628                     }
629                   else
630                     {
631                       if (strcmp (fmt, HERE_T_FMT))
632                         *decided = loc;
633                       break;
634                     }
635                   *decided = raw;
636                 }
637               if (!recursive (HERE_T_FMT))
638                 return NULL;
639               break;
640             default:
641               return NULL;
642             }
643           break;
644 #else
645           /* We have no information about the era format.  Just use
646              the normal format.  */
647           if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
648               && *fmt != 'x' && *fmt != 'X')
649             /* This is an illegal format.  */
650             return NULL;
651
652           goto start_over;
653 #endif
654         case 'O':
655           switch (*fmt++)
656             {
657             case 'd':
658             case 'e':
659               /* Match day of month using alternate numeric symbols.  */
660               get_alt_number (1, 31);
661               tm->tm_mday = val;
662               break;
663             case 'H':
664               /* Match hour in 24-hour clock using alternate numeric
665                  symbols.  */
666               get_alt_number (0, 23);
667               tm->tm_hour = val;
668               have_I = 0;
669               break;
670             case 'I':
671               /* Match hour in 12-hour clock using alternate numeric
672                  symbols.  */
673               get_alt_number (1, 12);
674               tm->tm_hour = val - 1;
675               have_I = 1;
676               break;
677             case 'm':
678               /* Match month using alternate numeric symbols.  */
679               get_alt_number (1, 12);
680               tm->tm_mon = val - 1;
681               break;
682             case 'M':
683               /* Match minutes using alternate numeric symbols.  */
684               get_alt_number (0, 59);
685               tm->tm_min = val;
686               break;
687             case 'S':
688               /* Match seconds using alternate numeric symbols.  */
689               get_alt_number (0, 61);
690               tm->tm_sec = val;
691               break;
692             case 'U':
693             case 'V':
694             case 'W':
695               get_alt_number (0, 53);
696               /* XXX This cannot determine any field in TM without
697                  further information.  */
698               break;
699             case 'w':
700               /* Match number of weekday using alternate numeric symbols.  */
701               get_alt_number (0, 6);
702               tm->tm_wday = val;
703               break;
704             case 'y':
705               /* Match year within century using alternate numeric symbols.  */
706               get_alt_number (0, 99);
707               break;
708             default:
709               return NULL;
710             }
711           break;
712         default:
713           return NULL;
714         }
715     }
716
717   if (have_I && is_pm)
718     tm->tm_hour += 12;
719
720   return (char *) rp;
721 }
722
723
724 char *
725 strptime (buf, format, tm)
726      const char *buf;
727      const char *format;
728      struct tm *tm;
729 {
730   enum locale_status decided;
731 #ifdef _NL_CURRENT
732   decided = not;
733 #else
734   decided = raw;
735 #endif
736   return strptime_internal (buf, format, tm, &decided);
737 }