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
__getzonename(const char * restrict s,struct tm * restrict tm)48 int __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
__getgmtoff(const char * restrict s,struct tm * restrict tm)80 int __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
strptime(const char * restrict s,const char * restrict f,struct tm * restrict tm)131 char *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 = ¢ury;
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 }