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