update from main archive 970120
[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    && 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, ABDAY_1 + cnt), rp))
243                     {
244                       if (*decided == not
245                           && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt),
246                                      ab_weekday_name[cnt]))
247                         *decided = loc;
248                       break;
249                     }
250                   if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
251                     {
252                       if (*decided == not
253                           && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt),
254                                      weekday_name[cnt]))
255                         *decided = loc;
256                       break;
257                     }
258                 }
259 #endif
260               if (*decided != loc
261                   && (match_string (ab_weekday_name[cnt], rp)
262                       || match_string (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, ABMON_1 + cnt), rp))
283                     {
284                       if (*decided == not
285                           && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt),
286                                      ab_month_name[cnt]))
287                         *decided = loc;
288                       break;
289                     }
290                   if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
291                     {
292                       if (*decided == not
293                           && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt),
294                                      month_name[cnt]))
295                         *decided = loc;
296                       break;
297                     }
298                 }
299 #endif
300               if (match_string (ab_month_name[cnt], rp)
301                   || match_string (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 - 1;
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;
541           break;
542         case 'Y':
543           /* Match year including century number.  */
544           get_number (0, INT_MAX);
545           tm->tm_year = val - (val >= 2000 ? 2000 : 1900);
546           break;
547         case 'Z':
548           /* XXX How to handle this?  */
549           break;
550         case 'E':
551 #ifdef _NL_CURRENT
552           switch (*fmt++)
553             {
554             case 'c':
555               /* Match locale's alternate date and time format.  */
556               if (*decided != raw)
557                 {
558                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
559
560                   if (*fmt == '\0')
561                     fmt = _NL_CURRENT (LC_TIME, D_T_FMT);
562
563                   if (!recursive (fmt))
564                     {
565                       if (*decided == loc)
566                         return NULL;
567                     }
568                   else
569                     {
570                       if (strcmp (fmt, HERE_D_T_FMT))
571                         *decided = loc;
572                       break;
573                     }
574                   *decided = raw;
575                 }
576               /* The C locale has no era information, so use the
577                  normal representation.  */
578               if (!recursive (HERE_D_T_FMT))
579                 return NULL;
580               break;
581             case 'C':
582             case 'y':
583             case 'Y':
584               /* Match name of base year in locale's alternate
585                  representation.  */
586               /* XXX This is currently not implemented.  It should
587                  use the value _NL_CURRENT (LC_TIME, ERA).  */
588               break;
589             case 'x':
590               if (*decided != raw)
591                 {
592                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
593
594                   if (*fmt == '\0')
595                     fmt = _NL_CURRENT (LC_TIME, D_FMT);
596
597                   if (!recursive (fmt))
598                     {
599                       if (*decided == loc)
600                         return NULL;
601                     }
602                   else
603                     {
604                       if (strcmp (fmt, HERE_D_FMT))
605                         *decided = loc;
606                       break;
607                     }
608                   *decided = raw;
609                 }
610               if (!recursive (HERE_D_FMT))
611                 return NULL;
612               break;
613             case 'X':
614               if (*decided != raw)
615                 {
616                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
617
618                   if (*fmt == '\0')
619                     fmt = _NL_CURRENT (LC_TIME, T_FMT);
620
621                   if (!recursive (fmt))
622                     {
623                       if (*decided == loc)
624                         return NULL;
625                     }
626                   else
627                     {
628                       if (strcmp (fmt, HERE_T_FMT))
629                         *decided = loc;
630                       break;
631                     }
632                   *decided = raw;
633                 }
634               if (!recursive (HERE_T_FMT))
635                 return NULL;
636               break;
637             default:
638               return NULL;
639             }
640           break;
641 #else
642           /* We have no information about the era format.  Just use
643              the normal format.  */
644           if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
645               && *fmt != 'x' && *fmt != 'X')
646             /* This is an illegal format.  */
647             return NULL;
648
649           goto start_over;
650 #endif
651         case 'O':
652           switch (*fmt++)
653             {
654             case 'd':
655             case 'e':
656               /* Match day of month using alternate numeric symbols.  */
657               get_alt_number (1, 31);
658               tm->tm_mday = val;
659               break;
660             case 'H':
661               /* Match hour in 24-hour clock using alternate numeric
662                  symbols.  */
663               get_alt_number (0, 23);
664               tm->tm_hour = val;
665               have_I = 0;
666               break;
667             case 'I':
668               /* Match hour in 12-hour clock using alternate numeric
669                  symbols.  */
670               get_alt_number (1, 12);
671               tm->tm_hour = val - 1;
672               have_I = 1;
673               break;
674             case 'm':
675               /* Match month using alternate numeric symbols.  */
676               get_alt_number (1, 12);
677               tm->tm_mon = val - 1;
678               break;
679             case 'M':
680               /* Match minutes using alternate numeric symbols.  */
681               get_alt_number (0, 59);
682               tm->tm_min = val;
683               break;
684             case 'S':
685               /* Match seconds using alternate numeric symbols.  */
686               get_alt_number (0, 61);
687               tm->tm_sec = val;
688               break;
689             case 'U':
690             case 'V':
691             case 'W':
692               get_alt_number (0, 53);
693               /* XXX This cannot determine any field in TM without
694                  further information.  */
695               break;
696             case 'w':
697               /* Match number of weekday using alternate numeric symbols.  */
698               get_alt_number (0, 6);
699               tm->tm_wday = val;
700               break;
701             case 'y':
702               /* Match year within century using alternate numeric symbols.  */
703               get_alt_number (0, 99);
704               break;
705             default:
706               return NULL;
707             }
708           break;
709         default:
710           return NULL;
711         }
712     }
713
714   if (have_I && is_pm)
715     tm->tm_hour += 12;
716
717   return (char *) rp;
718 }
719
720
721 char *
722 strptime (buf, format, tm)
723      const char *buf;
724      const char *format;
725      struct tm *tm;
726 {
727   enum locale_status decided;
728 #ifdef _NL_CURRENT
729   decided = not;
730 #else
731   decided = raw;
732 #endif
733   return strptime_internal (buf, format, tm, &decided);
734 }