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 }