1 /* 2 * Copyright 2003-2008, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <parsedate.h> 8 9 #include <ctype.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 14 #include <OS.h> 15 16 17 #define TRACE_PARSEDATE 0 18 #if TRACE_PARSEDATE 19 # define TRACE(x) printf x ; 20 #else 21 # define TRACE(x) ; 22 #endif 23 24 25 /* The date format is as follows: 26 * 27 * a/A weekday 28 * d day of month 29 * b/B month name 30 * m month 31 * y/Y year 32 * H/I hours 33 * M minute 34 * S seconds 35 * p meridian (i.e. am/pm) 36 * T time unit: last hour, next tuesday, today, ... 37 * z/Z time zone 38 * - dash or slash 39 * 40 * Any of ",.:" is allowed and will be expected in the input string as is. 41 * You can enclose a single field with "[]" to mark it as being optional. 42 * A space stands for white space. 43 * No other character is allowed. 44 */ 45 46 static const char * const kFormatsTable[] = { 47 "[A][,] B d[,] H:M:S [p] Y[,] [Z]", 48 "[A][,] B d[,] [Y][,] H:M:S [p] [Z]", 49 "[A][,] B d[,] [Y][,] H:M [p][,] [Z]", 50 "[A][,] B d[,] [Y][,] H [p][,] [Z]", 51 "[A][,] B d[,] H:M [p][,] [Y] [Z]", 52 "[A][,] d B[,] [Y][,] H:M [p][,] [Z]", 53 "[A][,] d B[,] [Y][,] H:M:S [p][,] [Z]", 54 "[A][,] d B[,] H:M:S [Y][,] [p][,] [Z]", 55 "[A][,] d B[,] H:M [Y][,] [p][,] [Z]", 56 "d.m.y H:M:S [p] [Z]", 57 "d.m.y H:M [p] [Z]", 58 "d.m.y", 59 "[A][,] m-d-y[,] [H][:][M] [p]", 60 "[A][,] m-d[,] H:M [p]", 61 "[A][,] m-d[,] H[p]", 62 "[A][,] m-d", 63 "[A][,] B d[,] Y", 64 "[A][,] H:M [p]", 65 "[A][,] H [p]", 66 "H:M [p][,] [A]", 67 "H [p][,] [A]", 68 "[A][,] B d[,] H:M:S [p] [Z] [Y]", 69 "[A][,] B d[,] H:M [p] [Z] [Y]", 70 "[A][,] d B [,] H:M:S [p] [Z] [Y]", 71 "[A][,] d B [,] H:M [p] [Z] [Y]", 72 "[A][,] d-B-Y H:M:S [p] [Z]", 73 "[A][,] d-B-Y H:M [p] [Z]", 74 "d B Y H:M:S [Z]", 75 "d B Y H:M [Z]", 76 "y-m-d", 77 "y-m-d H:M:S [p] [Z]", 78 "m-d-y H[p]", 79 "m-d-y H:M[p]", 80 "m-d-y H:M:S[p]", 81 "H[p] m-d-y", 82 "H:M[p] m-d-y", 83 "H:M:S[p] m-d-y", 84 "A[,] H:M:S [p] [Z]", 85 "A[,] H:M [p] [Z]", 86 "H:M:S [p] [Z]", 87 "H:M [p] [Z]", 88 "A[,] [B] [d] [Y]", 89 "A[,] [d] [B] [Y]", 90 "B d[,][Y] H[p][,] [Z]", 91 "B d[,] H[p]", 92 "B d [,] H:M [p]", 93 "d B [,][Y] H [p] [Z]", 94 "d B [,] H:M [p]", 95 "B d [,][Y]", 96 "B d [,] H:M [p][,] [Y]", 97 "B d [,] H [p][,] [Y]", 98 "d B [,][Y]", 99 "H[p] [,] B d", 100 "H:M[p] [,] B d", 101 "T [T][T][T][T][T]", 102 "T H:M:S [p]", 103 "T H:M [p]", 104 "T H [p]", 105 "H:M [p] T", 106 "H [p] T", 107 "H [p]", 108 NULL 109 }; 110 static const char* const* sFormatsTable = kFormatsTable; 111 112 113 enum field_type { 114 TYPE_UNKNOWN = 0, 115 116 TYPE_DAY, 117 TYPE_MONTH, 118 TYPE_YEAR, 119 TYPE_WEEKDAY, 120 TYPE_HOUR, 121 TYPE_MINUTE, 122 TYPE_SECOND, 123 TYPE_TIME_ZONE, 124 TYPE_MERIDIAN, 125 126 TYPE_DASH, 127 TYPE_DOT, 128 TYPE_COMMA, 129 TYPE_COLON, 130 131 TYPE_UNIT, 132 TYPE_MODIFIER, 133 TYPE_END, 134 }; 135 136 #define FLAG_NONE 0 137 #define FLAG_RELATIVE 1 138 #define FLAG_NOT_MODIFIABLE 2 139 #define FLAG_NOW 4 140 #define FLAG_NEXT_LAST_THIS 8 141 #define FLAG_PLUS_MINUS 16 142 #define FLAG_HAS_DASH 32 143 144 enum units { 145 UNIT_NONE, 146 UNIT_YEAR, 147 UNIT_MONTH, 148 UNIT_DAY, 149 UNIT_SECOND, 150 }; 151 152 enum value_type { 153 VALUE_NUMERICAL, 154 VALUE_STRING, 155 VALUE_CHAR, 156 }; 157 158 enum value_modifier { 159 MODIFY_MINUS = -2, 160 MODIFY_LAST = -1, 161 MODIFY_NONE = 0, 162 MODIFY_THIS = MODIFY_NONE, 163 MODIFY_NEXT = 1, 164 MODIFY_PLUS = 2, 165 }; 166 167 struct known_identifier { 168 const char *string; 169 const char *alternate_string; 170 uint8 type; 171 uint8 flags; 172 uint8 unit; 173 int32 value; 174 }; 175 176 static const known_identifier kIdentifiers[] = { 177 {"today", NULL, TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE, 178 UNIT_DAY, 0}, 179 {"tomorrow", NULL, TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE, 180 UNIT_DAY, 1}, 181 {"yesterday", NULL, TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE, 182 UNIT_DAY, -1}, 183 {"now", NULL, TYPE_UNIT, 184 FLAG_RELATIVE | FLAG_NOT_MODIFIABLE | FLAG_NOW, 0}, 185 186 {"this", NULL, TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE, 187 MODIFY_THIS}, 188 {"next", NULL, TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE, 189 MODIFY_NEXT}, 190 {"last", NULL, TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE, 191 MODIFY_LAST}, 192 193 {"years", "year", TYPE_UNIT, FLAG_RELATIVE, UNIT_YEAR, 1}, 194 {"months", "month",TYPE_UNIT, FLAG_RELATIVE, UNIT_MONTH, 1}, 195 {"weeks", "week", TYPE_UNIT, FLAG_RELATIVE, UNIT_DAY, 7}, 196 {"days", "day", TYPE_UNIT, FLAG_RELATIVE, UNIT_DAY, 1}, 197 {"hour", NULL, TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1 * 60 * 60}, 198 {"hours", "hrs", TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1 * 60 * 60}, 199 {"second", "sec", TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1}, 200 {"seconds", "secs", TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1}, 201 {"minute", "min", TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 60}, 202 {"minutes", "mins", TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 60}, 203 204 {"am", NULL, TYPE_MERIDIAN, FLAG_NOT_MODIFIABLE, UNIT_SECOND, 0}, 205 {"pm", NULL, TYPE_MERIDIAN, FLAG_NOT_MODIFIABLE, UNIT_SECOND, 206 12 * 60 * 60}, 207 208 {"sunday", "sun", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 0}, 209 {"monday", "mon", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 1}, 210 {"tuesday", "tue", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 2}, 211 {"wednesday", "wed", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 3}, 212 {"thursday", "thu", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 4}, 213 {"friday", "fri", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 5}, 214 {"saturday", "sat", TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 6}, 215 216 {"january", "jan", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 1}, 217 {"february", "feb", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 2}, 218 {"march", "mar", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 3}, 219 {"april", "apr", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 4}, 220 {"may", "may", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 5}, 221 {"june", "jun", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 6}, 222 {"july", "jul", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 7}, 223 {"august", "aug", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 8}, 224 {"september", "sep", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 9}, 225 {"october", "oct", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 10}, 226 {"november", "nov", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 11}, 227 {"december", "dec", TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 12}, 228 229 {"GMT", NULL, TYPE_TIME_ZONE, FLAG_NONE, UNIT_SECOND, 0}, 230 // TODO: add more time zones 231 232 {NULL} 233 }; 234 235 #define MAX_ELEMENTS 32 236 237 class DateMask { 238 public: 239 DateMask() : fMask(0UL) {} 240 241 void Set(uint8 type) { fMask |= Flag(type); } 242 bool IsSet(uint8 type) { return (fMask & Flag(type)) != 0; } 243 244 bool HasTime(); 245 bool IsComplete(); 246 247 private: 248 inline uint32 Flag(uint8 type) { return 1UL << type; } 249 250 uint32 fMask; 251 }; 252 253 254 struct parsed_element { 255 uint8 base_type; 256 uint8 type; 257 uint8 flags; 258 uint8 unit; 259 uint8 value_type; 260 int8 modifier; 261 bigtime_t value; 262 263 void SetCharType(uint8 fieldType, int8 modify = MODIFY_NONE); 264 265 void Adopt(const known_identifier& identifier); 266 void AdoptUnit(const known_identifier& identifier); 267 bool IsNextLastThis(); 268 }; 269 270 271 void 272 parsed_element::SetCharType(uint8 fieldType, int8 modify) 273 { 274 base_type = type = fieldType; 275 value_type = VALUE_CHAR; 276 modifier = modify; 277 } 278 279 280 void 281 parsed_element::Adopt(const known_identifier& identifier) 282 { 283 base_type = type = identifier.type; 284 flags = identifier.flags; 285 unit = identifier.unit; 286 287 if (identifier.type == TYPE_MODIFIER) 288 modifier = identifier.value; 289 290 value_type = VALUE_STRING; 291 value = identifier.value; 292 } 293 294 295 void 296 parsed_element::AdoptUnit(const known_identifier& identifier) 297 { 298 base_type = type = TYPE_UNIT; 299 flags = identifier.flags; 300 unit = identifier.unit; 301 value *= identifier.value; 302 } 303 304 305 inline bool 306 parsed_element::IsNextLastThis() 307 { 308 return base_type == TYPE_MODIFIER 309 && (modifier == MODIFY_NEXT || modifier == MODIFY_LAST 310 || modifier == MODIFY_THIS); 311 } 312 313 314 // #pragma mark - 315 316 317 bool 318 DateMask::HasTime() 319 { 320 // this will cause 321 return IsSet(TYPE_HOUR); 322 } 323 324 325 /*! This method checks if the date mask is complete in the 326 sense that it doesn't need to have a prefilled "struct tm" 327 when its time value is computed. 328 */ 329 bool 330 DateMask::IsComplete() 331 { 332 // mask must be absolute, at last 333 if ((fMask & Flag(TYPE_UNIT)) != 0) 334 return false; 335 336 // minimal set of flags to have a complete set 337 return !(~fMask & (Flag(TYPE_DAY) | Flag(TYPE_MONTH))); 338 } 339 340 341 // #pragma mark - 342 343 344 static status_t 345 preparseDate(const char* dateString, parsed_element* elements) 346 { 347 int32 index = 0, modify = MODIFY_NONE; 348 char c; 349 350 if (dateString == NULL) 351 return B_ERROR; 352 353 memset(&elements[0], 0, sizeof(parsed_element)); 354 355 for (; (c = dateString[0]) != '\0'; dateString++) { 356 // we don't care about spaces 357 if (isspace(c)) { 358 modify = MODIFY_NONE; 359 continue; 360 } 361 362 // if we're reached our maximum number of elements, bail out 363 if (index >= MAX_ELEMENTS) 364 return B_ERROR; 365 366 if (c == ',') { 367 elements[index].SetCharType(TYPE_COMMA); 368 } else if (c == '.') { 369 elements[index].SetCharType(TYPE_DOT); 370 } else if (c == '/') { 371 // "-" is handled differently (as a modifier) 372 elements[index].SetCharType(TYPE_DASH); 373 } else if (c == ':') { 374 elements[index].SetCharType(TYPE_COLON); 375 } else if (c == '+') { 376 modify = MODIFY_PLUS; 377 378 // this counts for the next element 379 continue; 380 } else if (c == '-') { 381 modify = MODIFY_MINUS; 382 elements[index].flags = FLAG_HAS_DASH; 383 384 // this counts for the next element 385 continue; 386 } else if (isdigit(c)) { 387 // fetch whole number 388 389 elements[index].type = TYPE_UNKNOWN; 390 elements[index].value_type = VALUE_NUMERICAL; 391 elements[index].value = atoll(dateString); 392 elements[index].modifier = modify; 393 394 // skip number 395 while (isdigit(dateString[1])) 396 dateString++; 397 398 // check for "1st", "2nd, "3rd", "4th", ... 399 400 const char* suffixes[] = {"th", "st", "nd", "rd"}; 401 const char* validSuffix = elements[index].value > 3 402 ? "th" : suffixes[elements[index].value]; 403 if (!strncasecmp(dateString + 1, validSuffix, 2) 404 && !isalpha(dateString[3])) { 405 // for now, just ignore the suffix - but we might be able 406 // to deduce some meaning out of it, since it's not really 407 // possible to put it in anywhere 408 dateString += 2; 409 } 410 } else if (isalpha(c)) { 411 // fetch whole string 412 413 const char* string = dateString; 414 while (isalpha(dateString[1])) 415 dateString++; 416 int32 length = dateString + 1 - string; 417 418 // compare with known strings 419 // ToDo: should understand other languages as well... 420 421 const known_identifier* identifier = kIdentifiers; 422 for (; identifier->string; identifier++) { 423 if (!strncasecmp(identifier->string, string, length) 424 && !identifier->string[length]) 425 break; 426 427 if (identifier->alternate_string != NULL 428 && !strncasecmp(identifier->alternate_string, string, length) 429 && !identifier->alternate_string[length]) 430 break; 431 } 432 if (identifier->string == NULL) { 433 // unknown string, we don't have to parse any further 434 return B_ERROR; 435 } 436 437 if (index > 0 && identifier->type == TYPE_UNIT) { 438 // this is just a unit, so it will give the last value a meaning 439 440 if (elements[--index].value_type != VALUE_NUMERICAL 441 && !elements[index].IsNextLastThis()) 442 return B_ERROR; 443 444 elements[index].AdoptUnit(*identifier); 445 } else if (index > 0 && elements[index - 1].IsNextLastThis()) { 446 if (identifier->type == TYPE_MONTH 447 || identifier->type == TYPE_WEEKDAY) { 448 index--; 449 450 switch (elements[index].value) { 451 case -1: 452 elements[index].modifier = MODIFY_LAST; 453 break; 454 case 0: 455 elements[index].modifier = MODIFY_THIS; 456 break; 457 case 1: 458 elements[index].modifier = MODIFY_NEXT; 459 break; 460 } 461 elements[index].Adopt(*identifier); 462 elements[index].type = TYPE_UNIT; 463 } else 464 return B_ERROR; 465 } else { 466 elements[index].Adopt(*identifier); 467 } 468 } 469 470 // see if we can join any preceding modifiers 471 472 if (index > 0 473 && elements[index - 1].type == TYPE_MODIFIER 474 && (elements[index].flags & FLAG_NOT_MODIFIABLE) == 0) { 475 // copy the current one to the last and go on 476 elements[index].modifier = elements[index - 1].modifier; 477 elements[index].value *= elements[index - 1].value; 478 elements[index].flags |= elements[index - 1].flags; 479 elements[index - 1] = elements[index]; 480 } else { 481 // we filled out one parsed_element 482 index++; 483 } 484 485 if (index < MAX_ELEMENTS) 486 memset(&elements[index], 0, sizeof(parsed_element)); 487 } 488 489 // were there any elements? 490 if (index == 0) 491 return B_ERROR; 492 493 elements[index].type = TYPE_END; 494 495 return B_OK; 496 } 497 498 499 static void 500 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags) 501 { 502 // set the relative start depending on unit 503 504 switch (element.unit) { 505 case UNIT_YEAR: 506 tm.tm_mon = 0; // supposed to fall through 507 case UNIT_MONTH: 508 tm.tm_mday = 1; // supposed to fall through 509 case UNIT_DAY: 510 tm.tm_hour = 0; 511 tm.tm_min = 0; 512 tm.tm_sec = 0; 513 break; 514 } 515 516 // adjust value 517 518 if ((element.flags & FLAG_RELATIVE) != 0) { 519 if (element.unit == UNIT_MONTH) 520 tm.tm_mon += element.value; 521 else if (element.unit == UNIT_DAY) 522 tm.tm_mday += element.value; 523 else if (element.unit == UNIT_SECOND) { 524 if (element.modifier == MODIFY_MINUS) 525 tm.tm_sec -= element.value; 526 else 527 tm.tm_sec += element.value; 528 529 *_flags |= PARSEDATE_MINUTE_RELATIVE_TIME; 530 } else if (element.unit == UNIT_YEAR) 531 tm.tm_year += element.value; 532 } else if (element.base_type == TYPE_WEEKDAY) { 533 tm.tm_mday += element.value - tm.tm_wday; 534 535 if (element.modifier == MODIFY_NEXT) 536 tm.tm_mday += 7; 537 else if (element.modifier == MODIFY_LAST) 538 tm.tm_mday -= 7; 539 } else if (element.base_type == TYPE_MONTH) { 540 tm.tm_mon = element.value - 1; 541 542 if (element.modifier == MODIFY_NEXT) 543 tm.tm_year++; 544 else if (element.modifier == MODIFY_LAST) 545 tm.tm_year--; 546 } 547 } 548 549 550 /*! Uses the format assignment (through "format", and "optional") for the 551 parsed elements and calculates the time value with respect to "now". 552 Will also set the day/minute relative flags in "_flags". 553 */ 554 static time_t 555 computeDate(const char* format, bool* optional, parsed_element* elements, 556 time_t now, DateMask dateMask, int* _flags) 557 { 558 TRACE(("matches: %s\n", format)); 559 560 parsed_element* element = elements; 561 uint32 position = 0; 562 struct tm tm; 563 564 if (now == -1) 565 now = time(NULL); 566 567 if (dateMask.IsComplete()) 568 memset(&tm, 0, sizeof(tm)); 569 else { 570 localtime_r(&now, &tm); 571 572 if (dateMask.HasTime()) { 573 tm.tm_min = 0; 574 tm.tm_sec = 0; 575 } 576 577 *_flags = PARSEDATE_RELATIVE_TIME; 578 } 579 580 while (element->type != TYPE_END) { 581 // skip whitespace 582 while (isspace(format[0])) 583 format++; 584 585 if (format[0] == '[' && format[2] == ']') { 586 // does this optional parameter not match our date string? 587 if (!optional[position]) { 588 format += 3; 589 position++; 590 continue; 591 } 592 593 format++; 594 } 595 596 switch (element->value_type) { 597 case VALUE_CHAR: 598 // skip the single character 599 break; 600 601 case VALUE_NUMERICAL: 602 switch (format[0]) { 603 case 'd': 604 tm.tm_mday = element->value; 605 break; 606 case 'm': 607 tm.tm_mon = element->value - 1; 608 break; 609 case 'H': 610 case 'I': 611 tm.tm_hour = element->value; 612 break; 613 case 'M': 614 tm.tm_min = element->value; 615 break; 616 case 'S': 617 tm.tm_sec = element->value; 618 break; 619 case 'y': 620 case 'Y': 621 tm.tm_year = element->value; 622 if (tm.tm_year > 1900) 623 tm.tm_year -= 1900; 624 break; 625 case 'T': 626 computeRelativeUnit(*element, tm, _flags); 627 break; 628 case '-': 629 // there is no TYPE_DASH element for this (just a flag) 630 format++; 631 continue; 632 } 633 break; 634 635 case VALUE_STRING: 636 switch (format[0]) { 637 case 'a': // weekday 638 case 'A': 639 // we'll apply this element later, if still necessary 640 if (!dateMask.IsComplete()) 641 computeRelativeUnit(*element, tm, _flags); 642 break; 643 case 'b': // month 644 case 'B': 645 tm.tm_mon = element->value - 1; 646 break; 647 case 'p': // meridian 648 tm.tm_sec += element->value; 649 break; 650 case 'z': // time zone 651 case 'Z': 652 tm.tm_sec += element->value - timezone; 653 break; 654 case 'T': // time unit 655 if ((element->flags & FLAG_NOW) != 0) { 656 *_flags = PARSEDATE_MINUTE_RELATIVE_TIME 657 | PARSEDATE_RELATIVE_TIME; 658 break; 659 } 660 661 computeRelativeUnit(*element, tm, _flags); 662 break; 663 } 664 break; 665 } 666 667 // format matched at this point, check next element 668 format++; 669 if (format[0] == ']') 670 format++; 671 672 position++; 673 element++; 674 } 675 676 return mktime(&tm); 677 } 678 679 680 time_t 681 parsedate_etc(const char* dateString, time_t now, int* _flags) 682 { 683 // preparse date string so that it can be easily compared to our formats 684 685 parsed_element elements[MAX_ELEMENTS]; 686 687 if (preparseDate(dateString, elements) < B_OK) { 688 *_flags = PARSEDATE_INVALID_DATE; 689 return B_ERROR; 690 } 691 692 #if TRACE_PARSEDATE 693 for (int32 index = 0; elements[index].type != TYPE_END; index++) { 694 parsed_element e = elements[index]; 695 696 printf(" %ld: type = %u, base_type = %u, unit = %u, flags = %u, " 697 "value = %Ld (%s)\n", index, e.type, e.base_type, e.unit, e.flags, 698 e.value, e.value_type == VALUE_NUMERICAL 699 ? "numerical" : (e.value_type == VALUE_STRING ? "string" : "char")); 700 } 701 #endif 702 703 bool optional[MAX_ELEMENTS]; 704 705 for (int32 index = 0; sFormatsTable[index]; index++) { 706 // test if this format matches our date string 707 708 const char* format = sFormatsTable[index]; 709 uint32 position = 0; 710 DateMask dateMask; 711 712 parsed_element* element = elements; 713 while (element->type != TYPE_END) { 714 // skip whitespace 715 while (isspace(format[0])) 716 format++; 717 718 if (format[0] == '[' && format[2] == ']') { 719 optional[position] = true; 720 format++; 721 } else 722 optional[position] = false; 723 724 switch (element->value_type) { 725 case VALUE_CHAR: 726 // check the allowed single characters 727 728 switch (element->type) { 729 case TYPE_DOT: 730 if (format[0] != '.') 731 goto next_format; 732 break; 733 case TYPE_DASH: 734 if (format[0] != '-') 735 goto next_format; 736 break; 737 case TYPE_COMMA: 738 if (format[0] != ',') 739 goto next_format; 740 break; 741 case TYPE_COLON: 742 if (format[0] != ':') 743 goto next_format; 744 break; 745 default: 746 goto next_format; 747 } 748 break; 749 750 case VALUE_NUMERICAL: 751 // make sure that unit types are respected 752 if (element->type == TYPE_UNIT && format[0] != 'T') 753 goto next_format; 754 755 switch (format[0]) { 756 case 'd': 757 if (element->value > 31) 758 goto next_format; 759 760 dateMask.Set(TYPE_DAY); 761 break; 762 case 'm': 763 if (element->value > 12) 764 goto next_format; 765 766 dateMask.Set(TYPE_MONTH); 767 break; 768 case 'H': 769 case 'I': 770 if (element->value > 24) 771 goto next_format; 772 773 dateMask.Set(TYPE_HOUR); 774 break; 775 case 'M': 776 dateMask.Set(TYPE_MINUTE); 777 case 'S': 778 if (element->value > 59) 779 goto next_format; 780 781 break; 782 case 'y': 783 case 'Y': 784 // accept all values 785 break; 786 case 'T': 787 dateMask.Set(TYPE_UNIT); 788 break; 789 case '-': 790 if ((element->flags & FLAG_HAS_DASH) != 0) { 791 element--; // consider this element again 792 break; 793 } 794 // supposed to fall through 795 default: 796 goto next_format; 797 } 798 break; 799 800 case VALUE_STRING: 801 switch (format[0]) { 802 case 'a': // weekday 803 case 'A': 804 if (element->type != TYPE_WEEKDAY) 805 goto next_format; 806 break; 807 case 'b': // month 808 case 'B': 809 if (element->type != TYPE_MONTH) 810 goto next_format; 811 812 dateMask.Set(TYPE_MONTH); 813 break; 814 case 'p': // meridian 815 if (element->type != TYPE_MERIDIAN) 816 goto next_format; 817 break; 818 case 'z': // time zone 819 case 'Z': 820 if (element->type != TYPE_TIME_ZONE) 821 goto next_format; 822 break; 823 case 'T': // time unit 824 if (element->type != TYPE_UNIT) 825 goto next_format; 826 827 dateMask.Set(TYPE_UNIT); 828 break; 829 default: 830 goto next_format; 831 } 832 break; 833 } 834 835 // format matched at this point, check next element 836 if (optional[position]) 837 format++; 838 format++; 839 position++; 840 element++; 841 continue; 842 843 next_format: 844 // format didn't match element - let's see if the current 845 // one is only optional (in which case we can continue) 846 if (!optional[position]) 847 goto skip_format; 848 849 optional[position] = false; 850 format += 2; 851 position++; 852 // skip the closing ']' 853 } 854 855 // check if the format is already empty (since we reached our last 856 // element) 857 while (format[0]) { 858 if (format[0] == '[') 859 format += 3; 860 else if (isspace(format[0])) 861 format++; 862 else 863 break; 864 } 865 if (format[0]) 866 goto skip_format; 867 868 // made it here? then we seem to have found our guy 869 870 return computeDate(sFormatsTable[index], optional, elements, now, 871 dateMask, _flags); 872 873 skip_format: 874 // check if the next format has the same beginning as the skipped one, 875 // and if so, skip that one, too. 876 877 int32 length = format + 1 - sFormatsTable[index]; 878 879 while (sFormatsTable[index + 1] 880 && !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length)) 881 index++; 882 } 883 884 // didn't find any matching formats 885 return B_ERROR; 886 } 887 888 889 time_t 890 parsedate(const char* dateString, time_t now) 891 { 892 int flags = 0; 893 894 return parsedate_etc(dateString, now, &flags); 895 } 896 897 898 void 899 set_dateformats(const char** table) 900 { 901 sFormatsTable = table ? table : kFormatsTable; 902 } 903 904 905 const char** 906 get_dateformats(void) 907 { 908 return const_cast<const char**>(sFormatsTable); 909 } 910 911