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 len = strlen(tptr->am); 305 if (strncasecmp_l(buf, tptr->am, len, locale) == 0) { 306 if (tm->tm_hour > 12) 307 return (NULL); 308 if (tm->tm_hour == 12) 309 tm->tm_hour = 0; 310 buf += len; 311 break; 312 } 313 314 len = strlen(tptr->pm); 315 if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) { 316 if (tm->tm_hour > 12) 317 return (NULL); 318 if (tm->tm_hour != 12) 319 tm->tm_hour += 12; 320 buf += len; 321 break; 322 } 323 324 return (NULL); 325 326 case 'A': 327 case 'a': 328 for (i = 0; i < asizeof(tptr->weekday); i++) { 329 len = strlen(tptr->weekday[i]); 330 if (strncasecmp_l(buf, tptr->weekday[i], 331 len, locale) == 0) 332 break; 333 len = strlen(tptr->wday[i]); 334 if (strncasecmp_l(buf, tptr->wday[i], 335 len, locale) == 0) 336 break; 337 } 338 if (i == asizeof(tptr->weekday)) 339 return (NULL); 340 341 buf += len; 342 tm->tm_wday = i; 343 flags |= FLAG_WDAY; 344 break; 345 346 case 'U': 347 case 'W': 348 /* 349 * XXX This is bogus, as we can not assume any valid 350 * information present in the tm structure at this 351 * point to calculate a real value, so just check the 352 * range for now. 353 */ 354 if (!isdigit_l((unsigned char)*buf, locale)) 355 return (NULL); 356 357 len = 2; 358 for (i = 0; len && *buf != 0 && 359 isdigit_l((unsigned char)*buf, locale); buf++) { 360 i *= 10; 361 i += *buf - '0'; 362 len--; 363 } 364 if (i > 53) 365 return (NULL); 366 367 if (c == 'U') 368 day_offset = TM_SUNDAY; 369 else 370 day_offset = TM_MONDAY; 371 372 373 week_offset = i; 374 375 break; 376 377 case 'w': 378 if (!isdigit_l((unsigned char)*buf, locale)) 379 return (NULL); 380 381 i = *buf - '0'; 382 if (i > 6) 383 return (NULL); 384 385 tm->tm_wday = i; 386 flags |= FLAG_WDAY; 387 388 break; 389 390 case 'e': 391 /* 392 * With %e format, our strftime(3) adds a blank space 393 * before single digits. 394 */ 395 if (*buf != 0 && 396 isspace_l((unsigned char)*buf, locale)) 397 buf++; 398 /* FALLTHROUGH */ 399 case 'd': 400 /* 401 * The %e specifier was once explicitly documented as 402 * not being zero-padded but was later changed to 403 * equivalent to %d. There is no harm in allowing 404 * such padding. 405 * 406 * XXX The %e specifier may gobble one too many 407 * digits if used incorrectly. 408 */ 409 if (!isdigit_l((unsigned char)*buf, locale)) 410 return (NULL); 411 412 len = 2; 413 for (i = 0; len && *buf != 0 && 414 isdigit_l((unsigned char)*buf, locale); buf++) { 415 i *= 10; 416 i += *buf - '0'; 417 len--; 418 } 419 if (i > 31) 420 return (NULL); 421 422 tm->tm_mday = i; 423 flags |= FLAG_MDAY; 424 425 break; 426 427 case 'B': 428 case 'b': 429 case 'h': 430 for (i = 0; i < asizeof(tptr->month); i++) { 431 if (Oalternative) { 432 if (c == 'B') { 433 len = strlen(tptr->alt_month[i]); 434 if (strncasecmp_l(buf, 435 tptr->alt_month[i], 436 len, locale) == 0) 437 break; 438 } 439 } else { 440 len = strlen(tptr->month[i]); 441 if (strncasecmp_l(buf, tptr->month[i], 442 len, locale) == 0) 443 break; 444 } 445 } 446 /* 447 * Try the abbreviated month name if the full name 448 * wasn't found and Oalternative was not requested. 449 */ 450 if (i == asizeof(tptr->month) && !Oalternative) { 451 for (i = 0; i < asizeof(tptr->month); i++) { 452 len = strlen(tptr->mon[i]); 453 if (strncasecmp_l(buf, tptr->mon[i], 454 len, locale) == 0) 455 break; 456 } 457 } 458 if (i == asizeof(tptr->month)) 459 return (NULL); 460 461 tm->tm_mon = i; 462 buf += len; 463 flags |= FLAG_MONTH; 464 465 break; 466 467 case 'm': 468 if (!isdigit_l((unsigned char)*buf, locale)) 469 return (NULL); 470 471 len = 2; 472 for (i = 0; len && *buf != 0 && 473 isdigit_l((unsigned char)*buf, locale); buf++) { 474 i *= 10; 475 i += *buf - '0'; 476 len--; 477 } 478 if (i < 1 || i > 12) 479 return (NULL); 480 481 tm->tm_mon = i - 1; 482 flags |= FLAG_MONTH; 483 484 break; 485 486 case 's': 487 { 488 char *cp; 489 int sverrno; 490 long n; 491 time_t t; 492 493 sverrno = errno; 494 errno = 0; 495 n = strtol_l(buf, &cp, 10, locale); 496 if (errno == ERANGE || (long)(t = n) != n) { 497 errno = sverrno; 498 return (NULL); 499 } 500 errno = sverrno; 501 buf = cp; 502 if (gmtime_r(&t, tm) == NULL) 503 return (NULL); 504 *GMTp = 1; 505 flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH | 506 FLAG_MDAY | FLAG_YEAR; 507 } 508 break; 509 510 case 'Y': 511 case 'y': 512 if (*buf == 0 || 513 isspace_l((unsigned char)*buf, locale)) 514 break; 515 516 if (!isdigit_l((unsigned char)*buf, locale)) 517 return (NULL); 518 519 len = (c == 'Y') ? 4 : 2; 520 for (i = 0; len && *buf != 0 && 521 isdigit_l((unsigned char)*buf, locale); buf++) { 522 i *= 10; 523 i += *buf - '0'; 524 len--; 525 } 526 if (c == 'Y') 527 i -= TM_YEAR_BASE; 528 if (c == 'y' && i < 69) 529 i += 100; 530 if (i < 0) 531 return (NULL); 532 533 tm->tm_year = i; 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 585 return (NULL); 586 } 587 588 tm->tm_hour -= sign * (i / 100); 589 tm->tm_min -= sign * (i % 100); 590 *GMTp = 1; 591 } 592 break; 593 594 case 'n': 595 case 't': 596 while (isspace_l((unsigned char)*buf, locale)) 597 buf++; 598 break; 599 600 default: 601 return (NULL); 602 } 603 } 604 605 if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) { 606 if ((flags & (FLAG_MONTH | FLAG_MDAY)) == 607 (FLAG_MONTH | FLAG_MDAY)) { 608 tm->tm_yday = start_of_month[isleap(tm->tm_year + 609 TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); 610 flags |= FLAG_YDAY; 611 } else if (day_offset != -1) { 612 /* Set the date to the first Sunday (or Monday) 613 * of the specified week of the year. 614 */ 615 if (!(flags & FLAG_WDAY)) { 616 tm->tm_wday = day_offset; 617 flags |= FLAG_WDAY; 618 } 619 tm->tm_yday = (7 - 620 first_wday_of(tm->tm_year + TM_YEAR_BASE) + 621 day_offset) % 7 + (week_offset - 1) * 7 + 622 tm->tm_wday - day_offset; 623 flags |= FLAG_YDAY; 624 } 625 } 626 627 if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) { 628 if (!(flags & FLAG_MONTH)) { 629 i = 0; 630 while (tm->tm_yday >= 631 start_of_month[isleap(tm->tm_year + 632 TM_YEAR_BASE)][i]) 633 i++; 634 if (i > 12) { 635 i = 1; 636 tm->tm_yday -= 637 start_of_month[isleap(tm->tm_year + 638 TM_YEAR_BASE)][12]; 639 tm->tm_year++; 640 } 641 tm->tm_mon = i - 1; 642 flags |= FLAG_MONTH; 643 } 644 if (!(flags & FLAG_MDAY)) { 645 tm->tm_mday = tm->tm_yday - 646 start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)] 647 [tm->tm_mon] + 1; 648 flags |= FLAG_MDAY; 649 } 650 if (!(flags & FLAG_WDAY)) { 651 i = 0; 652 wday_offset = first_wday_of(tm->tm_year); 653 while (i++ <= tm->tm_yday) { 654 if (wday_offset++ >= 6) 655 wday_offset = 0; 656 } 657 tm->tm_wday = wday_offset; 658 flags |= FLAG_WDAY; 659 } 660 } 661 662 return ((char *)buf); 663 } 664 665 char * 666 strptime_l(const char * __restrict buf, const char * __restrict fmt, 667 struct tm * __restrict tm, locale_t loc) 668 { 669 char *ret; 670 int gmt; 671 FIX_LOCALE(loc); 672 673 gmt = 0; 674 ret = _strptime(buf, fmt, tm, &gmt, loc); 675 if (ret && gmt) { 676 time_t t = timegm(tm); 677 678 localtime_r(&t, tm); 679 } 680 681 return (ret); 682 } 683 684 char * 685 strptime(const char * __restrict buf, const char * __restrict fmt, 686 struct tm * __restrict tm) 687 { 688 return strptime_l(buf, fmt, tm, __get_locale()); 689 } 690