1/**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *   http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <stdlib.h>
17#include <langinfo.h>
18#include <time.h>
19#include <ctype.h>
20#include <stddef.h>
21#include <string.h>
22#include <strings.h>
23/* seconds per hour */
24#define __STRPTIME_SECOND_IN_HOUR 3600
25/* character-to-number base */
26#define __STRPTIME_NUMBER_BASE 10
27/* epoch time */
28#define __STRPTIME_EPOCH 1900
29/* receive data buffer size */
30#define __STRPTIME_BUFFER_SIZE 16
31/* Width of hours and minutes when formatting %z */
32#define __STRPTIME_ZONE_WIDTH 2
33/* time base */
34#define __STRPTIME_TIME_BASE 60
35/* number of weeks per year */
36#define __STRPTIME_WEEKS_IN_YEAR 53
37/* days of the week */
38#define __STRPTIME_DAYS_IN_WEEK 7
39/* 12 hour clock */
40#define __STRPTIME_HOUR_CLOCK_12 12
41/* 24 hour clock */
42#define __STRPTIME_HOUR_CLOCK_24 24
43/* days per year */
44#define __STRPTIME_DAYS_PER_YEAR 366
45/* Years in each century */
46#define __STRPTIME_YEARS_PER_CENTURY 100
47
48int __getzonename(const char *restrict s, struct tm *restrict tm)
49{
50    const char *p = s;
51    struct tm old;
52    memcpy(&old, tm, sizeof(struct tm));
53    /* Possible time zone names like +XXX or -XXX */
54    if (*p == '+' || *p == '-') {
55        p++;
56    }
57
58    /* The time zone name is adjacent to the offset second data,
59     * and the following symbol belongs to the offset second */
60    while (*p && (*p != '+' && *p != '-' && *p != ' ')) {
61        p++;
62    }
63
64    /* In the structure struct tm, tm_zone is declared as const char * type, so use static */
65    static char buf[__STRPTIME_BUFFER_SIZE] = {0};
66    memset(buf, 0x0, sizeof(buf));
67    int len = p - s;
68    memcpy(buf, s, len);
69    tm->__tm_zone = buf;
70
71    /* Re-fetch local data, extract tm_isdst flag. */
72    time_t t = mktime(&old);
73    struct tm *tmp = localtime(&t);
74    if (tmp) {
75        tm->tm_isdst = tmp->tm_isdst;
76    }
77    return len;
78}
79
80int __getgmtoff(const char *restrict s, struct tm *restrict tm)
81{
82    const char *p = s;
83    int sign = 1;
84    int i;
85    int isexit = 0;
86    long m = 0;
87    long h = 0;
88
89    /* The possible formats for time offset are HHMM(-HHMM) or HH:MM(-HH:MM) */
90    if (*p == '-') {
91        sign = -1;
92    }
93    p++;
94    tm->__tm_gmtoff = 0;
95
96    /* get hours */
97    for (i=0; i<__STRPTIME_ZONE_WIDTH && *p; i++, p++) {
98        if (isdigit(*p)) {
99            h = h * __STRPTIME_NUMBER_BASE + (*p - 0x30);
100        } else {
101            p--;
102            isexit = 1;
103            break;
104        }
105    }
106
107    if (!isexit) {
108        /* Possible time zone formats are HH:MM. */
109        if (*p == ':') {
110            *p++;
111        }
112
113        /* get minutes */
114        for (i=0; i<__STRPTIME_ZONE_WIDTH && *p; i++, p++) {
115            if (isdigit(*p)) {
116                m = m * __STRPTIME_NUMBER_BASE + (*p - 0x30);
117            } else {
118                p--;
119                isexit = 1;
120                break;
121            }
122        }
123    }
124
125    /* Convert hours and minutes to seconds */
126    tm->__tm_gmtoff = sign * (h * __STRPTIME_SECOND_IN_HOUR + m * __STRPTIME_TIME_BASE);
127
128    return p - s;
129}
130
131char *strptime(const char *restrict s, const char *restrict f, struct tm *restrict tm)
132{
133    int i, w, neg, adj, min, range, *dest, dummy;
134    const char *ex;
135    size_t len;
136    int want_century = 0, century = 0, relyear = 0;
137    while (*f) {
138        if (*f != '%') {
139            if (isspace(*f)) {
140                for (; *s && isspace(*s); s++);
141            } else if (*s != *f) {
142                return 0;
143            } else {
144                s++;
145            }
146            f++;
147            continue;
148        }
149        f++;
150        if (*f == '+') {
151            f++;
152        }
153        if (isdigit(*f)) {
154            char *new_f;
155            w=strtoul(f, &new_f, __STRPTIME_NUMBER_BASE);
156            f = new_f;
157        } else {
158            w=-1;
159        }
160        adj=0;
161        switch (*f++) {
162            case 'a': case 'A':
163                dest = &tm->tm_wday;
164                min = ABDAY_1;
165                range = __STRPTIME_DAYS_IN_WEEK;
166                goto symbolic_range;
167            case 'b': case 'B': case 'h':
168                dest = &tm->tm_mon;
169                min = ABMON_1;
170                range = __STRPTIME_HOUR_CLOCK_12;
171                goto symbolic_range;
172            case 'c':
173                s = strptime(s, nl_langinfo(D_T_FMT), tm);
174                if (!s) {
175                    return 0;
176                }
177                break;
178            case 'C':
179                dest = &century;
180                if (w<0) {
181                    w=__STRPTIME_ZONE_WIDTH;
182                }
183                want_century |= __STRPTIME_ZONE_WIDTH;
184                goto numeric_digits;
185            case 'd': case 'e':
186                dest = &tm->tm_mday;
187                min = 1;
188                range = 31;
189                goto numeric_range;
190            case 'D':
191                s = strptime(s, "%m/%d/%y", tm);
192                if (!s) {
193                    return 0;
194                }
195                break;
196            case 'F':
197                s = strptime(s, "%Y-%m-%d", tm);
198                if (!s) {
199                    return 0;
200                }
201                break;
202            case 'g':
203                dest = &tm->tm_year;
204                min = 0;
205                range = 99;
206                w = __STRPTIME_ZONE_WIDTH;
207                want_century = 0;
208                goto numeric_digits;
209            case 'G':
210                do {
211                    ++s;
212                } while (isdigit(*s));
213                continue;
214            case 'k':
215            case 'H':
216                dest = &tm->tm_hour;
217                min = 0;
218                range = __STRPTIME_HOUR_CLOCK_24;
219                goto numeric_range;
220            case 'l':
221            case 'I':
222                dest = &tm->tm_hour;
223                min = 1;
224                range = __STRPTIME_HOUR_CLOCK_12;
225                goto numeric_range;
226            case 'j':
227                dest = &tm->tm_yday;
228                min = 1;
229                range = __STRPTIME_DAYS_PER_YEAR;
230                adj = 1;
231                goto numeric_range;
232            case 'm':
233                dest = &tm->tm_mon;
234                min = 1;
235                range = __STRPTIME_HOUR_CLOCK_12;
236                adj = 1;
237                goto numeric_range;
238            case 'M':
239                dest = &tm->tm_min;
240                min = 0;
241                range = __STRPTIME_TIME_BASE;
242                goto numeric_range;
243            case 'n': case 't':
244                for (; *s && isspace(*s); s++) {}
245                break;
246            case 'p':
247            case 'P':
248                ex = nl_langinfo(AM_STR);
249                len = strlen(ex);
250                if (!strncasecmp(s, ex, len)) {
251                    tm->tm_hour %= __STRPTIME_HOUR_CLOCK_12;
252                    s += len;
253                    break;
254                }
255                ex = nl_langinfo(PM_STR);
256                len = strlen(ex);
257                if (!strncasecmp(s, ex, len)) {
258                    tm->tm_hour %= __STRPTIME_HOUR_CLOCK_12;
259                    tm->tm_hour += __STRPTIME_HOUR_CLOCK_12;
260                    s += len;
261                    break;
262                }
263                return 0;
264            case 'r':
265                s = strptime(s, nl_langinfo(T_FMT_AMPM), tm);
266                if (!s) {
267                    return 0;
268                }
269                break;
270            case 'R':
271                s = strptime(s, "%H:%M", tm);
272                if (!s) {
273                    return 0;
274                }
275                break;
276            case 's': {
277                time_t secs = 0;
278                if (!isdigit(*s)) {
279                    return 0;
280                }
281                do {
282                    secs *= __STRPTIME_NUMBER_BASE;
283                    secs += *s - '0';
284                    s++;
285                } while (isdigit(*s));
286                if (localtime_r(&secs, tm) == NULL) {
287                    return 0;
288                }
289                break;
290            }
291            case 'S':
292                dest = &tm->tm_sec;
293                min = 0;
294                range = 61;
295                goto numeric_range;
296            case 'T':
297                s = strptime(s, "%H:%M:%S", tm);
298                if (!s) {
299                    return 0;
300                }
301                break;
302            case 'u': {
303                if (!isdigit(*s)) {
304                    return 0;
305                }
306                int wday = 0;
307                int rulim = __STRPTIME_DAYS_IN_WEEK;
308                do {
309                    wday *= __STRPTIME_NUMBER_BASE;
310                    wday += *s++ - '0';
311                    rulim /= __STRPTIME_NUMBER_BASE;
312                } while ((wday * __STRPTIME_NUMBER_BASE < __STRPTIME_DAYS_IN_WEEK) && rulim && isdigit(*s));
313                if (wday < 1 || wday > __STRPTIME_DAYS_IN_WEEK) {
314                    return 0;
315                }
316                tm->tm_wday = wday % __STRPTIME_DAYS_IN_WEEK;
317                continue;
318            }
319            case 'U':
320            case 'W':
321                dest = &dummy;
322                min = 0;
323                range = __STRPTIME_WEEKS_IN_YEAR + 1;
324                goto numeric_range;
325            case 'w':
326                dest = &tm->tm_wday;
327                min = 0;
328                range = __STRPTIME_DAYS_IN_WEEK;
329                goto numeric_range;
330            case 'v':
331                if (!(s = strptime(s, "%e-%b-%Y", tm))) {
332                    return 0;
333                }
334                break;
335            case 'V': {
336                int r = 0;
337                int rulim = __STRPTIME_WEEKS_IN_YEAR;
338                if (!isdigit(*s)) {
339                    return 0;
340                }
341                do {
342                    r *= __STRPTIME_NUMBER_BASE;
343                    r += *s++ - '0';
344                    rulim /= __STRPTIME_NUMBER_BASE;
345                } while ((r * __STRPTIME_NUMBER_BASE < __STRPTIME_WEEKS_IN_YEAR) && rulim && isdigit(*s));
346                if (r < 0 || r > __STRPTIME_WEEKS_IN_YEAR) {
347                    return 0;
348                }
349                continue;
350            }
351            case 'x':
352                s = strptime(s, nl_langinfo(D_FMT), tm);
353                if (!s) {
354                    return 0;
355                }
356                break;
357            case 'X':
358                s = strptime(s, nl_langinfo(T_FMT), tm);
359                if (!s) {
360                    return 0;
361                }
362                break;
363            case 'y':
364                dest = &relyear;
365                w = __STRPTIME_ZONE_WIDTH;
366                want_century |= 1;
367                goto numeric_digits;
368            case 'Y':
369                dest = &tm->tm_year;
370                if (w<0) {
371                    w=4;
372                }
373                adj = __STRPTIME_EPOCH;
374                want_century = 0;
375                goto numeric_digits;
376            case 'Z':
377                tzset();
378                s += __getzonename((const char *)s, tm);
379                continue;
380            case 'z':
381                s += __getgmtoff((const char *)s, tm);
382                continue;
383            case '%':
384                if (*s++ != '%') {
385                    return 0;
386                }
387                break;
388            default:
389                return 0;
390                numeric_range:
391                if (!isdigit(*s)) {
392                    return 0;
393                }
394                *dest = 0;
395                for (i=1; i<=min+range && isdigit(*s); i*=__STRPTIME_NUMBER_BASE) {
396                    *dest = *dest * __STRPTIME_NUMBER_BASE + *s++ - '0';
397                }
398                if (*dest - min >= (unsigned)range) {
399                    return 0;
400                }
401                *dest -= adj;
402                switch ((char *)dest - (char *)tm) {
403                    case offsetof(struct tm, tm_yday):
404                        ;
405                }
406                goto update;
407                numeric_digits:
408                neg = 0;
409                if (*s == '+') {
410                    s++;
411                } else if (*s == '-') {
412                    neg=1, s++;
413                }
414                if (!isdigit(*s)) {
415                    return 0;
416                }
417                for (*dest=i=0; i<w && isdigit(*s); i++) {
418                    *dest = *dest * __STRPTIME_NUMBER_BASE + *s++ - '0';
419                }
420                if (neg) {
421                    *dest = -*dest;
422                }
423                *dest -= adj;
424                goto update;
425                symbolic_range:
426                for (i=__STRPTIME_ZONE_WIDTH*range-1; i>=0; i--) {
427                    ex = nl_langinfo(min+i);
428                    len = strlen(ex);
429                    if (strncasecmp(s, ex, len)) continue;
430                    s += len;
431                    *dest = i % range;
432                    break;
433                }
434                if (i<0) {
435                    return 0;
436                }
437                goto update;
438            update:
439                ;
440            }
441        }
442    if (want_century) {
443        tm->tm_year = relyear;
444        if (want_century & __STRPTIME_ZONE_WIDTH) {
445            tm->tm_year += century * __STRPTIME_YEARS_PER_CENTURY - __STRPTIME_EPOCH;
446        }
447        else if (tm->tm_year <= 68) tm->tm_year += __STRPTIME_YEARS_PER_CENTURY;
448    }
449    return (char *)s;
450}