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