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"
38 #include <xercesc/util/Janitor.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 throw XMLParserException("Invalid separator between date and time.");
544 // [-]{CCYY-MM-DD}[TimeZone]
546 void DateTime::parseDate()
555 void DateTime::parseTime()
559 // time initialize to default values
560 fValue[CentYear]= YEAR_DEFAULT;
561 fValue[Month] = MONTH_DEFAULT;
562 fValue[Day] = DAY_DEFAULT;
575 void DateTime::parseDay()
579 if (fBuffer[0] != DATE_SEPARATOR ||
580 fBuffer[1] != DATE_SEPARATOR ||
581 fBuffer[2] != DATE_SEPARATOR )
583 throw XMLParserException("Invalid character in date.");
587 fValue[CentYear] = YEAR_DEFAULT;
588 fValue[Month] = MONTH_DEFAULT;
589 fValue[Day] = parseInt(fStart+3, fStart+5);
591 if ( DAY_SIZE < fEnd )
593 int sign = findUTCSign(DAY_SIZE);
596 throw XMLParserException("Invalid character in date.");
609 // {--MM--}[TimeZone]
613 void DateTime::parseMonth()
617 if (fBuffer[0] != DATE_SEPARATOR ||
618 fBuffer[1] != DATE_SEPARATOR )
620 throw XMLParserException("Invalid character in date.");
624 fValue[CentYear] = YEAR_DEFAULT;
625 fValue[Day] = DAY_DEFAULT;
626 fValue[Month] = parseInt(2, 4);
628 // REVISIT: allow both --MM and --MM-- now.
629 // need to remove the following lines to disallow --MM--
630 // when the errata is officially in the rec.
632 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
638 // parse TimeZone if any
642 int sign = findUTCSign(fStart);
645 throw XMLParserException("Invalid character in date.");
658 //[-]{CCYY}[TimeZone]
661 void DateTime::parseYear()
665 // skip the first '-' and search for timezone
667 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
669 if (sign == NOT_FOUND)
671 fValue[CentYear] = parseIntYear(fEnd);
675 fValue[CentYear] = parseIntYear(sign);
680 fValue[Month] = MONTH_DEFAULT;
681 fValue[Day] = DAY_DEFAULT; //java is 1
688 //{--MM-DD}[TimeZone]
691 void DateTime::parseMonthDay()
695 if (fBuffer[0] != DATE_SEPARATOR ||
696 fBuffer[1] != DATE_SEPARATOR ||
697 fBuffer[4] != DATE_SEPARATOR )
699 throw XMLParserException("Invalid character in date.");
704 fValue[CentYear] = YEAR_DEFAULT;
705 fValue[Month] = parseInt(2, 4);
706 fValue[Day] = parseInt(5, 7);
708 if ( MONTHDAY_SIZE < fEnd )
710 int sign = findUTCSign(MONTHDAY_SIZE);
713 throw XMLParserException("Invalid character in date.");
725 void DateTime::parseYearMonth()
731 fValue[Day] = DAY_DEFAULT;
739 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
741 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
743 // Note: the n above shall be >= 0
744 // if no time element found, 'T' shall be absent
746 void DateTime::parseDuration()
750 // must start with '-' or 'P'
752 XMLCh c = fBuffer[fStart++];
753 if ( (c != DURATION_STARTER) &&
756 throw XMLParserException("Invalid character in time.");
759 // 'P' must ALWAYS be present in either case
760 if ( (c == chDash) &&
761 (fBuffer[fStart++]!= DURATION_STARTER ))
763 throw XMLParserException("Invalid character in time.");
767 //date[utc]=(c=='-')?'-':0;
768 //fValue[utc] = UTC_STD;
769 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
771 int negate = ( fBuffer[0] == chDash ? -1 : 1);
774 // No negative value is allowed after 'P'
776 // eg P-1234, invalid
778 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
780 throw XMLParserException("Invalid character in time.");
783 //at least one number and designator must be seen after P
784 bool designator = false;
786 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
787 if ( endDate == NOT_FOUND )
789 endDate = fEnd; // 'T' absent
793 int end = indexOf(fStart, endDate, DURATION_Y);
794 if ( end != NOT_FOUND )
797 fValue[CentYear] = negate * parseInt(fStart, end);
802 end = indexOf(fStart, endDate, DURATION_M);
803 if ( end != NOT_FOUND )
806 fValue[Month] = negate * parseInt(fStart, end);
811 end = indexOf(fStart, endDate, DURATION_D);
812 if ( end != NOT_FOUND )
815 fValue[Day] = negate * parseInt(fStart,end);
820 if ( (fEnd == endDate) && // 'T' absent
821 (fStart != fEnd) ) // something after Day
823 throw XMLParserException("Invalid character in time.");
826 if ( fEnd != endDate ) // 'T' present
828 //scan hours, minutes, seconds
832 end = indexOf(++fStart, fEnd, DURATION_H);
833 if ( end != NOT_FOUND )
836 fValue[Hour] = negate * parseInt(fStart, end);
841 end = indexOf(fStart, fEnd, DURATION_M);
842 if ( end != NOT_FOUND )
845 fValue[Minute] = negate * parseInt(fStart, end);
850 end = indexOf(fStart, fEnd, DURATION_S);
851 if ( end != NOT_FOUND )
854 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
857 * Schema Errata: E2-23
858 * at least one digit must follow the decimal point if it appears.
859 * That is, the value of the seconds component must conform
860 * to the following pattern: [0-9]+(.[0-9]+)?
862 if ( mlsec != NOT_FOUND )
865 * make usure there is something after the '.' and before the end.
867 if ( mlsec+1 == end )
869 throw XMLParserException("Invalid character in time.");
872 fValue[Second] = negate * parseInt(fStart, mlsec);
873 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
877 fValue[Second] = negate * parseInt(fStart,end);
884 // no additional data should appear after last item
885 // P1Y1M1DT is illigal value as well
886 if ( (fStart != fEnd) ||
887 fBuffer[--fStart] == DATETIME_SEPARATOR )
889 throw XMLParserException("Invalid character in time.");
895 throw XMLParserException("Invalid character in time.");
900 // ---------------------------------------------------------------------------
902 // ---------------------------------------------------------------------------
907 // Note: CCYY could be more than 4 digits
908 // Assuming fStart point to the beginning of the Date Section
909 // fStart updated to point to the position right AFTER the second 'D'
910 // Since the lenght of CCYY might be variable, we can't check format upfront
912 void DateTime::getDate()
915 // Ensure enough chars in buffer
916 if ( (fStart+YMD_MIN_SIZE) > fEnd)
917 throw XMLParserException("Date/time string not complete.");
919 getYearMonth(); // Scan YearMonth and
920 // fStart point to the next '-'
922 if (fBuffer[fStart++] != DATE_SEPARATOR)
924 throw XMLParserException("CCYY-MM must be followed by '-' sign.");
927 fValue[Day] = parseInt(fStart, fStart+2);
928 fStart += 2 ; //fStart points right after the Day
934 // hh:mm:ss[.msssss]['Z']
935 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
938 // Note: Assuming fStart point to the beginning of the Time Section
939 // fStart updated to point to the position right AFTER the second 's'
942 void DateTime::getTime()
945 // Ensure enough chars in buffer
946 if ( (fStart+TIME_MIN_SIZE) > fEnd)
947 throw XMLParserException("Incomplete Time Format.");
949 // check (fixed) format first
950 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
951 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
953 throw XMLParserException("Error in parsing time.");
957 // get hours, minute and second
959 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
960 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
961 fValue[Second] = parseInt(fStart + 6, fStart + 8);
964 // to see if any ms and/or utc part after that
968 //find UTC sign if any
969 int sign = findUTCSign(fStart);
972 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
973 if ( milisec != NOT_FOUND )
975 fStart++; // skip the '.'
976 // make sure we have some thing between the '.' and fEnd
979 throw XMLParserException("ms should be present once '.' is present.");
982 if ( sign == NOT_FOUND )
984 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
989 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
992 else if(sign == 0 || sign != fStart)
994 throw XMLParserException("Seconds has more than 2 digits.");
997 //parse UTC time zone (hh:mm)
1007 // Note: CCYY could be more than 4 digits
1008 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1010 void DateTime::getYearMonth()
1013 // Ensure enough chars in buffer
1014 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1015 throw XMLParserException("Incomplete YearMonth Format.");
1017 // skip the first leading '-'
1018 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1021 // search for year separator '-'
1023 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1024 if ( yearSeparator == NOT_FOUND)
1025 throw XMLParserException("Year separator is missing or misplaced.");
1027 fValue[CentYear] = parseIntYear(yearSeparator);
1028 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1031 //gonna check we have enough byte for month
1033 if ((fStart + 2) > fEnd )
1034 throw XMLParserException("No month in buffer.");
1036 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1037 fStart += 2; //fStart points right after the MONTH
1042 void DateTime::parseTimeZone()
1044 if ( fStart < fEnd )
1046 int sign = findUTCSign(fStart);
1049 throw XMLParserException("Error in month parsing.");
1064 // Note: Assuming fStart points to the beginning of TimeZone section
1065 // fStart updated to meet fEnd
1067 void DateTime::getTimeZone(const int sign)
1070 if ( fBuffer[sign] == UTC_STD_CHAR )
1072 if ((sign + 1) != fEnd )
1074 throw XMLParserException("Error in parsing time zone.");
1081 // otherwise, it has to be this format
1086 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1087 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1089 throw XMLParserException("Error in parsing time zone.");
1092 fTimeZone[hh] = parseInt(sign+1, sign+3);
1093 fTimeZone[mm] = parseInt(sign+4, fEnd);
1098 // ---------------------------------------------------------------------------
1099 // Validator and normalizer
1100 // ---------------------------------------------------------------------------
1103 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1105 * @param date CCYY-MM-DDThh:mm:ss+03
1106 * @return CCYY-MM-DDThh:mm:ssZ
1108 void DateTime::normalize()
1111 if ((fValue[utc] == UTC_UNKNOWN) ||
1112 (fValue[utc] == UTC_STD) )
1115 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1118 int temp = fValue[Minute] + negate * fTimeZone[mm];
1119 int carry = fQuotient(temp, 60);
1120 fValue[Minute] = mod(temp, 60, carry);
1123 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1124 carry = fQuotient(temp, 24);
1125 fValue[Hour] = mod(temp, 24, carry);
1127 fValue[Day] += carry;
1131 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1132 if (fValue[Day] < 1)
1134 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1137 else if ( fValue[Day] > temp )
1139 fValue[Day] -= temp;
1147 temp = fValue[Month] + carry;
1148 fValue[Month] = modulo(temp, 1, 13);
1149 fValue[CentYear] += fQuotient(temp, 1, 13);
1152 // set to normalized
1153 fValue[utc] = UTC_STD;
1158 void DateTime::validateDateTime() const
1161 //REVISIT: should we throw an exception for not valid dates
1162 // or reporting an error message should be sufficient?
1163 if ( fValue[CentYear] == 0 )
1165 throw XMLParserException("The year \"0000\" is an illegal year value");
1168 if ( fValue[Month] < 1 ||
1169 fValue[Month] > 12 )
1171 throw XMLParserException("The month must have values 1 to 12");
1175 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1178 throw XMLParserException("The day must have values 1 to 31");
1182 if ((fValue[Hour] < 0) ||
1183 (fValue[Hour] > 24) ||
1184 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1185 (fValue[Second] !=0) ||
1186 (fMiliSecond !=0))))
1188 throw XMLParserException("Hour must have values 0-23");
1192 if ( fValue[Minute] < 0 ||
1193 fValue[Minute] > 59 )
1195 throw XMLParserException("Minute must have values 0-59");
1199 if ( fValue[Second] < 0 ||
1200 fValue[Second] > 60 )
1202 throw XMLParserException("Second must have values 0-60");
1205 //validate time-zone hours
1206 if ( (abs(fTimeZone[hh]) > 14) ||
1207 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1209 throw XMLParserException("Time zone should have range -14..+14");
1212 //validate time-zone minutes
1213 if ( abs(fTimeZone[mm]) > 59 )
1215 throw XMLParserException("Minute must have values 0-59");
1221 // -----------------------------------------------------------------------
1222 // locator and converter
1223 // -----------------------------------------------------------------------
1224 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1226 for ( int i = start; i < end; i++ )
1227 if ( fBuffer[i] == ch )
1233 int DateTime::findUTCSign (const int start)
1236 for ( int index = start; index < fEnd; index++ )
1238 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1239 if ( pos != NOT_FOUND)
1241 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1251 // start: starting point in fBuffer
1252 // end: ending point in fBuffer (exclusive)
1253 // fStart NOT updated
1255 int DateTime::parseInt(const int start, const int end) const
1257 unsigned int retVal = 0;
1258 for (int i=start; i < end; i++) {
1260 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1261 throw XMLParserException("Invalid non-numeric characters.");
1263 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1266 return (int) retVal;
1271 // start: pointing to the first digit after the '.'
1272 // end: pointing to one position after the last digit
1273 // fStart NOT updated
1275 double DateTime::parseMiliSecond(const int start, const int end) const
1278 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1279 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1280 ArrayJanitor<XMLCh> janMili(miliSecData);
1281 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1282 *(miliSecData + miliSecLen) = chNull;
1284 char *nptr = XMLString::transcode(miliSecData);
1285 ArrayJanitor<char> jan(nptr);
1286 size_t strLen = strlen(nptr);
1290 //printf("milisec=<%s>\n", nptr);
1292 double retVal = strtod(nptr, &endptr);
1294 // check if all chars are valid char
1295 if ( (endptr - nptr) != strLen)
1296 throw XMLParserException("Invalid non-numeric characters.");
1298 // we don't check underflow occurs since
1299 // nothing we can do about it.
1306 // Note: start from fStart
1308 // fStart NOT updated
1310 int DateTime::parseIntYear(const int end) const
1312 // skip the first leading '-'
1313 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1315 int length = end - start;
1318 throw XMLParserException("Year must have 'CCYY' format");
1320 else if (length > 4 &&
1321 fBuffer[start] == chDigit_0)
1323 throw XMLParserException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
1326 bool negative = (fBuffer[0] == chDash);
1327 int yearVal = parseInt((negative ? 1 : 0), end);
1328 return ( negative ? (-1) * yearVal : yearVal );
1334 * 3.2.7.2 Canonical representation
1336 * Except for trailing fractional zero digits in the seconds representation,
1337 * '24:00:00' time representations, and timezone (for timezoned values),
1338 * the mapping from literals to values is one-to-one. Where there is more
1339 * than one possible representation, the canonical representation is as follows:
1340 * redundant trailing zero digits in fractional-second literals are prohibited.
1341 * An hour representation of '24' is prohibited. Timezoned values are canonically
1342 * represented by appending 'Z' to the nontimezoned representation. (All
1343 * timezoned dateTime values are UTC.)
1345 * .'24:00:00' -> '00:00:00'
1346 * .milisecond: trailing zeros removed
1350 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1352 XMLCh *miliStartPtr, *miliEndPtr;
1353 searchMiliSeconds(miliStartPtr, miliEndPtr);
1354 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1356 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1357 XMLCh* retPtr = retBuf;
1359 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1362 int additionalLen = fillYearString(retPtr, CentYear);
1363 if(additionalLen != 0)
1365 // very bad luck; have to resize the buffer...
1366 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1367 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1368 retPtr = tmpBuf+(retPtr-retBuf);
1372 *retPtr++ = DATE_SEPARATOR;
1373 fillString(retPtr, Month, 2);
1374 *retPtr++ = DATE_SEPARATOR;
1375 fillString(retPtr, Day, 2);
1376 *retPtr++ = DATETIME_SEPARATOR;
1378 fillString(retPtr, Hour, 2);
1379 if (fValue[Hour] == 24)
1381 *(retPtr - 2) = chDigit_0;
1382 *(retPtr - 1) = chDigit_0;
1384 *retPtr++ = TIME_SEPARATOR;
1385 fillString(retPtr, Minute, 2);
1386 *retPtr++ = TIME_SEPARATOR;
1387 fillString(retPtr, Second, 2);
1391 *retPtr++ = chPeriod;
1392 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1393 retPtr += miliSecondsLen;
1396 *retPtr++ = UTC_STD_CHAR;
1405 * . either the time zone must be omitted or,
1406 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1408 * . Additionally, the canonical representation for midnight is 00:00:00.
1411 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1413 XMLCh *miliStartPtr, *miliEndPtr;
1414 searchMiliSeconds(miliStartPtr, miliEndPtr);
1415 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1417 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1418 XMLCh* retPtr = retBuf;
1420 // 'hh:mm:ss'Z' ('.'s+)?
1424 fillString(retPtr, Hour, 2);
1425 if (fValue[Hour] == 24)
1427 *(retPtr - 2) = chDigit_0;
1428 *(retPtr - 1) = chDigit_0;
1430 *retPtr++ = TIME_SEPARATOR;
1431 fillString(retPtr, Minute, 2);
1432 *retPtr++ = TIME_SEPARATOR;
1433 fillString(retPtr, Second, 2);
1437 *retPtr++ = chPeriod;
1438 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1439 retPtr += miliSecondsLen;
1442 *retPtr++ = UTC_STD_CHAR;
1448 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1450 XMLCh strBuffer[16];
1451 assert(expLen < 16);
1452 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1453 int actualLen = XMLString::stringLen(strBuffer);
1455 //append leading zeros
1456 for (i = 0; i < expLen - actualLen; i++)
1461 for (i = 0; i < actualLen; i++)
1463 *ptr++ = strBuffer[i];
1468 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1470 XMLCh strBuffer[16];
1471 // let's hope we get no years of 15 digits...
1472 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1473 int actualLen = XMLString::stringLen(strBuffer);
1474 // don't forget that years can be negative...
1475 int negativeYear = 0;
1476 if(strBuffer[0] == chDash)
1478 *ptr++ = strBuffer[0];
1482 //append leading zeros
1483 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1488 for (i = negativeYear; i < actualLen; i++)
1490 *ptr++ = strBuffer[i];
1499 * .check if the rawData has the mili second component
1500 * .capture the substring
1503 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1505 miliStartPtr = miliEndPtr = 0;
1507 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1511 miliStartPtr = fBuffer + milisec + 1;
1512 miliEndPtr = miliStartPtr;
1515 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1521 //remove trailing zeros
1522 while( *(miliEndPtr - 1) == chDigit_0)