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 = ¢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}