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