strptime.c revision 81baaf272cb79c69ca9b0969466f5f946c923a2b
1/*  $OpenBSD: strptime.c,v 1.11 2005/08/08 08:05:38 espie Exp $ */
2/*  $NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $    */
3
4/*-
5 * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code was contributed to The NetBSD Foundation by Klaus Klein.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *        This product includes software developed by the NetBSD
21 *        Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 *    contributors may be used to endorse or promote products derived
24 *    from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39//#include <sys/localedef.h>
40#include <ctype.h>
41#include <errno.h>
42#include <locale.h>
43#include <stdlib.h>
44#include <string.h>
45#include <time.h>
46#include "tzfile.h"
47
48static const struct {
49    const char *abday[7];
50    const char *day[7];
51    const char *abmon[12];
52    const char *mon[12];
53    const char *am_pm[2];
54    const char *d_t_fmt;
55    const char *d_fmt;
56    const char *t_fmt;
57    const char *t_fmt_ampm;
58} _DefaultTimeLocale = {
59    {
60        "Sun","Mon","Tue","Wed","Thu","Fri","Sat",
61    },
62    {
63        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
64        "Friday", "Saturday"
65    },
66    {
67        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
68        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
69    },
70    {
71        "January", "February", "March", "April", "May", "June", "July",
72        "August", "September", "October", "November", "December"
73    },
74    {
75        "AM", "PM"
76    },
77    "%a %b %d %H:%M:%S %Y",
78    "%m/%d/%y",
79    "%H:%M:%S",
80    "%I:%M:%S %p"
81};
82
83#define _ctloc(x) (_DefaultTimeLocale.x)
84
85/*
86 * We do not implement alternate representations. However, we always
87 * check whether a given modifier is allowed for a certain conversion.
88 */
89#define _ALT_E          0x01
90#define _ALT_O          0x02
91#define _LEGAL_ALT(x)       { if (alt_format & ~(x)) return (0); }
92
93
94struct century_relyear {
95    int century;
96    int relyear;
97};
98static  int _conv_num(const unsigned char **, int *, int, int);
99static  unsigned char *_strptime(const unsigned char *, const char *, struct tm *,
100        struct century_relyear *);
101
102
103char *
104strptime(const char *buf, const char *fmt, struct tm *tm)
105{
106    struct century_relyear cr;
107    cr.century = TM_YEAR_BASE;
108    cr.relyear = -1;
109    return (char*)(_strptime((const unsigned char*)buf, fmt, tm, &cr));
110}
111
112static unsigned char *
113_strptime(const unsigned char *buf, const char *fmt, struct tm *tm, struct century_relyear *cr)
114{
115    unsigned char c;
116    const unsigned char *bp;
117    size_t len = 0;
118    int alt_format, i;
119
120    bp = (unsigned char *)buf;
121    while ((c = *fmt) != '\0') {
122        /* Clear `alternate' modifier prior to new conversion. */
123        alt_format = 0;
124
125        /* Eat up white-space. */
126        if (isspace(c)) {
127            while (isspace(*bp))
128                bp++;
129
130            fmt++;
131            continue;
132        }
133
134        if ((c = *fmt++) != '%')
135            goto literal;
136
137
138again:      switch (c = *fmt++) {
139        case '%':   /* "%%" is converted to "%". */
140literal:
141        if (c != *bp++)
142            return (NULL);
143
144        break;
145
146        /*
147         * "Alternative" modifiers. Just set the appropriate flag
148         * and start over again.
149         */
150        case 'E':   /* "%E?" alternative conversion modifier. */
151            _LEGAL_ALT(0);
152            alt_format |= _ALT_E;
153            goto again;
154
155        case 'O':   /* "%O?" alternative conversion modifier. */
156            _LEGAL_ALT(0);
157            alt_format |= _ALT_O;
158            goto again;
159
160        /*
161         * "Complex" conversion rules, implemented through recursion.
162         */
163        case 'c':   /* Date and time, using the locale's format. */
164            _LEGAL_ALT(_ALT_E);
165            if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, cr)))
166                return (NULL);
167            break;
168
169        case 'D':   /* The date as "%m/%d/%y". */
170            _LEGAL_ALT(0);
171            if (!(bp = _strptime(bp, "%m/%d/%y", tm, cr)))
172                return (NULL);
173            break;
174
175        case 'R':   /* The time as "%H:%M". */
176            _LEGAL_ALT(0);
177            if (!(bp = _strptime(bp, "%H:%M", tm, cr)))
178                return (NULL);
179            break;
180
181        case 'r':   /* The time as "%I:%M:%S %p". */
182            _LEGAL_ALT(0);
183            if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, cr)))
184                return (NULL);
185            break;
186
187        case 'T':   /* The time as "%H:%M:%S". */
188            _LEGAL_ALT(0);
189            if (!(bp = _strptime(bp, "%H:%M:%S", tm, cr)))
190                return (NULL);
191            break;
192
193        case 'X':   /* The time, using the locale's format. */
194            _LEGAL_ALT(_ALT_E);
195            if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, cr)))
196                return (NULL);
197            break;
198
199        case 'x':   /* The date, using the locale's format. */
200            _LEGAL_ALT(_ALT_E);
201            if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, cr)))
202                return (NULL);
203            break;
204
205        /*
206         * "Elementary" conversion rules.
207         */
208        case 'A':   /* The day of week, using the locale's form. */
209        case 'a':
210            _LEGAL_ALT(0);
211            for (i = 0; i < 7; i++) {
212                /* Full name. */
213                len = strlen(_ctloc(day[i]));
214                if (strncasecmp(_ctloc(day[i]), (const char*)bp, len) == 0)
215                    break;
216
217                /* Abbreviated name. */
218                len = strlen(_ctloc(abday[i]));
219                if (strncasecmp(_ctloc(abday[i]), (const char*)bp, len) == 0)
220                    break;
221            }
222
223            /* Nothing matched. */
224            if (i == 7)
225                return (NULL);
226
227            tm->tm_wday = i;
228            bp += len;
229            break;
230
231        case 'B':   /* The month, using the locale's form. */
232        case 'b':
233        case 'h':
234            _LEGAL_ALT(0);
235            for (i = 0; i < 12; i++) {
236                /* Full name. */
237                len = strlen(_ctloc(mon[i]));
238                if (strncasecmp(_ctloc(mon[i]), (const char*)bp, len) == 0)
239                    break;
240
241                /* Abbreviated name. */
242                len = strlen(_ctloc(abmon[i]));
243                if (strncasecmp(_ctloc(abmon[i]), (const char*)bp, len) == 0)
244                    break;
245            }
246
247            /* Nothing matched. */
248            if (i == 12)
249                return (NULL);
250
251            tm->tm_mon = i;
252            bp += len;
253            break;
254
255        case 'C':   /* The century number. */
256            _LEGAL_ALT(_ALT_E);
257            if (!(_conv_num(&bp, &i, 0, 99)))
258                return (NULL);
259
260            cr->century = i * 100;
261            break;
262
263        case 'd':   /* The day of month. */
264        case 'e':
265            _LEGAL_ALT(_ALT_O);
266            if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
267                return (NULL);
268            break;
269
270        case 'k':   /* The hour (24-hour clock representation). */
271            _LEGAL_ALT(0);
272            /* FALLTHROUGH */
273        case 'H':
274            _LEGAL_ALT(_ALT_O);
275            if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
276                return (NULL);
277            break;
278
279        case 'l':   /* The hour (12-hour clock representation). */
280            _LEGAL_ALT(0);
281            /* FALLTHROUGH */
282        case 'I':
283            _LEGAL_ALT(_ALT_O);
284            if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
285                return (NULL);
286            break;
287
288        case 'j':   /* The day of year. */
289            _LEGAL_ALT(0);
290            if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
291                return (NULL);
292            tm->tm_yday--;
293            break;
294
295        case 'M':   /* The minute. */
296            _LEGAL_ALT(_ALT_O);
297            if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
298                return (NULL);
299            break;
300
301        case 'm':   /* The month. */
302            _LEGAL_ALT(_ALT_O);
303            if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
304                return (NULL);
305            tm->tm_mon--;
306            break;
307
308        case 'p':   /* The locale's equivalent of AM/PM. */
309            _LEGAL_ALT(0);
310            /* AM? */
311            len = strlen(_ctloc(am_pm[0]));
312            if (strncasecmp(_ctloc(am_pm[0]), (const char*)bp, len) == 0) {
313                if (tm->tm_hour > 12)   /* i.e., 13:00 AM ?! */
314                    return (NULL);
315                else if (tm->tm_hour == 12)
316                    tm->tm_hour = 0;
317
318                bp += len;
319                break;
320            }
321            /* PM? */
322            len = strlen(_ctloc(am_pm[1]));
323            if (strncasecmp(_ctloc(am_pm[1]), (const char*)bp, len) == 0) {
324                if (tm->tm_hour > 12)   /* i.e., 13:00 PM ?! */
325                    return (NULL);
326                else if (tm->tm_hour < 12)
327                    tm->tm_hour += 12;
328
329                bp += len;
330                break;
331            }
332
333            /* Nothing matched. */
334            return (NULL);
335
336        case 'S':   /* The seconds. */
337            _LEGAL_ALT(_ALT_O);
338            if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
339                return (NULL);
340            break;
341
342        case 's':
343            {
344                // Android addition, based on FreeBSD's implementation.
345                int saved_errno = errno;
346                errno = 0;
347                const unsigned char* old_bp = bp;
348                long n = strtol((const char*) bp, (char**) &bp, 10);
349                time_t t = n;
350                if (bp == old_bp || errno == ERANGE || ((long) t) != n) {
351                    errno = saved_errno;
352                    return NULL;
353                }
354                errno = saved_errno;
355
356                if (localtime_r(&t, tm) == NULL) return NULL;
357            }
358            break;
359
360
361        case 'U':   /* The week of year, beginning on sunday. */
362        case 'W':   /* The week of year, beginning on monday. */
363            _LEGAL_ALT(_ALT_O);
364            /*
365             * XXX This is bogus, as we can not assume any valid
366             * information present in the tm structure at this
367             * point to calculate a real value, so just check the
368             * range for now.
369             */
370             if (!(_conv_num(&bp, &i, 0, 53)))
371                return (NULL);
372             break;
373
374        case 'w':   /* The day of week, beginning on sunday. */
375            _LEGAL_ALT(_ALT_O);
376            if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
377                return (NULL);
378            break;
379
380        case 'Y':   /* The year. */
381            _LEGAL_ALT(_ALT_E);
382            if (!(_conv_num(&bp, &i, 0, 9999)))
383                return (NULL);
384
385            cr->relyear = -1;
386            tm->tm_year = i - TM_YEAR_BASE;
387            break;
388
389        case 'y':   /* The year within the century (2 digits). */
390            _LEGAL_ALT(_ALT_E | _ALT_O);
391            if (!(_conv_num(&bp, &cr->relyear, 0, 99)))
392                return (NULL);
393            break;
394
395        /*
396         * Miscellaneous conversions.
397         */
398        case 'n':   /* Any kind of white-space. */
399        case 't':
400            _LEGAL_ALT(0);
401            while (isspace(*bp))
402                bp++;
403            break;
404
405
406        default:    /* Unknown/unsupported conversion. */
407            return (NULL);
408        }
409
410
411    }
412
413    /*
414     * We need to evaluate the two digit year spec (%y)
415     * last as we can get a century spec (%C) at any time.
416     */
417    if (cr->relyear != -1) {
418        if (cr->century == TM_YEAR_BASE) {
419            if (cr->relyear <= 68)
420                tm->tm_year = cr->relyear + 2000 - TM_YEAR_BASE;
421            else
422                tm->tm_year = cr->relyear + 1900 - TM_YEAR_BASE;
423        } else {
424            tm->tm_year = cr->relyear + cr->century - TM_YEAR_BASE;
425        }
426    }
427
428    return (unsigned char*)bp;
429}
430
431
432static int
433_conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
434{
435    int result = 0;
436    int rulim = ulim;
437
438    if (**buf < '0' || **buf > '9')
439        return (0);
440
441    /* we use rulim to break out of the loop when we run out of digits */
442    do {
443        result *= 10;
444        result += *(*buf)++ - '0';
445        rulim /= 10;
446    } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
447
448    if (result < llim || result > ulim)
449        return (0);
450
451    *dest = result;
452    return (1);
453}
454
455char* strptime_l(const char* buf, const char* fmt, struct tm* tm, locale_t l) {
456  return strptime(buf, fmt, tm);
457}
458