update from main archive 961209
[kopensolaris-gnu/glibc.git] / time / strptime.c
1 /* strptime - Convert a string representation of time to a time value.
2    Copyright (C) 1996 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 #include <ctype.h>
22 #include <langinfo.h>
23 #include <limits.h>
24 #include <string.h>
25 #include <time.h>
26
27 #include "../locale/localeinfo.h"
28
29
30 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
31 #define match_string(cs1, s2)                                                 \
32   ({ size_t len = strlen (cs1);                                               \
33      int result = strncasecmp (cs1, s2, len) == 0;                            \
34      if (result) s2 += len;                                                   \
35      result; })
36 /* We intentionally do not use isdigit() for testing because this will
37    lead to problems with the wide character version.  */
38 #define get_number(from, to)                                                  \
39   do {                                                                        \
40     val = 0;                                                                  \
41     if (*rp < '0' || *rp > '9')                                               \
42       return NULL;                                                            \
43     do {                                                                      \
44       val *= 10;                                                              \
45       val += *rp++ - '0';                                                     \
46     } while (val * 10 <= to && *rp >= '0' && *rp <= '9');                     \
47     if (val < from || val > to)                                               \
48       return NULL;                                                            \
49   } while (0)
50 #define get_alt_number(from, to)                                              \
51   do {                                                                        \
52     const char *alts = _NL_CURRENT (LC_TIME, ALT_DIGITS);                     \
53     val = 0;                                                                  \
54     while (*alts != '\0')                                                     \
55       {                                                                       \
56         size_t len = strlen (alts);                                           \
57         if (strncasecmp (alts, rp, len) == 0)                                 \
58           break;                                                              \
59         alts = strchr (alts, '\0') + 1;                                       \
60         ++val;                                                                \
61       }                                                                       \
62     if (*alts == '\0')                                                        \
63       return NULL;                                                            \
64   } while (0)
65 #define recursive(new_fmt)                                                    \
66   do {                                                                        \
67     if (*new_fmt == '\0')                                                     \
68       return NULL;                                                            \
69     rp = strptime (rp, new_fmt, tm);                                          \
70     if (rp == NULL)                                                           \
71       return NULL;                                                            \
72   } while (0)
73
74
75 char *
76 strptime (const char *buf, const char *format, struct tm *tm)
77 {
78   const char *rp;
79   const char *fmt;
80   int cnt;
81   size_t val;
82   int have_I, is_pm;
83
84   rp = buf;
85   fmt = format;
86   have_I = is_pm = 0;
87
88   while (*fmt != '\0')
89     {
90       /* A white space in the format string matches 0 more or white
91          space in the input string.  */
92       if (isspace (*fmt))
93         {
94           while (isspace (*rp))
95             ++rp;
96           ++fmt;
97           continue;
98         }
99
100       /* Any character but `%' must be matched by the same character
101          in the iput string.  */
102       if (*fmt != '%')
103         {
104           match_char (*fmt++, *rp++);
105           continue;
106         }
107
108       ++fmt;
109       switch (*fmt++)
110         {
111         case '%':
112           /* Match the `%' character itself.  */
113           match_char ('%', *rp++);
114           break;
115         case 'a':
116         case 'A':
117           /* Match day of week.  */
118           for (cnt = 0; cnt < 7; ++cnt)
119             {
120               if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp))
121                 break;
122               if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
123                 break;
124             }
125           if (cnt == 7)
126             /* Does not match a weekday name.  */
127             return NULL;
128           tm->tm_wday = cnt;
129           break;
130         case 'b':
131         case 'B':
132         case 'h':
133           /* Match month name.  */
134           for (cnt = 0; cnt < 12; ++cnt)
135             {
136               if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp))
137                 break;
138               if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
139                 break;
140             }
141           if (cnt == 12)
142             /* Does not match a month name.  */
143             return NULL;
144           tm->tm_mon = cnt;
145           break;
146         case 'c':
147           /* Match locale's date and time format.  */
148           recursive (_NL_CURRENT (LC_TIME, D_T_FMT));
149           break;
150         case 'C':
151           /* Match century number.  */
152           get_number (0, 99);
153           /* We don't need the number.  */
154           break;
155         case 'd':
156         case 'e':
157           /* Match day of month.  */
158           get_number (1, 31);
159           tm->tm_mday = val;
160           break;
161         case 'D':
162           /* Match standard day format.  */
163           recursive ("%m/%d/%y");
164           break;
165         case 'H':
166           /* Match hour in 24-hour clock.  */
167           get_number (0, 23);
168           tm->tm_hour = val;
169           have_I = 0;
170           break;
171         case 'I':
172           /* Match hour in 12-hour clock.  */
173           get_number (1, 12);
174           tm->tm_hour = val - 1;
175           have_I = 1;
176           break;
177         case 'j':
178           /* Match day number of year.  */
179           get_number (1, 366);
180           tm->tm_yday = val - 1;
181           break;
182         case 'm':
183           /* Match number of month.  */
184           get_number (1, 12);
185           tm->tm_mon = val - 1;
186           break;
187         case 'M':
188           /* Match minute.  */
189           get_number (0, 59);
190           tm->tm_min = val;
191           break;
192         case 'n':
193         case 't':
194           /* Match any white space.  */
195           while (isspace (*rp))
196             ++rp;
197           break;
198         case 'p':
199           /* Match locale's equivalent of AM/PM.  */
200           if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp))
201             break;
202           if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp))
203             {
204               is_pm = 1;
205               break;
206             }
207           return NULL;
208         case 'r':
209           recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM));
210           break;
211         case 'R':
212           recursive ("%H:%M");
213           break;
214         case 's':
215           {
216             /* The number of seconds may be very high so we cannot use
217                the `get_number' macro.  Instead read the number
218                character for character and construct the result while
219                doing this.  */
220             time_t secs;
221             if (*rp < '0' || *rp > '9')
222               /* We need at least one digit.  */
223               return NULL;
224
225             do
226               {
227                 secs *= 10;
228                 secs += *rp++ - '0';
229               }
230             while (*rp >= '0' && *rp <= '9');
231
232             if (__localtime_r (&secs, tm) == NULL)
233               /* Error in function.  */
234               return NULL;
235           }
236           break;
237         case 'S':
238           get_number (0, 61);
239           tm->tm_sec = val;
240           break;
241         case 'T':
242           recursive ("%H:%M:%S");
243           break;
244         case 'u':
245           get_number (1, 7);
246           tm->tm_wday = val % 7;
247           break;
248         case 'g':
249           get_number (0, 99);
250           /* XXX This cannot determine any field in TM.  */
251           break;
252         case 'G':
253           if (*rp < '0' || *rp > '9')
254             return NULL;
255           /* XXX Ignore the number since we would need some more
256              information to compute a real date.  */
257           do
258             ++rp;
259           while (*rp >= '0' && *rp <= '9');
260           break;
261         case 'U':
262         case 'V':
263         case 'W':
264           get_number (0, 53);
265           /* XXX This cannot determine any field in TM.  */
266           break;
267         case 'w':
268           /* Match number of weekday.  */
269           get_number (0, 6);
270           tm->tm_wday = val;
271           break;
272         case 'x':
273           recursive (_NL_CURRENT (LC_TIME, D_FMT));
274           break;
275         case 'X':
276           recursive (_NL_CURRENT (LC_TIME, T_FMT));
277           break;
278         case 'y':
279           /* Match year within century.  */
280           get_number (0, 99);
281           tm->tm_year = val;
282           break;
283         case 'Y':
284           /* Match year including century number.  */
285           get_number (0, INT_MAX);
286           tm->tm_year = val - (val >= 2000 ? 2000 : 1900);
287           break;
288         case 'Z':
289           /* XXX How to handle this?  */
290           break;
291         case 'E':
292           switch (*fmt++)
293             {
294             case 'c':
295               /* Match locale's alternate date and time format.  */
296               recursive (_NL_CURRENT (LC_TIME, ERA_D_T_FMT));
297               break;
298             case 'C':
299             case 'y':
300             case 'Y':
301               /* Match name of base year in locale's alternate
302                  representation.  */
303               /* XXX This is currently not implemented.  It should
304                  use the value _NL_CURRENT (LC_TIME, ERA) but POSIX
305                  leaves this implementation defined and we haven't
306                  figured out how to do it yet.  */
307               break;
308             case 'x':
309               recursive (_NL_CURRENT (LC_TIME, ERA_D_FMT));
310               break;
311             case 'X':
312               recursive (_NL_CURRENT (LC_TIME, ERA_T_FMT));
313               break;
314             default:
315               return NULL;
316             }
317           break;
318         case 'O':
319           switch (*fmt++)
320             {
321             case 'd':
322             case 'e':
323               /* Match day of month using alternate numeric symbols.  */
324               get_alt_number (1, 31);
325               tm->tm_mday = val;
326               break;
327             case 'H':
328               /* Match hour in 24-hour clock using alternate numeric
329                  symbols.  */
330               get_alt_number (0, 23);
331               tm->tm_hour = val;
332               have_I = 0;
333               break;
334             case 'I':
335               /* Match hour in 12-hour clock using alternate numeric
336                  symbols.  */
337               get_alt_number (1, 12);
338               tm->tm_hour = val - 1;
339               have_I = 1;
340               break;
341             case 'm':
342               /* Match month using alternate numeric symbols.  */
343               get_alt_number (1, 12);
344               tm->tm_mon = val - 1;
345               break;
346             case 'M':
347               /* Match minutes using alternate numeric symbols.  */
348               get_alt_number (0, 59);
349               tm->tm_min = val;
350               break;
351             case 'S':
352               /* Match seconds using alternate numeric symbols.  */
353               get_alt_number (0, 61);
354               tm->tm_sec = val;
355               break;
356             case 'U':
357             case 'V':
358             case 'W':
359               get_alt_number (0, 53);
360               /* XXX This cannot determine any field in TM.  */
361               break;
362             case 'w':
363               /* Match number of weekday using alternate numeric symbols.  */
364               get_alt_number (0, 6);
365               tm->tm_wday = val;
366               break;
367             case 'y':
368               /* Match year within century using alternate numeric symbols.  */
369               get_alt_number (0, 99);
370               break;
371             default:
372               return NULL;
373             }
374           break;
375         default:
376           return NULL;
377         }
378     }
379
380   if (have_I && is_pm)
381     tm->tm_hour += 12;
382
383   return (char *) rp;
384 }