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