Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #568: iso8601.d

File iso8601.d, 28.9 kB (added by Deewiant, 12 years ago)
Line 
1 import tango.util.time.Date;
2
3 /// Returns the number of chars used to compose a valid date: 0 if no date can be composed.
4 /// Fields in date will either be correct (e.g. months will be >= 1 and <= 12) or zero.
5
6 size_t iso8601Date(T)(T[] src, inout Date date, size_t expanded = 0) {
7     ubyte dummy = void;
8     T* p = src.ptr;
9     return doIso8601Date(p, src, date, expanded, dummy);
10 }
11
12 private size_t doIso8601Date(T)(inout T* p, T[] src, inout Date date, size_t expanded, out ubyte separators)
13 out {
14     assert (!date.month || (              date.month >= 1 && date.month <= 12));
15     assert (!date.day   || (date.month && date.day   >= 1 && date.day   <= daysPerMonth(date.month, date.year)));
16 } body {
17
18     size_t eaten() { return p - src.ptr; }
19     bool done(T[] s) { return .done(eaten(), src.length, *p, s); }
20
21     if (!parseYear(p, expanded, date.year))
22         return (date.year = 0);
23
24     auto onlyYear = eaten();
25
26     // /([+-]Y{expanded})?(YYYY|YY)/
27     if (done("-0123W"))
28         return onlyYear;
29
30     if (accept(p, '-'))
31         separators = true;
32
33     if (accept(p, 'W')) {
34         // (year)-Www-D
35
36         T* p2 = p;
37
38         int i = parseInt(p, 3u);
39
40         if (i) if (p - p2 == 2) {
41
42             // (year)-Www
43             if (done("-")) {
44                 if (getMonthAndDayFromWeek(date, i))
45                     return eaten();
46
47             // (year)-Www-D
48             } else if (demand(p, '-'))
49                 if (getMonthAndDayFromWeek(date, i, *p++ - '0'))
50                     return eaten();
51
52         } else if (p - p2 == 3)
53             // (year)WwwD
54             if (getMonthAndDayFromWeek(date, i / 10, i % 10))
55                 return eaten();
56
57         return onlyYear;
58     }
59
60     // next up, MM or MM[-]DD or DDD
61
62     T* p2 = p;
63
64     int i = parseInt(p);
65     if (!i)
66         return onlyYear;
67
68     switch (p - p2) {
69         case 2:
70             date.month = i;
71
72             if (!(date.month >= 1 && date.month <= 12)) {
73                 date.month = 0;
74                 return onlyYear;
75             }
76
77             auto onlyMonth = eaten();
78
79             // (year)-MM
80             if (done("-"))
81                 return onlyMonth;
82
83             // (year)-MM-DD
84             if (!(
85                 demand(p, '-') &&
86                 (date.day = parseInt(p, 2u)) != 0 && date.day <= daysPerMonth(date.month, date.year)
87             )) {
88                 date.day = 0;
89                 return onlyMonth;
90             }
91
92             break;
93
94         case 4:
95             // e.g. 20010203, i = 203 now
96
97             date.month = i / 100;
98             date.day   = i % 100;
99
100             // (year)MMDD
101             if (!(
102                 date.month >= 1 && date.month <= 12 &&
103                 date.day   >= 0 && date.day   <= daysPerMonth(date.month, date.year)
104             )) {
105                 date.month = date.day = 0;
106                 return onlyYear;
107             }
108
109             break;
110
111         case 3:
112             // (year)-DDD
113             // i is the ordinal of the day within the year
114
115             bool leap = isLeapYear(date.year);
116
117             if (i > 365 + leap)
118                 return onlyYear;
119
120             if (i <= 31) {
121                 date.month = 1;
122                 date.day   = i;
123
124             } else if (i <= 59 + leap) {
125                 date.month = 2;
126                 date.day   = i - 31 - leap;
127
128             } else if (i <= 90 + leap) {
129                 date.month = 3;
130                 date.day   = i - 59 - leap;
131
132             } else if (i <= 120 + leap) {
133                 date.month = 4;
134                 date.day   = i - 90 - leap;
135
136             } else if (i <= 151 + leap) {
137                 date.month = 5;
138                 date.day   = i - 120 - leap;
139
140             } else if (i <= 181 + leap) {
141                 date.month = 6;
142                 date.day   = i - 151 - leap;
143
144             } else if (i <= 212 + leap) {
145                 date.month = 7;
146                 date.day   = i - 181 - leap;
147
148             } else if (i <= 243 + leap) {
149                 date.month = 8;
150                 date.day   = i - 212 - leap;
151
152             } else if (i <= 273 + leap) {
153                 date.month = 9;
154                 date.day   = i - 243 - leap;
155
156             } else if (i <= 304 + leap) {
157                 date.month = 10;
158                 date.day   = i - 273 - leap;
159
160             } else if (i <= 334 + leap) {
161                 date.month = 11;
162                 date.day   = i - 304 - leap;
163
164             } else {
165                 if (i > 365 + leap)
166                     assert (false);
167
168                 date.month = 12;
169                 date.day   = i - 334 - leap;
170             }
171
172         default: break;
173     }
174
175     return eaten();
176 }
177
178 /// Returns the number of chars used to compose a valid date: 0 if no date can be composed.
179 /// Fields in date will be zero if incorrect: since 00:00:00,000 is a valid time, the return value must be checked to be sure of the result.
180 /// date.sec may be 60 if the hours and minutes are 23 and 59, as leap seconds are occasionally added to UTC time.
181 /// date.hour may be 0 or 24: the latter marks the end of a day, the former the beginning.
182
183 size_t iso8601Time(T)(T[] src, inout Date date) {
184     bool dummy = void;
185     T* p = src.ptr;
186     return doIso8601Time(p, src, date, WHATEVER, dummy);
187 }
188
189 private enum : ubyte { NO = 0, YES = 1, WHATEVER }
190
191 // bothValid is used only to get iso8601() to catch errors correctly
192 private size_t doIso8601Time(T)(inout T* p, T[] src, inout Date date, ubyte separators, out bool bothValid)
193 out {
194     // yes, I could just write >= 0, but this emphasizes the difference between == 0 and != 0
195     assert (!date.hour || (date.hour > 0 && date.hour <=  24));
196     assert (!date.min  || (date.min  > 0 && date.min  <=  59));
197     assert (!date.sec  || (date.sec  > 0 && date.sec  <=  60));
198     assert (!date.ms   || (date.ms   > 0 && date.ms   <= 999));
199 } body {
200
201     size_t eaten() { return p - src.ptr; }
202     bool done(T[] s) { return .done(eaten(), src.length, *p, s); }
203
204     bool checkColon() {
205         if (separators == WHATEVER)
206             accept(p, ':');
207
208         else if (accept(p, ':') != separators)
209             return false;
210
211         return true;
212     }
213
214     byte getTimeZone() { return .getTimeZone(p, date, separators, &done); }
215
216     // TODO/BUG: need to convert from local time if got T
217     // however, Tango provides nothing like Phobos's std.date.getLocalTZA
218     // (which doesn't look like it should work on Windows, it should use tzi.bias only, and GetTimeZoneInformationForYear)
219     // (and which uses too complicated code for Posix, tzset should be enough)
220     // and I'm not interested in delving into system-specific code right now
221     // remember also that -1 BC is the year zero in ISO 8601... -2 BC is -1, etc
222     if (separators == WHATEVER)
223         accept(p, 'T');
224
225     if (parseInt(p, 2u, date.hour) != 2 || date.hour > 24)
226         return (date.hour = 0);
227
228     auto onlyHour = eaten();
229
230     // hh
231     if (done("+,-.012345:"))
232         return onlyHour;
233
234     switch (getDecimal(p, date, HOUR)) {
235         case NOTFOUND: break;
236         case    FOUND:
237             auto onlyDecimal = eaten();
238             if (getTimeZone() == BAD)
239                 return onlyDecimal;
240
241             // /hh,h+/
242             return eaten();
243
244         case BAD: return onlyHour;
245         default: assert (false);
246     }
247
248     switch (getTimeZone()) {
249         case NOTFOUND: break;
250         case    FOUND: return eaten();
251         case BAD:      return onlyHour;
252         default: assert (false);
253     }
254
255     if (
256         !checkColon() ||
257
258         parseInt(p, 2u, date.min) != 2 || date.min > 59 ||
259
260         // hour 24 is only for 24:00:00
261         (date.hour == 24 && date.min != 0)
262     ) {
263         date.min = 0;
264         return onlyHour;
265     }
266
267     auto onlyMinute = eaten();
268
269     // hh:mm
270     if (done("+,-.0123456:")) {
271         bothValid = true;
272         return onlyMinute;
273     }
274
275     switch (getDecimal(p, date, MINUTE)) {
276         case NOTFOUND: break;
277         case    FOUND:
278             auto onlyDecimal = eaten();
279             if (getTimeZone() == BAD)
280                 return onlyDecimal;
281
282             // /hh:mm,m+/
283             bothValid = true;
284             return eaten();
285
286         case BAD: return onlyMinute;
287         default: assert (false);
288     }
289
290     switch (getTimeZone()) {
291         case NOTFOUND: break;
292         case    FOUND: bothValid = true; return eaten();
293         case BAD:      return onlyMinute;
294         default: assert (false);
295     }
296
297     if (
298         !checkColon() ||
299
300         parseInt(p, 2u, date.sec) != 2 || date.sec > 60 ||
301
302         (date.hour == 24 && date.sec  != 0) ||
303         (date.sec  == 60 && date.hour != 23 && date.min != 59)
304     ) {
305         date.sec = 0;
306         return onlyMinute;
307     }
308
309     auto onlySecond = eaten();
310
311     // hh:mm:ss
312     if (done("+,-.Z")) {
313         bothValid = true;
314         return onlySecond;
315     }
316
317     switch (getDecimal(p, date, SECOND)) {
318         case NOTFOUND: break;
319         case    FOUND:
320             auto onlyDecimal = eaten();
321             if (getTimeZone() == BAD)
322                 return onlyDecimal;
323
324             // /hh:mm:ss,s+/
325             bothValid = true;
326             return eaten();
327
328         case BAD: return onlySecond;
329         default: assert (false);
330     }
331
332     if (getTimeZone() == BAD)
333         return onlySecond;
334     else {
335         bothValid = true;
336         return eaten(); // hh:mm:ss with timezone
337     }
338 }
339
340 // combination of date and time
341 // stricter than just date followed by time:
342 //  can't have an expanded or reduced date
343 //  either use separators everywhere or not at all
344
345 /// This function is very strict: either a complete date and time can be extracted, or nothing can.
346 /// If this function returns zero, the fields of date are undefined.
347
348 size_t iso8601(T)(T[] src, inout Date date) {
349     T* p = src.ptr;
350     ubyte sep;
351     bool bothValid = false;
352
353     if (
354         doIso8601Date(p, src, date, 0u, sep) &&
355         date.year && date.month && date.day &&
356
357         // by mutual agreement this T may be omitted
358         // but this is just a convenience method for date+time anyway
359         demand(p, 'T') &&
360
361         doIso8601Time(p, src, date, sep, bothValid) &&
362         bothValid
363     )
364         return p - src.ptr;
365     else
366         return 0;
367 }
368
369 /+ +++++++++++++++++++++++++++++++++++++++ +\
370
371    Privates used by date
372
373 \+ +++++++++++++++++++++++++++++++++++++++ +/
374
375 // /([+-]Y{expanded})?(YYYY|YY)/
376 private bool parseYear(T)(inout T* p, size_t expanded, out int year) {
377
378     bool doParse() {
379         T* p2 = p;
380
381         if (!parseInt(p, expanded + 4u, year))
382             return false;
383
384         // it's Y{expanded}YY, Y{expanded}YYYY, or unacceptable
385
386         if (p - p2 - expanded == 2u)
387             year *= 100;
388         else if (p - p2 - expanded != 4u)
389             return false;
390
391         return true;
392     }
393
394     if (accept(p, '-')) {
395         if (!doParse())
396             return false;
397         year = -year;
398     } else {
399         accept(p, '+');
400         if (!doParse())
401             return false;
402     }
403
404     return true;
405 }
406
407 // find the month and day based on the calendar week
408 // uses date.year for leap year calculations
409 // returns false if week and date.year are incompatible
410 // based on the VBA function at http://www.probabilityof.com/ISO8601.shtml
411 private bool getMonthAndDayFromWeek(inout Date date, int week, int day = 1) {
412     if (week < 1 || week > 53 || day < 1 || day > 7)
413         return false;
414
415     bool leap = isLeapYear(date.year);
416
417     // only years starting with Thursday and
418     // leap years starting with Wednesday have 53 weeks
419
420     if (week == 53) {
421         int startingDay = dayOfWeek(date.year, 1, 1, leap);
422
423         if (!(startingDay == 4 || (leap && startingDay == 3)))
424             return false;
425     }
426
427     // days since year-01-04
428     int delta = 7*(week - 1) - dayOfWeek(date.year, 1, 4, leap) + day;
429
430     if (delta <= -4) {
431         if (delta < -7)
432             assert (false);
433
434         --date.year;
435         date.month = 12;
436         date.day   = delta + 4 + 31;
437
438     } else if (delta <= 27) {
439         date.month = 1;
440         date.day   = delta + 4;
441
442     } else if (delta <= 56 + leap) {
443         date.month = 2;
444         date.day   = delta - 27;
445
446     } else if (delta <= 87 + leap) {
447         date.month = 3;
448         date.day   = delta - 55 - leap;
449
450     } else if (delta <= 117 + leap) {
451         date.month = 4;
452         date.day   = delta - 86 - leap;
453
454     } else if (delta <= 148 + leap) {
455         date.month = 5;
456         date.day   = delta - 116 - leap;
457
458     } else if (delta <= 178 + leap) {
459         date.month = 6;
460         date.day   = delta - 147 - leap;
461
462     } else if (delta <= 209 + leap) {
463         date.month = 7;
464         date.day   = delta - 177 - leap;
465
466     } else if (delta <= 240 + leap) {
467         date.month = 8;
468         date.day   = delta - 208 - leap;
469
470     } else if (delta <= 270 + leap) {
471         date.month = 9;
472         date.day   = delta - 239 - leap;
473
474     } else if (delta <= 301 + leap) {
475         date.month = 10;
476         date.day   = delta - 269 - leap;
477
478     } else if (delta <= 331 + leap) {
479         date.month = 11;
480         date.day   = delta - 300 - leap;
481
482     } else if (delta <= 361 + leap) {
483         date.month = 12;
484         date.day   = delta - 330 - leap;
485
486     } else {
487         if (delta > 365 + leap)
488             assert (false);
489
490         ++date.year;
491         date.month = 1;
492         date.day   = delta - 365 - leap + 4;
493     }
494
495     return true;
496 }
497
498 private bool isLeapYear(int year) {
499     return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
500 }
501
502 // Babwani's Congruence
503 private int dayOfWeek(int year, int month, int day, bool leap)
504 in {
505     assert (month  >= 1 && month  <= 12);
506     assert (day    >= 1 && day    <= 31);
507
508     // BUG: only works for 1900-3-1 to 2100-2-28
509     assert (year >= 1901 && year <= 2099, "iso8601 :: Can't calculate day of week outside the years 1900-2099");
510
511 } out(result) {
512     assert (result >= 1 && result <= 7);
513
514 } body {
515     int f() {
516         if (leap && month <= 2)
517             return [6,2][month-1];
518
519         return [0,3,3,6,1,4,6,2,5,0,3,5][month-1];
520     }
521
522     int d = ((5*(year % 100) / 4) - 2*((year / 100) % 4) + f() + day) % 7;
523
524     // defaults to Saturday=0, Friday=6: convert to Monday=1, Sunday=7
525     return (d <= 1 ? d+6 : d-1);
526 }
527
528 /+ +++++++++++++++++++++++++++++++++++++++ +\
529
530    Privates used by time
531
532 \+ +++++++++++++++++++++++++++++++++++++++ +/
533
534 private enum : ubyte { HOUR, MINUTE, SECOND }
535 private enum :  byte { BAD, FOUND, NOTFOUND }
536
537 private byte getDecimal(T)(inout T* p, inout Date date, ubyte which) {
538     if (accept(p, ',') || accept(p, '.')) {
539
540         T* p2 = p;
541
542         int i;
543         size_t iLen = parseInt(p, i);
544
545         if (
546             iLen == 0 ||
547
548             // if i is 0, must have at least 3 digits
549             // ... or at least that's what I think the standard means
550             // when it says "[i]f the magnitude of the number is less
551             // than unity, the decimal sign shall be preceded by two
552             // zeros"...
553             // surely that should read "followed" and not "preceded"
554
555             (i == 0 && iLen < 3)
556         )
557             return BAD;
558
559         // 10 to the power of (iLen - 1)
560         int pow = 1;
561         while (--iLen)
562             pow *= 10;
563
564         switch (which) {
565             case HOUR:
566                 date.min = 6 * i / pow;
567                 date.sec = 6 * i % pow;
568                 break;
569             case MINUTE:
570                 date.sec = 6    * i / pow;
571                 date.ms  = 6000 * i / pow % 1000;
572                 break;
573             case SECOND:
574                 date.ms = 100 * i / pow;
575                 break;
576
577             default: assert (false);
578         }
579
580         return FOUND;
581     }
582
583     return NOTFOUND;
584 }
585
586 // the Date is always UTC, so this just adds the offset to the date fields
587 // another option would be to add time zone fields to Date and have this fill them
588
589 private byte getTimeZone(T)(inout T* p, inout Date date, ubyte separators, bool delegate(T[]) done) {
590     if (accept(p, 'Z'))
591         return FOUND;
592
593     int factor = -1;
594
595     if (accept(p, '-'))
596         factor = 1;
597
598     else if (!accept(p, '+'))
599         return NOTFOUND;
600
601     int hour;
602     if (parseInt(p, 2u, hour) != 2 || hour > 12 || (hour == 0 && factor == 1))
603         return BAD;
604
605     date.hour += factor * hour;
606
607     void hourCheck() {
608         if (date.hour > 24 || (date.hour == 24 && (date.min || date.sec))) {
609             date.hour -= 24;
610
611             // BUG? what should be done?
612             // if we get a time like 20:00-05:00
613             // which needs to be converted to UTC by adding 05:00 to 20:00
614             // we just set the time to 01:00 and the day to 1
615             // even though this is time, which really has nothing to do with the day, which is part of the date
616            
617             // if this isn't a bug, it needs to be documented: it's not necessarily obvious
618
619             if (date.day++ && date.day > daysPerMonth(date.month, date.year)) {
620                 date.day = 1;
621                 if (++date.month > 12) {
622                     date.month = 1;
623                     ++date.year;
624                 }
625             }
626         } else if (date.hour < 0) {
627             date.hour += 24;
628
629             // ditto above BUG?
630
631             if (date.day-- && date.day < 1) {
632                 if (--date.month < 1) {
633                     date.month = 12;
634                     --date.year;
635                 }
636
637                 date.day = daysPerMonth(date.month, date.year);
638             }
639         }
640     }
641
642     if (done("012345:")) {
643         hourCheck();
644         return FOUND;
645     }
646
647     if (separators == WHATEVER)
648         accept(p, ':');
649
650     else if (accept(p, ':') != separators)
651         return BAD;
652
653     int minute;
654     if (parseInt(p, 2u, minute) != 2)
655         return BAD;
656
657     assert (minute <= 59);
658
659     date.min += factor * minute;
660
661     if (date.min > 59) {
662         date.min -= 60;
663         ++date.hour;
664
665     } else if (date.min < 0) {
666         date.min += 60;
667         --date.hour;
668     }
669
670     hourCheck();
671     return FOUND;
672 }
673
674 /+ +++++++++++++++++++++++++++++++++++++++ +\
675
676    Privates used by both date and time
677
678 \+ +++++++++++++++++++++++++++++++++++++++ +/
679
680 private bool accept(T)(inout T* p, char c) {
681     if (*p == c) {
682         ++p;
683         return true;
684     }
685     return false;
686 }
687
688 private bool demand(T)(inout T* p, char c) {
689     return (*p++ == c);
690 }
691
692 private bool done(T)(size_t eaten, size_t srcLen, T p, T[] s) {
693     if (eaten == srcLen)
694         return true;
695
696     // s is the array of characters which may come next
697     // (i.e. which p may be)
698     // sorted in ascending order
699     foreach (c; s) {
700         if (p < c)
701             return true;
702         else if (p == c)
703             break;
704     }
705
706     return false;
707 }
708
709 private int daysPerMonth(int month, int year) {
710     if (month == 2 && isLeapYear(year))
711         return 29;
712     else
713         return [31,28,31,30,31,30,31,31,30,31,30,31][month-1];
714 }
715
716 /******************************************************************************
717
718         Extract an integer from the input
719
720 ******************************************************************************/
721
722 // note: ISO 8601 code relies on these values always being positive, failing if *p == '-'
723
724 private int parseInt(T) (inout T* p) {
725     int value = 0;
726     while (*p >= '0' && *p <= '9')
727         value = value * 10 + *p++ - '0';
728     return value;
729 }
730
731 // ... but accept no more than max digits
732
733 private int parseInt(T)(inout T* p, size_t max) {
734     size_t i = 0;
735     int value = 0;
736     while (p[i] >= '0' && p[i] <= '9' && i < max)
737         value = value * 10 + p[i++] - '0';
738     p += i;
739     return value;
740 }
741
742 // ... and return the amount of digits processed
743
744 private size_t parseInt(T)(inout T* p, out int i) {
745     T* p2 = p;
746     i = parseInt(p);
747     return p - p2;
748 }
749
750 private size_t parseInt(T)(inout T* p, size_t max, out int i) {
751     T* p2 = p;
752     i = parseInt(p, max);
753     return p - p2;
754 }
755
756 ////////////////////
757
758 debug (UnitTest) {
759     import tango.io.Stdout;
760
761     void main() { }
762
763     unittest {
764         Date date;
765
766         // date
767
768         size_t d(char[] s, size_t e = 0) {
769             date = date.init;
770             return iso8601Date(s, date, e);
771         }
772
773         assert (d("20abc") == 2);
774         assert (date.year == 2000);
775
776         assert (d("2004") == 4);
777         assert (date.year == 2004);
778
779         assert (d("+0019", 2) == 5);
780         assert (date.year == 1900);
781
782         assert (d("+111985", 2) == 7);
783         assert (date.year == 111985);
784
785         assert (d("+111985", 1) == 6);
786         assert (date.year == 11198);
787
788         assert (d("+111985", 3) == 0);
789         assert (!date.year);
790
791         assert (d("+111985", 4) == 7);
792         assert (date.year == 11198500);
793
794         assert (d("-111985", 5) == 0);
795         assert (!date.year);
796
797         assert (d("abc") == 0);
798         assert (!date.year);
799
800         assert (d("abc123") == 0);
801         assert (!date.year);
802
803         assert (d("2007-08") == 7);
804         assert (date.year  == 2007);
805         assert (date.month ==    8);
806
807         assert (d("+001985-04", 2) == 10);
808         assert (date.year  == 1985);
809         assert (date.month ==    4);
810
811         assert (d("2007-08-07") == 10);
812         assert (date.year  == 2007);
813         assert (date.month ==    8);
814         assert (date.day   ==    7);
815
816         assert (d("2008-20-30") == 4);
817         assert (date.year == 2008);
818         assert (!date.month);
819
820         assert (d("2007-02-30") == 7);
821         assert (date.year  == 2007);
822         assert (date.month ==    2);
823
824         assert (d("20060708") == 8);
825         assert (date.year  == 2006);
826         assert (date.month ==    7);
827         assert (date.day   ==    8);
828
829         assert (d("19953080") == 4);
830         assert (date.year == 1995);
831         assert (!date.month);
832
833         assert (d("+001985-04-12", 2) == 13);
834         assert (date.year  == 1985);
835         assert (date.month ==    4);
836         assert (date.day   ==   12);
837
838         assert (d("-0123450607", 2) == 11);
839         assert (date.year  == -12345);
840         assert (date.month ==      6);
841         assert (date.day   ==      7);
842
843         assert (d("1985W15") == 7);
844         assert (date.year  == 1985);
845         assert (date.month ==    4);
846         assert (date.day   ==    8);
847
848         assert (d("2008-W01") == 8);
849         assert (date.year  == 2007);
850         assert (date.month ==   12);
851         assert (date.day   ==   31);
852
853         assert (d("2008-W01-2") == 10);
854         assert (date.year  == 2008);
855         assert (date.month ==    1);
856         assert (date.day   ==    1);
857
858         assert (d("2009-W53-4") == 10);
859         assert (date.year  == 2009);
860         assert (date.month ==   12);
861         assert (date.day   ==   31);
862
863         assert (d("2009-W01-1") == 10);
864         assert (date.year  == 2008);
865         assert (date.month ==   12);
866         assert (date.day   ==   29);
867
868         assert (d("2009W537") == 8);
869         assert (date.year  == 2010);
870         assert (date.month ==    1);
871         assert (date.day   ==    3);
872
873         assert (d("2010W537") == 4);
874         assert (date.year  == 2010);
875         assert (!date.month);
876
877         assert (d("2009-W01-3") == 10);
878         assert (date.year  == 2008);
879         assert (date.month ==   12);
880         assert (date.day   ==   31);
881
882         assert (d("2009-W01-4") == 10);
883         assert (date.year  == 2009);
884         assert (date.month ==    1);
885         assert (date.day   ==    1);
886
887         /+ BUG: these don't work due to dayOfWeek being crap
888
889         assert (d("1000-W07-7") == 10);
890         assert (date.year  == 1000);
891         assert (date.month ==    2);
892         assert (date.day   ==   16);
893
894         assert (d("1500-W11-1") == 10);
895         assert (date.year  == 1500);
896         assert (date.month ==    3);
897         assert (date.day   ==   12);
898
899         assert (d("1700-W14-2") == 10);
900         assert (date.year  == 1700);
901         assert (date.month ==    4);
902         assert (date.day   ==    6);
903
904         assert (d("1800-W19-3") == 10);
905         assert (date.year  == 1800);
906         assert (date.month ==    5);
907         assert (date.day   ==    7);
908
909         assert (d("1900-W25-4") == 10);
910         assert (date.year  == 1900);
911         assert (date.month ==    6);
912         assert (date.day   ==   21);
913
914         assert (d("0900-W27-5") == 10);
915         assert (date.year  ==  900);
916         assert (date.month ==    7);
917         assert (date.day   ==    9);
918
919         assert (d("0800-W33-6") == 10);
920         assert (date.year  ==  800);
921         assert (date.month ==    8);
922         assert (date.day   ==   19);
923
924         assert (d("0700-W37-7") == 10);
925         assert (date.year  ==  700);
926         assert (date.month ==    9);
927         assert (date.day   ==   16);
928
929         assert (d("0600-W41-4") == 10);
930         assert (date.year  ==  600);
931         assert (date.month ==   10);
932         assert (date.day   ==    9);
933
934         assert (d("0500-W45-7") == 10);
935         assert (date.year  ==  500);
936         assert (date.month ==   11);
937         assert (date.day   ==   14);+/
938
939         assert (d("2000-W55") == 4);
940         assert (date.year == 2000);
941
942         assert (d("1980-002") == 8);
943         assert (date.year  == 1980);
944         assert (date.month ==    1);
945         assert (date.day   ==    2);
946
947         assert (d("1981-034") == 8);
948         assert (date.year  == 1981);
949         assert (date.month ==    2);
950         assert (date.day   ==    3);
951
952         assert (d("1982-063") == 8);
953         assert (date.year  == 1982);
954         assert (date.month ==    3);
955         assert (date.day   ==    4);
956
957         assert (d("1983-095") == 8);
958         assert (date.year  == 1983);
959         assert (date.month ==    4);
960         assert (date.day   ==    5);
961
962         assert (d("1984-127") == 8);
963         assert (date.year  == 1984);
964         assert (date.month ==    5);
965         assert (date.day   ==    6);
966
967         assert (d("1985-158") == 8);
968         assert (date.year  == 1985);
969         assert (date.month ==    6);
970         assert (date.day   ==    7);
971
972         assert (d("1986-189") == 8);
973         assert (date.year  == 1986);
974         assert (date.month ==    7);
975         assert (date.day   ==    8);
976
977         assert (d("1987-221") == 8);
978         assert (date.year  == 1987);
979         assert (date.month ==    8);
980         assert (date.day   ==    9);
981
982         assert (d("1988-254") == 8);
983         assert (date.year  == 1988);
984         assert (date.month ==    9);
985         assert (date.day   ==   10);
986
987         assert (d("1989-284") == 8);
988         assert (date.year  == 1989);
989         assert (date.month ==   10);
990         assert (date.day   ==   11);
991
992         assert (d("1990316") == 7);
993         assert (date.year  == 1990);
994         assert (date.month ==   11);
995         assert (date.day   ==   12);
996
997         assert (d("1991-347") == 8);
998         assert (date.year  == 1991);
999         assert (date.month ==   12);
1000         assert (date.day   ==   13);
1001
1002         assert (d("1992-000") == 4);
1003         assert (date.year == 1992);
1004
1005         assert (d("1993-370") == 4);
1006         assert (date.year == 1993);
1007
1008         // time
1009
1010         size_t t(char[] s) {
1011             date = date.init;
1012             return iso8601Time(s, date);
1013         }
1014
1015         assert (t("20") == 2);
1016         assert (date.hour == 20);
1017         assert (date.min  ==  0);
1018         assert (date.sec  ==  0);
1019
1020         assert (t("30") == 0);
1021
1022         assert (t("2004") == 4);
1023         assert (date.hour == 20);
1024         assert (date.min  ==  4);
1025         assert (date.sec  ==  0);
1026
1027         assert (t("200406") == 6);
1028         assert (date.hour == 20);
1029         assert (date.min  ==  4);
1030         assert (date.sec  ==  6);
1031
1032         assert (t("24:00") == 5);
1033         assert (date.hour == 24); // should compare equal with 0... can't just set to 0, loss of information
1034         assert (date.min  ==  0);
1035         assert (date.sec  ==  0);
1036
1037         assert (t("00:00") == 5);
1038         assert (date.hour == 0);
1039         assert (date.min  == 0);
1040         assert (date.sec  == 0);
1041
1042         assert (t("23:59:60") == 8);
1043         assert (date.hour == 23);
1044         assert (date.min  == 59);
1045         assert (date.sec  == 60); // leap second
1046
1047         assert (t("16:49:30,001") == 12);
1048         assert (date.hour == 16);
1049         assert (date.min  == 49);
1050         assert (date.sec  == 30);
1051         assert (date.ms   ==  1);
1052
1053         assert (t("15:48:29,1") == 10);
1054         assert (date.hour ==  15);
1055         assert (date.min  ==  48);
1056         assert (date.sec  ==  29);
1057         assert (date.ms   == 100);
1058
1059         assert (t("02:10:34,a") ==  8);
1060         assert (date.hour ==  2);
1061         assert (date.min  == 10);
1062         assert (date.sec  == 34);
1063
1064         assert (t("14:50,5") == 7);
1065         assert (date.hour == 14);
1066         assert (date.min  == 50);
1067         assert (date.sec  == 30);
1068
1069         assert (t("1540,4") == 6);
1070         assert (date.hour == 15);
1071         assert (date.min  == 40);
1072         assert (date.sec  == 24);
1073
1074         assert (t("1250,") == 4);
1075         assert (date.hour == 12);
1076         assert (date.min  == 50);
1077
1078         assert (t("14,5") == 4);
1079         assert (date.hour == 14);
1080         assert (date.min  == 30);
1081
1082         assert (t("12,") == 2);
1083         assert (date.hour == 12);
1084         assert (date.min  ==  0);
1085
1086         assert (t("24:00:01") == 5);
1087         assert (date.hour == 24);
1088         assert (date.min  ==  0);
1089         assert (date.sec  ==  0);
1090
1091         assert (t("12:34+:56") == 5);
1092         assert (date.hour == 12);
1093         assert (date.min  == 34);
1094         assert (date.sec  ==  0);
1095
1096         // just convert to UTC time for time zones?
1097
1098         assert (t("14:45:15Z") == 9);
1099         assert (date.hour == 14);
1100         assert (date.min  == 45);
1101         assert (date.sec  == 15);
1102
1103         assert (t("23Z") == 3);
1104         assert (date.hour == 23);
1105         assert (date.min  ==  0);
1106         assert (date.sec  ==  0);
1107
1108         assert (t("21:32:43-12:34") == 14);
1109         assert (date.hour == 10);
1110         assert (date.min  ==  6);
1111         assert (date.sec  == 43);
1112
1113         assert (t("12:34,5+0000") == 12);
1114         assert (date.hour == 12);
1115         assert (date.min  == 34);
1116         assert (date.sec  == 30);
1117
1118         assert (t("03:04+07") == 8);
1119         assert (date.hour == 20);
1120         assert (date.min  ==  4);
1121         assert (date.sec  ==  0);
1122
1123         assert (t("11,5+") == 4);
1124         assert (date.hour == 11);
1125         assert (date.min  == 30);
1126
1127         assert (t("07-") == 2);
1128         assert (date.hour == 7);
1129
1130         assert (t("06:12,7-") == 7);
1131         assert (date.hour ==  6);
1132         assert (date.min  == 12);
1133         assert (date.sec  == 42);
1134
1135         assert (t("050403,2+") == 8);
1136         assert (date.hour ==   5);
1137         assert (date.min  ==   4);
1138         assert (date.sec  ==   3);
1139         assert (date.ms   == 200);
1140
1141         assert (t("061656-") == 6);
1142         assert (date.hour ==   6);
1143         assert (date.min  ==  16);
1144         assert (date.sec  ==  56);
1145
1146         // date and time together
1147
1148         size_t b(char[] s) {
1149             date = date.init;
1150             return iso8601(s, date);
1151         }
1152
1153         assert (b("2007-08-09T12:34:56") == 19);
1154         assert (date.year  == 2007);
1155         assert (date.month ==    8);
1156         assert (date.day   ==    9);
1157         assert (date.hour  ==   12);
1158         assert (date.min   ==   34);
1159         assert (date.sec   ==   56);
1160
1161         assert (b("1985W155T235030,768") == 19);
1162         assert (date.year  == 1985);
1163         assert (date.month ==    4);
1164         assert (date.day   ==   12);
1165         assert (date.hour  ==   23);
1166         assert (date.min   ==   50);
1167         assert (date.sec   ==   30);
1168         assert (date.ms    ==  768);
1169
1170         // just convert to UTC time for time zones?
1171
1172         assert (b("2009-08-07T01:02:03Z") == 20);
1173         assert (date.year  == 2009);
1174         assert (date.month ==    8);
1175         assert (date.day   ==    7);
1176         assert (date.hour  ==    1);
1177         assert (date.min   ==    2);
1178         assert (date.sec   ==    3);
1179
1180         assert (b("2007-08-09T03:02,5+04:56") == 24);
1181         assert (date.year  == 2007);
1182         assert (date.month ==    8);
1183         assert (date.day   ==    8);
1184         assert (date.hour  ==   22);
1185         assert (date.min   ==    6);
1186         assert (date.sec   ==   30);
1187
1188         assert (b("20000228T2330-01") == 16);
1189         assert (date.year  == 2000);
1190         assert (date.month ==    2);
1191         assert (date.day   ==   29);
1192         assert (date.hour  ==    0);
1193         assert (date.min   ==   30);
1194         assert (date.sec   ==    0);
1195        
1196         assert (b("2007-01-01T00:00+01") == 19);
1197         assert (date.year  == 2006);
1198         assert (date.month ==   12);
1199         assert (date.day   ==   31);
1200         assert (date.hour  ==   23);
1201         assert (date.min   ==    0);
1202         assert (date.sec   ==    0);
1203
1204         assert (b("2007-12-31T23:00-01") == 19);
1205         assert (date.year  == 2007);
1206         assert (date.month ==   12);
1207         assert (date.day   ==   31);
1208         assert (date.hour  ==   24);
1209         assert (date.min   ==    0);
1210         assert (date.sec   ==    0);
1211
1212         assert (b("2007-12-31T23:01-01") == 19);
1213         assert (date.year  == 2008);
1214         assert (date.month ==    1);
1215         assert (date.day   ==    1);
1216         assert (date.hour  ==    0);
1217         assert (date.min   ==    1);
1218         assert (date.sec   ==    0);
1219
1220         assert (b("1902-03-04T1a") == 0);
1221         assert (b("1902-03-04T10:aa") == 0);
1222         assert (b("1902-03-04T10:1aa") == 0);
1223         assert (b("1985-04-12T10:15:30+0400") == 0);
1224         assert (b("1985-04-12T10:15:30-05:4") == 0);
1225         assert (b("1985-04-12T10:15:30-06:4b") == 0);
1226         assert (b("19020304T05:06:07") == 0);
1227         assert (b("1902-03-04T050607") == 0);
1228         assert (b("19020304T05:06:07abcd") == 0);
1229         assert (b("1902-03-04T050607abcd") == 0);
1230
1231         // unimplemented: intervals, durations, recurring intervals
1232     }
1233 }