2 * Copyright 2001-2006 Internet2
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
20 * Manipulation of XML date/time data.
\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
29 #include "internal.h"
\r
30 #include "util/DateTime.h"
\r
37 #include <xercesc/util/Janitor.hpp>
\r
38 #include <xercesc/util/NumberFormatException.hpp>
\r
40 using namespace xmltooling;
\r
41 using namespace std;
\r
44 // constants used to process raw data (fBuffer)
\r
46 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
\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
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
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
67 static const XMLCh UTC_SET[] = {UTC_STD_CHAR //"Z+-"
\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
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
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
92 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
\r
93 const int DateTime::DATETIMES[][TOTAL_SIZE] =
\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
101 // ---------------------------------------------------------------------------
\r
103 // ---------------------------------------------------------------------------
\r
104 static inline int fQuotient(int a, int b)
\r
106 div_t div_result = div(a, b);
\r
107 return div_result.quot;
\r
110 static inline int fQuotient(int temp, int low, int high)
\r
112 return fQuotient(temp - low, high - low);
\r
115 static inline int mod(int a, int b, int quotient)
\r
117 return (a - quotient*b) ;
\r
120 static inline int modulo (int temp, int low, int high)
\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
128 static inline bool isLeapYear(int year)
\r
130 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
\r
133 static int maxDayInMonthFor(int year, int month)
\r
136 if ( month == 4 || month == 6 || month == 9 || month == 11 )
\r
140 else if ( month==2 )
\r
142 if ( isLeapYear(year) )
\r
154 // ---------------------------------------------------------------------------
\r
155 // static methods : for duration
\r
156 // ---------------------------------------------------------------------------
\r
158 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
\r
160 * 3.2.6.2 Order relation on duration
\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
167 * These values for s cause the greatest deviations in the addition of dateTimes and durations
\r
170 int DateTime::compare(const DateTime* const pDate1
\r
171 , const DateTime* const pDate2
\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
178 int resultA, resultB = XMLDateTime::INDETERMINATE;
\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
184 //long comparison algorithm is required
\r
185 DateTime tempA, *pTempA = &tempA;
\r
186 DateTime tempB, *pTempB = &tempB;
\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
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
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
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
218 // Form a new DateTime with duration and baseDate array
\r
220 // fNewDate duration
\r
224 void DateTime::addDuration(DateTime* fNewDate
\r
225 , const DateTime* const fDuration
\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
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
240 //add years (may be modified additionaly below)
\r
241 fNewDate->fValue[CentYear] =
\r
242 DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
\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
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
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
259 fNewDate->fValue[Day] =
\r
260 DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
\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
271 else if ( fNewDate->fValue[Day] > temp )
\r
273 fNewDate->fValue[Day] -= temp;
\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
286 //fNewDate->fValue[utc] = UTC_STD_CHAR;
\r
287 fNewDate->fValue[utc] = UTC_STD;
\r
290 int DateTime::compareResult(int resultA
\r
295 if ( resultB == XMLDateTime::INDETERMINATE )
\r
297 return XMLDateTime::INDETERMINATE;
\r
299 else if ( (resultA != resultB) &&
\r
302 return XMLDateTime::INDETERMINATE;
\r
304 else if ( (resultA != resultB) &&
\r
307 if ( (resultA != XMLDateTime::EQUAL) &&
\r
308 (resultB != XMLDateTime::EQUAL) )
\r
310 return XMLDateTime::INDETERMINATE;
\r
314 return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
\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
329 if (pDate1->fValue[utc] == pDate2->fValue[utc])
\r
331 return DateTime::compareOrder(pDate1, pDate2);
\r
336 if ( pDate1->isNormalized())
\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
342 else if ( pDate2->isNormalized())
\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
349 return XMLDateTime::INDETERMINATE;
\r
352 int DateTime::compareResult(const DateTime* const pDate1
\r
353 , const DateTime* const pDate2
\r
357 DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
\r
359 tmpDate.fTimeZone[hh] = 14;
\r
360 tmpDate.fTimeZone[mm] = 0;
\r
361 tmpDate.fValue[utc] = utc_type;
\r
362 tmpDate.normalize();
\r
364 return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
\r
365 DateTime::compareOrder(pDate1, &tmpDate));
\r
368 int DateTime::compareOrder(const DateTime* const lValue
\r
369 , const DateTime* const rValue)
\r
370 //, MemoryManager* const memMgr)
\r
373 // If any of the them is not normalized() yet,
\r
374 // we need to do something here.
\r
376 DateTime lTemp = *lValue;
\r
377 DateTime rTemp = *rValue;
\r
382 for ( int i = 0 ; i < TOTAL_SIZE; i++ )
\r
384 if ( lTemp.fValue[i] < rTemp.fValue[i] )
\r
386 return XMLDateTime::LESS_THAN;
\r
388 else if ( lTemp.fValue[i] > rTemp.fValue[i] )
\r
390 return XMLDateTime::GREATER_THAN;
\r
394 if ( lTemp.fHasTime)
\r
396 if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
\r
398 return XMLDateTime::LESS_THAN;
\r
400 else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
\r
402 return XMLDateTime::GREATER_THAN;
\r
406 return XMLDateTime::EQUAL;
\r
409 // ---------------------------------------------------------------------------
\r
411 // ---------------------------------------------------------------------------
\r
412 DateTime::~DateTime()
\r
417 DateTime::DateTime()
\r
428 DateTime::DateTime(const XMLCh* const aString)
\r
436 setBuffer(aString);
\r
439 DateTime::DateTime(time_t epoch)
\r
447 #ifndef HAVE_GMTIME_R
\r
448 struct tm* ptime=gmtime(&epoch);
\r
451 struct tm* ptime=gmtime_r(&epoch,&res);
\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
459 // -----------------------------------------------------------------------
\r
460 // Copy ctor and Assignment operators
\r
461 // -----------------------------------------------------------------------
\r
463 DateTime::DateTime(const DateTime &toCopy)
\r
470 DateTime& DateTime::operator=(const DateTime& rhs)
\r
479 // -----------------------------------------------------------------------
\r
480 // Implementation of Abstract Interface
\r
481 // -----------------------------------------------------------------------
\r
484 // We may simply return the handle to fBuffer
\r
486 const XMLCh* DateTime::getRawData() const
\r
493 const XMLCh* DateTime::getFormattedString() const
\r
495 return getRawData();
\r
498 int DateTime::getSign() const
\r
503 time_t DateTime::getEpoch() const
\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
513 #if defined(HAVE_TIMEGM)
\r
516 // Windows, and hopefully most others...?
\r
517 return mktime(&t) - timezone;
\r
521 // ---------------------------------------------------------------------------
\r
523 // ---------------------------------------------------------------------------
\r
526 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
\r
528 void DateTime::parseDateTime()
\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
540 validateDateTime();
\r
546 // [-]{CCYY-MM-DD}[TimeZone]
\r
548 void DateTime::parseDate()
\r
553 validateDateTime();
\r
557 void DateTime::parseTime()
\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
568 validateDateTime();
\r
574 // {---DD}[TimeZone]
\r
577 void DateTime::parseDay()
\r
581 if (fBuffer[0] != DATE_SEPARATOR ||
\r
582 fBuffer[1] != DATE_SEPARATOR ||
\r
583 fBuffer[2] != DATE_SEPARATOR )
\r
585 ThrowXML1(SchemaDateTimeException
\r
586 , XMLExcepts::DateTime_gDay_invalid
\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
595 if ( DAY_SIZE < fEnd )
\r
597 int sign = findUTCSign(DAY_SIZE);
\r
600 ThrowXML1(SchemaDateTimeException
\r
601 , XMLExcepts::DateTime_gDay_invalid
\r
610 validateDateTime();
\r
615 // {--MM--}[TimeZone]
\r
616 // {--MM}[TimeZone]
\r
619 void DateTime::parseMonth()
\r
623 if (fBuffer[0] != DATE_SEPARATOR ||
\r
624 fBuffer[1] != DATE_SEPARATOR )
\r
626 ThrowXML1(SchemaDateTimeException
\r
627 , XMLExcepts::DateTime_gMth_invalid
\r
632 fValue[CentYear] = YEAR_DEFAULT;
\r
633 fValue[Day] = DAY_DEFAULT;
\r
634 fValue[Month] = parseInt(2, 4);
\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
640 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
\r
646 // parse TimeZone if any
\r
648 if ( fStart < fEnd )
\r
650 int sign = findUTCSign(fStart);
\r
653 ThrowXML1(SchemaDateTimeException
\r
654 , XMLExcepts::DateTime_gMth_invalid
\r
663 validateDateTime();
\r
668 //[-]{CCYY}[TimeZone]
\r
671 void DateTime::parseYear()
\r
675 // skip the first '-' and search for timezone
\r
677 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
\r
679 if (sign == NOT_FOUND)
\r
681 fValue[CentYear] = parseIntYear(fEnd);
\r
685 fValue[CentYear] = parseIntYear(sign);
\r
689 //initialize values
\r
690 fValue[Month] = MONTH_DEFAULT;
\r
691 fValue[Day] = DAY_DEFAULT; //java is 1
\r
693 validateDateTime();
\r
698 //{--MM-DD}[TimeZone]
\r
701 void DateTime::parseMonthDay()
\r
705 if (fBuffer[0] != DATE_SEPARATOR ||
\r
706 fBuffer[1] != DATE_SEPARATOR ||
\r
707 fBuffer[4] != DATE_SEPARATOR )
\r
709 ThrowXML1(SchemaDateTimeException
\r
710 , XMLExcepts::DateTime_gMthDay_invalid
\r
716 fValue[CentYear] = YEAR_DEFAULT;
\r
717 fValue[Month] = parseInt(2, 4);
\r
718 fValue[Day] = parseInt(5, 7);
\r
720 if ( MONTHDAY_SIZE < fEnd )
\r
722 int sign = findUTCSign(MONTHDAY_SIZE);
\r
725 ThrowXML1(SchemaDateTimeException
\r
726 , XMLExcepts::DateTime_gMthDay_invalid
\r
735 validateDateTime();
\r
739 void DateTime::parseYearMonth()
\r
745 fValue[Day] = DAY_DEFAULT;
\r
748 validateDateTime();
\r
753 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
\r
755 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
\r
757 // Note: the n above shall be >= 0
\r
758 // if no time element found, 'T' shall be absent
\r
760 void DateTime::parseDuration()
\r
764 // must start with '-' or 'P'
\r
766 XMLCh c = fBuffer[fStart++];
\r
767 if ( (c != DURATION_STARTER) &&
\r
770 ThrowXML1(SchemaDateTimeException
\r
771 , XMLExcepts::DateTime_dur_Start_dashP
\r
775 // 'P' must ALWAYS be present in either case
\r
776 if ( (c == chDash) &&
\r
777 (fBuffer[fStart++]!= DURATION_STARTER ))
\r
779 ThrowXML1(SchemaDateTimeException
\r
780 , XMLExcepts::DateTime_dur_noP
\r
785 //date[utc]=(c=='-')?'-':0;
\r
786 //fValue[utc] = UTC_STD;
\r
787 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
\r
789 int negate = ( fBuffer[0] == chDash ? -1 : 1);
\r
792 // No negative value is allowed after 'P'
\r
794 // eg P-1234, invalid
\r
796 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
\r
798 ThrowXML1(SchemaDateTimeException
\r
799 , XMLExcepts::DateTime_dur_DashNotFirst
\r
803 //at least one number and designator must be seen after P
\r
804 bool designator = false;
\r
806 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
\r
807 if ( endDate == NOT_FOUND )
\r
809 endDate = fEnd; // 'T' absent
\r
813 int end = indexOf(fStart, endDate, DURATION_Y);
\r
814 if ( end != NOT_FOUND )
\r
817 fValue[CentYear] = negate * parseInt(fStart, end);
\r
822 end = indexOf(fStart, endDate, DURATION_M);
\r
823 if ( end != NOT_FOUND )
\r
826 fValue[Month] = negate * parseInt(fStart, end);
\r
831 end = indexOf(fStart, endDate, DURATION_D);
\r
832 if ( end != NOT_FOUND )
\r
835 fValue[Day] = negate * parseInt(fStart,end);
\r
840 if ( (fEnd == endDate) && // 'T' absent
\r
841 (fStart != fEnd) ) // something after Day
\r
843 ThrowXML1(SchemaDateTimeException
\r
844 , XMLExcepts::DateTime_dur_inv_b4T
\r
848 if ( fEnd != endDate ) // 'T' present
\r
850 //scan hours, minutes, seconds
\r
854 end = indexOf(++fStart, fEnd, DURATION_H);
\r
855 if ( end != NOT_FOUND )
\r
858 fValue[Hour] = negate * parseInt(fStart, end);
\r
863 end = indexOf(fStart, fEnd, DURATION_M);
\r
864 if ( end != NOT_FOUND )
\r
867 fValue[Minute] = negate * parseInt(fStart, end);
\r
872 end = indexOf(fStart, fEnd, DURATION_S);
\r
873 if ( end != NOT_FOUND )
\r
876 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
\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
884 if ( mlsec != NOT_FOUND )
\r
887 * make usure there is something after the '.' and before the end.
\r
889 if ( mlsec+1 == end )
\r
891 ThrowXML1(SchemaDateTimeException
\r
892 , XMLExcepts::DateTime_dur_inv_seconds
\r
896 fValue[Second] = negate * parseInt(fStart, mlsec);
\r
897 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
\r
901 fValue[Second] = negate * parseInt(fStart,end);
\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
913 ThrowXML1(SchemaDateTimeException
\r
914 , XMLExcepts::DateTime_dur_NoTimeAfterT
\r
921 ThrowXML1(SchemaDateTimeException
\r
922 , XMLExcepts::DateTime_dur_NoElementAtAll
\r
928 // ---------------------------------------------------------------------------
\r
930 // ---------------------------------------------------------------------------
\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
940 void DateTime::getDate()
\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
949 getYearMonth(); // Scan YearMonth and
\r
950 // fStart point to the next '-'
\r
952 if (fBuffer[fStart++] != DATE_SEPARATOR)
\r
954 ThrowXML1(SchemaDateTimeException
\r
955 , XMLExcepts::DateTime_date_invalid
\r
957 //("CCYY-MM must be followed by '-' sign");
\r
960 fValue[Day] = parseInt(fStart, fStart+2);
\r
961 fStart += 2 ; //fStart points right after the Day
\r
967 // hh:mm:ss[.msssss]['Z']
\r
968 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
\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
975 void DateTime::getTime()
\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
983 //"Imcomplete Time Format"
\r
985 // check (fixed) format first
\r
986 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
\r
987 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
\r
989 ThrowXML1(SchemaDateTimeException
\r
990 , XMLExcepts::DateTime_time_invalid
\r
992 //("Error in parsing time" );
\r
996 // get hours, minute and second
\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
1003 // to see if any ms and/or utc part after that
\r
1004 if (fStart >= fEnd)
\r
1007 //find UTC sign if any
\r
1008 int sign = findUTCSign(fStart);
\r
1010 //parse miliseconds
\r
1011 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
\r
1012 if ( milisec != NOT_FOUND )
\r
1014 fStart++; // skip the '.'
\r
1015 // make sure we have some thing between the '.' and fEnd
\r
1016 if (fStart >= fEnd)
\r
1018 ThrowXML1(SchemaDateTimeException
\r
1019 , XMLExcepts::DateTime_ms_noDigit
\r
1021 //("ms shall be present once '.' is present" );
\r
1024 if ( sign == NOT_FOUND )
\r
1026 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
\r
1031 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
\r
1034 else if(sign == 0 || sign != fStart)
\r
1036 // seconds has more than 2 digits
\r
1037 ThrowXML1(SchemaDateTimeException
\r
1038 , XMLExcepts::DateTime_min_invalid
\r
1042 //parse UTC time zone (hh:mm)
\r
1044 getTimeZone(sign);
\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
1055 void DateTime::getYearMonth()
\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
1063 //"Imcomplete YearMonth Format";
\r
1065 // skip the first leading '-'
\r
1066 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
\r
1069 // search for year separator '-'
\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
1076 //("Year separator is missing or misplaced");
\r
1078 fValue[CentYear] = parseIntYear(yearSeparator);
\r
1079 fStart = yearSeparator + 1; //skip the '-' and point to the first M
\r
1082 //gonna check we have enough byte for month
\r
1084 if ((fStart + 2) > fEnd )
\r
1085 ThrowXML1(SchemaDateTimeException
\r
1086 , XMLExcepts::DateTime_ym_noMonth
\r
1088 //"no month in buffer"
\r
1090 fValue[Month] = parseInt(fStart, yearSeparator + 3);
\r
1091 fStart += 2; //fStart points right after the MONTH
\r
1096 void DateTime::parseTimeZone()
\r
1098 if ( fStart < fEnd )
\r
1100 int sign = findUTCSign(fStart);
\r
1103 ThrowXML1(SchemaDateTimeException
\r
1104 , XMLExcepts::DateTime_tz_noUTCsign
\r
1106 //("Error in month parsing");
\r
1110 getTimeZone(sign);
\r
1121 // Note: Assuming fStart points to the beginning of TimeZone section
\r
1122 // fStart updated to meet fEnd
\r
1124 void DateTime::getTimeZone(const int sign)
\r
1127 if ( fBuffer[sign] == UTC_STD_CHAR )
\r
1129 if ((sign + 1) != fEnd )
\r
1131 ThrowXML1(SchemaDateTimeException
\r
1132 , XMLExcepts::DateTime_tz_stuffAfterZ
\r
1134 //"Error in parsing time zone");
\r
1141 // otherwise, it has to be this format
\r
1146 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
\r
1147 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
\r
1149 ThrowXML1(SchemaDateTimeException
\r
1150 , XMLExcepts::DateTime_tz_invalid
\r
1152 //("Error in parsing time zone");
\r
1155 fTimeZone[hh] = parseInt(sign+1, sign+3);
\r
1156 fTimeZone[mm] = parseInt(sign+4, fEnd);
\r
1161 // ---------------------------------------------------------------------------
\r
1162 // Validator and normalizer
\r
1163 // ---------------------------------------------------------------------------
\r
1166 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
\r
1168 * @param date CCYY-MM-DDThh:mm:ss+03
\r
1169 * @return CCYY-MM-DDThh:mm:ssZ
\r
1171 void DateTime::normalize()
\r
1174 if ((fValue[utc] == UTC_UNKNOWN) ||
\r
1175 (fValue[utc] == UTC_STD) )
\r
1178 int negate = (fValue[utc] == UTC_POS)? -1: 1;
\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
1186 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
\r
1187 carry = fQuotient(temp, 24);
\r
1188 fValue[Hour] = mod(temp, 24, carry);
\r
1190 fValue[Day] += carry;
\r
1194 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
\r
1195 if (fValue[Day] < 1)
\r
1197 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
\r
1200 else if ( fValue[Day] > temp )
\r
1202 fValue[Day] -= temp;
\r
1210 temp = fValue[Month] + carry;
\r
1211 fValue[Month] = modulo(temp, 1, 13);
\r
1212 fValue[CentYear] += fQuotient(temp, 1, 13);
\r
1215 // set to normalized
\r
1216 fValue[utc] = UTC_STD;
\r
1221 void DateTime::validateDateTime() const
\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
1228 ThrowXML1(SchemaDateTimeException
\r
1229 , XMLExcepts::DateTime_year_zero
\r
1231 //"The year \"0000\" is an illegal year value");
\r
1234 if ( fValue[Month] < 1 ||
\r
1235 fValue[Month] > 12 )
\r
1237 ThrowXML1(SchemaDateTimeException
\r
1238 , XMLExcepts::DateTime_mth_invalid
\r
1240 //"The month must have values 1 to 12");
\r
1244 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
\r
1245 fValue[Day] == 0 )
\r
1247 ThrowXML1(SchemaDateTimeException
\r
1248 , XMLExcepts::DateTime_day_invalid
\r
1250 //"The day must have values 1 to 31");
\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
1260 ThrowXML1(SchemaDateTimeException
\r
1261 , XMLExcepts::DateTime_hour_invalid
\r
1263 //("Hour must have values 0-23");
\r
1266 //validate minutes
\r
1267 if ( fValue[Minute] < 0 ||
\r
1268 fValue[Minute] > 59 )
\r
1270 ThrowXML1(SchemaDateTimeException
\r
1271 , XMLExcepts::DateTime_min_invalid
\r
1273 //"Minute must have values 0-59");
\r
1276 //validate seconds
\r
1277 if ( fValue[Second] < 0 ||
\r
1278 fValue[Second] > 60 )
\r
1280 ThrowXML1(SchemaDateTimeException
\r
1281 , XMLExcepts::DateTime_second_invalid
\r
1283 //"Second must have values 0-60");
\r
1286 //validate time-zone hours
\r
1287 if ( (abs(fTimeZone[hh]) > 14) ||
\r
1288 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
\r
1290 ThrowXML1(SchemaDateTimeException
\r
1291 , XMLExcepts::DateTime_tz_hh_invalid
\r
1293 //"Time zone should have range -14..+14");
\r
1296 //validate time-zone minutes
\r
1297 if ( abs(fTimeZone[mm]) > 59 )
\r
1299 ThrowXML1(SchemaDateTimeException
\r
1300 , XMLExcepts::DateTime_min_invalid
\r
1302 //("Minute must have values 0-59");
\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
1313 for ( int i = start; i < end; i++ )
\r
1314 if ( fBuffer[i] == ch )
\r
1320 int DateTime::findUTCSign (const int start)
\r
1323 for ( int index = start; index < fEnd; index++ )
\r
1325 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
\r
1326 if ( pos != NOT_FOUND)
\r
1328 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
\r
1338 // start: starting point in fBuffer
\r
1339 // end: ending point in fBuffer (exclusive)
\r
1340 // fStart NOT updated
\r
1342 int DateTime::parseInt(const int start, const int end) const
\r
1344 unsigned int retVal = 0;
\r
1345 for (int i=start; i < end; i++) {
\r
1347 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
\r
1348 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
\r
1350 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
\r
1353 return (int) retVal;
\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
1362 double DateTime::parseMiliSecond(const int start, const int end) const
\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
1371 char *nptr = XMLString::transcode(miliSecData);
\r
1372 ArrayJanitor<char> jan(nptr);
\r
1373 size_t strLen = strlen(nptr);
\r
1377 //printf("milisec=<%s>\n", nptr);
\r
1379 double retVal = strtod(nptr, &endptr);
\r
1381 // check if all chars are valid char
\r
1382 if ( (endptr - nptr) != strLen)
\r
1383 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
\r
1385 // we don't check underflow occurs since
\r
1386 // nothing we can do about it.
\r
1393 // Note: start from fStart
\r
1394 // end (exclusive)
\r
1395 // fStart NOT updated
\r
1397 int DateTime::parseIntYear(const int end) const
\r
1399 // skip the first leading '-'
\r
1400 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
\r
1402 int length = end - start;
\r
1405 ThrowXML1(SchemaDateTimeException
\r
1406 , XMLExcepts::DateTime_year_tooShort
\r
1408 //"Year must have 'CCYY' format");
\r
1410 else if (length > 4 &&
\r
1411 fBuffer[start] == chDigit_0)
\r
1413 ThrowXML1(SchemaDateTimeException
\r
1414 , XMLExcepts::DateTime_year_leadingZero
\r
1416 //"Leading zeros are required if the year value would otherwise have fewer than four digits;
\r
1417 // otherwise they are forbidden");
\r
1420 bool negative = (fBuffer[0] == chDash);
\r
1421 int yearVal = parseInt((negative ? 1 : 0), end);
\r
1422 return ( negative ? (-1) * yearVal : yearVal );
\r
1428 * 3.2.7.2 Canonical representation
\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
1439 * .'24:00:00' -> '00:00:00'
\r
1440 * .milisecond: trailing zeros removed
\r
1444 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
\r
1446 XMLCh *miliStartPtr, *miliEndPtr;
\r
1447 searchMiliSeconds(miliStartPtr, miliEndPtr);
\r
1448 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
\r
1450 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
\r
1451 XMLCh* retPtr = retBuf;
\r
1453 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
\r
1456 int additionalLen = fillYearString(retPtr, CentYear);
\r
1457 if(additionalLen != 0)
\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
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
1472 fillString(retPtr, Hour, 2);
\r
1473 if (fValue[Hour] == 24)
\r
1475 *(retPtr - 2) = chDigit_0;
\r
1476 *(retPtr - 1) = chDigit_0;
\r
1478 *retPtr++ = TIME_SEPARATOR;
\r
1479 fillString(retPtr, Minute, 2);
\r
1480 *retPtr++ = TIME_SEPARATOR;
\r
1481 fillString(retPtr, Second, 2);
\r
1483 if (miliSecondsLen)
\r
1485 *retPtr++ = chPeriod;
\r
1486 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
\r
1487 retPtr += miliSecondsLen;
\r
1490 *retPtr++ = UTC_STD_CHAR;
\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
1502 * . Additionally, the canonical representation for midnight is 00:00:00.
\r
1505 XMLCh* DateTime::getTimeCanonicalRepresentation() const
\r
1507 XMLCh *miliStartPtr, *miliEndPtr;
\r
1508 searchMiliSeconds(miliStartPtr, miliEndPtr);
\r
1509 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
\r
1511 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
\r
1512 XMLCh* retPtr = retBuf;
\r
1514 // 'hh:mm:ss'Z' ('.'s+)?
\r
1518 fillString(retPtr, Hour, 2);
\r
1519 if (fValue[Hour] == 24)
\r
1521 *(retPtr - 2) = chDigit_0;
\r
1522 *(retPtr - 1) = chDigit_0;
\r
1524 *retPtr++ = TIME_SEPARATOR;
\r
1525 fillString(retPtr, Minute, 2);
\r
1526 *retPtr++ = TIME_SEPARATOR;
\r
1527 fillString(retPtr, Second, 2);
\r
1529 if (miliSecondsLen)
\r
1531 *retPtr++ = chPeriod;
\r
1532 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
\r
1533 retPtr += miliSecondsLen;
\r
1536 *retPtr++ = UTC_STD_CHAR;
\r
1542 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
\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
1549 //append leading zeros
\r
1550 for (i = 0; i < expLen - actualLen; i++)
\r
1552 *ptr++ = chDigit_0;
\r
1555 for (i = 0; i < actualLen; i++)
\r
1557 *ptr++ = strBuffer[i];
\r
1562 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
\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
1572 *ptr++ = strBuffer[0];
\r
1576 //append leading zeros
\r
1577 for (i = 0; i < 4 - actualLen+negativeYear; i++)
\r
1579 *ptr++ = chDigit_0;
\r
1582 for (i = negativeYear; i < actualLen; i++)
\r
1584 *ptr++ = strBuffer[i];
\r
1587 return actualLen-4;
\r
1593 * .check if the rawData has the mili second component
\r
1594 * .capture the substring
\r
1597 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
\r
1599 miliStartPtr = miliEndPtr = 0;
\r
1601 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
\r
1602 if (milisec == -1)
\r
1605 miliStartPtr = fBuffer + milisec + 1;
\r
1606 miliEndPtr = miliStartPtr;
\r
1607 while (*miliEndPtr)
\r
1609 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
\r
1615 //remove trailing zeros
\r
1616 while( *(miliEndPtr - 1) == chDigit_0)
\r