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 1 18 #if TRACE_PARSEDATE 19 # define TRACE(x) debug_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 memset(&elements[index], 0, sizeof(parsed_element)); 486 } 487 488 // were there any elements? 489 if (index == 0) 490 return B_ERROR; 491 492 elements[index].type = TYPE_END; 493 494 return B_OK; 495 } 496 497 498 static void 499 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags) 500 { 501 // set the relative start depending on unit 502 503 switch (element.unit) { 504 case UNIT_YEAR: 505 tm.tm_mon = 0; // supposed to fall through 506 case UNIT_MONTH: 507 tm.tm_mday = 1; // supposed to fall through 508 case UNIT_DAY: 509 tm.tm_hour = 0; 510 tm.tm_min = 0; 511 tm.tm_sec = 0; 512 break; 513 } 514 515 // adjust value 516 517 if ((element.flags & FLAG_RELATIVE) != 0) { 518 if (element.unit == UNIT_MONTH) 519 tm.tm_mon += element.value; 520 else if (element.unit == UNIT_DAY) 521 tm.tm_mday += element.value; 522 else if (element.unit == UNIT_SECOND) { 523 if (element.modifier == MODIFY_MINUS) 524 tm.tm_sec -= element.value; 525 else 526 tm.tm_sec += element.value; 527 528 *_flags |= PARSEDATE_MINUTE_RELATIVE_TIME; 529 } else if (element.unit == UNIT_YEAR) 530 tm.tm_year += element.value; 531 } else if (element.base_type == TYPE_WEEKDAY) { 532 tm.tm_mday += element.value - tm.tm_wday; 533 534 if (element.modifier == MODIFY_NEXT) 535 tm.tm_mday += 7; 536 else if (element.modifier == MODIFY_LAST) 537 tm.tm_mday -= 7; 538 } else if (element.base_type == TYPE_MONTH) { 539 tm.tm_mon = element.value - 1; 540 541 if (element.modifier == MODIFY_NEXT) 542 tm.tm_year++; 543 else if (element.modifier == MODIFY_LAST) 544 tm.tm_year--; 545 } 546 } 547 548 549 /*! Uses the format assignment (through "format", and "optional") for the 550 parsed elements and calculates the time value with respect to "now". 551 Will also set the day/minute relative flags in "_flags". 552 */ 553 static time_t 554 computeDate(const char* format, bool* optional, parsed_element* elements, 555 time_t now, DateMask dateMask, int* _flags) 556 { 557 TRACE(("matches: %s\n", format)); 558 559 parsed_element* element = elements; 560 uint32 position = 0; 561 struct tm tm; 562 563 if (now == -1) 564 now = time(NULL); 565 566 if (dateMask.IsComplete()) 567 memset(&tm, 0, sizeof(tm)); 568 else { 569 localtime_r(&now, &tm); 570 571 if (dateMask.HasTime()) { 572 tm.tm_min = 0; 573 tm.tm_sec = 0; 574 } 575 576 *_flags = PARSEDATE_RELATIVE_TIME; 577 } 578 579 while (element->type != TYPE_END) { 580 // skip whitespace 581 while (isspace(format[0])) 582 format++; 583 584 if (format[0] == '[' && format[2] == ']') { 585 // does this optional parameter not match our date string? 586 if (!optional[position]) { 587 format += 3; 588 position++; 589 continue; 590 } 591 592 format++; 593 } 594 595 switch (element->value_type) { 596 case VALUE_CHAR: 597 // skip the single character 598 break; 599 600 case VALUE_NUMERICAL: 601 switch (format[0]) { 602 case 'd': 603 tm.tm_mday = element->value; 604 break; 605 case 'm': 606 tm.tm_mon = element->value - 1; 607 break; 608 case 'H': 609 case 'I': 610 tm.tm_hour = element->value; 611 break; 612 case 'M': 613 tm.tm_min = element->value; 614 break; 615 case 'S': 616 tm.tm_sec = element->value; 617 break; 618 case 'y': 619 case 'Y': 620 tm.tm_year = element->value; 621 if (tm.tm_year > 1900) 622 tm.tm_year -= 1900; 623 break; 624 case 'T': 625 computeRelativeUnit(*element, tm, _flags); 626 break; 627 case '-': 628 // there is no TYPE_DASH element for this (just a flag) 629 format++; 630 continue; 631 } 632 break; 633 634 case VALUE_STRING: 635 switch (format[0]) { 636 case 'a': // weekday 637 case 'A': 638 // we'll apply this element later, if still necessary 639 if (!dateMask.IsComplete()) 640 computeRelativeUnit(*element, tm, _flags); 641 break; 642 case 'b': // month 643 case 'B': 644 tm.tm_mon = element->value - 1; 645 break; 646 case 'p': // meridian 647 tm.tm_sec += element->value; 648 break; 649 case 'z': // time zone 650 case 'Z': 651 tm.tm_sec += element->value - timezone; 652 break; 653 case 'T': // time unit 654 if ((element->flags & FLAG_NOW) != 0) { 655 *_flags = PARSEDATE_MINUTE_RELATIVE_TIME 656 | PARSEDATE_RELATIVE_TIME; 657 break; 658 } 659 660 computeRelativeUnit(*element, tm, _flags); 661 break; 662 } 663 break; 664 } 665 666 // format matched at this point, check next element 667 format++; 668 if (format[0] == ']') 669 format++; 670 671 position++; 672 element++; 673 } 674 675 return mktime(&tm); 676 } 677 678 679 time_t 680 parsedate_etc(const char* dateString, time_t now, int* _flags) 681 { 682 // preparse date string so that it can be easily compared to our formats 683 684 parsed_element elements[MAX_ELEMENTS]; 685 686 if (preparseDate(dateString, elements) < B_OK) { 687 *_flags = PARSEDATE_INVALID_DATE; 688 return B_ERROR; 689 } 690 691 #if TRACE_PARSEDATE 692 for (int32 index = 0; elements[index].type != TYPE_END; index++) { 693 parsed_element e = elements[index]; 694 695 printf(" %ld: type = %u, base_type = %u, unit = %u, flags = %u, " 696 "value = %Ld (%s)\n", index, e.type, e.base_type, e.unit, e.flags, 697 e.value, e.value_type == VALUE_NUMERICAL 698 ? "numerical" : (e.value_type == VALUE_STRING ? "string" : "char")); 699 } 700 #endif 701 702 bool optional[MAX_ELEMENTS]; 703 704 for (int32 index = 0; sFormatsTable[index]; index++) { 705 // test if this format matches our date string 706 707 const char* format = sFormatsTable[index]; 708 uint32 position = 0; 709 DateMask dateMask; 710 711 parsed_element* element = elements; 712 while (element->type != TYPE_END) { 713 // skip whitespace 714 while (isspace(format[0])) 715 format++; 716 717 if (format[0] == '[' && format[2] == ']') { 718 optional[position] = true; 719 format++; 720 } else 721 optional[position] = false; 722 723 switch (element->value_type) { 724 case VALUE_CHAR: 725 // check the allowed single characters 726 727 switch (element->type) { 728 case TYPE_DOT: 729 if (format[0] != '.') 730 goto next_format; 731 break; 732 case TYPE_DASH: 733 if (format[0] != '-') 734 goto next_format; 735 break; 736 case TYPE_COMMA: 737 if (format[0] != ',') 738 goto next_format; 739 break; 740 case TYPE_COLON: 741 if (format[0] != ':') 742 goto next_format; 743 break; 744 default: 745 goto next_format; 746 } 747 break; 748 749 case VALUE_NUMERICAL: 750 // make sure that unit types are respected 751 if (element->type == TYPE_UNIT && format[0] != 'T') 752 goto next_format; 753 754 switch (format[0]) { 755 case 'd': 756 if (element->value > 31) 757 goto next_format; 758 759 dateMask.Set(TYPE_DAY); 760 break; 761 case 'm': 762 if (element->value > 12) 763 goto next_format; 764 765 dateMask.Set(TYPE_MONTH); 766 break; 767 case 'H': 768 case 'I': 769 if (element->value > 24) 770 goto next_format; 771 772 dateMask.Set(TYPE_HOUR); 773 break; 774 case 'M': 775 dateMask.Set(TYPE_MINUTE); 776 case 'S': 777 if (element->value > 59) 778 goto next_format; 779 780 break; 781 case 'y': 782 case 'Y': 783 // accept all values 784 break; 785 case 'T': 786 dateMask.Set(TYPE_UNIT); 787 break; 788 case '-': 789 if ((element->flags & FLAG_HAS_DASH) != 0) { 790 element--; // consider this element again 791 break; 792 } 793 // supposed to fall through 794 default: 795 goto next_format; 796 } 797 break; 798 799 case VALUE_STRING: 800 switch (format[0]) { 801 case 'a': // weekday 802 case 'A': 803 if (element->type != TYPE_WEEKDAY) 804 goto next_format; 805 break; 806 case 'b': // month 807 case 'B': 808 if (element->type != TYPE_MONTH) 809 goto next_format; 810 811 dateMask.Set(TYPE_MONTH); 812 break; 813 case 'p': // meridian 814 if (element->type != TYPE_MERIDIAN) 815 goto next_format; 816 break; 817 case 'z': // time zone 818 case 'Z': 819 if (element->type != TYPE_TIME_ZONE) 820 goto next_format; 821 break; 822 case 'T': // time unit 823 if (element->type != TYPE_UNIT) 824 goto next_format; 825 826 dateMask.Set(TYPE_UNIT); 827 break; 828 default: 829 goto next_format; 830 } 831 break; 832 } 833 834 // format matched at this point, check next element 835 if (optional[position]) 836 format++; 837 format++; 838 position++; 839 element++; 840 continue; 841 842 next_format: 843 // format didn't match element - let's see if the current 844 // one is only optional (in which case we can continue) 845 if (!optional[position]) 846 goto skip_format; 847 848 optional[position] = false; 849 format += 2; 850 position++; 851 // skip the closing ']' 852 } 853 854 // check if the format is already empty (since we reached our last 855 // element) 856 while (format[0]) { 857 if (format[0] == '[') 858 format += 3; 859 else if (isspace(format[0])) 860 format++; 861 else 862 break; 863 } 864 if (format[0]) 865 goto skip_format; 866 867 // made it here? then we seem to have found our guy 868 869 return computeDate(sFormatsTable[index], optional, elements, now, 870 dateMask, _flags); 871 872 skip_format: 873 // check if the next format has the same beginning as the skipped one, 874 // and if so, skip that one, too. 875 876 int32 length = format + 1 - sFormatsTable[index]; 877 878 while (sFormatsTable[index + 1] 879 && !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length)) 880 index++; 881 } 882 883 // didn't find any matching formats 884 return B_ERROR; 885 } 886 887 888 time_t 889 parsedate(const char* dateString, time_t now) 890 { 891 int flags = 0; 892 893 return parsedate_etc(dateString, now, &flags); 894 } 895 896 897 void 898 set_dateformats(const char** table) 899 { 900 sFormatsTable = table ? table : kFormatsTable; 901 } 902 903 904 const char** 905 get_dateformats(void) 906 { 907 return const_cast<const char**>(sFormatsTable); 908 } 909 910