1 /******************************************************************************* 2 3 This module is based on the ISO 8601:2004 standard, and has functions 4 for parsing (almost) every date/time format specified therein. (The 5 ones not supported are intervals, durations, and recurring intervals.) 6 7 Refer to the standard for a full description of the formats supported. 8 9 The functions (parseTime, parseDate, and parseDateAndTime) are 10 overloaded into two different versions of each: one updates a given 11 Time, and the other updates a given ExtendedDate struct. The purpose of 12 this struct is to support more detailed information which the Time data 13 type does not (and, given its simple integer nature, cannot) support. 14 15 Times with specified time zones are simply converted into UTC: this may 16 lead to the date changing when only a time was parsed: e.g. "01:00+03" 17 is the same as "22:00", except that when the former is parsed, one is 18 subtracted from the day. 19 20 Copyright: 21 Copyright (c) 2007-2008 Matti Niemenmaa. 22 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 23 All rights reserved. 24 25 License: 26 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 27 See LICENSE_TANGO.txt for details. 28 29 Version: 30 Aug 2007: Initial release 31 Feb 2008: Retooled 32 33 Authors: Matti Niemenmaa 34 35 *******************************************************************************/ 36 37 module ocean.time.ISO8601; 38 39 import ocean.meta.types.Qualifiers; 40 41 public import ocean.time.Time; 42 public import ocean.time.chrono.Gregorian; 43 44 import ocean.core.ExceptionDefinitions : IllegalArgumentException; 45 import ocean.math.Math : min; 46 47 private alias Time DT; 48 private alias ExtendedDate FullDate; 49 50 version (unittest) import ocean.core.Test; 51 52 /** An extended date type, wrapping a Time together with some additional 53 * information. */ 54 public struct ExtendedDate { 55 /** The Time value, containing the information it can. */ 56 DT val; 57 58 private int year_; 59 60 /** Returns the year part of the date: a value in the range 61 * [-1_000_000_000,-1] ∪ [1,999_999_999], where -1 is the year 1 BCE. 62 * 63 * Do not use val.year directly unless you are absolutely sure that it is in 64 * the range a Time can hold (-10000 to 9999). 65 */ 66 int year() 67 out(val) { 68 assert ( (val >= -1_000_000_000 && val <= -1) 69 || (val >= 1 && val <= 999_999_999)); 70 } do { 71 if (year_) 72 return year_; 73 74 auto era = Gregorian.generic.getEra(val); 75 if (era == Gregorian.AD_ERA) 76 return Gregorian.generic.getYear(val); 77 else 78 return -Gregorian.generic.getYear(val); 79 } 80 81 // y may be zero: if so, it refers to the year 1 BCE 82 private void year(int y) { 83 if (DTyear(y)) { 84 year_ = 0; 85 // getYear returns uint: be careful with promotion to unsigned 86 int toAdd = y - Gregorian.generic.getYear(val); 87 val = Gregorian.generic.addYears(val, toAdd); 88 } else 89 year_ = y < 0 ? y-1 : y; 90 } 91 92 private byte mask; // leap second and endofday 93 94 /** Returns the seconds part of the date: may be 60 if a leap second 95 * occurred. In such a case, val's seconds part is 59. 96 */ 97 uint seconds() { return val.time.seconds + ((mask >>> 0) & 1); } 98 alias seconds secs, second, sec; 99 100 /** Whether the ISO 8601 representation of this hour is 24 or 00: whether 101 * this instant of midnight is to be considered the end of the previous day 102 * or the start of the next. 103 * 104 * If the time of val is not exactly 00:00:00.000, this value is undefined. 105 */ 106 bool endOfDay() { return 1 == ((mask >>> 1) & 1); } 107 108 private void setLeap () { mask |= 1 << 0; } 109 private void setEndOfDay() { mask |= 1 << 1; } 110 111 debug (Tango_ISO8601) private char[] toStr() { 112 return Stdout.layout.convert( 113 "{:d} and {:d}-{:d2}-{:d2} :: {:d2}:{:d2}:{:d2}.{:d3} and {:d2}, {}", 114 year_, years(this), months(this), days(this), 115 hours(this), mins(this), .secs(this), ms(this), 116 this.seconds, this.endOfDay); 117 } 118 } 119 120 /** Parses a date in a format specified in ISO 8601:2004. 121 * 122 * Returns the number of characters used to compose a valid date: 0 if no date 123 * can be composed. 124 * 125 * Fields in dt will either be correct (e.g. months will be >= 1 and <= 12) or 126 * the default, which is 1 for year, month, and day, and 0 for all other 127 * fields. Unless one is absolutely sure that 0001-01-01 can never be 128 * encountered, one should check the return value to be sure that the parsing 129 * succeeded as expected. 130 * 131 * A third parameter is available for the ExtendedDate version: this allows for 132 * parsing expanded year representations. The parameter is the number of extra 133 * year digits beyond four, and defaults to zero. It must be within the range 134 * [0,5]: this allows for a maximum year of 999 999 999, which should be enough 135 * for now. 136 * 137 * When using expanded year representations, be careful to use 138 * ExtendedDate.year instead of the Time's year value. 139 * 140 * Examples: 141 * --- 142 * Time t; 143 * ExtendedDate ed; 144 * 145 * parseDate("19", t); // January 1st, 1900 146 * parseDate("1970", t); // January 1st, 1970 147 * parseDate("1970-02", t); // February 1st, 1970 148 * parseDate("19700203", t); // February 3rd, 1970 149 * parseDate("+19700203", ed, 2); // March 1st, 197002 150 * parseDate("-197002-04-01", ed, 2); // April 1st, -197003 (197003 BCE) 151 * parseDate("00000101", t); // January 1st, -1 (1 BCE) 152 * parseDate("1700-W14-2", t); // April 6th, 1700 153 * parseDate("2008W01", t); // December 31st, 2007 154 * parseDate("1987-221", t); // August 9th, 1987 155 * parseDate("1234abcd", t); // January 1st, 1234; return value is 4 156 * parseDate("12abcdef", t); // January 1st, 1200; return value is 2 157 * parseDate("abcdefgh", t); // January 1st, 0001; return value is 0 158 * --- 159 */ 160 public size_t parseDate(T)(T[] src, ref DT dt) { 161 auto fd = FullDate(dt); 162 163 auto ret = parseDate(src, fd); 164 dt = fd.val; 165 return ret; 166 } 167 /** ditto */ 168 public size_t parseDate(T)(T[] src, ref FullDate fd, ubyte expanded = 0) { 169 ubyte dummy = void; 170 T* p = src.ptr; 171 return doIso8601Date(p, src, fd, expanded, dummy); 172 } 173 174 private size_t doIso8601Date(T)( 175 176 ref T* p, T[] src, 177 ref FullDate fd, 178 ubyte expanded, 179 out ubyte separators 180 181 ) { 182 if (expanded > 5) 183 throw new IllegalArgumentException( 184 "ISO8601 :: year expanded by more than 5 digits does not fit in int"); 185 186 size_t eaten() { return p - src.ptr; } 187 size_t remaining() { return src.length - eaten(); } 188 bool done(const(T)[] s) { return .done(eaten(), src.length, p, s); } 189 190 if (!parseYear(p, src.length, expanded, fd)) 191 return 0; 192 193 auto onlyYear = eaten(); 194 195 // /([+-]Y{expanded})?(YYYY|YY)/ 196 if (done("-0123W")) 197 return onlyYear; 198 199 if (accept(p, '-')) { 200 separators = YES; 201 202 if (remaining() == 0) 203 return eaten() - 1; 204 } 205 206 if (accept(p, 'W')) { 207 T* p2 = p; 208 209 int i = parseInt(p, min(cast(size_t)3, remaining())); 210 211 if (i) 212 { 213 if (p - p2 == 2) { 214 215 // (year)-Www 216 if (done("-")) { 217 if (getMonthAndDayFromWeek(fd, i)) 218 return eaten(); 219 220 // (year)-Www-D 221 } else if (demand(p, '-')) { 222 if (remaining() == 0) 223 return eaten() - 1; 224 225 if (separators == NO) { 226 // (year)Www after all 227 if (getMonthAndDayFromWeek(fd, i)) 228 return eaten() - 1; 229 230 } else if (getMonthAndDayFromWeek(fd, i, *p++ - '0')) 231 return eaten(); 232 } 233 234 } else if (p - p2 == 3) { 235 // (year)WwwD, i == wwD 236 237 if (separators == YES) { 238 // (year)-Www after all 239 if (getMonthAndDayFromWeek(fd, i / 10)) 240 return eaten() - 1; 241 242 } else if (getMonthAndDayFromWeek(fd, i / 10, i % 10)) 243 return eaten(); 244 } 245 } 246 return onlyYear; 247 } 248 249 // next up, MM or MM[-]DD or DDD 250 251 T* p2 = p; 252 253 int i = parseInt(p, remaining()); 254 if (!i) 255 return onlyYear; 256 257 switch (p - p2) { 258 case 2: 259 // MM or MM-DD 260 261 if (i >= 1 && i <= 12) 262 addMonths(fd, i); 263 else 264 return onlyYear; 265 266 auto onlyMonth = eaten(); 267 268 // (year)-MM 269 if (done("-") || !demand(p, '-') || separators == NO) 270 return onlyMonth; 271 272 int day = parseInt(p, min(cast(size_t)2, remaining())); 273 274 // (year)-MM-DD 275 if (day && day <= daysPerMonth(months(fd), fd.year)) 276 addDays(fd, day); 277 else 278 return onlyMonth; 279 280 break; 281 282 case 4: 283 // e.g. 20010203, i = 203 now 284 285 int month = i / 100; 286 int day = i % 100; 287 288 if (separators == YES) { 289 // Don't accept the day: behave as though we only got the month 290 p -= 2; 291 i = month; 292 goto case 2; 293 } 294 295 // (year)MMDD 296 if ( 297 month >= 1 && month <= 12 && 298 day >= 1 && day <= daysPerMonth(month, fd.year) 299 ) { 300 addMonths(fd, month); 301 addDays (fd, day); 302 } else 303 return onlyYear; 304 305 break; 306 307 case 3: 308 // (year)-DDD 309 // i is the ordinal of the day within the year 310 311 if (i > 365 + isLeapYear(fd.year)) 312 return onlyYear; 313 314 addDays(fd, i); 315 316 break; 317 318 default: break; 319 } 320 return eaten(); 321 } 322 323 /** Parses a time of day in a format specified in ISO 8601:2004. 324 * 325 * Returns the number of characters used to compose a valid time: 0 if no time 326 * can be composed. 327 * 328 * Fields in dt will either be correct or the default, which is 0 for all 329 * time-related fields. fields. Unless one is absolutely sure that midnight 330 * can never be encountered, one should check the return value to be sure that 331 * the parsing succeeded as expected. 332 * 333 * Extra fields in ExtendedDate: 334 * 335 * Seconds may be 60 if the hours and minutes are 23 and 59, as leap seconds 336 * are occasionally added to UTC time. A Time's seconds will be 59 in this 337 * case. 338 * 339 * Hours may be 0 or 24: the latter marks the end of a day and the former the 340 * beginning, although they both refer to the same instant in time. A Time 341 * will be precisely 00:00 in either case. 342 * 343 * Examples: 344 * --- 345 * Time t; 346 * ExtendedDate ed; 347 * 348 * // ",000" omitted for clarity 349 * parseTime("20", t); // 20:00:00 350 * parseTime("2004", t); // 20:04:00 351 * parseTime("20:04:06", t); // 20:04:06 352 * parseTime("16:49:30,001", t); // 16:49:30,001 353 * parseTime("16:49:30,1", t); // 16:49:30,100 354 * parseTime("16:49,4", t); // 16:49:24 355 * parseTime("23:59:60", ed); // 23:59:60 356 * parseTime("24:00:01", t); // 00:00:00; return value is 5 357 * parseTime("24:00:01", ed); // 00:00:00; return value is 5; endOfDay 358 * parseTime("30", t); // 00:00:00; return value is 0 359 * parseTime("21:32:43-12:34", t); // 10:06:43; day increased by one 360 * --- 361 */ 362 public size_t parseTime(T)(T[] src, ref DT dt) { 363 auto fd = FullDate(dt); 364 365 auto ret = parseTime(src, fd); 366 dt = fd.val; 367 return ret; 368 } 369 /** ditto */ 370 public size_t parseTime(T)(T[] src, ref FullDate fd) { 371 bool dummy = void; 372 T* p = src.ptr; 373 return doIso8601Time(p, src, fd, WHATEVER, dummy); 374 } 375 376 // separators 377 private enum : ubyte { NO = 0, YES = 1, WHATEVER } 378 379 // bothValid is used only to get parseDateAndTime() to catch errors correctly 380 private size_t doIso8601Time(T)( 381 382 ref T* p, T[] src, 383 ref FullDate fd, 384 ubyte separators, 385 out bool bothValid 386 387 ) { 388 size_t eaten() { return p - src.ptr; } 389 size_t remaining() { return src.length - eaten(); } 390 bool done(const(T)[] s) { return .done(eaten(), src.length, p, s); } 391 bool checkColon() { return .checkColon(p, separators); } 392 393 byte getTimeZone() { return .getTimeZone(p, remaining(), fd, separators, &done); } 394 395 if (separators == WHATEVER) 396 accept(p, 'T'); 397 398 int hour = void; 399 if (parseInt(p, min(cast(size_t)2, remaining()), hour) != 2 || hour > 24) 400 return 0; 401 402 if (hour == 24) 403 fd.setEndOfDay(); 404 405 // Add the hours even if endOfDay: the day should be the next day, not the 406 // previous 407 addHours(fd, hour); 408 409 auto onlyHour = eaten(); 410 411 // hh 412 if (done("+,-.012345:")) 413 return onlyHour; 414 415 switch (getDecimal(p, remaining(), fd, HOUR)) { 416 case NOTFOUND: break; 417 case FOUND: 418 auto onlyDecimal = eaten(); 419 if (getTimeZone() == BAD) 420 return onlyDecimal; 421 422 // /hh,h+/ 423 return eaten(); 424 425 case BAD: return onlyHour; 426 default: assert (false); 427 } 428 429 switch (getTimeZone()) { 430 case NOTFOUND: break; 431 case FOUND: return eaten(); 432 case BAD: return onlyHour; 433 default: assert (false); 434 } 435 436 if (!checkColon()) 437 return onlyHour; 438 439 int mins = void; 440 if ( 441 parseInt(p, min(cast(size_t)2, remaining()), mins) != 2 || 442 mins > 59 || 443 // end of day is only for 24:00:00 444 (fd.endOfDay && mins != 0) 445 ) 446 return onlyHour; 447 448 addMins(fd, mins); 449 450 auto onlyMinute = eaten(); 451 452 // hh:mm 453 if (done("+,-.0123456:")) { 454 bothValid = true; 455 return onlyMinute; 456 } 457 458 switch (getDecimal(p, remaining(), fd, MINUTE)) { 459 case NOTFOUND: break; 460 case FOUND: 461 auto onlyDecimal = eaten(); 462 if (getTimeZone() == BAD) 463 return onlyDecimal; 464 465 // /hh:mm,m+/ 466 bothValid = true; 467 return eaten(); 468 469 case BAD: return onlyMinute; 470 default: assert (false); 471 } 472 473 switch (getTimeZone()) { 474 case NOTFOUND: break; 475 case FOUND: bothValid = true; return eaten(); 476 case BAD: return onlyMinute; 477 default: assert (false); 478 } 479 480 if (!checkColon()) 481 return onlyMinute; 482 483 int sec = void; 484 if ( 485 parseInt(p, min(cast(size_t)2, remaining()), sec) != 2 || 486 sec > 60 || 487 (fd.endOfDay && sec != 0) 488 ) 489 return onlyMinute; 490 491 if (sec == 60) { 492 if (hours(fd) != 23 && .mins(fd) != 59) 493 return onlyMinute; 494 495 fd.setLeap(); 496 --sec; 497 } 498 addSecs(fd, sec); 499 500 auto onlySecond = eaten(); 501 502 // hh:mm:ss 503 if (done("+,-.Z")) { 504 bothValid = true; 505 return onlySecond; 506 } 507 508 switch (getDecimal(p, remaining(), fd, SECOND)) { 509 case NOTFOUND: break; 510 case FOUND: 511 auto onlyDecimal = eaten(); 512 if (getTimeZone() == BAD) 513 return onlyDecimal; 514 515 // /hh:mm:ss,s+/ 516 bothValid = true; 517 return eaten(); 518 519 case BAD: return onlySecond; 520 default: assert (false); 521 } 522 523 if (getTimeZone() == BAD) 524 return onlySecond; 525 else { 526 bothValid = true; 527 return eaten(); // hh:mm:ss with timezone 528 } 529 } 530 531 /** Parses a combined date and time in a format specified in ISO 8601:2004. 532 * 533 * Returns the number of characters used to compose a valid date and time. 534 * Zero is returned if a complete date and time cannot be extracted. In that 535 * case, the value of the resulting Time or ExtendedDate is undefined. 536 * 537 * This function is stricter than just calling parseDate followed by 538 * parseTime: there are no allowances for expanded years or reduced dates 539 * (two-digit years), and separator usage must be consistent. 540 * 541 * Although the standard allows for omitting the T between the date and the 542 * time, this function requires it. 543 * 544 * Examples: 545 * --- 546 * Time t; 547 * 548 * // January 1st, 2008 00:01:00 549 * parseDateAndTime("2007-12-31T23:01-01", t); 550 * 551 * // April 12th, 1985 23:50:30,042 552 * parseDateAndTime("1985W155T235030,042", t); 553 * 554 * // Invalid time: returns zero 555 * parseDateAndTime("1902-03-04T10:1a", t); 556 * 557 * // Separating T omitted: returns zero 558 * parseDateAndTime("1985-04-1210:15:30+04:00", t); 559 * 560 * // Inconsistent separators: all return zero 561 * parseDateAndTime("200512-01T10:02", t); 562 * parseDateAndTime("1985-04-12T10:15:30+0400", t); 563 * parseDateAndTime("1902-03-04T050607", t); 564 * --- 565 */ 566 public size_t parseDateAndTime(T)(T[] src, ref DT dt) { 567 FullDate fd; 568 auto ret = parseDateAndTime(src, fd); 569 dt = fd.val; 570 return ret; 571 } 572 /** ditto */ 573 public size_t parseDateAndTime(T)(T[] src, ref FullDate fd) { 574 T* p = src.ptr; 575 ubyte sep; 576 bool bothValid = false; 577 578 if ( 579 doIso8601Date(p, src, fd, cast(ubyte)0, sep) && 580 581 // by mutual agreement this T may be omitted 582 // but this is just a convenience method for date+time anyway 583 src.length - (p - src.ptr) >= 1 && 584 demand(p, 'T') && 585 586 doIso8601Time(p, src, fd, sep, bothValid) && 587 bothValid 588 ) 589 return p - src.ptr; 590 else 591 return 0; 592 } 593 594 /+ +++++++++++++++++++++++++++++++++++++++ +\ 595 596 Privates used by date 597 598 \+ +++++++++++++++++++++++++++++++++++++++ +/ 599 600 private: 601 602 // /([+-]Y{expanded})?(YYYY|YY)/ 603 bool parseYear(T)(ref T* p, size_t len, ubyte expanded, ref FullDate fd) { 604 605 int year = void; 606 607 bool doParse() { 608 T* p2 = p; 609 610 if (!parseInt(p, min(cast(size_t)(expanded + 4), len), year)) 611 return false; 612 613 // it's Y{expanded}YY, Y{expanded}YYYY, or unacceptable 614 615 if (p - p2 - expanded == 2) 616 year *= 100; 617 else if (p - p2 - expanded != 4) 618 return false; 619 620 return true; 621 } 622 623 if (accept(p, '-')) { 624 if (!doParse() || year < 0) 625 return false; 626 year = -year; 627 } else { 628 accept(p, '+'); 629 if (!doParse() || year < 0) 630 return false; 631 } 632 633 fd.year = year; 634 635 return true; 636 } 637 638 // find the month and day given a calendar week and the day of the week 639 // uses fd.year for leap year calculations 640 // returns false if week and fd.year are incompatible 641 bool getMonthAndDayFromWeek(ref FullDate fd, int week, int day = 1) { 642 if (week < 1 || week > 53 || day < 1 || day > 7) 643 return false; 644 645 int year = fd.year; 646 647 // only years starting with Thursday and leap years starting with Wednesday 648 // have 53 weeks 649 if (week == 53) { 650 int startingDay = dayOfWeek(year, 1, 1); 651 652 if (!(startingDay == 4 || (isLeapYear(year) && startingDay == 3))) 653 return false; 654 } 655 656 // XXX 657 // days since year-01-04, plus 4 (?)... 658 /* This is a bit scary, actually: I have ***no idea why this works***. I 659 * came up with this completely by accident. It seems to work though - 660 * unless it fails in some (very) obscure case which isn't represented in 661 * the unit tests. 662 */ 663 addDays(fd, 7*(week - 1) + day - dayOfWeek(year, 1, 4) + 4); 664 665 return true; 666 } 667 668 bool isLeapYear(int year) { 669 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); 670 } 671 672 int dayOfWeek(int year, int month, int day) 673 in { 674 assert (month >= 1 && month <= 12); 675 assert (day >= 1 && day <= 31); 676 } out(result) { 677 assert (result >= 1 && result <= 7); 678 } do { 679 uint era = erafy(year); 680 681 int result = 682 Gregorian.generic.getDayOfWeek( 683 Gregorian.generic.toTime(year, month, day, 0, 0, 0, 0, era)); 684 685 if (result == Gregorian.DayOfWeek.Sunday) 686 return 7; 687 else 688 return result; 689 } 690 691 /+ +++++++++++++++++++++++++++++++++++++++ +\ 692 693 Privates used by time 694 695 \+ +++++++++++++++++++++++++++++++++++++++ +/ 696 697 enum : ubyte { HOUR, MINUTE, SECOND } 698 enum : byte { BAD, FOUND, NOTFOUND } 699 700 bool checkColon(T)(ref T* p, ref ubyte separators) { 701 ubyte foundSep = accept(p, ':') ? YES : NO; 702 if (foundSep != separators) { 703 if (separators == WHATEVER) 704 separators = foundSep; 705 else 706 return false; 707 } 708 return true; 709 } 710 711 byte getDecimal(T)(ref T* p, size_t len, ref FullDate fd, ubyte which) { 712 if (!(accept(p, ',') || accept(p, '.'))) 713 return NOTFOUND; 714 715 T* p2 = p; 716 717 int i = void; 718 auto iLen = parseInt(p, len-1, i); 719 720 if ( 721 iLen == 0 || 722 723 // if i is 0, must have at least 3 digits 724 // ... or at least that's what I think the standard means 725 // when it says "[i]f the magnitude of the number is less 726 // than unity, the decimal sign shall be preceded by two 727 // zeros"... 728 // surely that should read "followed" and not "preceded" 729 730 (i == 0 && iLen < 3) 731 ) 732 return BAD; 733 734 // 10 to the power of (iLen - 1) 735 int pow = 1; 736 while (--iLen) 737 pow *= 10; 738 739 switch (which) { 740 case HOUR: 741 addMins(fd, 6 * i / pow); 742 addSecs(fd, 6 * i % pow); 743 break; 744 case MINUTE: 745 addSecs(fd, 6 * i / pow); 746 addMs (fd, 6000 * i / pow % 1000); 747 break; 748 case SECOND: 749 addMs(fd, 100 * i / pow); 750 break; 751 752 default: assert (false); 753 } 754 755 return FOUND; 756 } 757 758 // the DT is always UTC, so this just adds the offset to the date fields 759 // another option would be to add time zone fields to DT and have this fill them 760 761 byte getTimeZone(T, T2)(ref T* p, size_t len, ref FullDate fd, ubyte separators, scope bool delegate(T2[]) done) { 762 static assert (is(Unqual!(T) == Unqual!(T2))); 763 bool checkColon() { return .checkColon(p, separators); } 764 765 if (len == 0) 766 return NOTFOUND; 767 768 auto p0 = p; 769 770 if (accept(p, 'Z')) 771 return FOUND; 772 773 int factor = -1; 774 775 if (accept(p, '-')) 776 factor = 1; 777 else if (!accept(p, '+')) 778 return NOTFOUND; 779 780 int hour = void; 781 if (parseInt(p, min(cast(size_t)2, len-1), hour) != 2 || hour > 12 || (hour == 0 && factor == 1)) 782 return BAD; 783 784 addHours(fd, factor * hour); 785 786 // if we go forward in time to midnight, it's 24:00 787 if ( 788 factor > 0 && 789 hours(fd) == 0 && mins(fd) == 0 && secs(fd) == 0 && ms(fd) == 0 790 ) 791 fd.setEndOfDay(); 792 793 if (done("012345:")) 794 return FOUND; 795 796 auto afterHours = p; 797 798 if (!checkColon()) 799 return BAD; 800 801 int minute = void; 802 if (parseInt(p, min(cast(size_t)2, len - (p-p0)), minute) != 2) { 803 // The hours were valid even if the minutes weren't 804 p = afterHours; 805 return FOUND; 806 } 807 808 addMins(fd, factor * minute); 809 810 // as above 811 if ( 812 factor > 0 && 813 hours(fd) == 0 && mins(fd) == 0 && secs(fd) == 0 && ms(fd) == 0 814 ) 815 fd.setEndOfDay(); 816 817 return FOUND; 818 } 819 820 /+ +++++++++++++++++++++++++++++++++++++++ +\ 821 822 Privates used by both date and time 823 824 \+ +++++++++++++++++++++++++++++++++++++++ +/ 825 826 bool accept(T)(ref T* p, char c) { 827 if (*p == c) { 828 ++p; 829 return true; 830 } 831 return false; 832 } 833 834 bool demand(T)(ref T* p, char c) { 835 return (*p++ == c); 836 } 837 838 bool done(T, T2)(size_t eaten, size_t srcLen, T* p, T2[] s) { 839 if (eaten == srcLen) 840 return true; 841 842 // s is the array of characters which may come next 843 // (i.e. which *p may be) 844 // sorted in ascending order 845 T t = *p; 846 foreach (c; s) { 847 if (t < c) 848 return true; 849 else if (t == c) 850 break; 851 } 852 return false; 853 } 854 855 int daysPerMonth(int month, int year) { 856 uint era = erafy(year); 857 return Gregorian.generic.getDaysInMonth(year, month, era); 858 } 859 860 uint erafy(ref int year) { 861 if (year < 0) { 862 year *= -1; 863 return Gregorian.BC_ERA; 864 } else 865 return Gregorian.AD_ERA; 866 } 867 868 /+ +++++++++++++++++++++++++++++++++++++++ +\ 869 870 Extract an integer from the input, accept no more than max digits 871 872 \+ +++++++++++++++++++++++++++++++++++++++ +/ 873 874 // note: code relies on these always being positive, failing if *p == '-' 875 876 int parseInt(T)(ref T* p, size_t max) { 877 size_t i = 0; 878 int value = 0; 879 while (i < max && p[i] >= '0' && p[i] <= '9') 880 value = value * 10 + p[i++] - '0'; 881 p += i; 882 return value; 883 } 884 885 // ... and return the amount of digits processed 886 887 size_t parseInt(T)(ref T* p, size_t max, out int i) { 888 T* p2 = p; 889 i = parseInt(p, max); 890 return p - p2; 891 } 892 893 894 /+ +++++++++++++++++++++++++++++++++++++++ +\ 895 896 Helpers for DT/FullDate manipulation 897 898 \+ +++++++++++++++++++++++++++++++++++++++ +/ 899 900 // as documented in ocean.time.Time 901 bool DTyear(int year) { return year >= -10000 && year <= 9999; } 902 903 void addMonths(ref FullDate d, int n) { d.val = Gregorian.generic.addMonths(d.val, n-1); } // -1 due to initial being 1 904 void addDays (ref FullDate d, int n) { d.val += TimeSpan.fromDays (n-1); } // ditto 905 void addHours (ref FullDate d, int n) { d.val += TimeSpan.fromHours (n); } 906 void addMins (ref FullDate d, int n) { d.val += TimeSpan.fromMinutes(n); } 907 void addSecs (ref FullDate d, int n) { d.val += TimeSpan.fromSeconds(n); } 908 void addMs (ref FullDate d, int n) { d.val += TimeSpan.fromMillis (n); } 909 910 // years and secs always just get the DT value 911 int years (FullDate d) { return Gregorian.generic.getYear (d.val); } 912 int months(FullDate d) { return Gregorian.generic.getMonth (d.val); } 913 int days (FullDate d) { return Gregorian.generic.getDayOfMonth(d.val); } 914 int hours (FullDate d) { return d.val.time.hours; } 915 int mins (FullDate d) { return d.val.time.minutes; } 916 int secs (FullDate d) { return d.val.time.seconds; } 917 int ms (FullDate d) { return d.val.time.millis; } 918 919 //////////////////// 920 921 // Unit tests 922 923 debug (Tango_ISO8601_Valgrind) import core.stdc.stdlib : malloc, free; 924 925 unittest { 926 FullDate fd; 927 928 // date 929 930 size_t d(cstring s, ubyte e = 0) { 931 fd = fd.init; 932 return parseDate(s, fd, e); 933 } 934 935 auto 936 INIT_YEAR = years (FullDate.init), 937 INIT_MONTH = months(FullDate.init), 938 INIT_DAY = days (FullDate.init); 939 940 test (d("20abc") == 2); 941 test (years(fd) == 2000); 942 943 test (d("2004") == 4); 944 test (years(fd) == 2004); 945 946 test (d("+0019", 2) == 5); 947 test (years(fd) == 1900); 948 949 test (d("+111985", 2) == 7); 950 test (years(fd) == INIT_YEAR); 951 test (fd.year == 111985); 952 953 test (d("+111985", 1) == 6); 954 test (years(fd) == INIT_YEAR); 955 test (fd.year == 11198); 956 957 test (d("+111985", 3) == 0); 958 test (years(fd) == INIT_YEAR); 959 test (fd.year == INIT_YEAR); 960 961 test (d("+111985", 4) == 7); 962 test (years(fd) == INIT_YEAR); 963 test (fd.year == 11198500); 964 965 test (d("-111985", 5) == 0); 966 test (years(fd) == INIT_YEAR); 967 test (fd.year == INIT_YEAR); 968 969 test (d("+999999999", 5) == 10); 970 test (years(fd) == INIT_YEAR); 971 test (fd.year == 999_999_999); 972 973 try { 974 d("+10000000000", 6); 975 assert (false); 976 } catch (IllegalArgumentException) { 977 test (years(fd) == INIT_YEAR); 978 test (fd.year == INIT_YEAR); 979 } 980 981 test (d("-999999999", 5) == 10); 982 test (years(fd) == INIT_YEAR); 983 test (fd.year == -1_000_000_000); 984 985 test (d("0001") == 4); 986 test (years(fd) == 1); 987 test (fd.year == 1); 988 989 test (d("0000") == 4); 990 test (fd.year == -1); 991 992 test (d("-0001") == 5); 993 test (fd.year == -2); 994 995 test (d("abc") == 0); 996 test (years(fd) == INIT_YEAR); 997 test (fd.year == INIT_YEAR); 998 999 test (d("abc123") == 0); 1000 test (years(fd) == INIT_YEAR); 1001 test (fd.year == INIT_YEAR); 1002 1003 test (d("2007-08") == 7); 1004 test (years(fd) == 2007); 1005 test (months(fd) == 8); 1006 1007 test (d("+001985-04", 2) == 10); 1008 test (years(fd) == 1985); 1009 test (months(fd) == 4); 1010 1011 test (d("2007-08-07") == 10); 1012 test (years(fd) == 2007); 1013 test (months(fd) == 8); 1014 test (days(fd) == 7); 1015 1016 test (d("2008-20-30") == 4); 1017 test (years(fd) == 2008); 1018 test (months(fd) == INIT_MONTH); 1019 1020 test (d("2007-02-30") == 7); 1021 test (years(fd) == 2007); 1022 test (months(fd) == 2); 1023 1024 test (d("20060708") == 8); 1025 test (years(fd) == 2006); 1026 test (months(fd) == 7); 1027 test (days(fd) == 8); 1028 1029 test (d("19953080") == 4); 1030 test (years(fd) == 1995); 1031 test (months(fd) == INIT_MONTH); 1032 1033 test (d("2007-0201") == 7); 1034 test (years(fd) == 2007); 1035 test (months(fd) == 2); 1036 1037 test (d("200702-01") == 6); 1038 test (years(fd) == 2007); 1039 test (months(fd) == 2); 1040 1041 test (d("+001985-04-12", 2) == 13); 1042 test (years(fd) == 1985); 1043 test (fd.year == 1985); 1044 test (months(fd) == 4); 1045 test (days(fd) == 12); 1046 1047 test (d("-0123450607", 2) == 11); 1048 test (years(fd) == INIT_YEAR); 1049 test (fd.year == -12346); 1050 test (months(fd) == 6); 1051 test (days(fd) == 7); 1052 1053 test (d("1985W15") == 7); 1054 test (years(fd) == 1985); 1055 test (months(fd) == 4); 1056 test (days(fd) == 8); 1057 1058 test (d("2008-W01") == 8); 1059 test (years(fd) == 2007); 1060 test (months(fd) == 12); 1061 test (days(fd) == 31); 1062 1063 test (d("2008-W012") == 8); 1064 test (years(fd) == 2007); 1065 test (months(fd) == 12); 1066 test (days(fd) == 31); 1067 1068 test (d("2008W01-2") == 7); 1069 test (years(fd) == 2007); 1070 test (months(fd) == 12); 1071 test (days(fd) == 31); 1072 1073 test (d("2008-W01-2") == 10); 1074 test (years(fd) == 2008); 1075 test (months(fd) == 1); 1076 test (days(fd) == 1); 1077 1078 test (d("2009-W53-4") == 10); 1079 test (years(fd) == 2009); 1080 test (months(fd) == 12); 1081 test (days(fd) == 31); 1082 1083 test (d("2009-W01-1") == 10); 1084 test (years(fd) == 2008); 1085 test (months(fd) == 12); 1086 test (days(fd) == 29); 1087 1088 test (d("2009W537") == 8); 1089 test (years(fd) == 2010); 1090 test (months(fd) == 1); 1091 test (days(fd) == 3); 1092 1093 test (d("2010W537") == 4); 1094 test (years(fd) == 2010); 1095 test (months(fd) == INIT_MONTH); 1096 1097 test (d("2009-W01-3") == 10); 1098 test (years(fd) == 2008); 1099 test (months(fd) == 12); 1100 test (days(fd) == 31); 1101 1102 test (d("2009-W01-4") == 10); 1103 test (years(fd) == 2009); 1104 test (months(fd) == 1); 1105 test (days(fd) == 1); 1106 1107 test (d("2004-W53-6") == 10); 1108 test (years(fd) == 2005); 1109 test (months(fd) == 1); 1110 test (days(fd) == 1); 1111 1112 test (d("2004-W53-7") == 10); 1113 test (years(fd) == 2005); 1114 test (months(fd) == 1); 1115 test (days(fd) == 2); 1116 1117 test (d("2005-W52-6") == 10); 1118 test (years(fd) == 2005); 1119 test (months(fd) == 12); 1120 test (days(fd) == 31); 1121 1122 test (d("2007-W01-1") == 10); 1123 test (years(fd) == 2007); 1124 test (months(fd) == 1); 1125 test (days(fd) == 1); 1126 1127 test (d("1000-W07-7") == 10); 1128 test (years(fd) == 1000); 1129 test (months(fd) == 2); 1130 test (days(fd) == 16); 1131 1132 test (d("1500-W11-1") == 10); 1133 test (years(fd) == 1500); 1134 test (months(fd) == 3); 1135 test (days(fd) == 12); 1136 1137 test (d("1700-W14-2") == 10); 1138 test (years(fd) == 1700); 1139 test (months(fd) == 4); 1140 test (days(fd) == 6); 1141 1142 test (d("1800-W19-3") == 10); 1143 test (years(fd) == 1800); 1144 test (months(fd) == 5); 1145 test (days(fd) == 7); 1146 1147 test (d("1900-W25-4") == 10); 1148 test (years(fd) == 1900); 1149 test (months(fd) == 6); 1150 test (days(fd) == 21); 1151 1152 test (d("0900-W27-5") == 10); 1153 test (years(fd) == 900); 1154 test (months(fd) == 7); 1155 test (days(fd) == 9); 1156 1157 test (d("0800-W33-6") == 10); 1158 test (years(fd) == 800); 1159 test (months(fd) == 8); 1160 test (days(fd) == 19); 1161 1162 test (d("0700-W37-7") == 10); 1163 test (years(fd) == 700); 1164 test (months(fd) == 9); 1165 test (days(fd) == 16); 1166 1167 test (d("0600-W41-4") == 10); 1168 test (years(fd) == 600); 1169 test (months(fd) == 10); 1170 test (days(fd) == 9); 1171 1172 test (d("0500-W45-7") == 10); 1173 test (years(fd) == 500); 1174 test (months(fd) == 11); 1175 test (days(fd) == 14); 1176 1177 test (d("2000-W55") == 4); 1178 test (years(fd) == 2000); 1179 1180 test (d("1980-002") == 8); 1181 test (years(fd) == 1980); 1182 test (months(fd) == 1); 1183 test (days(fd) == 2); 1184 1185 test (d("1981-034") == 8); 1186 test (years(fd) == 1981); 1187 test (months(fd) == 2); 1188 test (days(fd) == 3); 1189 1190 test (d("1982-063") == 8); 1191 test (years(fd) == 1982); 1192 test (months(fd) == 3); 1193 test (days(fd) == 4); 1194 1195 test (d("1983-095") == 8); 1196 test (years(fd) == 1983); 1197 test (months(fd) == 4); 1198 test (days(fd) == 5); 1199 1200 test (d("1984-127") == 8); 1201 test (years(fd) == 1984); 1202 test (months(fd) == 5); 1203 test (days(fd) == 6); 1204 1205 test (d("1985-158") == 8); 1206 test (years(fd) == 1985); 1207 test (months(fd) == 6); 1208 test (days(fd) == 7); 1209 1210 test (d("1986-189") == 8); 1211 test (years(fd) == 1986); 1212 test (months(fd) == 7); 1213 test (days(fd) == 8); 1214 1215 test (d("1987-221") == 8); 1216 test (years(fd) == 1987); 1217 test (months(fd) == 8); 1218 test (days(fd) == 9); 1219 1220 test (d("1988-254") == 8); 1221 test (years(fd) == 1988); 1222 test (months(fd) == 9); 1223 test (days(fd) == 10); 1224 1225 test (d("1989-284") == 8); 1226 test (years(fd) == 1989); 1227 test (months(fd) == 10); 1228 test (days(fd) == 11); 1229 1230 test (d("1990316") == 7); 1231 test (years(fd) == 1990); 1232 test (months(fd) == 11); 1233 test (days(fd) == 12); 1234 1235 test (d("1991-347") == 8); 1236 test (years(fd) == 1991); 1237 test (months(fd) == 12); 1238 test (days(fd) == 13); 1239 1240 test (d("1992-000") == 4); 1241 test (years(fd) == 1992); 1242 1243 test (d("1993-370") == 4); 1244 test (years(fd) == 1993); 1245 1246 // time 1247 1248 size_t t(cstring s) { 1249 fd = fd.init; 1250 return parseTime(s, fd); 1251 } 1252 1253 test (t("20") == 2); 1254 test (hours(fd) == 20); 1255 test (mins(fd) == 0); 1256 test (secs(fd) == 0); 1257 1258 test (t("30") == 0); 1259 1260 test (t("T15") == 3); 1261 test (hours(fd) == 15); 1262 test (mins(fd) == 0); 1263 test (secs(fd) == 0); 1264 1265 test (t("T1") == 0); 1266 test (t("T") == 0); 1267 1268 test (t("2004") == 4); 1269 test (hours(fd) == 20); 1270 test (mins(fd) == 4); 1271 test (secs(fd) == 0); 1272 1273 test (t("200406") == 6); 1274 test (hours(fd) == 20); 1275 test (mins(fd) == 4); 1276 test (secs(fd) == 6); 1277 1278 test (t("24:00") == 5); 1279 test (fd.endOfDay); 1280 test (days(fd) == INIT_DAY + 1); 1281 test (hours(fd) == 0); 1282 test (mins(fd) == 0); 1283 test (secs(fd) == 0); 1284 1285 test (t("00:00") == 5); 1286 test (hours(fd) == 0); 1287 test (mins(fd) == 0); 1288 test (secs(fd) == 0); 1289 1290 test (t("23:59:60") == 8); 1291 test (hours(fd) == 23); 1292 test (mins(fd) == 59); 1293 test (secs(fd) == 59); 1294 test (fd.seconds == 60); 1295 1296 test (t("12:3456") == 5); 1297 test (hours(fd) == 12); 1298 test (mins(fd) == 34); 1299 1300 test (t("1234:56") == 4); 1301 test (hours(fd) == 12); 1302 test (mins(fd) == 34); 1303 1304 test (t("16:49:30,001") == 12); 1305 test (hours(fd) == 16); 1306 test (mins(fd) == 49); 1307 test (secs(fd) == 30); 1308 test (ms(fd) == 1); 1309 1310 test (t("15:48:29,1") == 10); 1311 test (hours(fd) == 15); 1312 test (mins(fd) == 48); 1313 test (secs(fd) == 29); 1314 test (ms(fd) == 100); 1315 1316 test (t("02:10:34,a") == 8); 1317 test (hours(fd) == 2); 1318 test (mins(fd) == 10); 1319 test (secs(fd) == 34); 1320 1321 test (t("14:50,5") == 7); 1322 test (hours(fd) == 14); 1323 test (mins(fd) == 50); 1324 test (secs(fd) == 30); 1325 1326 test (t("1540,4") == 6); 1327 test (hours(fd) == 15); 1328 test (mins(fd) == 40); 1329 test (secs(fd) == 24); 1330 1331 test (t("1250,") == 4); 1332 test (hours(fd) == 12); 1333 test (mins(fd) == 50); 1334 1335 test (t("14,5") == 4); 1336 test (hours(fd) == 14); 1337 test (mins(fd) == 30); 1338 1339 test (t("12,") == 2); 1340 test (hours(fd) == 12); 1341 test (mins(fd) == 0); 1342 1343 test (t("24:00:01") == 5); 1344 test (fd.endOfDay); 1345 test (hours(fd) == 0); 1346 test (mins(fd) == 0); 1347 test (secs(fd) == 0); 1348 1349 test (t("12:34+:56") == 5); 1350 test (hours(fd) == 12); 1351 test (mins(fd) == 34); 1352 test (secs(fd) == 0); 1353 1354 // time zones 1355 1356 test (t("14:45:15Z") == 9); 1357 test (hours(fd) == 14); 1358 test (mins(fd) == 45); 1359 test (secs(fd) == 15); 1360 1361 test (t("23Z") == 3); 1362 test (hours(fd) == 23); 1363 test (mins(fd) == 0); 1364 test (secs(fd) == 0); 1365 1366 test (t("21:32:43-12:34") == 14); 1367 test (days(fd) == INIT_DAY + 1); 1368 test (hours(fd) == 10); 1369 test (mins(fd) == 6); 1370 test (secs(fd) == 43); 1371 1372 test (t("12:34,5+00:00") == 13); 1373 test (hours(fd) == 12); 1374 test (mins(fd) == 34); 1375 test (secs(fd) == 30); 1376 1377 test (t("03:04+07") == 8); 1378 test (hours(fd) == 20); 1379 test (mins(fd) == 4); 1380 test (secs(fd) == 0); 1381 1382 test (t("11,5+") == 4); 1383 test (hours(fd) == 11); 1384 test (mins(fd) == 30); 1385 1386 test (t("07-") == 2); 1387 test (hours(fd) == 7); 1388 1389 test (t("06:12,7-") == 7); 1390 test (hours(fd) == 6); 1391 test (mins(fd) == 12); 1392 test (secs(fd) == 42); 1393 1394 test (t("050403,2+") == 8); 1395 test (hours(fd) == 5); 1396 test (mins(fd) == 4); 1397 test (secs(fd) == 3); 1398 test (ms(fd) == 200); 1399 1400 test (t("061656-") == 6); 1401 test (hours(fd) == 6); 1402 test (mins(fd) == 16); 1403 test (secs(fd) == 56); 1404 1405 // date and time together 1406 1407 size_t b(cstring s) { 1408 fd = fd.init; 1409 return parseDateAndTime(s, fd); 1410 } 1411 1412 test (b("2007-08-09T12:34:56") == 19); 1413 test (years(fd) == 2007); 1414 test (months(fd) == 8); 1415 test (days(fd) == 9); 1416 test (hours(fd) == 12); 1417 test (mins(fd) == 34); 1418 test (secs(fd) == 56); 1419 1420 test (b("1985W155T235030,768") == 19); 1421 test (years(fd) == 1985); 1422 test (months(fd) == 4); 1423 test (days(fd) == 12); 1424 test (hours(fd) == 23); 1425 test (mins(fd) == 50); 1426 test (secs(fd) == 30); 1427 test (ms(fd) == 768); 1428 1429 // time zones 1430 1431 test (b("2009-08-07T01:02:03Z") == 20); 1432 test (years(fd) == 2009); 1433 test (months(fd) == 8); 1434 test (days(fd) == 7); 1435 test (hours(fd) == 1); 1436 test (mins(fd) == 2); 1437 test (secs(fd) == 3); 1438 1439 test (b("2007-08-09T03:02,5+04:56") == 24); 1440 test (years(fd) == 2007); 1441 test (months(fd) == 8); 1442 test (days(fd) == 8); 1443 test (hours(fd) == 22); 1444 test (mins(fd) == 6); 1445 test (secs(fd) == 30); 1446 1447 test (b("20000228T2330-01") == 16); 1448 test (years(fd) == 2000); 1449 test (months(fd) == 2); 1450 test (days(fd) == 29); 1451 test (hours(fd) == 0); 1452 test (mins(fd) == 30); 1453 test (secs(fd) == 0); 1454 1455 test (b("2007-01-01T00:00+01") == 19); 1456 test (years(fd) == 2006); 1457 test (months(fd) == 12); 1458 test (days(fd) == 31); 1459 test (hours(fd) == 23); 1460 test (mins(fd) == 0); 1461 test (secs(fd) == 0); 1462 1463 test (b("2007-12-31T23:00-01") == 19); 1464 test (fd.endOfDay); 1465 test (years(fd) == 2008); 1466 test (months(fd) == 1); 1467 test (days(fd) == 1); 1468 test (hours(fd) == 0); 1469 test (mins(fd) == 0); 1470 test (secs(fd) == 0); 1471 1472 test (b("2007-12-31T23:01-01") == 19); 1473 test (!fd.endOfDay); 1474 test (years(fd) == 2008); 1475 test (months(fd) == 1); 1476 test (days(fd) == 1); 1477 test (hours(fd) == 0); 1478 test (mins(fd) == 1); 1479 test (secs(fd) == 0); 1480 1481 test (b("1902-03-04T1a") == 0); 1482 test (b("1902-03-04T10:aa") == 0); 1483 test (b("1902-03-04T10:1aa") == 0); 1484 test (b("200512-01T10:02") == 0); 1485 test (b("1985-04-1210:15:30+04:00") == 0); 1486 test (b("1985-04-12T10:15:30+0400") == 0); 1487 test (b("19020304T05:06:07") == 0); 1488 test (b("1902-03-04T050607") == 0); 1489 test (b("19020304T05:06:07abcd") == 0); 1490 test (b("1902-03-04T050607abcd") == 0); 1491 1492 test (b("1985-04-12T10:15:30-05:4") == 22); 1493 test (years(fd) == 1985); 1494 test (months(fd) == 4); 1495 test (days(fd) == 12); 1496 test (hours(fd) == 15); 1497 test (mins(fd) == 15); 1498 test (secs(fd) == 30); 1499 test (b("2009-04-13T23:00-01") == 19); 1500 test (fd.endOfDay); 1501 test (years(fd) == 2009); 1502 test (months(fd) == 4); 1503 test (days(fd) == 14); 1504 test (hours(fd) == 0); 1505 test (mins(fd) == 0); 1506 test (secs(fd) == 0); 1507 test (b("2009-04-13T24:00Z") == 17); 1508 test (fd.endOfDay); 1509 test (years(fd) == 2009); 1510 test (months(fd) == 4); 1511 test (days(fd) == 14); 1512 test (hours(fd) == 0); 1513 test (mins(fd) == 0); 1514 test (secs(fd) == 0); 1515 1516 // unimplemented: intervals, durations, recurring intervals 1517 1518 debug (Tango_ISO8601_Valgrind) { 1519 size_t valgrind(scope size_t delegate(char[]) f, char[] s) { 1520 auto p = cast(char*)malloc(s.length); 1521 auto ps = p[0..s.length]; 1522 ps[] = s[]; 1523 auto result = f(ps); 1524 free(p); 1525 return result; 1526 } 1527 size_t vd(char[] s) { 1528 size_t date(char[] ss) { return d(ss); } 1529 return valgrind(&date, s); 1530 } 1531 size_t vt(char[] s) { return valgrind(&t, s); } 1532 size_t vb(char[] s) { return valgrind(&b, s); } 1533 1534 test (vd("1") == 0); 1535 test (vd("19") == 2); 1536 test (vd("199") == 0); 1537 test (vd("1999") == 4); 1538 test (vd("1999-") == 4); 1539 test (vd("1999-W") == 4); 1540 test (vd("1999-W0") == 4); 1541 test (vd("1999-W01") == 8); 1542 test (vd("1999-W01-") == 8); 1543 test (vd("1999-W01-3") == 10); 1544 test (vd("1999W") == 4); 1545 test (vd("1999W0") == 4); 1546 test (vd("1999W01") == 7); 1547 test (vd("1999W01-") == 7); 1548 test (vd("1999W01-3") == 7); 1549 test (vd("1999W013") == 8); 1550 test (vd("1999-0") == 4); 1551 test (vd("1999-01") == 7); 1552 test (vd("1999-01-") == 7); 1553 test (vd("1999-01-0") == 7); 1554 test (vd("1999-01-01") == 10); 1555 test (vd("1999-0101") == 7); 1556 test (vd("1999-365") == 8); 1557 test (vd("1999365") == 7); 1558 1559 test (vt("1") == 0); 1560 test (vt("15") == 2); 1561 test (vt("15:") == 2); 1562 test (vt("15:3") == 2); 1563 test (vt("15:30") == 5); 1564 test (vt("153") == 2); 1565 test (vt("1530") == 4); 1566 test (vt("1530:") == 4); 1567 test (vt("15304") == 4); 1568 test (vt("153045") == 6); 1569 test (vt("15:30:") == 5); 1570 test (vt("15:30:4") == 5); 1571 test (vt("15:30:45") == 8); 1572 test (vt("T15") == 3); 1573 test (vt("T1") == 0); 1574 test (vt("T") == 0); 1575 test (vt("15,") == 2); 1576 test (vt("15,2") == 4); 1577 test (vt("1530,") == 4); 1578 test (vt("1530,2") == 6); 1579 test (vt("15:30:45,") == 8); 1580 test (vt("15:30:45,2") == 10); 1581 test (vt("153045,") == 6); 1582 test (vt("153045,2") == 8); 1583 test (vt("153045,22") == 9); 1584 test (vt("153045,222") == 10); 1585 test (vt("15Z") == 3); 1586 test (vt("15+") == 2); 1587 test (vt("15-") == 2); 1588 test (vt("15+0") == 2); 1589 test (vt("15+00") == 5); 1590 test (vt("15+00:") == 5); 1591 test (vt("15+00:0") == 5); 1592 test (vt("15+00:00") == 8); 1593 test (vb("1999-01-01") == 0); 1594 test (vb("1999-01-01T") == 0); 1595 test (vb("1999-01-01T15:30:45") == 19); 1596 } 1597 }