comparison tango/tango/time/ISO8601.d @ 132:1700239cab2e trunk

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