2 * Copyright 2001-2007 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * Manipulation of XML date/time data.
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.
30 #include "util/DateTime.h"
37 #include <xercesc/util/Janitor.hpp>
38 #include <xercesc/util/NumberFormatException.hpp>
40 using namespace xmltooling;
44 // constants used to process raw data (fBuffer)
46 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
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'
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; // '.'
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; // '-'
67 static const XMLCh UTC_SET[] = {UTC_STD_CHAR //"Z+-"
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;
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;
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
92 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
93 const int DateTime::DATETIMES[][TOTAL_SIZE] =
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}
101 // ---------------------------------------------------------------------------
103 // ---------------------------------------------------------------------------
104 static inline int fQuotient(int a, int b)
106 div_t div_result = div(a, b);
107 return div_result.quot;
110 static inline int fQuotient(int temp, int low, int high)
112 return fQuotient(temp - low, high - low);
115 static inline int mod(int a, int b, int quotient)
117 return (a - quotient*b) ;
120 static inline int modulo (int temp, int low, int high)
122 //modulo(a - low, high - low) + low
125 return (mod (a, b, fQuotient(a, b)) + low) ;
128 static inline bool isLeapYear(int year)
130 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
133 static int maxDayInMonthFor(int year, int month)
136 if ( month == 4 || month == 6 || month == 9 || month == 11 )
142 if ( isLeapYear(year) )
154 // ---------------------------------------------------------------------------
155 // static methods : for duration
156 // ---------------------------------------------------------------------------
158 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
160 * 3.2.6.2 Order relation on duration
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.
167 * These values for s cause the greatest deviations in the addition of dateTimes and durations
170 int DateTime::compare(const DateTime* const pDate1
171 , const DateTime* const pDate2
174 //REVISIT: this is unoptimazed vs of comparing 2 durations
175 // Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
178 int resultA, resultB = XMLDateTime::INDETERMINATE;
180 //try and see if the objects are equal
181 if ( (resultA = compareOrder(pDate1, pDate2)) == XMLDateTime::EQUAL)
182 return XMLDateTime::EQUAL;
184 //long comparison algorithm is required
185 DateTime tempA, *pTempA = &tempA;
186 DateTime tempB, *pTempB = &tempB;
188 addDuration(pTempA, pDate1, 0);
189 addDuration(pTempB, pDate2, 0);
190 resultA = compareOrder(pTempA, pTempB);
191 if ( resultA == XMLDateTime::INDETERMINATE )
192 return XMLDateTime::INDETERMINATE;
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;
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;
208 addDuration(pTempA, pDate1, 3);
209 addDuration(pTempB, pDate2, 3);
210 resultB = compareOrder(pTempA, pTempB);
211 resultA = compareResult(resultA, resultB, strict);
218 // Form a new DateTime with duration and baseDate array
224 void DateTime::addDuration(DateTime* fNewDate
225 , const DateTime* const fDuration
230 //REVISIT: some code could be shared between normalize() and this method,
231 // however is it worth moving it? The structures are different...
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);
240 //add years (may be modified additionaly below)
241 fNewDate->fValue[CentYear] =
242 DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
245 temp = DATETIMES[index][Second] + fDuration->fValue[Second];
246 carry = fQuotient (temp, 60);
247 fNewDate->fValue[Second] = mod(temp, 60, carry);
250 temp = DATETIMES[index][Minute] + fDuration->fValue[Minute] + carry;
251 carry = fQuotient(temp, 60);
252 fNewDate->fValue[Minute] = mod(temp, 60, carry);
255 temp = DATETIMES[index][Hour] + fDuration->fValue[Hour] + carry;
256 carry = fQuotient(temp, 24);
257 fNewDate->fValue[Hour] = mod(temp, 24, carry);
259 fNewDate->fValue[Day] =
260 DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
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);
271 else if ( fNewDate->fValue[Day] > temp )
273 fNewDate->fValue[Day] -= temp;
281 temp = fNewDate->fValue[Month] + carry;
282 fNewDate->fValue[Month] = modulo(temp, 1, 13);
283 fNewDate->fValue[CentYear] += fQuotient(temp, 1, 13);
286 //fNewDate->fValue[utc] = UTC_STD_CHAR;
287 fNewDate->fValue[utc] = UTC_STD;
290 int DateTime::compareResult(int resultA
295 if ( resultB == XMLDateTime::INDETERMINATE )
297 return XMLDateTime::INDETERMINATE;
299 else if ( (resultA != resultB) &&
302 return XMLDateTime::INDETERMINATE;
304 else if ( (resultA != resultB) &&
307 if ( (resultA != XMLDateTime::EQUAL) &&
308 (resultB != XMLDateTime::EQUAL) )
310 return XMLDateTime::INDETERMINATE;
314 return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
322 // ---------------------------------------------------------------------------
323 // static methods : for others
324 // ---------------------------------------------------------------------------
325 int DateTime::compare(const DateTime* const pDate1
326 , const DateTime* const pDate2)
329 if (pDate1->fValue[utc] == pDate2->fValue[utc])
331 return DateTime::compareOrder(pDate1, pDate2);
336 if ( pDate1->isNormalized())
338 c1 = compareResult(pDate1, pDate2, false, UTC_POS);
339 c2 = compareResult(pDate1, pDate2, false, UTC_NEG);
340 return getRetVal(c1, c2);
342 else if ( pDate2->isNormalized())
344 c1 = compareResult(pDate1, pDate2, true, UTC_POS);
345 c2 = compareResult(pDate1, pDate2, true, UTC_NEG);
346 return getRetVal(c1, c2);
349 return XMLDateTime::INDETERMINATE;
352 int DateTime::compareResult(const DateTime* const pDate1
353 , const DateTime* const pDate2
357 DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
359 tmpDate.fTimeZone[hh] = 14;
360 tmpDate.fTimeZone[mm] = 0;
361 tmpDate.fValue[utc] = utc_type;
364 return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
365 DateTime::compareOrder(pDate1, &tmpDate));
368 int DateTime::compareOrder(const DateTime* const lValue
369 , const DateTime* const rValue)
370 //, MemoryManager* const memMgr)
373 // If any of the them is not normalized() yet,
374 // we need to do something here.
376 DateTime lTemp = *lValue;
377 DateTime rTemp = *rValue;
382 for ( int i = 0 ; i < TOTAL_SIZE; i++ )
384 if ( lTemp.fValue[i] < rTemp.fValue[i] )
386 return XMLDateTime::LESS_THAN;
388 else if ( lTemp.fValue[i] > rTemp.fValue[i] )
390 return XMLDateTime::GREATER_THAN;
396 if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
398 return XMLDateTime::LESS_THAN;
400 else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
402 return XMLDateTime::GREATER_THAN;
406 return XMLDateTime::EQUAL;
409 // ---------------------------------------------------------------------------
411 // ---------------------------------------------------------------------------
412 DateTime::~DateTime()
428 DateTime::DateTime(const XMLCh* const aString)
439 DateTime::DateTime(time_t epoch)
447 #ifndef HAVE_GMTIME_R
448 struct tm* ptime=gmtime(&epoch);
451 struct tm* ptime=gmtime_r(&epoch,&res);
454 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
455 auto_ptr_XMLCh timeptr(timebuf);
456 setBuffer(timeptr.get());
459 // -----------------------------------------------------------------------
460 // Copy ctor and Assignment operators
461 // -----------------------------------------------------------------------
463 DateTime::DateTime(const DateTime &toCopy)
470 DateTime& DateTime::operator=(const DateTime& rhs)
479 // -----------------------------------------------------------------------
480 // Implementation of Abstract Interface
481 // -----------------------------------------------------------------------
484 // We may simply return the handle to fBuffer
486 const XMLCh* DateTime::getRawData() const
493 const XMLCh* DateTime::getFormattedString() const
498 int DateTime::getSign() const
503 time_t DateTime::getEpoch() const
506 t.tm_sec=getSecond();
507 t.tm_min=getMinute();
510 t.tm_mon=getMonth()-1;
511 t.tm_year=getYear()-1900;
513 #if defined(HAVE_TIMEGM)
516 // Windows, and hopefully most others...?
517 return mktime(&t) - timezone;
521 // ---------------------------------------------------------------------------
523 // ---------------------------------------------------------------------------
526 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
528 void DateTime::parseDateTime()
533 //fStart is supposed to point to 'T'
534 if (fBuffer[fStart++] != DATETIME_SEPARATOR)
535 ThrowXML1(SchemaDateTimeException
536 , XMLExcepts::DateTime_gDay_invalid
546 // [-]{CCYY-MM-DD}[TimeZone]
548 void DateTime::parseDate()
557 void DateTime::parseTime()
561 // time initialize to default values
562 fValue[CentYear]= YEAR_DEFAULT;
563 fValue[Month] = MONTH_DEFAULT;
564 fValue[Day] = DAY_DEFAULT;
577 void DateTime::parseDay()
581 if (fBuffer[0] != DATE_SEPARATOR ||
582 fBuffer[1] != DATE_SEPARATOR ||
583 fBuffer[2] != DATE_SEPARATOR )
585 ThrowXML1(SchemaDateTimeException
586 , XMLExcepts::DateTime_gDay_invalid
591 fValue[CentYear] = YEAR_DEFAULT;
592 fValue[Month] = MONTH_DEFAULT;
593 fValue[Day] = parseInt(fStart+3, fStart+5);
595 if ( DAY_SIZE < fEnd )
597 int sign = findUTCSign(DAY_SIZE);
600 ThrowXML1(SchemaDateTimeException
601 , XMLExcepts::DateTime_gDay_invalid
615 // {--MM--}[TimeZone]
619 void DateTime::parseMonth()
623 if (fBuffer[0] != DATE_SEPARATOR ||
624 fBuffer[1] != DATE_SEPARATOR )
626 ThrowXML1(SchemaDateTimeException
627 , XMLExcepts::DateTime_gMth_invalid
632 fValue[CentYear] = YEAR_DEFAULT;
633 fValue[Day] = DAY_DEFAULT;
634 fValue[Month] = parseInt(2, 4);
636 // REVISIT: allow both --MM and --MM-- now.
637 // need to remove the following lines to disallow --MM--
638 // when the errata is officially in the rec.
640 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
646 // parse TimeZone if any
650 int sign = findUTCSign(fStart);
653 ThrowXML1(SchemaDateTimeException
654 , XMLExcepts::DateTime_gMth_invalid
668 //[-]{CCYY}[TimeZone]
671 void DateTime::parseYear()
675 // skip the first '-' and search for timezone
677 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
679 if (sign == NOT_FOUND)
681 fValue[CentYear] = parseIntYear(fEnd);
685 fValue[CentYear] = parseIntYear(sign);
690 fValue[Month] = MONTH_DEFAULT;
691 fValue[Day] = DAY_DEFAULT; //java is 1
698 //{--MM-DD}[TimeZone]
701 void DateTime::parseMonthDay()
705 if (fBuffer[0] != DATE_SEPARATOR ||
706 fBuffer[1] != DATE_SEPARATOR ||
707 fBuffer[4] != DATE_SEPARATOR )
709 ThrowXML1(SchemaDateTimeException
710 , XMLExcepts::DateTime_gMthDay_invalid
716 fValue[CentYear] = YEAR_DEFAULT;
717 fValue[Month] = parseInt(2, 4);
718 fValue[Day] = parseInt(5, 7);
720 if ( MONTHDAY_SIZE < fEnd )
722 int sign = findUTCSign(MONTHDAY_SIZE);
725 ThrowXML1(SchemaDateTimeException
726 , XMLExcepts::DateTime_gMthDay_invalid
739 void DateTime::parseYearMonth()
745 fValue[Day] = DAY_DEFAULT;
753 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
755 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
757 // Note: the n above shall be >= 0
758 // if no time element found, 'T' shall be absent
760 void DateTime::parseDuration()
764 // must start with '-' or 'P'
766 XMLCh c = fBuffer[fStart++];
767 if ( (c != DURATION_STARTER) &&
770 ThrowXML1(SchemaDateTimeException
771 , XMLExcepts::DateTime_dur_Start_dashP
775 // 'P' must ALWAYS be present in either case
776 if ( (c == chDash) &&
777 (fBuffer[fStart++]!= DURATION_STARTER ))
779 ThrowXML1(SchemaDateTimeException
780 , XMLExcepts::DateTime_dur_noP
785 //date[utc]=(c=='-')?'-':0;
786 //fValue[utc] = UTC_STD;
787 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
789 int negate = ( fBuffer[0] == chDash ? -1 : 1);
792 // No negative value is allowed after 'P'
794 // eg P-1234, invalid
796 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
798 ThrowXML1(SchemaDateTimeException
799 , XMLExcepts::DateTime_dur_DashNotFirst
803 //at least one number and designator must be seen after P
804 bool designator = false;
806 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
807 if ( endDate == NOT_FOUND )
809 endDate = fEnd; // 'T' absent
813 int end = indexOf(fStart, endDate, DURATION_Y);
814 if ( end != NOT_FOUND )
817 fValue[CentYear] = negate * parseInt(fStart, end);
822 end = indexOf(fStart, endDate, DURATION_M);
823 if ( end != NOT_FOUND )
826 fValue[Month] = negate * parseInt(fStart, end);
831 end = indexOf(fStart, endDate, DURATION_D);
832 if ( end != NOT_FOUND )
835 fValue[Day] = negate * parseInt(fStart,end);
840 if ( (fEnd == endDate) && // 'T' absent
841 (fStart != fEnd) ) // something after Day
843 ThrowXML1(SchemaDateTimeException
844 , XMLExcepts::DateTime_dur_inv_b4T
848 if ( fEnd != endDate ) // 'T' present
850 //scan hours, minutes, seconds
854 end = indexOf(++fStart, fEnd, DURATION_H);
855 if ( end != NOT_FOUND )
858 fValue[Hour] = negate * parseInt(fStart, end);
863 end = indexOf(fStart, fEnd, DURATION_M);
864 if ( end != NOT_FOUND )
867 fValue[Minute] = negate * parseInt(fStart, end);
872 end = indexOf(fStart, fEnd, DURATION_S);
873 if ( end != NOT_FOUND )
876 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
879 * Schema Errata: E2-23
880 * at least one digit must follow the decimal point if it appears.
881 * That is, the value of the seconds component must conform
882 * to the following pattern: [0-9]+(.[0-9]+)?
884 if ( mlsec != NOT_FOUND )
887 * make usure there is something after the '.' and before the end.
889 if ( mlsec+1 == end )
891 ThrowXML1(SchemaDateTimeException
892 , XMLExcepts::DateTime_dur_inv_seconds
896 fValue[Second] = negate * parseInt(fStart, mlsec);
897 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
901 fValue[Second] = negate * parseInt(fStart,end);
908 // no additional data should appear after last item
909 // P1Y1M1DT is illigal value as well
910 if ( (fStart != fEnd) ||
911 fBuffer[--fStart] == DATETIME_SEPARATOR )
913 ThrowXML1(SchemaDateTimeException
914 , XMLExcepts::DateTime_dur_NoTimeAfterT
921 ThrowXML1(SchemaDateTimeException
922 , XMLExcepts::DateTime_dur_NoElementAtAll
928 // ---------------------------------------------------------------------------
930 // ---------------------------------------------------------------------------
935 // Note: CCYY could be more than 4 digits
936 // Assuming fStart point to the beginning of the Date Section
937 // fStart updated to point to the position right AFTER the second 'D'
938 // Since the lenght of CCYY might be variable, we can't check format upfront
940 void DateTime::getDate()
943 // Ensure enough chars in buffer
944 if ( (fStart+YMD_MIN_SIZE) > fEnd)
945 ThrowXML1(SchemaDateTimeException
946 , XMLExcepts::DateTime_date_incomplete
949 getYearMonth(); // Scan YearMonth and
950 // fStart point to the next '-'
952 if (fBuffer[fStart++] != DATE_SEPARATOR)
954 ThrowXML1(SchemaDateTimeException
955 , XMLExcepts::DateTime_date_invalid
957 //("CCYY-MM must be followed by '-' sign");
960 fValue[Day] = parseInt(fStart, fStart+2);
961 fStart += 2 ; //fStart points right after the Day
967 // hh:mm:ss[.msssss]['Z']
968 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
971 // Note: Assuming fStart point to the beginning of the Time Section
972 // fStart updated to point to the position right AFTER the second 's'
975 void DateTime::getTime()
978 // Ensure enough chars in buffer
979 if ( (fStart+TIME_MIN_SIZE) > fEnd)
980 ThrowXML1(SchemaDateTimeException
981 , XMLExcepts::DateTime_time_incomplete
983 //"Imcomplete Time Format"
985 // check (fixed) format first
986 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
987 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
989 ThrowXML1(SchemaDateTimeException
990 , XMLExcepts::DateTime_time_invalid
992 //("Error in parsing time" );
996 // get hours, minute and second
998 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
999 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
1000 fValue[Second] = parseInt(fStart + 6, fStart + 8);
1003 // to see if any ms and/or utc part after that
1007 //find UTC sign if any
1008 int sign = findUTCSign(fStart);
1011 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
1012 if ( milisec != NOT_FOUND )
1014 fStart++; // skip the '.'
1015 // make sure we have some thing between the '.' and fEnd
1018 ThrowXML1(SchemaDateTimeException
1019 , XMLExcepts::DateTime_ms_noDigit
1021 //("ms shall be present once '.' is present" );
1024 if ( sign == NOT_FOUND )
1026 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
1031 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
1034 else if(sign == 0 || sign != fStart)
1036 // seconds has more than 2 digits
1037 ThrowXML1(SchemaDateTimeException
1038 , XMLExcepts::DateTime_min_invalid
1042 //parse UTC time zone (hh:mm)
1052 // Note: CCYY could be more than 4 digits
1053 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1055 void DateTime::getYearMonth()
1058 // Ensure enough chars in buffer
1059 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1060 ThrowXML1(SchemaDateTimeException
1061 , XMLExcepts::DateTime_ym_incomplete
1063 //"Imcomplete YearMonth Format";
1065 // skip the first leading '-'
1066 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1069 // search for year separator '-'
1071 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1072 if ( yearSeparator == NOT_FOUND)
1073 ThrowXML1(SchemaDateTimeException
1074 , XMLExcepts::DateTime_ym_invalid
1076 //("Year separator is missing or misplaced");
1078 fValue[CentYear] = parseIntYear(yearSeparator);
1079 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1082 //gonna check we have enough byte for month
1084 if ((fStart + 2) > fEnd )
1085 ThrowXML1(SchemaDateTimeException
1086 , XMLExcepts::DateTime_ym_noMonth
1088 //"no month in buffer"
1090 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1091 fStart += 2; //fStart points right after the MONTH
1096 void DateTime::parseTimeZone()
1098 if ( fStart < fEnd )
1100 int sign = findUTCSign(fStart);
1103 ThrowXML1(SchemaDateTimeException
1104 , XMLExcepts::DateTime_tz_noUTCsign
1106 //("Error in month parsing");
1121 // Note: Assuming fStart points to the beginning of TimeZone section
1122 // fStart updated to meet fEnd
1124 void DateTime::getTimeZone(const int sign)
1127 if ( fBuffer[sign] == UTC_STD_CHAR )
1129 if ((sign + 1) != fEnd )
1131 ThrowXML1(SchemaDateTimeException
1132 , XMLExcepts::DateTime_tz_stuffAfterZ
1134 //"Error in parsing time zone");
1141 // otherwise, it has to be this format
1146 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1147 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1149 ThrowXML1(SchemaDateTimeException
1150 , XMLExcepts::DateTime_tz_invalid
1152 //("Error in parsing time zone");
1155 fTimeZone[hh] = parseInt(sign+1, sign+3);
1156 fTimeZone[mm] = parseInt(sign+4, fEnd);
1161 // ---------------------------------------------------------------------------
1162 // Validator and normalizer
1163 // ---------------------------------------------------------------------------
1166 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1168 * @param date CCYY-MM-DDThh:mm:ss+03
1169 * @return CCYY-MM-DDThh:mm:ssZ
1171 void DateTime::normalize()
1174 if ((fValue[utc] == UTC_UNKNOWN) ||
1175 (fValue[utc] == UTC_STD) )
1178 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1181 int temp = fValue[Minute] + negate * fTimeZone[mm];
1182 int carry = fQuotient(temp, 60);
1183 fValue[Minute] = mod(temp, 60, carry);
1186 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1187 carry = fQuotient(temp, 24);
1188 fValue[Hour] = mod(temp, 24, carry);
1190 fValue[Day] += carry;
1194 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1195 if (fValue[Day] < 1)
1197 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1200 else if ( fValue[Day] > temp )
1202 fValue[Day] -= temp;
1210 temp = fValue[Month] + carry;
1211 fValue[Month] = modulo(temp, 1, 13);
1212 fValue[CentYear] += fQuotient(temp, 1, 13);
1215 // set to normalized
1216 fValue[utc] = UTC_STD;
1221 void DateTime::validateDateTime() const
1224 //REVISIT: should we throw an exception for not valid dates
1225 // or reporting an error message should be sufficient?
1226 if ( fValue[CentYear] == 0 )
1228 ThrowXML1(SchemaDateTimeException
1229 , XMLExcepts::DateTime_year_zero
1231 //"The year \"0000\" is an illegal year value");
1234 if ( fValue[Month] < 1 ||
1235 fValue[Month] > 12 )
1237 ThrowXML1(SchemaDateTimeException
1238 , XMLExcepts::DateTime_mth_invalid
1240 //"The month must have values 1 to 12");
1244 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1247 ThrowXML1(SchemaDateTimeException
1248 , XMLExcepts::DateTime_day_invalid
1250 //"The day must have values 1 to 31");
1254 if ((fValue[Hour] < 0) ||
1255 (fValue[Hour] > 24) ||
1256 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1257 (fValue[Second] !=0) ||
1258 (fMiliSecond !=0))))
1260 ThrowXML1(SchemaDateTimeException
1261 , XMLExcepts::DateTime_hour_invalid
1263 //("Hour must have values 0-23");
1267 if ( fValue[Minute] < 0 ||
1268 fValue[Minute] > 59 )
1270 ThrowXML1(SchemaDateTimeException
1271 , XMLExcepts::DateTime_min_invalid
1273 //"Minute must have values 0-59");
1277 if ( fValue[Second] < 0 ||
1278 fValue[Second] > 60 )
1280 ThrowXML1(SchemaDateTimeException
1281 , XMLExcepts::DateTime_second_invalid
1283 //"Second must have values 0-60");
1286 //validate time-zone hours
1287 if ( (abs(fTimeZone[hh]) > 14) ||
1288 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1290 ThrowXML1(SchemaDateTimeException
1291 , XMLExcepts::DateTime_tz_hh_invalid
1293 //"Time zone should have range -14..+14");
1296 //validate time-zone minutes
1297 if ( abs(fTimeZone[mm]) > 59 )
1299 ThrowXML1(SchemaDateTimeException
1300 , XMLExcepts::DateTime_min_invalid
1302 //("Minute must have values 0-59");
1308 // -----------------------------------------------------------------------
1309 // locator and converter
1310 // -----------------------------------------------------------------------
1311 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1313 for ( int i = start; i < end; i++ )
1314 if ( fBuffer[i] == ch )
1320 int DateTime::findUTCSign (const int start)
1323 for ( int index = start; index < fEnd; index++ )
1325 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1326 if ( pos != NOT_FOUND)
1328 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1338 // start: starting point in fBuffer
1339 // end: ending point in fBuffer (exclusive)
1340 // fStart NOT updated
1342 int DateTime::parseInt(const int start, const int end) const
1344 unsigned int retVal = 0;
1345 for (int i=start; i < end; i++) {
1347 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1348 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
1350 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1353 return (int) retVal;
1358 // start: pointing to the first digit after the '.'
1359 // end: pointing to one position after the last digit
1360 // fStart NOT updated
1362 double DateTime::parseMiliSecond(const int start, const int end) const
1365 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1366 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1367 ArrayJanitor<XMLCh> janMili(miliSecData);
1368 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1369 *(miliSecData + miliSecLen) = chNull;
1371 char *nptr = XMLString::transcode(miliSecData);
1372 ArrayJanitor<char> jan(nptr);
1373 size_t strLen = strlen(nptr);
1377 //printf("milisec=<%s>\n", nptr);
1379 double retVal = strtod(nptr, &endptr);
1381 // check if all chars are valid char
1382 if ( (endptr - nptr) != strLen)
1383 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
1385 // we don't check underflow occurs since
1386 // nothing we can do about it.
1393 // Note: start from fStart
1395 // fStart NOT updated
1397 int DateTime::parseIntYear(const int end) const
1399 // skip the first leading '-'
1400 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1402 int length = end - start;
1405 ThrowXML1(SchemaDateTimeException
1406 , XMLExcepts::DateTime_year_tooShort
1408 //"Year must have 'CCYY' format");
1410 else if (length > 4 &&
1411 fBuffer[start] == chDigit_0)
1413 ThrowXML1(SchemaDateTimeException
1414 , XMLExcepts::DateTime_year_leadingZero
1416 //"Leading zeros are required if the year value would otherwise have fewer than four digits;
1417 // otherwise they are forbidden");
1420 bool negative = (fBuffer[0] == chDash);
1421 int yearVal = parseInt((negative ? 1 : 0), end);
1422 return ( negative ? (-1) * yearVal : yearVal );
1428 * 3.2.7.2 Canonical representation
1430 * Except for trailing fractional zero digits in the seconds representation,
1431 * '24:00:00' time representations, and timezone (for timezoned values),
1432 * the mapping from literals to values is one-to-one. Where there is more
1433 * than one possible representation, the canonical representation is as follows:
1434 * redundant trailing zero digits in fractional-second literals are prohibited.
1435 * An hour representation of '24' is prohibited. Timezoned values are canonically
1436 * represented by appending 'Z' to the nontimezoned representation. (All
1437 * timezoned dateTime values are UTC.)
1439 * .'24:00:00' -> '00:00:00'
1440 * .milisecond: trailing zeros removed
1444 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1446 XMLCh *miliStartPtr, *miliEndPtr;
1447 searchMiliSeconds(miliStartPtr, miliEndPtr);
1448 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1450 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1451 XMLCh* retPtr = retBuf;
1453 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1456 int additionalLen = fillYearString(retPtr, CentYear);
1457 if(additionalLen != 0)
1459 // very bad luck; have to resize the buffer...
1460 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1461 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1462 retPtr = tmpBuf+(retPtr-retBuf);
1466 *retPtr++ = DATE_SEPARATOR;
1467 fillString(retPtr, Month, 2);
1468 *retPtr++ = DATE_SEPARATOR;
1469 fillString(retPtr, Day, 2);
1470 *retPtr++ = DATETIME_SEPARATOR;
1472 fillString(retPtr, Hour, 2);
1473 if (fValue[Hour] == 24)
1475 *(retPtr - 2) = chDigit_0;
1476 *(retPtr - 1) = chDigit_0;
1478 *retPtr++ = TIME_SEPARATOR;
1479 fillString(retPtr, Minute, 2);
1480 *retPtr++ = TIME_SEPARATOR;
1481 fillString(retPtr, Second, 2);
1485 *retPtr++ = chPeriod;
1486 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1487 retPtr += miliSecondsLen;
1490 *retPtr++ = UTC_STD_CHAR;
1499 * . either the time zone must be omitted or,
1500 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1502 * . Additionally, the canonical representation for midnight is 00:00:00.
1505 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1507 XMLCh *miliStartPtr, *miliEndPtr;
1508 searchMiliSeconds(miliStartPtr, miliEndPtr);
1509 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1511 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1512 XMLCh* retPtr = retBuf;
1514 // 'hh:mm:ss'Z' ('.'s+)?
1518 fillString(retPtr, Hour, 2);
1519 if (fValue[Hour] == 24)
1521 *(retPtr - 2) = chDigit_0;
1522 *(retPtr - 1) = chDigit_0;
1524 *retPtr++ = TIME_SEPARATOR;
1525 fillString(retPtr, Minute, 2);
1526 *retPtr++ = TIME_SEPARATOR;
1527 fillString(retPtr, Second, 2);
1531 *retPtr++ = chPeriod;
1532 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1533 retPtr += miliSecondsLen;
1536 *retPtr++ = UTC_STD_CHAR;
1542 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1544 XMLCh strBuffer[16];
1545 assert(expLen < 16);
1546 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1547 int actualLen = XMLString::stringLen(strBuffer);
1549 //append leading zeros
1550 for (i = 0; i < expLen - actualLen; i++)
1555 for (i = 0; i < actualLen; i++)
1557 *ptr++ = strBuffer[i];
1562 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1564 XMLCh strBuffer[16];
1565 // let's hope we get no years of 15 digits...
1566 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1567 int actualLen = XMLString::stringLen(strBuffer);
1568 // don't forget that years can be negative...
1569 int negativeYear = 0;
1570 if(strBuffer[0] == chDash)
1572 *ptr++ = strBuffer[0];
1576 //append leading zeros
1577 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1582 for (i = negativeYear; i < actualLen; i++)
1584 *ptr++ = strBuffer[i];
1593 * .check if the rawData has the mili second component
1594 * .capture the substring
1597 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1599 miliStartPtr = miliEndPtr = 0;
1601 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1605 miliStartPtr = fBuffer + milisec + 1;
1606 miliEndPtr = miliStartPtr;
1609 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1615 //remove trailing zeros
1616 while( *(miliEndPtr - 1) == chDigit_0)