1 /* 2 * Powerdog Industries kindly requests feedback from anyone modifying 3 * this function: 4 * 5 * Date: Thu, 05 Jun 1997 23:17:17 -0400 6 * From: Kevin Ruddy <kevin.ruddy@powerdog.com> 7 * To: James FitzGibbon <james@nexis.net> 8 * Subject: Re: Use of your strptime(3) code (fwd) 9 * 10 * The reason for the "no mod" clause was so that modifications would 11 * come back and we could integrate them and reissue so that a wider 12 * audience could use it (thereby spreading the wealth). This has 13 * made it possible to get strptime to work on many operating systems. 14 * I'm not sure why that's "plain unacceptable" to the FreeBSD team. 15 * 16 * Anyway, you can change it to "with or without modification" as 17 * you see fit. Enjoy. 18 * 19 * Kevin Ruddy 20 * Powerdog Industries, Inc. 21 */ 22 /* 23 * Copyright (c) 1994 Powerdog Industries. All rights reserved. 24 * 25 * Copyright (c) 2011 The FreeBSD Foundation 26 * All rights reserved. 27 * Portions of this software were developed by David Chisnall 28 * under sponsorship from the FreeBSD Foundation. 29 * 30 * Redistribution and use in source and binary forms, with or without 31 * modification, are permitted provided that the following conditions 32 * are met: 33 * 1. Redistributions of source code must retain the above copyright 34 * notice, this list of conditions and the following disclaimer. 35 * 2. Redistributions in binary form must reproduce the above copyright 36 * notice, this list of conditions and the following disclaimer 37 * in the documentation and/or other materials provided with the 38 * distribution. 39 * 3. All advertising materials mentioning features or use of this 40 * software must display the following acknowledgement: 41 * This product includes software developed by Powerdog Industries. 42 * 4. The name of Powerdog Industries may not be used to endorse or 43 * promote products derived from this software without specific prior 44 * written permission. 45 * 46 * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY 47 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 49 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE 50 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 51 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 52 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 53 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 54 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 55 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 56 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 57 */ 58 59 #include <sys/cdefs.h> 60 #ifndef lint 61 #ifndef NOID 62 static char copyright[] __unused = 63 "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved."; 64 static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27"; 65 #endif /* !defined NOID */ 66 #endif /* not lint */ 67 __FBSDID("$FreeBSD$"); 68 69 #include "namespace.h" 70 #include <time.h> 71 #include <ctype.h> 72 #include <errno.h> 73 #include <stdlib.h> 74 #include <string.h> 75 #include <pthread.h> 76 #include "un-namespace.h" 77 #include "libc_private.h" 78 #include "timelocal.h" 79 80 static char * _strptime(const char *, const char *, struct tm *, int *, locale_t); 81 82 #define asizeof(a) (sizeof (a) / sizeof ((a)[0])) 83 84 static char * 85 _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, 86 locale_t locale) 87 { 88 char c; 89 const char *ptr; 90 int i, 91 len; 92 int Ealternative, Oalternative; 93 struct lc_time_T *tptr = __get_current_time_locale(locale); 94 95 ptr = fmt; 96 while (*ptr != 0) { 97 if (*buf == 0) 98 break; 99 100 c = *ptr++; 101 102 if (c != '%') { 103 if (isspace_l((unsigned char)c, locale)) 104 while (*buf != 0 && 105 isspace_l((unsigned char)*buf, locale)) 106 buf++; 107 else if (c != *buf++) 108 return 0; 109 continue; 110 } 111 112 Ealternative = 0; 113 Oalternative = 0; 114 label: 115 c = *ptr++; 116 switch (c) { 117 case 0: 118 case '%': 119 if (*buf++ != '%') 120 return 0; 121 break; 122 123 case '+': 124 buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale); 125 if (buf == 0) 126 return 0; 127 break; 128 129 case 'C': 130 if (!isdigit_l((unsigned char)*buf, locale)) 131 return 0; 132 133 /* XXX This will break for 3-digit centuries. */ 134 len = 2; 135 for (i = 0; len && *buf != 0 && 136 isdigit_l((unsigned char)*buf, locale); buf++) { 137 i *= 10; 138 i += *buf - '0'; 139 len--; 140 } 141 if (i < 19) 142 return 0; 143 144 tm->tm_year = i * 100 - 1900; 145 break; 146 147 case 'c': 148 buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale); 149 if (buf == 0) 150 return 0; 151 break; 152 153 case 'D': 154 buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale); 155 if (buf == 0) 156 return 0; 157 break; 158 159 case 'E': 160 if (Ealternative || Oalternative) 161 break; 162 Ealternative++; 163 goto label; 164 165 case 'O': 166 if (Ealternative || Oalternative) 167 break; 168 Oalternative++; 169 goto label; 170 171 case 'F': 172 buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale); 173 if (buf == 0) 174 return 0; 175 break; 176 177 case 'R': 178 buf = _strptime(buf, "%H:%M", tm, GMTp, locale); 179 if (buf == 0) 180 return 0; 181 break; 182 183 case 'r': 184 buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale); 185 if (buf == 0) 186 return 0; 187 break; 188 189 case 'T': 190 buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale); 191 if (buf == 0) 192 return 0; 193 break; 194 195 case 'X': 196 buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale); 197 if (buf == 0) 198 return 0; 199 break; 200 201 case 'x': 202 buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale); 203 if (buf == 0) 204 return 0; 205 break; 206 207 case 'j': 208 if (!isdigit_l((unsigned char)*buf, locale)) 209 return 0; 210 211 len = 3; 212 for (i = 0; len && *buf != 0 && 213 isdigit_l((unsigned char)*buf, locale); buf++){ 214 i *= 10; 215 i += *buf - '0'; 216 len--; 217 } 218 if (i < 1 || i > 366) 219 return 0; 220 221 tm->tm_yday = i - 1; 222 break; 223 224 case 'M': 225 case 'S': 226 if (*buf == 0 || 227 isspace_l((unsigned char)*buf, locale)) 228 break; 229 230 if (!isdigit_l((unsigned char)*buf, locale)) 231 return 0; 232 233 len = 2; 234 for (i = 0; len && *buf != 0 && 235 isdigit_l((unsigned char)*buf, locale); buf++){ 236 i *= 10; 237 i += *buf - '0'; 238 len--; 239 } 240 241 if (c == 'M') { 242 if (i > 59) 243 return 0; 244 tm->tm_min = i; 245 } else { 246 if (i > 60) 247 return 0; 248 tm->tm_sec = i; 249 } 250 251 if (*buf != 0 && 252 isspace_l((unsigned char)*buf, locale)) 253 while (*ptr != 0 && 254 !isspace_l((unsigned char)*ptr, locale)) 255 ptr++; 256 break; 257 258 case 'H': 259 case 'I': 260 case 'k': 261 case 'l': 262 /* 263 * Of these, %l is the only specifier explicitly 264 * documented as not being zero-padded. However, 265 * there is no harm in allowing zero-padding. 266 * 267 * XXX The %l specifier may gobble one too many 268 * digits if used incorrectly. 269 */ 270 if (!isdigit_l((unsigned char)*buf, locale)) 271 return 0; 272 273 len = 2; 274 for (i = 0; len && *buf != 0 && 275 isdigit_l((unsigned char)*buf, locale); buf++) { 276 i *= 10; 277 i += *buf - '0'; 278 len--; 279 } 280 if (c == 'H' || c == 'k') { 281 if (i > 23) 282 return 0; 283 } else if (i > 12) 284 return 0; 285 286 tm->tm_hour = i; 287 288 if (*buf != 0 && 289 isspace_l((unsigned char)*buf, locale)) 290 while (*ptr != 0 && 291 !isspace_l((unsigned char)*ptr, locale)) 292 ptr++; 293 break; 294 295 case 'p': 296 /* 297 * XXX This is bogus if parsed before hour-related 298 * specifiers. 299 */ 300 len = strlen(tptr->am); 301 if (strncasecmp_l(buf, tptr->am, len, locale) == 0) { 302 if (tm->tm_hour > 12) 303 return 0; 304 if (tm->tm_hour == 12) 305 tm->tm_hour = 0; 306 buf += len; 307 break; 308 } 309 310 len = strlen(tptr->pm); 311 if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) { 312 if (tm->tm_hour > 12) 313 return 0; 314 if (tm->tm_hour != 12) 315 tm->tm_hour += 12; 316 buf += len; 317 break; 318 } 319 320 return 0; 321 322 case 'A': 323 case 'a': 324 for (i = 0; i < asizeof(tptr->weekday); i++) { 325 len = strlen(tptr->weekday[i]); 326 if (strncasecmp_l(buf, tptr->weekday[i], 327 len, locale) == 0) 328 break; 329 len = strlen(tptr->wday[i]); 330 if (strncasecmp_l(buf, tptr->wday[i], 331 len, locale) == 0) 332 break; 333 } 334 if (i == asizeof(tptr->weekday)) 335 return 0; 336 337 tm->tm_wday = i; 338 buf += len; 339 break; 340 341 case 'U': 342 case 'W': 343 /* 344 * XXX This is bogus, as we can not assume any valid 345 * information present in the tm structure at this 346 * point to calculate a real value, so just check the 347 * range for now. 348 */ 349 if (!isdigit_l((unsigned char)*buf, locale)) 350 return 0; 351 352 len = 2; 353 for (i = 0; len && *buf != 0 && 354 isdigit_l((unsigned char)*buf, locale); buf++) { 355 i *= 10; 356 i += *buf - '0'; 357 len--; 358 } 359 if (i > 53) 360 return 0; 361 362 if (*buf != 0 && 363 isspace_l((unsigned char)*buf, locale)) 364 while (*ptr != 0 && 365 !isspace_l((unsigned char)*ptr, locale)) 366 ptr++; 367 break; 368 369 case 'w': 370 if (!isdigit_l((unsigned char)*buf, locale)) 371 return 0; 372 373 i = *buf - '0'; 374 if (i > 6) 375 return 0; 376 377 tm->tm_wday = i; 378 379 if (*buf != 0 && 380 isspace_l((unsigned char)*buf, locale)) 381 while (*ptr != 0 && 382 !isspace_l((unsigned char)*ptr, locale)) 383 ptr++; 384 break; 385 386 case 'd': 387 case 'e': 388 /* 389 * The %e specifier is explicitly documented as not 390 * being zero-padded but there is no harm in allowing 391 * such padding. 392 * 393 * XXX The %e specifier may gobble one too many 394 * digits if used incorrectly. 395 */ 396 if (!isdigit_l((unsigned char)*buf, locale)) 397 return 0; 398 399 len = 2; 400 for (i = 0; len && *buf != 0 && 401 isdigit_l((unsigned char)*buf, locale); buf++) { 402 i *= 10; 403 i += *buf - '0'; 404 len--; 405 } 406 if (i > 31) 407 return 0; 408 409 tm->tm_mday = i; 410 411 if (*buf != 0 && 412 isspace_l((unsigned char)*buf, locale)) 413 while (*ptr != 0 && 414 !isspace_l((unsigned char)*ptr, locale)) 415 ptr++; 416 break; 417 418 case 'B': 419 case 'b': 420 case 'h': 421 for (i = 0; i < asizeof(tptr->month); i++) { 422 if (Oalternative) { 423 if (c == 'B') { 424 len = strlen(tptr->alt_month[i]); 425 if (strncasecmp_l(buf, 426 tptr->alt_month[i], 427 len, locale) == 0) 428 break; 429 } 430 } else { 431 len = strlen(tptr->month[i]); 432 if (strncasecmp_l(buf, tptr->month[i], 433 len, locale) == 0) 434 break; 435 } 436 } 437 /* 438 * Try the abbreviated month name if the full name 439 * wasn't found and Oalternative was not requested. 440 */ 441 if (i == asizeof(tptr->month) && !Oalternative) { 442 for (i = 0; i < asizeof(tptr->month); i++) { 443 len = strlen(tptr->mon[i]); 444 if (strncasecmp_l(buf, tptr->mon[i], 445 len, locale) == 0) 446 break; 447 } 448 } 449 if (i == asizeof(tptr->month)) 450 return 0; 451 452 tm->tm_mon = i; 453 buf += len; 454 break; 455 456 case 'm': 457 if (!isdigit_l((unsigned char)*buf, locale)) 458 return 0; 459 460 len = 2; 461 for (i = 0; len && *buf != 0 && 462 isdigit_l((unsigned char)*buf, locale); buf++) { 463 i *= 10; 464 i += *buf - '0'; 465 len--; 466 } 467 if (i < 1 || i > 12) 468 return 0; 469 470 tm->tm_mon = i - 1; 471 472 if (*buf != 0 && 473 isspace_l((unsigned char)*buf, locale)) 474 while (*ptr != 0 && 475 !isspace_l((unsigned char)*ptr, locale)) 476 ptr++; 477 break; 478 479 case 's': 480 { 481 char *cp; 482 int sverrno; 483 long n; 484 time_t t; 485 486 sverrno = errno; 487 errno = 0; 488 n = strtol_l(buf, &cp, 10, locale); 489 if (errno == ERANGE || (long)(t = n) != n) { 490 errno = sverrno; 491 return 0; 492 } 493 errno = sverrno; 494 buf = cp; 495 gmtime_r(&t, tm); 496 *GMTp = 1; 497 } 498 break; 499 500 case 'Y': 501 case 'y': 502 if (*buf == 0 || 503 isspace_l((unsigned char)*buf, locale)) 504 break; 505 506 if (!isdigit_l((unsigned char)*buf, locale)) 507 return 0; 508 509 len = (c == 'Y') ? 4 : 2; 510 for (i = 0; len && *buf != 0 && 511 isdigit_l((unsigned char)*buf, locale); buf++) { 512 i *= 10; 513 i += *buf - '0'; 514 len--; 515 } 516 if (c == 'Y') 517 i -= 1900; 518 if (c == 'y' && i < 69) 519 i += 100; 520 if (i < 0) 521 return 0; 522 523 tm->tm_year = i; 524 525 if (*buf != 0 && 526 isspace_l((unsigned char)*buf, locale)) 527 while (*ptr != 0 && 528 !isspace_l((unsigned char)*ptr, locale)) 529 ptr++; 530 break; 531 532 case 'Z': 533 { 534 const char *cp; 535 char *zonestr; 536 537 for (cp = buf; *cp && 538 isupper_l((unsigned char)*cp, locale); ++cp) { 539 /*empty*/} 540 if (cp - buf) { 541 zonestr = alloca(cp - buf + 1); 542 strncpy(zonestr, buf, cp - buf); 543 zonestr[cp - buf] = '\0'; 544 tzset(); 545 if (0 == strcmp(zonestr, "GMT")) { 546 *GMTp = 1; 547 } else if (0 == strcmp(zonestr, tzname[0])) { 548 tm->tm_isdst = 0; 549 } else if (0 == strcmp(zonestr, tzname[1])) { 550 tm->tm_isdst = 1; 551 } else { 552 return 0; 553 } 554 buf += cp - buf; 555 } 556 } 557 break; 558 559 case 'z': 560 { 561 int sign = 1; 562 563 if (*buf != '+') { 564 if (*buf == '-') 565 sign = -1; 566 else 567 return 0; 568 } 569 570 buf++; 571 i = 0; 572 for (len = 4; len > 0; len--) { 573 if (isdigit_l((unsigned char)*buf, locale)) { 574 i *= 10; 575 i += *buf - '0'; 576 buf++; 577 } else 578 return 0; 579 } 580 581 tm->tm_hour -= sign * (i / 100); 582 tm->tm_min -= sign * (i % 100); 583 *GMTp = 1; 584 } 585 break; 586 } 587 } 588 return (char *)buf; 589 } 590 591 592 char * 593 strptime_l(const char * __restrict buf, const char * __restrict fmt, 594 struct tm * __restrict tm, locale_t loc) 595 { 596 char *ret; 597 int gmt; 598 FIX_LOCALE(loc); 599 600 gmt = 0; 601 ret = _strptime(buf, fmt, tm, &gmt, loc); 602 if (ret && gmt) { 603 time_t t = timegm(tm); 604 localtime_r(&t, tm); 605 } 606 607 return (ret); 608 } 609 char * 610 strptime(const char * __restrict buf, const char * __restrict fmt, 611 struct tm * __restrict tm) 612 { 613 return strptime_l(buf, fmt, tm, __get_locale()); 614 } 615