1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2014 Gary Mills 5 * Copyright 2011, Nexenta Systems, Inc. All rights reserved. 6 * Copyright (c) 1994 Powerdog Industries. All rights reserved. 7 * 8 * Copyright (c) 2011 The FreeBSD Foundation 9 * All rights reserved. 10 * Portions of this software were developed by David Chisnall 11 * under sponsorship from the FreeBSD Foundation. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer 20 * in the documentation and/or other materials provided with the 21 * distribution. 22 * 23 * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY 24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE 27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 33 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * The views and conclusions contained in the software and documentation 36 * are those of the authors and should not be interpreted as representing 37 * official policies, either expressed or implied, of Powerdog Industries. 38 */ 39 40 #include <sys/cdefs.h> 41 #ifndef lint 42 #ifndef NOID 43 static char copyright[] __unused = 44 "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved."; 45 static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27"; 46 #endif /* !defined NOID */ 47 #endif /* not lint */ 48 __FBSDID("$FreeBSD$"); 49 50 #include "namespace.h" 51 #include <time.h> 52 #include <ctype.h> 53 #include <errno.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <pthread.h> 57 #include "un-namespace.h" 58 #include "libc_private.h" 59 #include "timelocal.h" 60 #include "tzfile.h" 61 62 static char * _strptime(const char *, const char *, struct tm *, int *, locale_t); 63 64 #define asizeof(a) (sizeof(a) / sizeof((a)[0])) 65 66 #define FLAG_NONE (1 << 0) 67 #define FLAG_YEAR (1 << 1) 68 #define FLAG_MONTH (1 << 2) 69 #define FLAG_YDAY (1 << 3) 70 #define FLAG_MDAY (1 << 4) 71 #define FLAG_WDAY (1 << 5) 72 73 /* 74 * Calculate the week day of the first day of a year. Valid for 75 * the Gregorian calendar, which began Sept 14, 1752 in the UK 76 * and its colonies. Ref: 77 * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week 78 */ 79 80 static int 81 first_wday_of(int year) 82 { 83 return (((2 * (3 - (year / 100) % 4)) + (year % 100) + 84 ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7); 85 } 86 87 static char * 88 _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, 89 locale_t locale) 90 { 91 char c; 92 const char *ptr; 93 int day_offset = -1, wday_offset; 94 int week_offset; 95 int i, len; 96 int flags; 97 int Ealternative, Oalternative; 98 int century, year; 99 const struct lc_time_T *tptr = __get_current_time_locale(locale); 100 static int start_of_month[2][13] = { 101 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, 102 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} 103 }; 104 105 flags = FLAG_NONE; 106 century = -1; 107 year = -1; 108 109 ptr = fmt; 110 while (*ptr != 0) { 111 c = *ptr++; 112 113 if (c != '%') { 114 if (isspace_l((unsigned char)c, locale)) 115 while (*buf != 0 && 116 isspace_l((unsigned char)*buf, locale)) 117 buf++; 118 else if (c != *buf++) 119 return (NULL); 120 continue; 121 } 122 123 Ealternative = 0; 124 Oalternative = 0; 125 label: 126 c = *ptr++; 127 switch (c) { 128 case '%': 129 if (*buf++ != '%') 130 return (NULL); 131 break; 132 133 case '+': 134 buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale); 135 if (buf == NULL) 136 return (NULL); 137 flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; 138 break; 139 140 case 'C': 141 if (!isdigit_l((unsigned char)*buf, locale)) 142 return (NULL); 143 144 /* XXX This will break for 3-digit centuries. */ 145 len = 2; 146 for (i = 0; len && *buf != 0 && 147 isdigit_l((unsigned char)*buf, locale); buf++) { 148 i *= 10; 149 i += *buf - '0'; 150 len--; 151 } 152 153 century = i; 154 flags |= FLAG_YEAR; 155 156 break; 157 158 case 'c': 159 buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale); 160 if (buf == NULL) 161 return (NULL); 162 flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; 163 break; 164 165 case 'D': 166 buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale); 167 if (buf == NULL) 168 return (NULL); 169 flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; 170 break; 171 172 case 'E': 173 if (Ealternative || Oalternative) 174 break; 175 Ealternative++; 176 goto label; 177 178 case 'O': 179 if (Ealternative || Oalternative) 180 break; 181 Oalternative++; 182 goto label; 183 184 case 'F': 185 buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale); 186 if (buf == NULL) 187 return (NULL); 188 flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; 189 break; 190 191 case 'R': 192 buf = _strptime(buf, "%H:%M", tm, GMTp, locale); 193 if (buf == NULL) 194 return (NULL); 195 break; 196 197 case 'r': 198 buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale); 199 if (buf == NULL) 200 return (NULL); 201 break; 202 203 case 'T': 204 buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale); 205 if (buf == NULL) 206 return (NULL); 207 break; 208 209 case 'X': 210 buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale); 211 if (buf == NULL) 212 return (NULL); 213 break; 214 215 case 'x': 216 buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale); 217 if (buf == NULL) 218 return (NULL); 219 flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; 220 break; 221 222 case 'j': 223 if (!isdigit_l((unsigned char)*buf, locale)) 224 return (NULL); 225 226 len = 3; 227 for (i = 0; len && *buf != 0 && 228 isdigit_l((unsigned char)*buf, locale); buf++){ 229 i *= 10; 230 i += *buf - '0'; 231 len--; 232 } 233 if (i < 1 || i > 366) 234 return (NULL); 235 236 tm->tm_yday = i - 1; 237 flags |= FLAG_YDAY; 238 239 break; 240 241 case 'M': 242 case 'S': 243 if (*buf == 0 || 244 isspace_l((unsigned char)*buf, locale)) 245 break; 246 247 if (!isdigit_l((unsigned char)*buf, locale)) 248 return (NULL); 249 250 len = 2; 251 for (i = 0; len && *buf != 0 && 252 isdigit_l((unsigned char)*buf, locale); buf++){ 253 i *= 10; 254 i += *buf - '0'; 255 len--; 256 } 257 258 if (c == 'M') { 259 if (i > 59) 260 return (NULL); 261 tm->tm_min = i; 262 } else { 263 if (i > 60) 264 return (NULL); 265 tm->tm_sec = i; 266 } 267 268 break; 269 270 case 'H': 271 case 'I': 272 case 'k': 273 case 'l': 274 /* 275 * Of these, %l is the only specifier explicitly 276 * documented as not being zero-padded. However, 277 * there is no harm in allowing zero-padding. 278 * 279 * XXX The %l specifier may gobble one too many 280 * digits if used incorrectly. 281 */ 282 if (!isdigit_l((unsigned char)*buf, locale)) 283 return (NULL); 284 285 len = 2; 286 for (i = 0; len && *buf != 0 && 287 isdigit_l((unsigned char)*buf, locale); buf++) { 288 i *= 10; 289 i += *buf - '0'; 290 len--; 291 } 292 if (c == 'H' || c == 'k') { 293 if (i > 23) 294 return (NULL); 295 } else if (i == 0 || i > 12) 296 return (NULL); 297 298 tm->tm_hour = i; 299 300 break; 301 302 case 'p': 303 /* 304 * XXX This is bogus if parsed before hour-related 305 * specifiers. 306 */ 307 if (tm->tm_hour > 12) 308 return (NULL); 309 310 len = strlen(tptr->am); 311 if (strncasecmp_l(buf, tptr->am, len, locale) == 0) { 312 if (tm->tm_hour == 12) 313 tm->tm_hour = 0; 314 buf += len; 315 break; 316 } 317 318 len = strlen(tptr->pm); 319 if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) { 320 if (tm->tm_hour != 12) 321 tm->tm_hour += 12; 322 buf += len; 323 break; 324 } 325 326 return (NULL); 327 328 case 'A': 329 case 'a': 330 for (i = 0; i < asizeof(tptr->weekday); i++) { 331 len = strlen(tptr->weekday[i]); 332 if (strncasecmp_l(buf, tptr->weekday[i], 333 len, locale) == 0) 334 break; 335 len = strlen(tptr->wday[i]); 336 if (strncasecmp_l(buf, tptr->wday[i], 337 len, locale) == 0) 338 break; 339 } 340 if (i == asizeof(tptr->weekday)) 341 return (NULL); 342 343 buf += len; 344 tm->tm_wday = i; 345 flags |= FLAG_WDAY; 346 break; 347 348 case 'U': 349 case 'W': 350 /* 351 * XXX This is bogus, as we can not assume any valid 352 * information present in the tm structure at this 353 * point to calculate a real value, so just check the 354 * range for now. 355 */ 356 if (!isdigit_l((unsigned char)*buf, locale)) 357 return (NULL); 358 359 len = 2; 360 for (i = 0; len && *buf != 0 && 361 isdigit_l((unsigned char)*buf, locale); buf++) { 362 i *= 10; 363 i += *buf - '0'; 364 len--; 365 } 366 if (i > 53) 367 return (NULL); 368 369 if (c == 'U') 370 day_offset = TM_SUNDAY; 371 else 372 day_offset = TM_MONDAY; 373 374 375 week_offset = i; 376 377 break; 378 379 case 'u': 380 case 'w': 381 if (!isdigit_l((unsigned char)*buf, locale)) 382 return (NULL); 383 384 i = *buf++ - '0'; 385 if (i < 0 || i > 7 || (c == 'u' && i < 1) || 386 (c == 'w' && i > 6)) 387 return (NULL); 388 389 tm->tm_wday = i % 7; 390 flags |= FLAG_WDAY; 391 392 break; 393 394 case 'e': 395 /* 396 * With %e format, our strftime(3) adds a blank space 397 * before single digits. 398 */ 399 if (*buf != 0 && 400 isspace_l((unsigned char)*buf, locale)) 401 buf++; 402 /* FALLTHROUGH */ 403 case 'd': 404 /* 405 * The %e specifier was once explicitly documented as 406 * not being zero-padded but was later changed to 407 * equivalent to %d. There is no harm in allowing 408 * such padding. 409 * 410 * XXX The %e specifier may gobble one too many 411 * digits if used incorrectly. 412 */ 413 if (!isdigit_l((unsigned char)*buf, locale)) 414 return (NULL); 415 416 len = 2; 417 for (i = 0; len && *buf != 0 && 418 isdigit_l((unsigned char)*buf, locale); buf++) { 419 i *= 10; 420 i += *buf - '0'; 421 len--; 422 } 423 if (i == 0 || i > 31) 424 return (NULL); 425 426 tm->tm_mday = i; 427 flags |= FLAG_MDAY; 428 429 break; 430 431 case 'B': 432 case 'b': 433 case 'h': 434 for (i = 0; i < asizeof(tptr->month); i++) { 435 if (Oalternative) { 436 if (c == 'B') { 437 len = strlen(tptr->alt_month[i]); 438 if (strncasecmp_l(buf, 439 tptr->alt_month[i], 440 len, locale) == 0) 441 break; 442 } 443 } else { 444 len = strlen(tptr->month[i]); 445 if (strncasecmp_l(buf, tptr->month[i], 446 len, locale) == 0) 447 break; 448 } 449 } 450 /* 451 * Try the abbreviated month name if the full name 452 * wasn't found and Oalternative was not requested. 453 */ 454 if (i == asizeof(tptr->month) && !Oalternative) { 455 for (i = 0; i < asizeof(tptr->month); i++) { 456 len = strlen(tptr->mon[i]); 457 if (strncasecmp_l(buf, tptr->mon[i], 458 len, locale) == 0) 459 break; 460 } 461 } 462 if (i == asizeof(tptr->month)) 463 return (NULL); 464 465 tm->tm_mon = i; 466 buf += len; 467 flags |= FLAG_MONTH; 468 469 break; 470 471 case 'm': 472 if (!isdigit_l((unsigned char)*buf, locale)) 473 return (NULL); 474 475 len = 2; 476 for (i = 0; len && *buf != 0 && 477 isdigit_l((unsigned char)*buf, locale); buf++) { 478 i *= 10; 479 i += *buf - '0'; 480 len--; 481 } 482 if (i < 1 || i > 12) 483 return (NULL); 484 485 tm->tm_mon = i - 1; 486 flags |= FLAG_MONTH; 487 488 break; 489 490 case 's': 491 { 492 char *cp; 493 int sverrno; 494 long n; 495 time_t t; 496 497 sverrno = errno; 498 errno = 0; 499 n = strtol_l(buf, &cp, 10, locale); 500 if (errno == ERANGE || (long)(t = n) != n) { 501 errno = sverrno; 502 return (NULL); 503 } 504 errno = sverrno; 505 buf = cp; 506 if (gmtime_r(&t, tm) == NULL) 507 return (NULL); 508 *GMTp = 1; 509 flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH | 510 FLAG_MDAY | FLAG_YEAR; 511 } 512 break; 513 514 case 'Y': 515 case 'y': 516 if (*buf == 0 || 517 isspace_l((unsigned char)*buf, locale)) 518 break; 519 520 if (!isdigit_l((unsigned char)*buf, locale)) 521 return (NULL); 522 523 len = (c == 'Y') ? 4 : 2; 524 for (i = 0; len && *buf != 0 && 525 isdigit_l((unsigned char)*buf, locale); buf++) { 526 i *= 10; 527 i += *buf - '0'; 528 len--; 529 } 530 if (c == 'Y') 531 century = i / 100; 532 year = i % 100; 533 534 flags |= FLAG_YEAR; 535 536 break; 537 538 case 'Z': 539 { 540 const char *cp; 541 char *zonestr; 542 543 for (cp = buf; *cp && 544 isupper_l((unsigned char)*cp, locale); ++cp) { 545 /*empty*/} 546 if (cp - buf) { 547 zonestr = alloca(cp - buf + 1); 548 strncpy(zonestr, buf, cp - buf); 549 zonestr[cp - buf] = '\0'; 550 tzset(); 551 if (0 == strcmp(zonestr, "GMT") || 552 0 == strcmp(zonestr, "UTC")) { 553 *GMTp = 1; 554 } else if (0 == strcmp(zonestr, tzname[0])) { 555 tm->tm_isdst = 0; 556 } else if (0 == strcmp(zonestr, tzname[1])) { 557 tm->tm_isdst = 1; 558 } else { 559 return (NULL); 560 } 561 buf += cp - buf; 562 } 563 } 564 break; 565 566 case 'z': 567 { 568 int sign = 1; 569 570 if (*buf != '+') { 571 if (*buf == '-') 572 sign = -1; 573 else 574 return (NULL); 575 } 576 577 buf++; 578 i = 0; 579 for (len = 4; len > 0; len--) { 580 if (isdigit_l((unsigned char)*buf, locale)) { 581 i *= 10; 582 i += *buf - '0'; 583 buf++; 584 } else if (len == 2) { 585 i *= 100; 586 break; 587 } else 588 return (NULL); 589 } 590 591 if (i > 1400 || (sign == -1 && i > 1200) || 592 (i % 100) >= 60) 593 return (NULL); 594 tm->tm_hour -= sign * (i / 100); 595 tm->tm_min -= sign * (i % 100); 596 *GMTp = 1; 597 } 598 break; 599 600 case 'n': 601 case 't': 602 while (isspace_l((unsigned char)*buf, locale)) 603 buf++; 604 break; 605 606 default: 607 return (NULL); 608 } 609 } 610 611 if (century != -1 || year != -1) { 612 if (year == -1) 613 year = 0; 614 if (century == -1) { 615 if (year < 69) 616 year += 100; 617 } else 618 year += century * 100 - TM_YEAR_BASE; 619 tm->tm_year = year; 620 } 621 622 if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) { 623 if ((flags & (FLAG_MONTH | FLAG_MDAY)) == 624 (FLAG_MONTH | FLAG_MDAY)) { 625 tm->tm_yday = start_of_month[isleap(tm->tm_year + 626 TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); 627 flags |= FLAG_YDAY; 628 } else if (day_offset != -1) { 629 int tmpwday, tmpyday, fwo; 630 631 fwo = first_wday_of(tm->tm_year + TM_YEAR_BASE); 632 /* No incomplete week (week 0). */ 633 if (week_offset == 0 && fwo == day_offset) 634 return (NULL); 635 636 /* Set the date to the first Sunday (or Monday) 637 * of the specified week of the year. 638 */ 639 tmpwday = (flags & FLAG_WDAY) ? tm->tm_wday : 640 day_offset; 641 tmpyday = (7 - fwo + day_offset) % 7 + 642 (week_offset - 1) * 7 + 643 (tmpwday - day_offset + 7) % 7; 644 /* Impossible yday for incomplete week (week 0). */ 645 if (tmpyday < 0) { 646 if (flags & FLAG_WDAY) 647 return (NULL); 648 tmpyday = 0; 649 } 650 tm->tm_yday = tmpyday; 651 flags |= FLAG_YDAY; 652 } 653 } 654 655 if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) { 656 if (!(flags & FLAG_MONTH)) { 657 i = 0; 658 while (tm->tm_yday >= 659 start_of_month[isleap(tm->tm_year + 660 TM_YEAR_BASE)][i]) 661 i++; 662 if (i > 12) { 663 i = 1; 664 tm->tm_yday -= 665 start_of_month[isleap(tm->tm_year + 666 TM_YEAR_BASE)][12]; 667 tm->tm_year++; 668 } 669 tm->tm_mon = i - 1; 670 flags |= FLAG_MONTH; 671 } 672 if (!(flags & FLAG_MDAY)) { 673 tm->tm_mday = tm->tm_yday - 674 start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)] 675 [tm->tm_mon] + 1; 676 flags |= FLAG_MDAY; 677 } 678 if (!(flags & FLAG_WDAY)) { 679 i = 0; 680 wday_offset = first_wday_of(tm->tm_year); 681 while (i++ <= tm->tm_yday) { 682 if (wday_offset++ >= 6) 683 wday_offset = 0; 684 } 685 tm->tm_wday = wday_offset; 686 flags |= FLAG_WDAY; 687 } 688 } 689 690 return ((char *)buf); 691 } 692 693 char * 694 strptime_l(const char * __restrict buf, const char * __restrict fmt, 695 struct tm * __restrict tm, locale_t loc) 696 { 697 char *ret; 698 int gmt; 699 FIX_LOCALE(loc); 700 701 gmt = 0; 702 ret = _strptime(buf, fmt, tm, &gmt, loc); 703 if (ret && gmt) { 704 time_t t = timegm(tm); 705 706 localtime_r(&t, tm); 707 } 708 709 return (ret); 710 } 711 712 char * 713 strptime(const char * __restrict buf, const char * __restrict fmt, 714 struct tm * __restrict tm) 715 { 716 return strptime_l(buf, fmt, tm, __get_locale()); 717 } 718