Change Xerces exceptions to my own.
[shibboleth/xmltooling.git] / xmltooling / util / DateTime.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * DateTime.cpp
19  * 
20  * Manipulation of XML date/time data. 
21  */
22
23 /*
24  * This is mostly copied from Xerces-C, but they don't seem inclined to produce a usable
25  * class, so I had to incorporate my own version of it for now. I can't inherit it
26  * since the fields I need are private.
27  */
28
29 #include "internal.h"
30 #include "util/DateTime.h"
31
32 #ifndef WIN32
33 # include <errno.h>
34 #endif
35
36 #include <ctime>
37 #include <assert.h>
38 #include <xercesc/util/Janitor.hpp>
39
40 using namespace xmltooling;
41 using namespace std;
42
43 //
44 // constants used to process raw data (fBuffer)
45 //
46 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
47 //                                [{+|-}hh:mm']
48 //
49
50 static const XMLCh DURATION_STARTER     = chLatin_P;              // 'P'
51 static const XMLCh DURATION_Y           = chLatin_Y;              // 'Y'
52 static const XMLCh DURATION_M           = chLatin_M;              // 'M'
53 static const XMLCh DURATION_D           = chLatin_D;              // 'D'
54 static const XMLCh DURATION_H           = chLatin_H;              // 'H'
55 static const XMLCh DURATION_S           = chLatin_S;              // 'S'
56
57 static const XMLCh DATE_SEPARATOR       = chDash;                 // '-'
58 static const XMLCh TIME_SEPARATOR       = chColon;                // ':'
59 static const XMLCh TIMEZONE_SEPARATOR   = chColon;                // ':'
60 static const XMLCh DATETIME_SEPARATOR   = chLatin_T;              // 'T'
61 static const XMLCh MILISECOND_SEPARATOR = chPeriod;               // '.'
62
63 static const XMLCh UTC_STD_CHAR         = chLatin_Z;              // 'Z'
64 static const XMLCh UTC_POS_CHAR         = chPlus;                 // '+'
65 static const XMLCh UTC_NEG_CHAR         = chDash;                 // '-'
66
67 static const XMLCh UTC_SET[]            = {UTC_STD_CHAR           //"Z+-"
68                                          , UTC_POS_CHAR
69                                          , UTC_NEG_CHAR
70                                          , chNull};
71
72 static const int YMD_MIN_SIZE    = 10;   // CCYY-MM-DD
73 static const int YMONTH_MIN_SIZE = 7;    // CCYY_MM
74 static const int TIME_MIN_SIZE   = 8;    // hh:mm:ss
75 static const int TIMEZONE_SIZE   = 5;    // hh:mm
76 static const int DAY_SIZE        = 5;    // ---DD
77 //static const int MONTH_SIZE      = 6;    // --MM--
78 static const int MONTHDAY_SIZE   = 7;    // --MM-DD
79 static const int NOT_FOUND       = -1;
80
81 //define constants to be used in assigning default values for
82 //all date/time excluding duration
83 static const int YEAR_DEFAULT  = 2000;
84 static const int MONTH_DEFAULT = 01;
85 static const int DAY_DEFAULT   = 15;
86
87 // order-relation on duration is a partial order. The dates below are used to
88 // for comparison of 2 durations, based on the fact that
89 // duration x and y is x<=y iff s+x<=s+y
90 // see 3.2.6 duration W3C schema datatype specs
91 //
92 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
93 const int DateTime::DATETIMES[][TOTAL_SIZE] =
94 {
95     {1696, 9, 1, 0, 0, 0, 0, UTC_STD},
96         {1697, 2, 1, 0, 0, 0, 0, UTC_STD},
97         {1903, 3, 1, 0, 0, 0, 0, UTC_STD},
98         {1903, 7, 1, 0, 0, 0, 0, UTC_STD}
99 };
100
101 // ---------------------------------------------------------------------------
102 //  local methods
103 // ---------------------------------------------------------------------------
104 static inline int fQuotient(int a, int b)
105 {
106     div_t div_result = div(a, b);
107     return div_result.quot;
108 }
109
110 static inline int fQuotient(int temp, int low, int high)
111 {
112     return fQuotient(temp - low, high - low);
113 }
114
115 static inline int mod(int a, int b, int quotient)
116 {
117         return (a - quotient*b) ;
118 }
119
120 static inline int modulo (int temp, int low, int high)
121 {
122     //modulo(a - low, high - low) + low
123     int a = temp - low;
124     int b = high - low;
125     return (mod (a, b, fQuotient(a, b)) + low) ;
126 }
127
128 static inline bool isLeapYear(int year)
129 {
130     return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
131 }
132
133 static int maxDayInMonthFor(int year, int month)
134 {
135
136     if ( month == 4 || month == 6 || month == 9 || month == 11 )
137     {
138         return 30;
139     }
140     else if ( month==2 )
141     {
142         if ( isLeapYear(year) )
143             return 29;
144         else
145             return 28;
146     }
147     else
148     {
149         return 31;
150     }
151
152 }
153
154 // ---------------------------------------------------------------------------
155 //  static methods : for duration
156 // ---------------------------------------------------------------------------
157 /**
158  * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
159  *
160  * 3.2.6.2 Order relation on duration
161  *
162  *     In general, the order-relation on duration is a partial order since there is no
163  *  determinate relationship between certain durations such as one month (P1M) and 30 days (P30D).
164  *  The order-relation of two duration values x and y is x < y iff s+x < s+y for each qualified
165  *  dateTime s in the list below.
166  *
167  *     These values for s cause the greatest deviations in the addition of dateTimes and durations
168  *
169  **/
170 int DateTime::compare(const DateTime* const pDate1
171                        , const DateTime* const pDate2
172                        , bool  strict)
173 {
174     //REVISIT: this is unoptimazed vs of comparing 2 durations
175     //         Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
176     //
177
178     int resultA, resultB = XMLDateTime::INDETERMINATE;
179
180     //try and see if the objects are equal
181     if ( (resultA = compareOrder(pDate1, pDate2)) == XMLDateTime::EQUAL)
182         return XMLDateTime::EQUAL;
183
184     //long comparison algorithm is required
185     DateTime tempA, *pTempA = &tempA;
186     DateTime tempB, *pTempB = &tempB;
187
188     addDuration(pTempA, pDate1, 0);
189     addDuration(pTempB, pDate2, 0);
190     resultA = compareOrder(pTempA, pTempB);
191     if ( resultA == XMLDateTime::INDETERMINATE )
192         return XMLDateTime::INDETERMINATE;
193
194     addDuration(pTempA, pDate1, 1);
195     addDuration(pTempB, pDate2, 1);
196     resultB = compareOrder(pTempA, pTempB);
197     resultA = compareResult(resultA, resultB, strict);
198     if ( resultA == XMLDateTime::INDETERMINATE )
199         return XMLDateTime::INDETERMINATE;
200
201     addDuration(pTempA, pDate1, 2);
202     addDuration(pTempB, pDate2, 2);
203     resultB = compareOrder(pTempA, pTempB);
204     resultA = compareResult(resultA, resultB, strict);
205     if ( resultA == XMLDateTime::INDETERMINATE )
206         return XMLDateTime::INDETERMINATE;
207
208     addDuration(pTempA, pDate1, 3);
209     addDuration(pTempB, pDate2, 3);
210     resultB = compareOrder(pTempA, pTempB);
211     resultA = compareResult(resultA, resultB, strict);
212
213     return resultA;
214
215 }
216
217 //
218 // Form a new DateTime with duration and baseDate array
219 // Note: C++        Java
220 //       fNewDate   duration
221 //       fDuration  date
222 //
223
224 void DateTime::addDuration(DateTime*             fNewDate
225                             , const DateTime* const fDuration
226                             , int index)
227
228 {
229
230     //REVISIT: some code could be shared between normalize() and this method,
231     //         however is it worth moving it? The structures are different...
232     //
233
234     fNewDate->reset();
235     //add months (may be modified additionaly below)
236     int temp = DATETIMES[index][Month] + fDuration->fValue[Month];
237     fNewDate->fValue[Month] = modulo(temp, 1, 13);
238     int carry = fQuotient(temp, 1, 13);
239
240     //add years (may be modified additionaly below)
241     fNewDate->fValue[CentYear] =
242         DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
243
244     //add seconds
245     temp = DATETIMES[index][Second] + fDuration->fValue[Second];
246     carry = fQuotient (temp, 60);
247     fNewDate->fValue[Second] =  mod(temp, 60, carry);
248                 
249     //add minutes
250     temp = DATETIMES[index][Minute] + fDuration->fValue[Minute] + carry;
251     carry = fQuotient(temp, 60);
252     fNewDate->fValue[Minute] = mod(temp, 60, carry);
253
254     //add hours
255     temp = DATETIMES[index][Hour] + fDuration->fValue[Hour] + carry;
256     carry = fQuotient(temp, 24);
257     fNewDate->fValue[Hour] = mod(temp, 24, carry);
258                 
259     fNewDate->fValue[Day] =
260         DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
261
262     while ( true )
263     {
264         temp = maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]);
265         if ( fNewDate->fValue[Day] < 1 )
266         { //original fNewDate was negative
267             fNewDate->fValue[Day] +=
268                 maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]-1);
269             carry = -1;
270         }
271         else if ( fNewDate->fValue[Day] > temp )
272         {
273             fNewDate->fValue[Day] -= temp;
274             carry = 1;
275         }
276         else
277         {
278             break;
279         }
280
281         temp = fNewDate->fValue[Month] + carry;
282         fNewDate->fValue[Month] = modulo(temp, 1, 13);
283         fNewDate->fValue[CentYear] += fQuotient(temp, 1, 13);
284     }
285
286     //fNewDate->fValue[utc] = UTC_STD_CHAR;
287     fNewDate->fValue[utc] = UTC_STD;
288 }
289
290 int DateTime::compareResult(int resultA
291                              , int resultB
292                              , bool strict)
293 {
294
295     if ( resultB == XMLDateTime::INDETERMINATE )
296     {
297         return XMLDateTime::INDETERMINATE;
298     }
299     else if ( (resultA != resultB) &&
300               strict                )
301     {
302         return XMLDateTime::INDETERMINATE;
303     }
304     else if ( (resultA != resultB) &&
305               !strict               )
306     {
307         if ( (resultA != XMLDateTime::EQUAL) &&
308              (resultB != XMLDateTime::EQUAL)  )
309         {
310             return XMLDateTime::INDETERMINATE;
311         }
312         else
313         {
314             return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
315         }
316     }
317
318     return resultA;
319         
320 }
321
322 // ---------------------------------------------------------------------------
323 //  static methods : for others
324 // ---------------------------------------------------------------------------
325 int DateTime::compare(const DateTime* const pDate1
326                        , const DateTime* const pDate2)
327 {
328
329     if (pDate1->fValue[utc] == pDate2->fValue[utc])
330     {
331         return DateTime::compareOrder(pDate1, pDate2);
332     }
333
334     int c1, c2;
335
336     if ( pDate1->isNormalized())
337     {
338         c1 = compareResult(pDate1, pDate2, false, UTC_POS);
339         c2 = compareResult(pDate1, pDate2, false, UTC_NEG);
340         return getRetVal(c1, c2);
341     }
342     else if ( pDate2->isNormalized())
343     {
344         c1 = compareResult(pDate1, pDate2, true, UTC_POS);
345         c2 = compareResult(pDate1, pDate2, true, UTC_NEG);
346         return getRetVal(c1, c2);
347     }
348
349     return XMLDateTime::INDETERMINATE;  
350 }
351
352 int DateTime::compareResult(const DateTime* const pDate1
353                              , const DateTime* const pDate2
354                              , bool  set2Left
355                              , int   utc_type)
356 {
357     DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
358
359     tmpDate.fTimeZone[hh] = 14;
360     tmpDate.fTimeZone[mm] = 0;
361     tmpDate.fValue[utc] = utc_type;
362     tmpDate.normalize();
363
364     return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
365                       DateTime::compareOrder(pDate1, &tmpDate));
366 }
367
368 int DateTime::compareOrder(const DateTime* const lValue
369                             , const DateTime* const rValue)
370                             //, MemoryManager* const memMgr)
371 {
372     //
373     // If any of the them is not normalized() yet,
374     // we need to do something here.
375     //
376     DateTime lTemp = *lValue;
377     DateTime rTemp = *rValue;
378
379     lTemp.normalize();
380     rTemp.normalize();
381
382     for ( int i = 0 ; i < TOTAL_SIZE; i++ )
383     {
384         if ( lTemp.fValue[i] < rTemp.fValue[i] )
385         {
386             return XMLDateTime::LESS_THAN;
387         }
388         else if ( lTemp.fValue[i] > rTemp.fValue[i] )
389         {
390             return XMLDateTime::GREATER_THAN;
391         }
392     }
393
394     if ( lTemp.fHasTime)
395     {
396         if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
397         {
398             return XMLDateTime::LESS_THAN;
399         }
400         else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
401         {
402             return XMLDateTime::GREATER_THAN;
403         }
404     }
405
406     return XMLDateTime::EQUAL;
407 }
408
409 // ---------------------------------------------------------------------------
410 //  ctor and dtor
411 // ---------------------------------------------------------------------------
412 DateTime::~DateTime()
413 {
414     delete[] fBuffer;
415 }
416
417 DateTime::DateTime()
418 : fStart(0)
419 , fEnd(0)
420 , fBufferMaxLen(0)
421 , fBuffer(0)
422 , fMiliSecond(0)
423 , fHasTime(false)
424 {
425     reset();
426 }
427
428 DateTime::DateTime(const XMLCh* const aString)
429 : fStart(0)
430 , fEnd(0)
431 , fBufferMaxLen(0)
432 , fBuffer(0)
433 , fMiliSecond(0)
434 , fHasTime(false)
435 {
436     setBuffer(aString);
437 }
438
439 DateTime::DateTime(time_t epoch)
440 : fStart(0)
441 , fEnd(0)
442 , fBufferMaxLen(0)
443 , fBuffer(0)
444 , fMiliSecond(0)
445 , fHasTime(false)
446 {
447 #ifndef HAVE_GMTIME_R
448     struct tm* ptime=gmtime(&epoch);
449 #else
450     struct tm res;
451     struct tm* ptime=gmtime_r(&epoch,&res);
452 #endif
453     char timebuf[32];
454     strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
455     auto_ptr_XMLCh timeptr(timebuf);
456     setBuffer(timeptr.get());
457 }
458
459 // -----------------------------------------------------------------------
460 // Copy ctor and Assignment operators
461 // -----------------------------------------------------------------------
462
463 DateTime::DateTime(const DateTime &toCopy)
464 : fBufferMaxLen(0)
465 , fBuffer(0)
466 {
467     copy(toCopy);
468 }
469
470 DateTime& DateTime::operator=(const DateTime& rhs)
471 {
472     if (this == &rhs)
473         return *this;
474
475     copy(rhs);
476     return *this;
477 }
478
479 // -----------------------------------------------------------------------
480 // Implementation of Abstract Interface
481 // -----------------------------------------------------------------------
482
483 //
484 // We may simply return the handle to fBuffer
485 //
486 const XMLCh*  DateTime::getRawData() const
487 {
488     //assertBuffer();
489     return fBuffer;
490 }
491
492
493 const XMLCh*  DateTime::getFormattedString() const
494 {
495     return getRawData();
496 }
497
498 int DateTime::getSign() const
499 {
500     return 0;
501 }
502
503 time_t DateTime::getEpoch() const
504 {
505     struct tm t;
506     t.tm_sec=getSecond();
507     t.tm_min=getMinute();
508     t.tm_hour=getHour();
509     t.tm_mday=getDay();
510     t.tm_mon=getMonth()-1;
511     t.tm_year=getYear()-1900;
512     t.tm_isdst=0;
513 #if defined(HAVE_TIMEGM)
514     return timegm(&t);
515 #else
516     // Windows, and hopefully most others...?
517     return mktime(&t) - timezone;
518 #endif
519 }
520
521 // ---------------------------------------------------------------------------
522 //  Parsers
523 // ---------------------------------------------------------------------------
524
525 //
526 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
527 //
528 void DateTime::parseDateTime()
529 {
530     initParser();
531     getDate();
532
533     //fStart is supposed to point to 'T'
534     if (fBuffer[fStart++] != DATETIME_SEPARATOR)
535         throw XMLParserException("Invalid separator between date and time.");
536
537     getTime();
538     validateDateTime();
539     normalize();
540     fHasTime = true;
541 }
542
543 //
544 // [-]{CCYY-MM-DD}[TimeZone]
545 //
546 void DateTime::parseDate()
547 {
548     initParser();
549     getDate();
550     parseTimeZone();
551     validateDateTime();
552     normalize();
553 }
554
555 void DateTime::parseTime()
556 {
557     initParser();
558
559     // time initialize to default values
560     fValue[CentYear]= YEAR_DEFAULT;
561     fValue[Month]   = MONTH_DEFAULT;
562     fValue[Day]     = DAY_DEFAULT;
563
564     getTime();
565
566     validateDateTime();
567     normalize();
568     fHasTime = true;
569 }
570
571 //
572 // {---DD}[TimeZone]
573 //  01234
574 //
575 void DateTime::parseDay()
576 {
577     initParser();
578
579     if (fBuffer[0] != DATE_SEPARATOR ||
580         fBuffer[1] != DATE_SEPARATOR ||
581         fBuffer[2] != DATE_SEPARATOR  )
582     {
583         throw XMLParserException("Invalid character in date.");
584     }
585
586     //initialize values
587     fValue[CentYear] = YEAR_DEFAULT;
588     fValue[Month]    = MONTH_DEFAULT;
589     fValue[Day]      = parseInt(fStart+3, fStart+5);
590
591     if ( DAY_SIZE < fEnd )
592     {
593         int sign = findUTCSign(DAY_SIZE);
594         if ( sign < 0 )
595         {
596             throw XMLParserException("Invalid character in date.");
597         }
598         else
599         {
600             getTimeZone(sign);
601         }
602     }
603
604     validateDateTime();
605     normalize();
606 }
607
608 //
609 // {--MM--}[TimeZone]
610 // {--MM}[TimeZone]
611 //  012345
612 //
613 void DateTime::parseMonth()
614 {
615     initParser();
616
617     if (fBuffer[0] != DATE_SEPARATOR ||
618         fBuffer[1] != DATE_SEPARATOR  )
619     {
620         throw XMLParserException("Invalid character in date.");
621     }
622
623     //set constants
624     fValue[CentYear] = YEAR_DEFAULT;
625     fValue[Day]      = DAY_DEFAULT;
626     fValue[Month]    = parseInt(2, 4);
627
628     // REVISIT: allow both --MM and --MM-- now. 
629     // need to remove the following lines to disallow --MM-- 
630     // when the errata is officially in the rec. 
631     fStart = 4;
632     if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR ) 
633     { 
634         fStart += 2; 
635     } 
636
637     //
638     // parse TimeZone if any
639     //
640     if ( fStart < fEnd )
641     {
642         int sign = findUTCSign(fStart);
643         if ( sign < 0 )
644         {
645             throw XMLParserException("Invalid character in date.");
646         }
647         else
648         {
649             getTimeZone(sign);
650         }
651     }
652
653     validateDateTime();
654     normalize();
655 }
656
657 //
658 //[-]{CCYY}[TimeZone]
659 // 0  1234
660 //
661 void DateTime::parseYear()
662 {
663     initParser();
664
665     // skip the first '-' and search for timezone
666     //
667     int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
668
669     if (sign == NOT_FOUND)
670     {
671         fValue[CentYear] = parseIntYear(fEnd);
672     }
673     else
674     {
675         fValue[CentYear] = parseIntYear(sign);
676         getTimeZone(sign);
677     }
678
679     //initialize values
680     fValue[Month] = MONTH_DEFAULT;
681     fValue[Day]   = DAY_DEFAULT;   //java is 1
682
683     validateDateTime();
684     normalize();
685 }
686
687 //
688 //{--MM-DD}[TimeZone]
689 // 0123456
690 //
691 void DateTime::parseMonthDay()
692 {
693     initParser();
694
695     if (fBuffer[0] != DATE_SEPARATOR ||
696         fBuffer[1] != DATE_SEPARATOR ||
697         fBuffer[4] != DATE_SEPARATOR )
698     {
699         throw XMLParserException("Invalid character in date.");
700     }
701
702
703     //initialize
704     fValue[CentYear] = YEAR_DEFAULT;
705     fValue[Month]    = parseInt(2, 4);  
706     fValue[Day]      = parseInt(5, 7);
707
708     if ( MONTHDAY_SIZE < fEnd )
709     {
710         int sign = findUTCSign(MONTHDAY_SIZE);
711         if ( sign<0 )
712         {
713             throw XMLParserException("Invalid character in date.");
714         }
715         else
716         {
717             getTimeZone(sign);
718         }
719     }
720
721     validateDateTime();
722     normalize();
723 }
724
725 void DateTime::parseYearMonth()
726 {
727     initParser();
728
729     // get date
730     getYearMonth();
731     fValue[Day] = DAY_DEFAULT;
732     parseTimeZone();
733
734     validateDateTime();
735     normalize();
736 }
737
738 //
739 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
740 //
741 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
742 //
743 //  Note: the n above shall be >= 0
744 //        if no time element found, 'T' shall be absent
745 //
746 void DateTime::parseDuration()
747 {
748     initParser();
749
750     // must start with '-' or 'P'
751     //
752     XMLCh c = fBuffer[fStart++];
753     if ( (c != DURATION_STARTER) &&
754          (c != chDash)            )
755     {
756         throw XMLParserException("Invalid character in time.");
757     }
758
759     // 'P' must ALWAYS be present in either case
760     if ( (c == chDash) &&
761          (fBuffer[fStart++]!= DURATION_STARTER ))
762     {
763         throw XMLParserException("Invalid character in time.");
764     }
765
766     // java code
767     //date[utc]=(c=='-')?'-':0;
768     //fValue[utc] = UTC_STD;
769     fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
770
771     int negate = ( fBuffer[0] == chDash ? -1 : 1);
772
773     //
774     // No negative value is allowed after 'P'
775     //
776     // eg P-1234, invalid
777     //
778     if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
779     {
780         throw XMLParserException("Invalid character in time.");
781     }
782
783     //at least one number and designator must be seen after P
784     bool designator = false;
785
786     int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
787     if ( endDate == NOT_FOUND )
788     {
789         endDate = fEnd;  // 'T' absent
790     }
791
792     //find 'Y'
793     int end = indexOf(fStart, endDate, DURATION_Y);
794     if ( end != NOT_FOUND )
795     {
796         //scan year
797         fValue[CentYear] = negate * parseInt(fStart, end);
798         fStart = end+1;
799         designator = true;
800     }
801
802     end = indexOf(fStart, endDate, DURATION_M);
803     if ( end != NOT_FOUND )
804     {
805         //scan month
806         fValue[Month] = negate * parseInt(fStart, end);
807         fStart = end+1;
808         designator = true;
809     }
810
811     end = indexOf(fStart, endDate, DURATION_D);
812     if ( end != NOT_FOUND )
813     {
814         //scan day
815         fValue[Day] = negate * parseInt(fStart,end);
816         fStart = end+1;
817         designator = true;
818     }
819
820     if ( (fEnd == endDate) &&   // 'T' absent
821          (fStart != fEnd)   )   // something after Day
822     {
823         throw XMLParserException("Invalid character in time.");
824     }
825
826     if ( fEnd != endDate ) // 'T' present
827     {
828         //scan hours, minutes, seconds
829         //
830
831         // skip 'T' first
832         end = indexOf(++fStart, fEnd, DURATION_H);
833         if ( end != NOT_FOUND )
834         {
835             //scan hours
836             fValue[Hour] = negate * parseInt(fStart, end);
837             fStart = end+1;
838             designator = true;
839         }
840
841         end = indexOf(fStart, fEnd, DURATION_M);
842         if ( end != NOT_FOUND )
843         {
844             //scan min
845             fValue[Minute] = negate * parseInt(fStart, end);
846             fStart = end+1;
847             designator = true;
848         }
849
850         end = indexOf(fStart, fEnd, DURATION_S);
851         if ( end != NOT_FOUND )
852         {
853             //scan seconds
854             int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
855
856             /***
857              * Schema Errata: E2-23
858              * at least one digit must follow the decimal point if it appears. 
859              * That is, the value of the seconds component must conform 
860              * to the following pattern: [0-9]+(.[0-9]+)? 
861              */
862             if ( mlsec != NOT_FOUND )
863             {
864                 /***
865                  * make usure there is something after the '.' and before the end.
866                  */
867                 if ( mlsec+1 == end )
868                 {
869                     throw XMLParserException("Invalid character in time.");
870                 }
871
872                 fValue[Second]     = negate * parseInt(fStart, mlsec);
873                 fMiliSecond        = negate * parseMiliSecond(mlsec+1, end);
874             }
875             else
876             {
877                 fValue[Second] = negate * parseInt(fStart,end);
878             }
879
880             fStart = end+1;
881             designator = true;
882         }
883
884         // no additional data should appear after last item
885         // P1Y1M1DT is illigal value as well
886         if ( (fStart != fEnd) ||
887               fBuffer[--fStart] == DATETIME_SEPARATOR )
888         {
889             throw XMLParserException("Invalid character in time.");
890         }
891     }
892
893     if ( !designator )
894     {
895         throw XMLParserException("Invalid character in time.");
896     }
897
898 }
899
900 // ---------------------------------------------------------------------------
901 //  Scanners
902 // ---------------------------------------------------------------------------
903
904 //
905 // [-]{CCYY-MM-DD}
906 //
907 // Note: CCYY could be more than 4 digits
908 //       Assuming fStart point to the beginning of the Date Section
909 //       fStart updated to point to the position right AFTER the second 'D'
910 //       Since the lenght of CCYY might be variable, we can't check format upfront
911 //
912 void DateTime::getDate()
913 {
914
915     // Ensure enough chars in buffer
916     if ( (fStart+YMD_MIN_SIZE) > fEnd)
917         throw XMLParserException("Date/time string not complete.");
918
919     getYearMonth();    // Scan YearMonth and
920                        // fStart point to the next '-'
921
922     if (fBuffer[fStart++] != DATE_SEPARATOR)
923     {
924         throw XMLParserException("CCYY-MM must be followed by '-' sign.");
925     }
926
927     fValue[Day] = parseInt(fStart, fStart+2);
928     fStart += 2 ;  //fStart points right after the Day
929
930     return;
931 }
932
933 //
934 // hh:mm:ss[.msssss]['Z']
935 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
936 // 012345678
937 //
938 // Note: Assuming fStart point to the beginning of the Time Section
939 //       fStart updated to point to the position right AFTER the second 's'
940 //                                                  or ms if any
941 //
942 void DateTime::getTime()
943 {
944
945     // Ensure enough chars in buffer
946     if ( (fStart+TIME_MIN_SIZE) > fEnd)
947         throw XMLParserException("Incomplete Time Format.");
948
949     // check (fixed) format first
950     if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
951         (fBuffer[fStart + 5] != TIME_SEPARATOR)  )
952     {
953         throw XMLParserException("Error in parsing time.");
954     }
955
956     //
957     // get hours, minute and second
958     //
959     fValue[Hour]   = parseInt(fStart + 0, fStart + 2);
960     fValue[Minute] = parseInt(fStart + 3, fStart + 5);
961     fValue[Second] = parseInt(fStart + 6, fStart + 8);
962     fStart += 8;
963
964     // to see if any ms and/or utc part after that
965     if (fStart >= fEnd)
966         return;
967
968     //find UTC sign if any
969     int sign = findUTCSign(fStart);
970
971     //parse miliseconds
972     int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
973     if ( milisec != NOT_FOUND )
974     {
975         fStart++;   // skip the '.'
976         // make sure we have some thing between the '.' and fEnd
977         if (fStart >= fEnd)
978         {
979             throw XMLParserException("ms should be present once '.' is present.");
980         }
981
982         if ( sign == NOT_FOUND )
983         {
984             fMiliSecond = parseMiliSecond(fStart, fEnd);  //get ms between '.' and fEnd
985             fStart = fEnd;
986         }
987         else
988         {
989             fMiliSecond = parseMiliSecond(fStart, sign);  //get ms between UTC sign and fEnd
990         }
991         }
992     else if(sign == 0 || sign != fStart)
993     {
994         throw XMLParserException("Seconds has more than 2 digits.");
995     }
996
997     //parse UTC time zone (hh:mm)
998     if ( sign > 0 ) {
999         getTimeZone(sign);
1000     }
1001
1002 }
1003
1004 //
1005 // [-]{CCYY-MM}
1006 //
1007 // Note: CCYY could be more than 4 digits
1008 //       fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1009 //
1010 void DateTime::getYearMonth()
1011 {
1012
1013     // Ensure enough chars in buffer
1014     if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1015         throw XMLParserException("Incomplete YearMonth Format.");
1016
1017     // skip the first leading '-'
1018     int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1019
1020     //
1021     // search for year separator '-'
1022     //
1023     int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1024     if ( yearSeparator == NOT_FOUND)
1025         throw XMLParserException("Year separator is missing or misplaced.");
1026
1027     fValue[CentYear] = parseIntYear(yearSeparator);
1028     fStart = yearSeparator + 1;  //skip the '-' and point to the first M
1029
1030     //
1031     //gonna check we have enough byte for month
1032     //
1033     if ((fStart + 2) > fEnd )
1034         throw XMLParserException("No month in buffer.");
1035
1036     fValue[Month] = parseInt(fStart, yearSeparator + 3);
1037     fStart += 2;  //fStart points right after the MONTH
1038
1039     return;
1040 }
1041
1042 void DateTime::parseTimeZone()
1043 {
1044     if ( fStart < fEnd )
1045     {
1046         int sign = findUTCSign(fStart);
1047         if ( sign < 0 )
1048         {
1049             throw XMLParserException("Error in month parsing.");
1050         }
1051         else
1052         {
1053             getTimeZone(sign);
1054         }
1055     }
1056
1057     return;
1058 }
1059
1060 //
1061 // 'Z'
1062 // ['+'|'-']hh:mm
1063 //
1064 // Note: Assuming fStart points to the beginning of TimeZone section
1065 //       fStart updated to meet fEnd
1066 //
1067 void DateTime::getTimeZone(const int sign)
1068 {
1069
1070     if ( fBuffer[sign] == UTC_STD_CHAR )
1071     {
1072         if ((sign + 1) != fEnd )
1073         {
1074             throw XMLParserException("Error in parsing time zone.");
1075         }               
1076
1077         return; 
1078     }
1079
1080     //
1081     // otherwise, it has to be this format
1082     // '[+|-]'hh:mm
1083     //    1   23456 7
1084     //   sign      fEnd
1085     //
1086     if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd )      ||
1087          ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1088     {
1089         throw XMLParserException("Error in parsing time zone.");
1090     }
1091
1092     fTimeZone[hh] = parseInt(sign+1, sign+3);           
1093     fTimeZone[mm] = parseInt(sign+4, fEnd);
1094                         
1095     return;
1096 }
1097
1098 // ---------------------------------------------------------------------------
1099 //  Validator and normalizer
1100 // ---------------------------------------------------------------------------
1101
1102 /**
1103  * If timezone present - normalize dateTime  [E Adding durations to dateTimes]
1104  *
1105  * @param date   CCYY-MM-DDThh:mm:ss+03
1106  * @return CCYY-MM-DDThh:mm:ssZ
1107  */
1108 void DateTime::normalize()
1109 {
1110
1111     if ((fValue[utc] == UTC_UNKNOWN) ||
1112         (fValue[utc] == UTC_STD)      )
1113         return;
1114
1115     int negate = (fValue[utc] == UTC_POS)? -1: 1;
1116
1117     // add mins
1118     int temp = fValue[Minute] + negate * fTimeZone[mm];
1119     int carry = fQuotient(temp, 60);
1120     fValue[Minute] = mod(temp, 60, carry);
1121
1122     //add hours
1123     temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1124     carry = fQuotient(temp, 24);
1125     fValue[Hour] = mod(temp, 24, carry);
1126
1127     fValue[Day] += carry;
1128
1129     while (1)
1130     {
1131         temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1132         if (fValue[Day] < 1)
1133         {
1134             fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1135             carry = -1;
1136         }
1137         else if ( fValue[Day] > temp )
1138         {
1139             fValue[Day] -= temp;
1140             carry = 1;
1141         }
1142         else
1143         {
1144             break;
1145         }
1146
1147         temp = fValue[Month] + carry;
1148         fValue[Month] = modulo(temp, 1, 13);
1149         fValue[CentYear] += fQuotient(temp, 1, 13);
1150     }
1151
1152     // set to normalized
1153     fValue[utc] = UTC_STD;
1154
1155     return;
1156 }
1157
1158 void DateTime::validateDateTime() const
1159 {
1160
1161     //REVISIT: should we throw an exception for not valid dates
1162     //          or reporting an error message should be sufficient?
1163     if ( fValue[CentYear] == 0 )
1164     {
1165         throw XMLParserException("The year \"0000\" is an illegal year value");
1166     }
1167
1168     if ( fValue[Month] < 1  ||
1169          fValue[Month] > 12  )
1170     {
1171         throw XMLParserException("The month must have values 1 to 12");
1172     }
1173
1174     //validate days
1175     if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1176          fValue[Day] == 0 )
1177     {
1178         throw XMLParserException("The day must have values 1 to 31");
1179     }
1180
1181     //validate hours
1182     if ((fValue[Hour] < 0)  ||
1183         (fValue[Hour] > 24) ||
1184         ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1185                                   (fValue[Second] !=0) ||
1186                                   (fMiliSecond    !=0))))
1187     {
1188         throw XMLParserException("Hour must have values 0-23");
1189     }
1190
1191     //validate minutes
1192     if ( fValue[Minute] < 0 ||
1193          fValue[Minute] > 59 )
1194     {
1195         throw XMLParserException("Minute must have values 0-59");
1196     }
1197
1198     //validate seconds
1199     if ( fValue[Second] < 0 ||
1200          fValue[Second] > 60 )
1201     {
1202         throw XMLParserException("Second must have values 0-60");
1203     }
1204
1205     //validate time-zone hours
1206     if ( (abs(fTimeZone[hh]) > 14) ||
1207          ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1208     {
1209         throw XMLParserException("Time zone should have range -14..+14");
1210     }
1211
1212     //validate time-zone minutes
1213     if ( abs(fTimeZone[mm]) > 59 )
1214     {
1215         throw XMLParserException("Minute must have values 0-59");
1216     }
1217         
1218     return;
1219 }
1220
1221 // -----------------------------------------------------------------------
1222 // locator and converter
1223 // -----------------------------------------------------------------------
1224 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1225 {
1226     for ( int i = start; i < end; i++ )
1227         if ( fBuffer[i] == ch )
1228             return i;
1229
1230     return NOT_FOUND;
1231 }
1232
1233 int DateTime::findUTCSign (const int start)
1234 {
1235     int  pos;
1236     for ( int index = start; index < fEnd; index++ )
1237     {
1238         pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1239         if ( pos != NOT_FOUND)
1240         {
1241             fValue[utc] = pos+1;   // refer to utcType, there is 1 diff
1242             return index;
1243         }
1244     }
1245
1246     return NOT_FOUND;
1247 }
1248
1249 //
1250 // Note:
1251 //    start: starting point in fBuffer
1252 //    end:   ending point in fBuffer (exclusive)
1253 //    fStart NOT updated
1254 //
1255 int DateTime::parseInt(const int start, const int end) const
1256 {
1257     unsigned int retVal = 0;
1258     for (int i=start; i < end; i++) {
1259
1260         if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1261             throw XMLParserException("Invalid non-numeric characters.");
1262
1263         retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1264     }
1265
1266     return (int) retVal;
1267 }
1268
1269 //
1270 // Note:
1271 //    start: pointing to the first digit after the '.'
1272 //    end:   pointing to one position after the last digit
1273 //    fStart NOT updated
1274 //
1275 double DateTime::parseMiliSecond(const int start, const int end) const
1276 {
1277
1278     unsigned int  miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1279     XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1280     ArrayJanitor<XMLCh> janMili(miliSecData);
1281     XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1282     *(miliSecData + miliSecLen) = chNull;
1283
1284     char *nptr = XMLString::transcode(miliSecData);
1285     ArrayJanitor<char> jan(nptr);
1286     size_t   strLen = strlen(nptr);
1287     char *endptr = 0;
1288     errno = 0;
1289
1290     //printf("milisec=<%s>\n", nptr);
1291
1292     double retVal = strtod(nptr, &endptr);
1293
1294     // check if all chars are valid char
1295     if ( (endptr - nptr) != strLen)
1296         throw XMLParserException("Invalid non-numeric characters.");
1297
1298     // we don't check underflow occurs since
1299     // nothing we can do about it.
1300     return retVal;
1301 }
1302
1303 //
1304 // [-]CCYY
1305 //
1306 // Note: start from fStart
1307 //       end (exclusive)
1308 //       fStart NOT updated
1309 //
1310 int DateTime::parseIntYear(const int end) const
1311 {
1312     // skip the first leading '-'
1313     int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1314
1315     int length = end - start;
1316     if (length < 4)
1317     {
1318         throw XMLParserException("Year must have 'CCYY' format");
1319     }
1320     else if (length > 4 &&
1321              fBuffer[start] == chDigit_0)
1322     {
1323         throw XMLParserException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
1324     }
1325
1326     bool negative = (fBuffer[0] == chDash);
1327     int  yearVal = parseInt((negative ? 1 : 0), end);
1328     return ( negative ? (-1) * yearVal : yearVal );
1329 }
1330
1331 /***
1332  * E2-41
1333  *
1334  *  3.2.7.2 Canonical representation
1335  * 
1336  *  Except for trailing fractional zero digits in the seconds representation, 
1337  *  '24:00:00' time representations, and timezone (for timezoned values), 
1338  *  the mapping from literals to values is one-to-one. Where there is more 
1339  *  than one possible representation, the canonical representation is as follows: 
1340  *  redundant trailing zero digits in fractional-second literals are prohibited. 
1341  *  An hour representation of '24' is prohibited. Timezoned values are canonically
1342  *  represented by appending 'Z' to the nontimezoned representation. (All 
1343  *  timezoned dateTime values are UTC.) 
1344  *
1345  *  .'24:00:00' -> '00:00:00'
1346  *  .milisecond: trailing zeros removed
1347  *  .'Z'
1348  *
1349  ***/
1350 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1351 {
1352     XMLCh *miliStartPtr, *miliEndPtr;
1353     searchMiliSeconds(miliStartPtr, miliEndPtr);
1354     size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1355
1356     XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1357     XMLCh* retPtr = retBuf;
1358
1359     // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z'    ('.'s+)?
1360     //      2+  8       1      8   1
1361     //
1362     int additionalLen = fillYearString(retPtr, CentYear);
1363     if(additionalLen != 0)
1364     {
1365         // very bad luck; have to resize the buffer...
1366         XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1367         XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1368         retPtr = tmpBuf+(retPtr-retBuf);
1369         delete[] retBuf;
1370         retBuf = tmpBuf;
1371     }
1372     *retPtr++ = DATE_SEPARATOR;
1373     fillString(retPtr, Month, 2);
1374     *retPtr++ = DATE_SEPARATOR;
1375     fillString(retPtr, Day, 2);
1376     *retPtr++ = DATETIME_SEPARATOR;
1377
1378     fillString(retPtr, Hour, 2);
1379     if (fValue[Hour] == 24)
1380     {
1381         *(retPtr - 2) = chDigit_0;
1382         *(retPtr - 1) = chDigit_0;
1383     }
1384     *retPtr++ = TIME_SEPARATOR;
1385     fillString(retPtr, Minute, 2);
1386     *retPtr++ = TIME_SEPARATOR;
1387     fillString(retPtr, Second, 2);
1388
1389     if (miliSecondsLen)
1390     {
1391         *retPtr++ = chPeriod;
1392         XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1393         retPtr += miliSecondsLen;
1394     }
1395
1396     *retPtr++ = UTC_STD_CHAR;
1397     *retPtr = chNull;
1398
1399     return retBuf;
1400 }
1401
1402 /***
1403  * 3.2.8 time
1404  *
1405  *  . either the time zone must be omitted or, 
1406  *    if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".   
1407  *
1408  *  . Additionally, the canonical representation for midnight is 00:00:00.
1409  *
1410 ***/
1411 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1412 {
1413     XMLCh *miliStartPtr, *miliEndPtr;
1414     searchMiliSeconds(miliStartPtr, miliEndPtr);
1415     size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1416
1417     XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1418     XMLCh* retPtr = retBuf;
1419
1420     // 'hh:mm:ss'Z'    ('.'s+)?
1421     //      8    1
1422     //
1423
1424     fillString(retPtr, Hour, 2);
1425     if (fValue[Hour] == 24)
1426     {
1427         *(retPtr - 2) = chDigit_0;
1428         *(retPtr - 1) = chDigit_0;
1429     }
1430     *retPtr++ = TIME_SEPARATOR;
1431     fillString(retPtr, Minute, 2);
1432     *retPtr++ = TIME_SEPARATOR;
1433     fillString(retPtr, Second, 2);
1434
1435     if (miliSecondsLen)
1436     {
1437         *retPtr++ = chPeriod;
1438         XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1439         retPtr += miliSecondsLen;
1440     }
1441
1442     *retPtr++ = UTC_STD_CHAR;
1443     *retPtr = chNull;
1444
1445     return retBuf;
1446 }
1447
1448 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1449 {
1450     XMLCh strBuffer[16];
1451     assert(expLen < 16);
1452     XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1453     int   actualLen = XMLString::stringLen(strBuffer);
1454     int   i;
1455     //append leading zeros
1456     for (i = 0; i < expLen - actualLen; i++)
1457     {
1458         *ptr++ = chDigit_0;
1459     }
1460
1461     for (i = 0; i < actualLen; i++)
1462     {
1463         *ptr++ = strBuffer[i];
1464     }
1465
1466 }
1467
1468 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1469 {
1470     XMLCh strBuffer[16];
1471     // let's hope we get no years of 15 digits...
1472     XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1473     int   actualLen = XMLString::stringLen(strBuffer);
1474     // don't forget that years can be negative...
1475     int negativeYear = 0;
1476     if(strBuffer[0] == chDash)
1477     {
1478         *ptr++ = strBuffer[0];
1479         negativeYear = 1;
1480     }
1481     int   i;
1482     //append leading zeros
1483     for (i = 0; i < 4 - actualLen+negativeYear; i++)
1484     {
1485         *ptr++ = chDigit_0;
1486     }
1487
1488     for (i = negativeYear; i < actualLen; i++)
1489     {
1490         *ptr++ = strBuffer[i];
1491     }
1492     if(actualLen > 4)
1493         return actualLen-4;
1494     return 0;
1495 }
1496
1497 /***
1498  *
1499  *   .check if the rawData has the mili second component
1500  *   .capture the substring
1501  *
1502  ***/
1503 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1504 {
1505     miliStartPtr = miliEndPtr = 0;
1506
1507     int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1508     if (milisec == -1)
1509         return;
1510
1511     miliStartPtr = fBuffer + milisec + 1;
1512     miliEndPtr   = miliStartPtr;
1513     while (*miliEndPtr)
1514     {
1515         if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1516             break;
1517
1518         miliEndPtr++;
1519     }
1520
1521     //remove trailing zeros
1522     while( *(miliEndPtr - 1) == chDigit_0)
1523         miliEndPtr--;
1524
1525     return;
1526 }
1527