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"
39 #include <xercesc/util/Janitor.hpp>
41 using namespace xmltooling;
45 // constants used to process raw data (fBuffer)
47 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
51 static const XMLCh DURATION_STARTER = chLatin_P; // 'P'
52 static const XMLCh DURATION_Y = chLatin_Y; // 'Y'
53 static const XMLCh DURATION_M = chLatin_M; // 'M'
54 static const XMLCh DURATION_D = chLatin_D; // 'D'
55 static const XMLCh DURATION_H = chLatin_H; // 'H'
56 static const XMLCh DURATION_S = chLatin_S; // 'S'
58 static const XMLCh DATE_SEPARATOR = chDash; // '-'
59 static const XMLCh TIME_SEPARATOR = chColon; // ':'
60 static const XMLCh TIMEZONE_SEPARATOR = chColon; // ':'
61 static const XMLCh DATETIME_SEPARATOR = chLatin_T; // 'T'
62 static const XMLCh MILISECOND_SEPARATOR = chPeriod; // '.'
64 static const XMLCh UTC_STD_CHAR = chLatin_Z; // 'Z'
65 static const XMLCh UTC_POS_CHAR = chPlus; // '+'
66 static const XMLCh UTC_NEG_CHAR = chDash; // '-'
68 static const XMLCh UTC_SET[] = {UTC_STD_CHAR //"Z+-"
73 static const int YMD_MIN_SIZE = 10; // CCYY-MM-DD
74 static const int YMONTH_MIN_SIZE = 7; // CCYY_MM
75 static const int TIME_MIN_SIZE = 8; // hh:mm:ss
76 static const int TIMEZONE_SIZE = 5; // hh:mm
77 static const int DAY_SIZE = 5; // ---DD
78 //static const int MONTH_SIZE = 6; // --MM--
79 static const int MONTHDAY_SIZE = 7; // --MM-DD
80 static const int NOT_FOUND = -1;
82 //define constants to be used in assigning default values for
83 //all date/time excluding duration
84 static const int YEAR_DEFAULT = 2000;
85 static const int MONTH_DEFAULT = 01;
86 static const int DAY_DEFAULT = 15;
88 // order-relation on duration is a partial order. The dates below are used to
89 // for comparison of 2 durations, based on the fact that
90 // duration x and y is x<=y iff s+x<=s+y
91 // see 3.2.6 duration W3C schema datatype specs
93 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
94 const int DateTime::DATETIMES[][TOTAL_SIZE] =
96 {1696, 9, 1, 0, 0, 0, 0, UTC_STD},
97 {1697, 2, 1, 0, 0, 0, 0, UTC_STD},
98 {1903, 3, 1, 0, 0, 0, 0, UTC_STD},
99 {1903, 7, 1, 0, 0, 0, 0, UTC_STD}
102 // ---------------------------------------------------------------------------
104 // ---------------------------------------------------------------------------
105 static inline int fQuotient(int a, int b)
107 div_t div_result = div(a, b);
108 return div_result.quot;
111 static inline int fQuotient(int temp, int low, int high)
113 return fQuotient(temp - low, high - low);
116 static inline int mod(int a, int b, int quotient)
118 return (a - quotient*b) ;
121 static inline int modulo (int temp, int low, int high)
123 //modulo(a - low, high - low) + low
126 return (mod (a, b, fQuotient(a, b)) + low) ;
129 static inline bool isLeapYear(int year)
131 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
134 static int maxDayInMonthFor(int year, int month)
137 if ( month == 4 || month == 6 || month == 9 || month == 11 )
143 if ( isLeapYear(year) )
155 // ---------------------------------------------------------------------------
156 // static methods : for duration
157 // ---------------------------------------------------------------------------
159 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
161 * 3.2.6.2 Order relation on duration
163 * In general, the order-relation on duration is a partial order since there is no
164 * determinate relationship between certain durations such as one month (P1M) and 30 days (P30D).
165 * The order-relation of two duration values x and y is x < y iff s+x < s+y for each qualified
166 * dateTime s in the list below.
168 * These values for s cause the greatest deviations in the addition of dateTimes and durations
171 int DateTime::compare(const DateTime* const pDate1
172 , const DateTime* const pDate2
175 //REVISIT: this is unoptimazed vs of comparing 2 durations
176 // Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
179 int resultA, resultB = XMLDateTime::INDETERMINATE;
181 //try and see if the objects are equal
182 if ( (resultA = compareOrder(pDate1, pDate2)) == XMLDateTime::EQUAL)
183 return XMLDateTime::EQUAL;
185 //long comparison algorithm is required
186 DateTime tempA, *pTempA = &tempA;
187 DateTime tempB, *pTempB = &tempB;
189 addDuration(pTempA, pDate1, 0);
190 addDuration(pTempB, pDate2, 0);
191 resultA = compareOrder(pTempA, pTempB);
192 if ( resultA == XMLDateTime::INDETERMINATE )
193 return XMLDateTime::INDETERMINATE;
195 addDuration(pTempA, pDate1, 1);
196 addDuration(pTempB, pDate2, 1);
197 resultB = compareOrder(pTempA, pTempB);
198 resultA = compareResult(resultA, resultB, strict);
199 if ( resultA == XMLDateTime::INDETERMINATE )
200 return XMLDateTime::INDETERMINATE;
202 addDuration(pTempA, pDate1, 2);
203 addDuration(pTempB, pDate2, 2);
204 resultB = compareOrder(pTempA, pTempB);
205 resultA = compareResult(resultA, resultB, strict);
206 if ( resultA == XMLDateTime::INDETERMINATE )
207 return XMLDateTime::INDETERMINATE;
209 addDuration(pTempA, pDate1, 3);
210 addDuration(pTempB, pDate2, 3);
211 resultB = compareOrder(pTempA, pTempB);
212 resultA = compareResult(resultA, resultB, strict);
219 // Form a new DateTime with duration and baseDate array
225 void DateTime::addDuration(DateTime* fNewDate
226 , const DateTime* const fDuration
231 //REVISIT: some code could be shared between normalize() and this method,
232 // however is it worth moving it? The structures are different...
236 //add months (may be modified additionaly below)
237 int temp = DATETIMES[index][Month] + fDuration->fValue[Month];
238 fNewDate->fValue[Month] = modulo(temp, 1, 13);
239 int carry = fQuotient(temp, 1, 13);
241 //add years (may be modified additionaly below)
242 fNewDate->fValue[CentYear] =
243 DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
246 temp = DATETIMES[index][Second] + fDuration->fValue[Second];
247 carry = fQuotient (temp, 60);
248 fNewDate->fValue[Second] = mod(temp, 60, carry);
251 temp = DATETIMES[index][Minute] + fDuration->fValue[Minute] + carry;
252 carry = fQuotient(temp, 60);
253 fNewDate->fValue[Minute] = mod(temp, 60, carry);
256 temp = DATETIMES[index][Hour] + fDuration->fValue[Hour] + carry;
257 carry = fQuotient(temp, 24);
258 fNewDate->fValue[Hour] = mod(temp, 24, carry);
260 fNewDate->fValue[Day] =
261 DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
265 temp = maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]);
266 if ( fNewDate->fValue[Day] < 1 )
267 { //original fNewDate was negative
268 fNewDate->fValue[Day] +=
269 maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]-1);
272 else if ( fNewDate->fValue[Day] > temp )
274 fNewDate->fValue[Day] -= temp;
282 temp = fNewDate->fValue[Month] + carry;
283 fNewDate->fValue[Month] = modulo(temp, 1, 13);
284 fNewDate->fValue[CentYear] += fQuotient(temp, 1, 13);
287 //fNewDate->fValue[utc] = UTC_STD_CHAR;
288 fNewDate->fValue[utc] = UTC_STD;
291 int DateTime::compareResult(int resultA
296 if ( resultB == XMLDateTime::INDETERMINATE )
298 return XMLDateTime::INDETERMINATE;
300 else if ( (resultA != resultB) &&
303 return XMLDateTime::INDETERMINATE;
305 else if ( (resultA != resultB) &&
308 if ( (resultA != XMLDateTime::EQUAL) &&
309 (resultB != XMLDateTime::EQUAL) )
311 return XMLDateTime::INDETERMINATE;
315 return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
323 // ---------------------------------------------------------------------------
324 // static methods : for others
325 // ---------------------------------------------------------------------------
326 int DateTime::compare(const DateTime* const pDate1
327 , const DateTime* const pDate2)
330 if (pDate1->fValue[utc] == pDate2->fValue[utc])
332 return DateTime::compareOrder(pDate1, pDate2);
337 if ( pDate1->isNormalized())
339 c1 = compareResult(pDate1, pDate2, false, UTC_POS);
340 c2 = compareResult(pDate1, pDate2, false, UTC_NEG);
341 return getRetVal(c1, c2);
343 else if ( pDate2->isNormalized())
345 c1 = compareResult(pDate1, pDate2, true, UTC_POS);
346 c2 = compareResult(pDate1, pDate2, true, UTC_NEG);
347 return getRetVal(c1, c2);
350 return XMLDateTime::INDETERMINATE;
353 int DateTime::compareResult(const DateTime* const pDate1
354 , const DateTime* const pDate2
358 DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
360 tmpDate.fTimeZone[hh] = 14;
361 tmpDate.fTimeZone[mm] = 0;
362 tmpDate.fValue[utc] = utc_type;
365 return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
366 DateTime::compareOrder(pDate1, &tmpDate));
369 int DateTime::compareOrder(const DateTime* const lValue
370 , const DateTime* const rValue)
371 //, MemoryManager* const memMgr)
374 // If any of the them is not normalized() yet,
375 // we need to do something here.
377 DateTime lTemp = *lValue;
378 DateTime rTemp = *rValue;
383 for ( int i = 0 ; i < TOTAL_SIZE; i++ )
385 if ( lTemp.fValue[i] < rTemp.fValue[i] )
387 return XMLDateTime::LESS_THAN;
389 else if ( lTemp.fValue[i] > rTemp.fValue[i] )
391 return XMLDateTime::GREATER_THAN;
397 if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
399 return XMLDateTime::LESS_THAN;
401 else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
403 return XMLDateTime::GREATER_THAN;
407 return XMLDateTime::EQUAL;
410 // ---------------------------------------------------------------------------
412 // ---------------------------------------------------------------------------
413 DateTime::~DateTime()
429 DateTime::DateTime(const XMLCh* const aString)
440 DateTime::DateTime(time_t epoch, bool duration)
454 time_t days = epoch / 86400;
456 time_t hours = epoch / 3600;
458 time_t minutes = epoch / 60;
460 s << "P" << days << "DT" << hours << "H" << minutes << "M" << epoch << "S";
461 auto_ptr_XMLCh timeptr(s.str().c_str());
462 setBuffer(timeptr.get());
465 #ifndef HAVE_GMTIME_R
466 struct tm* ptime=gmtime(&epoch);
469 struct tm* ptime=gmtime_r(&epoch,&res);
472 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
473 auto_ptr_XMLCh timeptr(timebuf);
474 setBuffer(timeptr.get());
478 // -----------------------------------------------------------------------
479 // Copy ctor and Assignment operators
480 // -----------------------------------------------------------------------
482 DateTime::DateTime(const DateTime &toCopy)
489 DateTime& DateTime::operator=(const DateTime& rhs)
498 // -----------------------------------------------------------------------
499 // Implementation of Abstract Interface
500 // -----------------------------------------------------------------------
503 // We may simply return the handle to fBuffer
505 const XMLCh* DateTime::getRawData() const
512 const XMLCh* DateTime::getFormattedString() const
517 int DateTime::getSign() const
522 time_t DateTime::getEpoch(bool duration) const
525 time_t epoch = getSecond() + (60 * getMinute()) + (3600 * getHour()) + (86400 * getDay());
527 epoch += (((365 * 4) + 1)/48 * 86400);
529 epoch += 365.25 * 86400;
530 return getSign()!=UTC_NEG ? epoch : -epoch;
534 t.tm_sec=getSecond();
535 t.tm_min=getMinute();
538 t.tm_mon=getMonth()-1;
539 t.tm_year=getYear()-1900;
541 #if defined(HAVE_TIMEGM)
544 // Windows, and hopefully most others...?
545 return mktime(&t) - timezone;
550 // ---------------------------------------------------------------------------
552 // ---------------------------------------------------------------------------
555 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
557 void DateTime::parseDateTime()
562 //fStart is supposed to point to 'T'
563 if (fBuffer[fStart++] != DATETIME_SEPARATOR)
564 throw XMLParserException("Invalid separator between date and time.");
573 // [-]{CCYY-MM-DD}[TimeZone]
575 void DateTime::parseDate()
584 void DateTime::parseTime()
588 // time initialize to default values
589 fValue[CentYear]= YEAR_DEFAULT;
590 fValue[Month] = MONTH_DEFAULT;
591 fValue[Day] = DAY_DEFAULT;
604 void DateTime::parseDay()
608 if (fBuffer[0] != DATE_SEPARATOR ||
609 fBuffer[1] != DATE_SEPARATOR ||
610 fBuffer[2] != DATE_SEPARATOR )
612 throw XMLParserException("Invalid character in date.");
616 fValue[CentYear] = YEAR_DEFAULT;
617 fValue[Month] = MONTH_DEFAULT;
618 fValue[Day] = parseInt(fStart+3, fStart+5);
620 if ( DAY_SIZE < fEnd )
622 int sign = findUTCSign(DAY_SIZE);
625 throw XMLParserException("Invalid character in date.");
638 // {--MM--}[TimeZone]
642 void DateTime::parseMonth()
646 if (fBuffer[0] != DATE_SEPARATOR ||
647 fBuffer[1] != DATE_SEPARATOR )
649 throw XMLParserException("Invalid character in date.");
653 fValue[CentYear] = YEAR_DEFAULT;
654 fValue[Day] = DAY_DEFAULT;
655 fValue[Month] = parseInt(2, 4);
657 // REVISIT: allow both --MM and --MM-- now.
658 // need to remove the following lines to disallow --MM--
659 // when the errata is officially in the rec.
661 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
667 // parse TimeZone if any
671 int sign = findUTCSign(fStart);
674 throw XMLParserException("Invalid character in date.");
687 //[-]{CCYY}[TimeZone]
690 void DateTime::parseYear()
694 // skip the first '-' and search for timezone
696 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
698 if (sign == NOT_FOUND)
700 fValue[CentYear] = parseIntYear(fEnd);
704 fValue[CentYear] = parseIntYear(sign);
709 fValue[Month] = MONTH_DEFAULT;
710 fValue[Day] = DAY_DEFAULT; //java is 1
717 //{--MM-DD}[TimeZone]
720 void DateTime::parseMonthDay()
724 if (fBuffer[0] != DATE_SEPARATOR ||
725 fBuffer[1] != DATE_SEPARATOR ||
726 fBuffer[4] != DATE_SEPARATOR )
728 throw XMLParserException("Invalid character in date.");
733 fValue[CentYear] = YEAR_DEFAULT;
734 fValue[Month] = parseInt(2, 4);
735 fValue[Day] = parseInt(5, 7);
737 if ( MONTHDAY_SIZE < fEnd )
739 int sign = findUTCSign(MONTHDAY_SIZE);
742 throw XMLParserException("Invalid character in date.");
754 void DateTime::parseYearMonth()
760 fValue[Day] = DAY_DEFAULT;
768 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
770 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
772 // Note: the n above shall be >= 0
773 // if no time element found, 'T' shall be absent
775 void DateTime::parseDuration()
779 // must start with '-' or 'P'
781 XMLCh c = fBuffer[fStart++];
782 if ( (c != DURATION_STARTER) &&
785 throw XMLParserException("Invalid character in time.");
788 // 'P' must ALWAYS be present in either case
789 if ( (c == chDash) &&
790 (fBuffer[fStart++]!= DURATION_STARTER ))
792 throw XMLParserException("Invalid character in time.");
796 //date[utc]=(c=='-')?'-':0;
797 //fValue[utc] = UTC_STD;
798 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
800 int negate = ( fBuffer[0] == chDash ? -1 : 1);
803 // No negative value is allowed after 'P'
805 // eg P-1234, invalid
807 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
809 throw XMLParserException("Invalid character in time.");
812 //at least one number and designator must be seen after P
813 bool designator = false;
815 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
816 if ( endDate == NOT_FOUND )
818 endDate = fEnd; // 'T' absent
822 int end = indexOf(fStart, endDate, DURATION_Y);
823 if ( end != NOT_FOUND )
826 fValue[CentYear] = negate * parseInt(fStart, end);
831 end = indexOf(fStart, endDate, DURATION_M);
832 if ( end != NOT_FOUND )
835 fValue[Month] = negate * parseInt(fStart, end);
840 end = indexOf(fStart, endDate, DURATION_D);
841 if ( end != NOT_FOUND )
844 fValue[Day] = negate * parseInt(fStart,end);
849 if ( (fEnd == endDate) && // 'T' absent
850 (fStart != fEnd) ) // something after Day
852 throw XMLParserException("Invalid character in time.");
855 if ( fEnd != endDate ) // 'T' present
857 //scan hours, minutes, seconds
861 end = indexOf(++fStart, fEnd, DURATION_H);
862 if ( end != NOT_FOUND )
865 fValue[Hour] = negate * parseInt(fStart, end);
870 end = indexOf(fStart, fEnd, DURATION_M);
871 if ( end != NOT_FOUND )
874 fValue[Minute] = negate * parseInt(fStart, end);
879 end = indexOf(fStart, fEnd, DURATION_S);
880 if ( end != NOT_FOUND )
883 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
886 * Schema Errata: E2-23
887 * at least one digit must follow the decimal point if it appears.
888 * That is, the value of the seconds component must conform
889 * to the following pattern: [0-9]+(.[0-9]+)?
891 if ( mlsec != NOT_FOUND )
894 * make usure there is something after the '.' and before the end.
896 if ( mlsec+1 == end )
898 throw XMLParserException("Invalid character in time.");
901 fValue[Second] = negate * parseInt(fStart, mlsec);
902 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
906 fValue[Second] = negate * parseInt(fStart,end);
913 // no additional data should appear after last item
914 // P1Y1M1DT is illigal value as well
915 if ( (fStart != fEnd) ||
916 fBuffer[--fStart] == DATETIME_SEPARATOR )
918 throw XMLParserException("Invalid character in time.");
924 throw XMLParserException("Invalid character in time.");
929 // ---------------------------------------------------------------------------
931 // ---------------------------------------------------------------------------
936 // Note: CCYY could be more than 4 digits
937 // Assuming fStart point to the beginning of the Date Section
938 // fStart updated to point to the position right AFTER the second 'D'
939 // Since the lenght of CCYY might be variable, we can't check format upfront
941 void DateTime::getDate()
944 // Ensure enough chars in buffer
945 if ( (fStart+YMD_MIN_SIZE) > fEnd)
946 throw XMLParserException("Date/time string not complete.");
948 getYearMonth(); // Scan YearMonth and
949 // fStart point to the next '-'
951 if (fBuffer[fStart++] != DATE_SEPARATOR)
953 throw XMLParserException("CCYY-MM must be followed by '-' sign.");
956 fValue[Day] = parseInt(fStart, fStart+2);
957 fStart += 2 ; //fStart points right after the Day
963 // hh:mm:ss[.msssss]['Z']
964 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
967 // Note: Assuming fStart point to the beginning of the Time Section
968 // fStart updated to point to the position right AFTER the second 's'
971 void DateTime::getTime()
974 // Ensure enough chars in buffer
975 if ( (fStart+TIME_MIN_SIZE) > fEnd)
976 throw XMLParserException("Incomplete Time Format.");
978 // check (fixed) format first
979 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
980 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
982 throw XMLParserException("Error in parsing time.");
986 // get hours, minute and second
988 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
989 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
990 fValue[Second] = parseInt(fStart + 6, fStart + 8);
993 // to see if any ms and/or utc part after that
997 //find UTC sign if any
998 int sign = findUTCSign(fStart);
1001 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
1002 if ( milisec != NOT_FOUND )
1004 fStart++; // skip the '.'
1005 // make sure we have some thing between the '.' and fEnd
1008 throw XMLParserException("ms should be present once '.' is present.");
1011 if ( sign == NOT_FOUND )
1013 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
1018 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
1021 else if(sign == 0 || sign != fStart)
1023 throw XMLParserException("Seconds has more than 2 digits.");
1026 //parse UTC time zone (hh:mm)
1036 // Note: CCYY could be more than 4 digits
1037 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1039 void DateTime::getYearMonth()
1042 // Ensure enough chars in buffer
1043 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1044 throw XMLParserException("Incomplete YearMonth Format.");
1046 // skip the first leading '-'
1047 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1050 // search for year separator '-'
1052 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1053 if ( yearSeparator == NOT_FOUND)
1054 throw XMLParserException("Year separator is missing or misplaced.");
1056 fValue[CentYear] = parseIntYear(yearSeparator);
1057 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1060 //gonna check we have enough byte for month
1062 if ((fStart + 2) > fEnd )
1063 throw XMLParserException("No month in buffer.");
1065 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1066 fStart += 2; //fStart points right after the MONTH
1071 void DateTime::parseTimeZone()
1073 if ( fStart < fEnd )
1075 int sign = findUTCSign(fStart);
1078 throw XMLParserException("Error in month parsing.");
1093 // Note: Assuming fStart points to the beginning of TimeZone section
1094 // fStart updated to meet fEnd
1096 void DateTime::getTimeZone(const int sign)
1099 if ( fBuffer[sign] == UTC_STD_CHAR )
1101 if ((sign + 1) != fEnd )
1103 throw XMLParserException("Error in parsing time zone.");
1110 // otherwise, it has to be this format
1115 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1116 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1118 throw XMLParserException("Error in parsing time zone.");
1121 fTimeZone[hh] = parseInt(sign+1, sign+3);
1122 fTimeZone[mm] = parseInt(sign+4, fEnd);
1127 // ---------------------------------------------------------------------------
1128 // Validator and normalizer
1129 // ---------------------------------------------------------------------------
1132 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1134 * @param date CCYY-MM-DDThh:mm:ss+03
1135 * @return CCYY-MM-DDThh:mm:ssZ
1137 void DateTime::normalize()
1140 if ((fValue[utc] == UTC_UNKNOWN) ||
1141 (fValue[utc] == UTC_STD) )
1144 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1147 int temp = fValue[Minute] + negate * fTimeZone[mm];
1148 int carry = fQuotient(temp, 60);
1149 fValue[Minute] = mod(temp, 60, carry);
1152 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1153 carry = fQuotient(temp, 24);
1154 fValue[Hour] = mod(temp, 24, carry);
1156 fValue[Day] += carry;
1160 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1161 if (fValue[Day] < 1)
1163 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1166 else if ( fValue[Day] > temp )
1168 fValue[Day] -= temp;
1176 temp = fValue[Month] + carry;
1177 fValue[Month] = modulo(temp, 1, 13);
1178 fValue[CentYear] += fQuotient(temp, 1, 13);
1181 // set to normalized
1182 fValue[utc] = UTC_STD;
1187 void DateTime::validateDateTime() const
1190 //REVISIT: should we throw an exception for not valid dates
1191 // or reporting an error message should be sufficient?
1192 if ( fValue[CentYear] == 0 )
1194 throw XMLParserException("The year \"0000\" is an illegal year value");
1197 if ( fValue[Month] < 1 ||
1198 fValue[Month] > 12 )
1200 throw XMLParserException("The month must have values 1 to 12");
1204 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1207 throw XMLParserException("The day must have values 1 to 31");
1211 if ((fValue[Hour] < 0) ||
1212 (fValue[Hour] > 24) ||
1213 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1214 (fValue[Second] !=0) ||
1215 (fMiliSecond !=0))))
1217 throw XMLParserException("Hour must have values 0-23");
1221 if ( fValue[Minute] < 0 ||
1222 fValue[Minute] > 59 )
1224 throw XMLParserException("Minute must have values 0-59");
1228 if ( fValue[Second] < 0 ||
1229 fValue[Second] > 60 )
1231 throw XMLParserException("Second must have values 0-60");
1234 //validate time-zone hours
1235 if ( (abs(fTimeZone[hh]) > 14) ||
1236 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1238 throw XMLParserException("Time zone should have range -14..+14");
1241 //validate time-zone minutes
1242 if ( abs(fTimeZone[mm]) > 59 )
1244 throw XMLParserException("Minute must have values 0-59");
1250 // -----------------------------------------------------------------------
1251 // locator and converter
1252 // -----------------------------------------------------------------------
1253 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1255 for ( int i = start; i < end; i++ )
1256 if ( fBuffer[i] == ch )
1262 int DateTime::findUTCSign (const int start)
1265 for ( int index = start; index < fEnd; index++ )
1267 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1268 if ( pos != NOT_FOUND)
1270 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1280 // start: starting point in fBuffer
1281 // end: ending point in fBuffer (exclusive)
1282 // fStart NOT updated
1284 int DateTime::parseInt(const int start, const int end) const
1286 unsigned int retVal = 0;
1287 for (int i=start; i < end; i++) {
1289 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1290 throw XMLParserException("Invalid non-numeric characters.");
1292 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1295 return (int) retVal;
1300 // start: pointing to the first digit after the '.'
1301 // end: pointing to one position after the last digit
1302 // fStart NOT updated
1304 double DateTime::parseMiliSecond(const int start, const int end) const
1307 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1308 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1309 ArrayJanitor<XMLCh> janMili(miliSecData);
1310 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1311 *(miliSecData + miliSecLen) = chNull;
1313 char *nptr = XMLString::transcode(miliSecData);
1314 ArrayJanitor<char> jan(nptr);
1315 size_t strLen = strlen(nptr);
1319 //printf("milisec=<%s>\n", nptr);
1321 double retVal = strtod(nptr, &endptr);
1323 // check if all chars are valid char
1324 if ( (endptr - nptr) != strLen)
1325 throw XMLParserException("Invalid non-numeric characters.");
1327 // we don't check underflow occurs since
1328 // nothing we can do about it.
1335 // Note: start from fStart
1337 // fStart NOT updated
1339 int DateTime::parseIntYear(const int end) const
1341 // skip the first leading '-'
1342 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1344 int length = end - start;
1347 throw XMLParserException("Year must have 'CCYY' format");
1349 else if (length > 4 &&
1350 fBuffer[start] == chDigit_0)
1352 throw XMLParserException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
1355 bool negative = (fBuffer[0] == chDash);
1356 int yearVal = parseInt((negative ? 1 : 0), end);
1357 return ( negative ? (-1) * yearVal : yearVal );
1363 * 3.2.7.2 Canonical representation
1365 * Except for trailing fractional zero digits in the seconds representation,
1366 * '24:00:00' time representations, and timezone (for timezoned values),
1367 * the mapping from literals to values is one-to-one. Where there is more
1368 * than one possible representation, the canonical representation is as follows:
1369 * redundant trailing zero digits in fractional-second literals are prohibited.
1370 * An hour representation of '24' is prohibited. Timezoned values are canonically
1371 * represented by appending 'Z' to the nontimezoned representation. (All
1372 * timezoned dateTime values are UTC.)
1374 * .'24:00:00' -> '00:00:00'
1375 * .milisecond: trailing zeros removed
1379 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1381 XMLCh *miliStartPtr, *miliEndPtr;
1382 searchMiliSeconds(miliStartPtr, miliEndPtr);
1383 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1385 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1386 XMLCh* retPtr = retBuf;
1388 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1391 int additionalLen = fillYearString(retPtr, CentYear);
1392 if(additionalLen != 0)
1394 // very bad luck; have to resize the buffer...
1395 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1396 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1397 retPtr = tmpBuf+(retPtr-retBuf);
1401 *retPtr++ = DATE_SEPARATOR;
1402 fillString(retPtr, Month, 2);
1403 *retPtr++ = DATE_SEPARATOR;
1404 fillString(retPtr, Day, 2);
1405 *retPtr++ = DATETIME_SEPARATOR;
1407 fillString(retPtr, Hour, 2);
1408 if (fValue[Hour] == 24)
1410 *(retPtr - 2) = chDigit_0;
1411 *(retPtr - 1) = chDigit_0;
1413 *retPtr++ = TIME_SEPARATOR;
1414 fillString(retPtr, Minute, 2);
1415 *retPtr++ = TIME_SEPARATOR;
1416 fillString(retPtr, Second, 2);
1420 *retPtr++ = chPeriod;
1421 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1422 retPtr += miliSecondsLen;
1425 *retPtr++ = UTC_STD_CHAR;
1434 * . either the time zone must be omitted or,
1435 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1437 * . Additionally, the canonical representation for midnight is 00:00:00.
1440 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1442 XMLCh *miliStartPtr, *miliEndPtr;
1443 searchMiliSeconds(miliStartPtr, miliEndPtr);
1444 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1446 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1447 XMLCh* retPtr = retBuf;
1449 // 'hh:mm:ss'Z' ('.'s+)?
1453 fillString(retPtr, Hour, 2);
1454 if (fValue[Hour] == 24)
1456 *(retPtr - 2) = chDigit_0;
1457 *(retPtr - 1) = chDigit_0;
1459 *retPtr++ = TIME_SEPARATOR;
1460 fillString(retPtr, Minute, 2);
1461 *retPtr++ = TIME_SEPARATOR;
1462 fillString(retPtr, Second, 2);
1466 *retPtr++ = chPeriod;
1467 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1468 retPtr += miliSecondsLen;
1471 *retPtr++ = UTC_STD_CHAR;
1477 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1479 XMLCh strBuffer[16];
1480 assert(expLen < 16);
1481 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1482 int actualLen = XMLString::stringLen(strBuffer);
1484 //append leading zeros
1485 for (i = 0; i < expLen - actualLen; i++)
1490 for (i = 0; i < actualLen; i++)
1492 *ptr++ = strBuffer[i];
1497 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1499 XMLCh strBuffer[16];
1500 // let's hope we get no years of 15 digits...
1501 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1502 int actualLen = XMLString::stringLen(strBuffer);
1503 // don't forget that years can be negative...
1504 int negativeYear = 0;
1505 if(strBuffer[0] == chDash)
1507 *ptr++ = strBuffer[0];
1511 //append leading zeros
1512 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1517 for (i = negativeYear; i < actualLen; i++)
1519 *ptr++ = strBuffer[i];
1528 * .check if the rawData has the mili second component
1529 * .capture the substring
1532 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1534 miliStartPtr = miliEndPtr = 0;
1536 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1540 miliStartPtr = fBuffer + milisec + 1;
1541 miliEndPtr = miliStartPtr;
1544 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1550 //remove trailing zeros
1551 while( *(miliEndPtr - 1) == chDigit_0)