source: trunk/libs/newlib/src/newlib/libc/time/strptime.c @ 577

Last change on this file since 577 was 444, checked in by satin@…, 6 years ago

add newlib,libalmos-mkh, restructure shared_syscalls.h and mini-libc

File size: 11.6 KB
Line 
1/*
2 * Copyright (c) 1999 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of KTH nor the names of its contributors may be
18 *    used to endorse or promote products derived from this software without
19 *    specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32
33#define _GNU_SOURCE
34#include <stddef.h>
35#include <stdio.h>
36#include <time.h>
37#include <string.h>
38#include <strings.h>
39#include <ctype.h>
40#include <stdlib.h>
41#include <errno.h>
42#include <inttypes.h>
43#include <limits.h>
44#include "../locale/setlocale.h"
45
46#define _ctloc(x) (_CurrentTimeLocale->x)
47
48static const int _DAYS_BEFORE_MONTH[12] =
49{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
50
51#define SET_MDAY 1
52#define SET_MON  2
53#define SET_YEAR 4
54#define SET_WDAY 8
55#define SET_YDAY 16
56#define SET_YMD  (SET_YEAR | SET_MON | SET_MDAY)
57
58/*
59 * tm_year is relative this year
60 */
61const int tm_year_base = 1900;
62
63/*
64 * Return TRUE iff `year' was a leap year.
65 * Needed for strptime.
66 */
67static int
68is_leap_year (int year)
69{
70    return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
71}
72
73/* Needed for strptime. */
74static int
75match_string (const char *__restrict *buf, const char * const*strs,
76              locale_t locale)
77{
78    int i = 0;
79
80    for (i = 0; strs[i] != NULL; ++i) {
81        int len = strlen (strs[i]);
82
83        if (strncasecmp_l (*buf, strs[i], len, locale) == 0) {
84            *buf += len;
85            return i;
86        }
87    }
88    return -1;
89}
90
91/* Needed for strptime. */
92static int
93first_day (int year)
94{
95    int ret = 4;
96
97    while (--year >= 1970)
98        ret = (ret + 365 + is_leap_year (year)) % 7;
99    return ret;
100}
101
102/*
103 * Set `timeptr' given `wnum' (week number [0, 53])
104 * Needed for strptime
105 */
106
107static void
108set_week_number_sun (struct tm *timeptr, int wnum)
109{
110    int fday = first_day (timeptr->tm_year + tm_year_base);
111
112    timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday;
113    if (timeptr->tm_yday < 0) {
114        timeptr->tm_wday = fday;
115        timeptr->tm_yday = 0;
116    }
117}
118
119/*
120 * Set `timeptr' given `wnum' (week number [0, 53])
121 * Needed for strptime
122 */
123
124static void
125set_week_number_mon (struct tm *timeptr, int wnum)
126{
127    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
128
129    timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday;
130    if (timeptr->tm_yday < 0) {
131        timeptr->tm_wday = (fday + 1) % 7;
132        timeptr->tm_yday = 0;
133    }
134}
135
136/*
137 * Set `timeptr' given `wnum' (week number [0, 53])
138 * Needed for strptime
139 */
140static void
141set_week_number_mon4 (struct tm *timeptr, int wnum)
142{
143    int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
144    int offset = 0;
145
146    if (fday < 4)
147        offset += 7;
148
149    timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday;
150    if (timeptr->tm_yday < 0) {
151        timeptr->tm_wday = fday;
152        timeptr->tm_yday = 0;
153    }
154}
155
156char *
157strptime_l (const char *buf, const char *format, struct tm *timeptr,
158            locale_t locale)
159{
160    char c;
161    int ymd = 0;
162
163    const struct lc_time_T *_CurrentTimeLocale = __get_time_locale (locale);
164    for (; (c = *format) != '\0'; ++format) {
165        char *s;
166        int ret;
167
168        if (isspace_l ((unsigned char) c, locale)) {
169            while (isspace_l ((unsigned char) *buf, locale))
170                ++buf;
171        } else if (c == '%' && format[1] != '\0') {
172            c = *++format;
173            if (c == 'E' || c == 'O')
174                c = *++format;
175            switch (c) {
176            case 'A' :
177                ret = match_string (&buf, _ctloc (weekday), locale);
178                if (ret < 0)
179                    return NULL;
180                timeptr->tm_wday = ret;
181                ymd |= SET_WDAY;
182                break;
183            case 'a' :
184                ret = match_string (&buf, _ctloc (wday), locale);
185                if (ret < 0)
186                    return NULL;
187                timeptr->tm_wday = ret;
188                ymd |= SET_WDAY;
189                break;
190            case 'B' :
191                ret = match_string (&buf, _ctloc (month), locale);
192                if (ret < 0)
193                    return NULL;
194                timeptr->tm_mon = ret;
195                ymd |= SET_MON;
196                break;
197            case 'b' :
198            case 'h' :
199                ret = match_string (&buf, _ctloc (mon), locale);
200                if (ret < 0)
201                    return NULL;
202                timeptr->tm_mon = ret;
203                ymd |= SET_MON;
204                break;
205            case 'C' :
206                ret = strtol_l (buf, &s, 10, locale);
207                if (s == buf)
208                    return NULL;
209                timeptr->tm_year = (ret * 100) - tm_year_base;
210                buf = s;
211                ymd |= SET_YEAR;
212                break;
213            case 'c' :          /* %a %b %e %H:%M:%S %Y */
214                s = strptime_l (buf, _ctloc (c_fmt), timeptr, locale);
215                if (s == NULL)
216                    return NULL;
217                buf = s;
218                ymd |= SET_WDAY | SET_YMD;
219                break;
220            case 'D' :          /* %m/%d/%y */
221                s = strptime_l (buf, "%m/%d/%y", timeptr, locale);
222                if (s == NULL)
223                    return NULL;
224                buf = s;
225                ymd |= SET_YMD;
226                break;
227            case 'd' :
228            case 'e' :
229                ret = strtol_l (buf, &s, 10, locale);
230                if (s == buf)
231                    return NULL;
232                timeptr->tm_mday = ret;
233                buf = s;
234                ymd |= SET_MDAY;
235                break;
236            case 'F' :          /* %Y-%m-%d - GNU extension */
237                s = strptime_l (buf, "%Y-%m-%d", timeptr, locale);
238                if (s == NULL || s == buf)
239                    return NULL;
240                buf = s;
241                ymd |= SET_YMD;
242                break;
243            case 'H' :
244            case 'k' :          /* hour with leading space - GNU extension */
245                ret = strtol_l (buf, &s, 10, locale);
246                if (s == buf)
247                    return NULL;
248                timeptr->tm_hour = ret;
249                buf = s;
250                break;
251            case 'I' :
252            case 'l' :          /* hour with leading space - GNU extension */
253                ret = strtol_l (buf, &s, 10, locale);
254                if (s == buf)
255                    return NULL;
256                if (ret == 12)
257                    timeptr->tm_hour = 0;
258                else
259                    timeptr->tm_hour = ret;
260                buf = s;
261                break;
262            case 'j' :
263                ret = strtol_l (buf, &s, 10, locale);
264                if (s == buf)
265                    return NULL;
266                timeptr->tm_yday = ret - 1;
267                buf = s;
268                ymd |= SET_YDAY;
269                break;
270            case 'm' :
271                ret = strtol_l (buf, &s, 10, locale);
272                if (s == buf)
273                    return NULL;
274                timeptr->tm_mon = ret - 1;
275                buf = s;
276                ymd |= SET_MON;
277                break;
278            case 'M' :
279                ret = strtol_l (buf, &s, 10, locale);
280                if (s == buf)
281                    return NULL;
282                timeptr->tm_min = ret;
283                buf = s;
284                break;
285            case 'n' :
286                if (*buf == '\n')
287                    ++buf;
288                else
289                    return NULL;
290                break;
291            case 'p' :
292                ret = match_string (&buf, _ctloc (am_pm), locale);
293                if (ret < 0)
294                    return NULL;
295                if (timeptr->tm_hour == 0) {
296                    if (ret == 1)
297                        timeptr->tm_hour = 12;
298                } else
299                    timeptr->tm_hour += 12;
300                break;
301            case 'r' :          /* %I:%M:%S %p */
302                s = strptime_l (buf, _ctloc (ampm_fmt), timeptr, locale);
303                if (s == NULL)
304                    return NULL;
305                buf = s;
306                break;
307            case 'R' :          /* %H:%M */
308                s = strptime_l (buf, "%H:%M", timeptr, locale);
309                if (s == NULL)
310                    return NULL;
311                buf = s;
312                break;
313            case 's' :          /* seconds since Unix epoch - GNU extension */
314                {
315                    long long sec;
316                    time_t t;
317                    int save_errno;
318
319                    save_errno = errno;
320                    errno = 0;
321                    sec = strtoll_l (buf, &s, 10, locale);
322                    t = sec;
323                    if (s == buf
324                        || errno != 0
325                        || t != sec
326                        || localtime_r (&t, timeptr) != timeptr)
327                        return NULL;
328                    errno = save_errno;
329                    buf = s;
330                    ymd |= SET_YDAY | SET_WDAY | SET_YMD;
331                    break;
332                }
333            case 'S' :
334                ret = strtol_l (buf, &s, 10, locale);
335                if (s == buf)
336                    return NULL;
337                timeptr->tm_sec = ret;
338                buf = s;
339                break;
340            case 't' :
341                if (*buf == '\t')
342                    ++buf;
343                else
344                    return NULL;
345                break;
346            case 'T' :          /* %H:%M:%S */
347                s = strptime_l (buf, "%H:%M:%S", timeptr, locale);
348                if (s == NULL)
349                    return NULL;
350                buf = s;
351                break;
352            case 'u' :
353                ret = strtol_l (buf, &s, 10, locale);
354                if (s == buf)
355                    return NULL;
356                timeptr->tm_wday = ret - 1;
357                buf = s;
358                ymd |= SET_WDAY;
359                break;
360            case 'w' :
361                ret = strtol_l (buf, &s, 10, locale);
362                if (s == buf)
363                    return NULL;
364                timeptr->tm_wday = ret;
365                buf = s;
366                ymd |= SET_WDAY;
367                break;
368            case 'U' :
369                ret = strtol_l (buf, &s, 10, locale);
370                if (s == buf)
371                    return NULL;
372                set_week_number_sun (timeptr, ret);
373                buf = s;
374                ymd |= SET_YDAY;
375                break;
376            case 'V' :
377                ret = strtol_l (buf, &s, 10, locale);
378                if (s == buf)
379                    return NULL;
380                set_week_number_mon4 (timeptr, ret);
381                buf = s;
382                ymd |= SET_YDAY;
383                break;
384            case 'W' :
385                ret = strtol_l (buf, &s, 10, locale);
386                if (s == buf)
387                    return NULL;
388                set_week_number_mon (timeptr, ret);
389                buf = s;
390                ymd |= SET_YDAY;
391                break;
392            case 'x' :
393                s = strptime_l (buf, _ctloc (x_fmt), timeptr, locale);
394                if (s == NULL)
395                    return NULL;
396                buf = s;
397                ymd |= SET_YMD;
398                break;
399            case 'X' :
400                s = strptime_l (buf, _ctloc (X_fmt), timeptr, locale);
401                if (s == NULL)
402                    return NULL;
403                buf = s;
404                break;
405            case 'y' :
406                ret = strtol_l (buf, &s, 10, locale);
407                if (s == buf)
408                    return NULL;
409                if (ret < 70)
410                    timeptr->tm_year = 100 + ret;
411                else
412                    timeptr->tm_year = ret;
413                buf = s;
414                ymd |= SET_YEAR;
415                break;
416            case 'Y' :
417                ret = strtol_l (buf, &s, 10, locale);
418                if (s == buf)
419                    return NULL;
420                timeptr->tm_year = ret - tm_year_base;
421                buf = s;
422                ymd |= SET_YEAR;
423                break;
424            case 'Z' :
425                /* Unsupported. Just ignore.  */
426                break;
427            case '\0' :
428                --format;
429                /* FALLTHROUGH */
430            case '%' :
431                if (*buf == '%')
432                    ++buf;
433                else
434                    return NULL;
435                break;
436            default :
437                if (*buf == '%' || *++buf == c)
438                    ++buf;
439                else
440                    return NULL;
441                break;
442            }
443        } else {
444            if (*buf == c)
445                ++buf;
446            else
447                return NULL;
448        }
449    }
450
451    if ((ymd & SET_YMD) == SET_YMD) {
452        /* all of tm_year, tm_mon and tm_mday, but... */
453
454        if (!(ymd & SET_YDAY)) {
455            /* ...not tm_yday, so fill it in */
456            timeptr->tm_yday = _DAYS_BEFORE_MONTH[timeptr->tm_mon]
457                + timeptr->tm_mday;
458            if (!is_leap_year (timeptr->tm_year + tm_year_base)
459                || timeptr->tm_mon < 2)
460            {
461                timeptr->tm_yday--;
462            }
463            ymd |= SET_YDAY;
464        }
465    }
466    else if ((ymd & (SET_YEAR | SET_YDAY)) == (SET_YEAR | SET_YDAY)) {
467        /* both of tm_year and tm_yday, but... */
468
469        if (!(ymd & SET_MON)) {
470            /* ...not tm_mon, so fill it in, and/or... */
471            if (timeptr->tm_yday < _DAYS_BEFORE_MONTH[1])
472                timeptr->tm_mon = 0;
473            else {
474                int leap = is_leap_year (timeptr->tm_year + tm_year_base);
475                int i;
476                for (i = 2; i < 12; ++i) {
477                    if (timeptr->tm_yday < _DAYS_BEFORE_MONTH[i] + leap)
478                        break;
479                }
480                timeptr->tm_mon = i - 1;
481            }
482        }
483
484        if (!(ymd & SET_MDAY)) {
485            /* ...not tm_mday, so fill it in */
486            timeptr->tm_mday = timeptr->tm_yday
487                - _DAYS_BEFORE_MONTH[timeptr->tm_mon];
488            if (!is_leap_year (timeptr->tm_year + tm_year_base)
489                || timeptr->tm_mon < 2)
490            {
491                timeptr->tm_mday++;
492            }
493        }
494    }
495
496    if ((ymd & (SET_YEAR | SET_YDAY | SET_WDAY)) == (SET_YEAR | SET_YDAY)) {
497        /* fill in tm_wday */
498        int fday = first_day (timeptr->tm_year + tm_year_base);
499        timeptr->tm_wday = (fday + timeptr->tm_yday) % 7;
500    }
501
502    return (char *)buf;
503}
504
505char *
506strptime (const char *buf, const char *format, struct tm *timeptr)
507{
508  return strptime_l (buf, format, timeptr, __get_current_locale ());
509}
Note: See TracBrowser for help on using the repository browser.