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