2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
24 * Manipulation of XML date/time data.
28 * This is mostly copied from Xerces-C, but they don't seem inclined to produce a usable
29 * class, so I had to incorporate my own version of it for now. I can't inherit it
30 * since the fields I need are private.
34 #include "util/DateTime.h"
43 #include <xercesc/util/Janitor.hpp>
45 using namespace xmltooling;
46 using namespace xercesc;
50 // constants used to process raw data (fBuffer)
52 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
56 static const XMLCh DURATION_STARTER = chLatin_P; // 'P'
57 static const XMLCh DURATION_Y = chLatin_Y; // 'Y'
58 static const XMLCh DURATION_M = chLatin_M; // 'M'
59 static const XMLCh DURATION_D = chLatin_D; // 'D'
60 static const XMLCh DURATION_H = chLatin_H; // 'H'
61 static const XMLCh DURATION_S = chLatin_S; // 'S'
63 static const XMLCh DATE_SEPARATOR = chDash; // '-'
64 static const XMLCh TIME_SEPARATOR = chColon; // ':'
65 static const XMLCh TIMEZONE_SEPARATOR = chColon; // ':'
66 static const XMLCh DATETIME_SEPARATOR = chLatin_T; // 'T'
67 static const XMLCh MILISECOND_SEPARATOR = chPeriod; // '.'
69 static const XMLCh UTC_STD_CHAR = chLatin_Z; // 'Z'
70 static const XMLCh UTC_POS_CHAR = chPlus; // '+'
71 static const XMLCh UTC_NEG_CHAR = chDash; // '-'
73 static const XMLCh UTC_SET[] = {UTC_STD_CHAR //"Z+-"
78 static const int YMD_MIN_SIZE = 10; // CCYY-MM-DD
79 static const int YMONTH_MIN_SIZE = 7; // CCYY_MM
80 static const int TIME_MIN_SIZE = 8; // hh:mm:ss
81 static const int TIMEZONE_SIZE = 5; // hh:mm
82 static const int DAY_SIZE = 5; // ---DD
83 //static const int MONTH_SIZE = 6; // --MM--
84 static const int MONTHDAY_SIZE = 7; // --MM-DD
85 static const int NOT_FOUND = -1;
87 //define constants to be used in assigning default values for
88 //all date/time excluding duration
89 static const int YEAR_DEFAULT = 2000;
90 static const int MONTH_DEFAULT = 01;
91 static const int DAY_DEFAULT = 15;
93 // order-relation on duration is a partial order. The dates below are used to
94 // for comparison of 2 durations, based on the fact that
95 // duration x and y is x<=y iff s+x<=s+y
96 // see 3.2.6 duration W3C schema datatype specs
98 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
99 const int DateTime::DATETIMES[][TOTAL_SIZE] =
101 {1696, 9, 1, 0, 0, 0, 0, UTC_STD},
102 {1697, 2, 1, 0, 0, 0, 0, UTC_STD},
103 {1903, 3, 1, 0, 0, 0, 0, UTC_STD},
104 {1903, 7, 1, 0, 0, 0, 0, UTC_STD}
107 // ---------------------------------------------------------------------------
109 // ---------------------------------------------------------------------------
110 static inline int fQuotient(int a, int b)
112 div_t div_result = div(a, b);
113 return div_result.quot;
116 static inline int fQuotient(int temp, int low, int high)
118 return fQuotient(temp - low, high - low);
121 static inline int mod(int a, int b, int quotient)
123 return (a - quotient*b) ;
126 static inline int modulo (int temp, int low, int high)
128 //modulo(a - low, high - low) + low
131 return (mod (a, b, fQuotient(a, b)) + low) ;
134 static inline bool isLeapYear(int year)
136 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
139 static int maxDayInMonthFor(int year, int month)
142 if ( month == 4 || month == 6 || month == 9 || month == 11 )
148 if ( isLeapYear(year) )
160 // ---------------------------------------------------------------------------
161 // static methods : for duration
162 // ---------------------------------------------------------------------------
164 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
166 * 3.2.6.2 Order relation on duration
168 * In general, the order-relation on duration is a partial order since there is no
169 * determinate relationship between certain durations such as one month (P1M) and 30 days (P30D).
170 * The order-relation of two duration values x and y is x < y iff s+x < s+y for each qualified
171 * dateTime s in the list below.
173 * These values for s cause the greatest deviations in the addition of dateTimes and durations
176 int DateTime::compare(const DateTime* const pDate1
177 , const DateTime* const pDate2
180 //REVISIT: this is unoptimazed vs of comparing 2 durations
181 // Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
184 int resultA, resultB = XMLDateTime::INDETERMINATE;
186 //try and see if the objects are equal
187 if ( (resultA = compareOrder(pDate1, pDate2)) == XMLDateTime::EQUAL)
188 return XMLDateTime::EQUAL;
190 //long comparison algorithm is required
191 DateTime tempA, *pTempA = &tempA;
192 DateTime tempB, *pTempB = &tempB;
194 addDuration(pTempA, pDate1, 0);
195 addDuration(pTempB, pDate2, 0);
196 resultA = compareOrder(pTempA, pTempB);
197 if ( resultA == XMLDateTime::INDETERMINATE )
198 return XMLDateTime::INDETERMINATE;
200 addDuration(pTempA, pDate1, 1);
201 addDuration(pTempB, pDate2, 1);
202 resultB = compareOrder(pTempA, pTempB);
203 resultA = compareResult(resultA, resultB, strict);
204 if ( resultA == XMLDateTime::INDETERMINATE )
205 return XMLDateTime::INDETERMINATE;
207 addDuration(pTempA, pDate1, 2);
208 addDuration(pTempB, pDate2, 2);
209 resultB = compareOrder(pTempA, pTempB);
210 resultA = compareResult(resultA, resultB, strict);
211 if ( resultA == XMLDateTime::INDETERMINATE )
212 return XMLDateTime::INDETERMINATE;
214 addDuration(pTempA, pDate1, 3);
215 addDuration(pTempB, pDate2, 3);
216 resultB = compareOrder(pTempA, pTempB);
217 resultA = compareResult(resultA, resultB, strict);
224 // Form a new DateTime with duration and baseDate array
230 void DateTime::addDuration(DateTime* fNewDate
231 , const DateTime* const fDuration
236 //REVISIT: some code could be shared between normalize() and this method,
237 // however is it worth moving it? The structures are different...
241 //add months (may be modified additionaly below)
242 int temp = DATETIMES[index][Month] + fDuration->fValue[Month];
243 fNewDate->fValue[Month] = modulo(temp, 1, 13);
244 int carry = fQuotient(temp, 1, 13);
246 //add years (may be modified additionaly below)
247 fNewDate->fValue[CentYear] =
248 DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
251 temp = DATETIMES[index][Second] + fDuration->fValue[Second];
252 carry = fQuotient (temp, 60);
253 fNewDate->fValue[Second] = mod(temp, 60, carry);
256 temp = DATETIMES[index][Minute] + fDuration->fValue[Minute] + carry;
257 carry = fQuotient(temp, 60);
258 fNewDate->fValue[Minute] = mod(temp, 60, carry);
261 temp = DATETIMES[index][Hour] + fDuration->fValue[Hour] + carry;
262 carry = fQuotient(temp, 24);
263 fNewDate->fValue[Hour] = mod(temp, 24, carry);
265 fNewDate->fValue[Day] =
266 DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
270 temp = maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]);
271 if ( fNewDate->fValue[Day] < 1 )
272 { //original fNewDate was negative
273 fNewDate->fValue[Day] +=
274 maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]-1);
277 else if ( fNewDate->fValue[Day] > temp )
279 fNewDate->fValue[Day] -= temp;
287 temp = fNewDate->fValue[Month] + carry;
288 fNewDate->fValue[Month] = modulo(temp, 1, 13);
289 fNewDate->fValue[CentYear] += fQuotient(temp, 1, 13);
292 //fNewDate->fValue[utc] = UTC_STD_CHAR;
293 fNewDate->fValue[utc] = UTC_STD;
296 int DateTime::compareResult(int resultA
301 if ( resultB == XMLDateTime::INDETERMINATE )
303 return XMLDateTime::INDETERMINATE;
305 else if ( (resultA != resultB) &&
308 return XMLDateTime::INDETERMINATE;
310 else if ( (resultA != resultB) &&
313 if ( (resultA != XMLDateTime::EQUAL) &&
314 (resultB != XMLDateTime::EQUAL) )
316 return XMLDateTime::INDETERMINATE;
320 return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
328 // ---------------------------------------------------------------------------
329 // static methods : for others
330 // ---------------------------------------------------------------------------
331 int DateTime::compare(const DateTime* const pDate1
332 , const DateTime* const pDate2)
335 if (pDate1->fValue[utc] == pDate2->fValue[utc])
337 return DateTime::compareOrder(pDate1, pDate2);
342 if ( pDate1->isNormalized())
344 c1 = compareResult(pDate1, pDate2, false, UTC_POS);
345 c2 = compareResult(pDate1, pDate2, false, UTC_NEG);
346 return getRetVal(c1, c2);
348 else if ( pDate2->isNormalized())
350 c1 = compareResult(pDate1, pDate2, true, UTC_POS);
351 c2 = compareResult(pDate1, pDate2, true, UTC_NEG);
352 return getRetVal(c1, c2);
355 return XMLDateTime::INDETERMINATE;
358 int DateTime::compareResult(const DateTime* const pDate1
359 , const DateTime* const pDate2
363 DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
365 tmpDate.fTimeZone[hh] = 14;
366 tmpDate.fTimeZone[mm] = 0;
367 tmpDate.fValue[utc] = utc_type;
370 return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
371 DateTime::compareOrder(pDate1, &tmpDate));
374 int DateTime::compareOrder(const DateTime* const lValue
375 , const DateTime* const rValue)
376 //, MemoryManager* const memMgr)
379 // If any of the them is not normalized() yet,
380 // we need to do something here.
382 DateTime lTemp = *lValue;
383 DateTime rTemp = *rValue;
388 for ( int i = 0 ; i < TOTAL_SIZE; i++ )
390 if ( lTemp.fValue[i] < rTemp.fValue[i] )
392 return XMLDateTime::LESS_THAN;
394 else if ( lTemp.fValue[i] > rTemp.fValue[i] )
396 return XMLDateTime::GREATER_THAN;
402 if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
404 return XMLDateTime::LESS_THAN;
406 else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
408 return XMLDateTime::GREATER_THAN;
412 return XMLDateTime::EQUAL;
415 // ---------------------------------------------------------------------------
417 // ---------------------------------------------------------------------------
418 DateTime::~DateTime()
434 DateTime::DateTime(const XMLCh* const aString)
445 DateTime::DateTime(time_t epoch, bool duration)
459 time_t days = epoch / 86400;
461 time_t hours = epoch / 3600;
463 time_t minutes = epoch / 60;
465 s << "P" << days << "DT" << hours << "H" << minutes << "M" << epoch << "S";
466 auto_ptr_XMLCh timeptr(s.str().c_str());
467 setBuffer(timeptr.get());
470 #ifndef HAVE_GMTIME_R
471 struct tm* ptime=gmtime(&epoch);
474 struct tm* ptime=gmtime_r(&epoch,&res);
477 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
478 auto_ptr_XMLCh timeptr(timebuf);
479 setBuffer(timeptr.get());
483 // -----------------------------------------------------------------------
484 // Copy ctor and Assignment operators
485 // -----------------------------------------------------------------------
487 DateTime::DateTime(const DateTime &toCopy)
494 DateTime& DateTime::operator=(const DateTime& rhs)
503 // -----------------------------------------------------------------------
504 // Implementation of Abstract Interface
505 // -----------------------------------------------------------------------
508 // We may simply return the handle to fBuffer
510 const XMLCh* DateTime::getRawData() const
517 const XMLCh* DateTime::getFormattedString() const
522 int DateTime::getSign() const
527 time_t DateTime::getEpoch(bool duration) const
530 time_t epoch = getSecond() + (60 * getMinute()) + (3600 * getHour()) + (86400 * getDay());
532 epoch += (((365 * 4) + 1)/48 * 86400);
534 epoch += 365.25 * 86400;
535 return getSign()!=UTC_NEG ? epoch : -epoch;
539 t.tm_sec=getSecond();
540 t.tm_min=getMinute();
543 t.tm_mon=getMonth()-1;
544 t.tm_year=getYear()-1900;
546 #if defined(HAVE_TIMEGM)
549 // Windows, and hopefully most others...?
550 return mktime(&t) - timezone;
555 // ---------------------------------------------------------------------------
557 // ---------------------------------------------------------------------------
560 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
562 void DateTime::parseDateTime()
567 //fStart is supposed to point to 'T'
568 if (fBuffer[fStart++] != DATETIME_SEPARATOR)
569 throw XMLParserException("Invalid separator between date and time.");
578 // [-]{CCYY-MM-DD}[TimeZone]
580 void DateTime::parseDate()
589 void DateTime::parseTime()
593 // time initialize to default values
594 fValue[CentYear]= YEAR_DEFAULT;
595 fValue[Month] = MONTH_DEFAULT;
596 fValue[Day] = DAY_DEFAULT;
609 void DateTime::parseDay()
613 if (fBuffer[0] != DATE_SEPARATOR ||
614 fBuffer[1] != DATE_SEPARATOR ||
615 fBuffer[2] != DATE_SEPARATOR )
617 throw XMLParserException("Invalid character in date.");
621 fValue[CentYear] = YEAR_DEFAULT;
622 fValue[Month] = MONTH_DEFAULT;
623 fValue[Day] = parseInt(fStart+3, fStart+5);
625 if ( DAY_SIZE < fEnd )
627 int sign = findUTCSign(DAY_SIZE);
630 throw XMLParserException("Invalid character in date.");
643 // {--MM--}[TimeZone]
647 void DateTime::parseMonth()
651 if (fBuffer[0] != DATE_SEPARATOR ||
652 fBuffer[1] != DATE_SEPARATOR )
654 throw XMLParserException("Invalid character in date.");
658 fValue[CentYear] = YEAR_DEFAULT;
659 fValue[Day] = DAY_DEFAULT;
660 fValue[Month] = parseInt(2, 4);
662 // REVISIT: allow both --MM and --MM-- now.
663 // need to remove the following lines to disallow --MM--
664 // when the errata is officially in the rec.
666 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
672 // parse TimeZone if any
676 int sign = findUTCSign(fStart);
679 throw XMLParserException("Invalid character in date.");
692 //[-]{CCYY}[TimeZone]
695 void DateTime::parseYear()
699 // skip the first '-' and search for timezone
701 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
703 if (sign == NOT_FOUND)
705 fValue[CentYear] = parseIntYear(fEnd);
709 fValue[CentYear] = parseIntYear(sign);
714 fValue[Month] = MONTH_DEFAULT;
715 fValue[Day] = DAY_DEFAULT; //java is 1
722 //{--MM-DD}[TimeZone]
725 void DateTime::parseMonthDay()
729 if (fBuffer[0] != DATE_SEPARATOR ||
730 fBuffer[1] != DATE_SEPARATOR ||
731 fBuffer[4] != DATE_SEPARATOR )
733 throw XMLParserException("Invalid character in date.");
738 fValue[CentYear] = YEAR_DEFAULT;
739 fValue[Month] = parseInt(2, 4);
740 fValue[Day] = parseInt(5, 7);
742 if ( MONTHDAY_SIZE < fEnd )
744 int sign = findUTCSign(MONTHDAY_SIZE);
747 throw XMLParserException("Invalid character in date.");
759 void DateTime::parseYearMonth()
765 fValue[Day] = DAY_DEFAULT;
773 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
775 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
777 // Note: the n above shall be >= 0
778 // if no time element found, 'T' shall be absent
780 void DateTime::parseDuration()
784 // must start with '-' or 'P'
786 XMLCh c = fBuffer[fStart++];
787 if ( (c != DURATION_STARTER) &&
790 throw XMLParserException("Invalid character in time.");
793 // 'P' must ALWAYS be present in either case
794 if ( (c == chDash) &&
795 (fBuffer[fStart++]!= DURATION_STARTER ))
797 throw XMLParserException("Invalid character in time.");
801 //date[utc]=(c=='-')?'-':0;
802 //fValue[utc] = UTC_STD;
803 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
805 int negate = ( fBuffer[0] == chDash ? -1 : 1);
808 // No negative value is allowed after 'P'
810 // eg P-1234, invalid
812 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
814 throw XMLParserException("Invalid character in time.");
817 //at least one number and designator must be seen after P
818 bool designator = false;
820 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
821 if ( endDate == NOT_FOUND )
823 endDate = fEnd; // 'T' absent
827 int end = indexOf(fStart, endDate, DURATION_Y);
828 if ( end != NOT_FOUND )
831 fValue[CentYear] = negate * parseInt(fStart, end);
836 end = indexOf(fStart, endDate, DURATION_M);
837 if ( end != NOT_FOUND )
840 fValue[Month] = negate * parseInt(fStart, end);
845 end = indexOf(fStart, endDate, DURATION_D);
846 if ( end != NOT_FOUND )
849 fValue[Day] = negate * parseInt(fStart,end);
854 if ( (fEnd == endDate) && // 'T' absent
855 (fStart != fEnd) ) // something after Day
857 throw XMLParserException("Invalid character in time.");
860 if ( fEnd != endDate ) // 'T' present
862 //scan hours, minutes, seconds
866 end = indexOf(++fStart, fEnd, DURATION_H);
867 if ( end != NOT_FOUND )
870 fValue[Hour] = negate * parseInt(fStart, end);
875 end = indexOf(fStart, fEnd, DURATION_M);
876 if ( end != NOT_FOUND )
879 fValue[Minute] = negate * parseInt(fStart, end);
884 end = indexOf(fStart, fEnd, DURATION_S);
885 if ( end != NOT_FOUND )
888 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
891 * Schema Errata: E2-23
892 * at least one digit must follow the decimal point if it appears.
893 * That is, the value of the seconds component must conform
894 * to the following pattern: [0-9]+(.[0-9]+)?
896 if ( mlsec != NOT_FOUND )
899 * make usure there is something after the '.' and before the end.
901 if ( mlsec+1 == end )
903 throw XMLParserException("Invalid character in time.");
906 fValue[Second] = negate * parseInt(fStart, mlsec);
907 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
911 fValue[Second] = negate * parseInt(fStart,end);
918 // no additional data should appear after last item
919 // P1Y1M1DT is illigal value as well
920 if ( (fStart != fEnd) ||
921 fBuffer[--fStart] == DATETIME_SEPARATOR )
923 throw XMLParserException("Invalid character in time.");
929 throw XMLParserException("Invalid character in time.");
934 // ---------------------------------------------------------------------------
936 // ---------------------------------------------------------------------------
941 // Note: CCYY could be more than 4 digits
942 // Assuming fStart point to the beginning of the Date Section
943 // fStart updated to point to the position right AFTER the second 'D'
944 // Since the lenght of CCYY might be variable, we can't check format upfront
946 void DateTime::getDate()
949 // Ensure enough chars in buffer
950 if ( (fStart+YMD_MIN_SIZE) > fEnd)
951 throw XMLParserException("Date/time string not complete.");
953 getYearMonth(); // Scan YearMonth and
954 // fStart point to the next '-'
956 if (fBuffer[fStart++] != DATE_SEPARATOR)
958 throw XMLParserException("CCYY-MM must be followed by '-' sign.");
961 fValue[Day] = parseInt(fStart, fStart+2);
962 fStart += 2 ; //fStart points right after the Day
968 // hh:mm:ss[.msssss]['Z']
969 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
972 // Note: Assuming fStart point to the beginning of the Time Section
973 // fStart updated to point to the position right AFTER the second 's'
976 void DateTime::getTime()
979 // Ensure enough chars in buffer
980 if ( (fStart+TIME_MIN_SIZE) > fEnd)
981 throw XMLParserException("Incomplete Time Format.");
983 // check (fixed) format first
984 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
985 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
987 throw XMLParserException("Error in parsing time.");
991 // get hours, minute and second
993 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
994 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
995 fValue[Second] = parseInt(fStart + 6, fStart + 8);
998 // to see if any ms and/or utc part after that
1002 //find UTC sign if any
1003 int sign = findUTCSign(fStart);
1006 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
1007 if ( milisec != NOT_FOUND )
1009 fStart++; // skip the '.'
1010 // make sure we have some thing between the '.' and fEnd
1013 throw XMLParserException("ms should be present once '.' is present.");
1016 if ( sign == NOT_FOUND )
1018 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
1023 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
1026 else if(sign == 0 || sign != fStart)
1028 throw XMLParserException("Seconds has more than 2 digits.");
1031 //parse UTC time zone (hh:mm)
1041 // Note: CCYY could be more than 4 digits
1042 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1044 void DateTime::getYearMonth()
1047 // Ensure enough chars in buffer
1048 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1049 throw XMLParserException("Incomplete YearMonth Format.");
1051 // skip the first leading '-'
1052 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1055 // search for year separator '-'
1057 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1058 if ( yearSeparator == NOT_FOUND)
1059 throw XMLParserException("Year separator is missing or misplaced.");
1061 fValue[CentYear] = parseIntYear(yearSeparator);
1062 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1065 //gonna check we have enough byte for month
1067 if ((fStart + 2) > fEnd )
1068 throw XMLParserException("No month in buffer.");
1070 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1071 fStart += 2; //fStart points right after the MONTH
1076 void DateTime::parseTimeZone()
1078 if ( fStart < fEnd )
1080 int sign = findUTCSign(fStart);
1083 throw XMLParserException("Error in month parsing.");
1098 // Note: Assuming fStart points to the beginning of TimeZone section
1099 // fStart updated to meet fEnd
1101 void DateTime::getTimeZone(const int sign)
1104 if ( fBuffer[sign] == UTC_STD_CHAR )
1106 if ((sign + 1) != fEnd )
1108 throw XMLParserException("Error in parsing time zone.");
1115 // otherwise, it has to be this format
1120 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1121 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1123 throw XMLParserException("Error in parsing time zone.");
1126 fTimeZone[hh] = parseInt(sign+1, sign+3);
1127 fTimeZone[mm] = parseInt(sign+4, fEnd);
1132 // ---------------------------------------------------------------------------
1133 // Validator and normalizer
1134 // ---------------------------------------------------------------------------
1137 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1139 * @param date CCYY-MM-DDThh:mm:ss+03
1140 * @return CCYY-MM-DDThh:mm:ssZ
1142 void DateTime::normalize()
1145 if ((fValue[utc] == UTC_UNKNOWN) ||
1146 (fValue[utc] == UTC_STD) )
1149 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1152 int temp = fValue[Minute] + negate * fTimeZone[mm];
1153 int carry = fQuotient(temp, 60);
1154 fValue[Minute] = mod(temp, 60, carry);
1157 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1158 carry = fQuotient(temp, 24);
1159 fValue[Hour] = mod(temp, 24, carry);
1161 fValue[Day] += carry;
1165 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1166 if (fValue[Day] < 1)
1168 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1171 else if ( fValue[Day] > temp )
1173 fValue[Day] -= temp;
1181 temp = fValue[Month] + carry;
1182 fValue[Month] = modulo(temp, 1, 13);
1183 fValue[CentYear] += fQuotient(temp, 1, 13);
1186 // set to normalized
1187 fValue[utc] = UTC_STD;
1192 void DateTime::validateDateTime() const
1195 //REVISIT: should we throw an exception for not valid dates
1196 // or reporting an error message should be sufficient?
1197 if ( fValue[CentYear] == 0 )
1199 throw XMLParserException("The year \"0000\" is an illegal year value");
1202 if ( fValue[Month] < 1 ||
1203 fValue[Month] > 12 )
1205 throw XMLParserException("The month must have values 1 to 12");
1209 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1212 throw XMLParserException("The day must have values 1 to 31");
1216 if ((fValue[Hour] < 0) ||
1217 (fValue[Hour] > 24) ||
1218 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1219 (fValue[Second] !=0) ||
1220 (fMiliSecond !=0))))
1222 throw XMLParserException("Hour must have values 0-23");
1226 if ( fValue[Minute] < 0 ||
1227 fValue[Minute] > 59 )
1229 throw XMLParserException("Minute must have values 0-59");
1233 if ( fValue[Second] < 0 ||
1234 fValue[Second] > 60 )
1236 throw XMLParserException("Second must have values 0-60");
1239 //validate time-zone hours
1240 if ( (abs(fTimeZone[hh]) > 14) ||
1241 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1243 throw XMLParserException("Time zone should have range -14..+14");
1246 //validate time-zone minutes
1247 if ( abs(fTimeZone[mm]) > 59 )
1249 throw XMLParserException("Minute must have values 0-59");
1255 // -----------------------------------------------------------------------
1256 // locator and converter
1257 // -----------------------------------------------------------------------
1258 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1260 for ( int i = start; i < end; i++ )
1261 if ( fBuffer[i] == ch )
1267 int DateTime::findUTCSign (const int start)
1270 for ( int index = start; index < fEnd; index++ )
1272 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1273 if ( pos != NOT_FOUND)
1275 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1285 // start: starting point in fBuffer
1286 // end: ending point in fBuffer (exclusive)
1287 // fStart NOT updated
1289 int DateTime::parseInt(const int start, const int end) const
1291 unsigned int retVal = 0;
1292 for (int i=start; i < end; i++) {
1294 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1295 throw XMLParserException("Invalid non-numeric characters.");
1297 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1300 return (int) retVal;
1305 // start: pointing to the first digit after the '.'
1306 // end: pointing to one position after the last digit
1307 // fStart NOT updated
1309 double DateTime::parseMiliSecond(const int start, const int end) const
1312 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1313 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1314 ArrayJanitor<XMLCh> janMili(miliSecData);
1315 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1316 *(miliSecData + miliSecLen) = chNull;
1318 char *nptr = XMLString::transcode(miliSecData);
1319 ArrayJanitor<char> jan(nptr);
1320 size_t strLen = strlen(nptr);
1324 //printf("milisec=<%s>\n", nptr);
1326 double retVal = strtod(nptr, &endptr);
1328 // check if all chars are valid char
1329 if ( (endptr - nptr) != strLen)
1330 throw XMLParserException("Invalid non-numeric characters.");
1332 // we don't check underflow occurs since
1333 // nothing we can do about it.
1340 // Note: start from fStart
1342 // fStart NOT updated
1344 int DateTime::parseIntYear(const int end) const
1346 // skip the first leading '-'
1347 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1349 int length = end - start;
1352 throw XMLParserException("Year must have 'CCYY' format");
1354 else if (length > 4 &&
1355 fBuffer[start] == chDigit_0)
1357 throw XMLParserException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
1360 bool negative = (fBuffer[0] == chDash);
1361 int yearVal = parseInt((negative ? 1 : 0), end);
1362 return ( negative ? (-1) * yearVal : yearVal );
1368 * 3.2.7.2 Canonical representation
1370 * Except for trailing fractional zero digits in the seconds representation,
1371 * '24:00:00' time representations, and timezone (for timezoned values),
1372 * the mapping from literals to values is one-to-one. Where there is more
1373 * than one possible representation, the canonical representation is as follows:
1374 * redundant trailing zero digits in fractional-second literals are prohibited.
1375 * An hour representation of '24' is prohibited. Timezoned values are canonically
1376 * represented by appending 'Z' to the nontimezoned representation. (All
1377 * timezoned dateTime values are UTC.)
1379 * .'24:00:00' -> '00:00:00'
1380 * .milisecond: trailing zeros removed
1384 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1386 XMLCh *miliStartPtr, *miliEndPtr;
1387 searchMiliSeconds(miliStartPtr, miliEndPtr);
1388 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1390 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1391 XMLCh* retPtr = retBuf;
1393 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1396 int additionalLen = fillYearString(retPtr, CentYear);
1397 if(additionalLen != 0)
1399 // very bad luck; have to resize the buffer...
1400 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1401 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1402 retPtr = tmpBuf+(retPtr-retBuf);
1406 *retPtr++ = DATE_SEPARATOR;
1407 fillString(retPtr, Month, 2);
1408 *retPtr++ = DATE_SEPARATOR;
1409 fillString(retPtr, Day, 2);
1410 *retPtr++ = DATETIME_SEPARATOR;
1412 fillString(retPtr, Hour, 2);
1413 if (fValue[Hour] == 24)
1415 *(retPtr - 2) = chDigit_0;
1416 *(retPtr - 1) = chDigit_0;
1418 *retPtr++ = TIME_SEPARATOR;
1419 fillString(retPtr, Minute, 2);
1420 *retPtr++ = TIME_SEPARATOR;
1421 fillString(retPtr, Second, 2);
1425 *retPtr++ = chPeriod;
1426 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1427 retPtr += miliSecondsLen;
1430 *retPtr++ = UTC_STD_CHAR;
1439 * . either the time zone must be omitted or,
1440 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1442 * . Additionally, the canonical representation for midnight is 00:00:00.
1445 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1447 XMLCh *miliStartPtr, *miliEndPtr;
1448 searchMiliSeconds(miliStartPtr, miliEndPtr);
1449 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1451 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1452 XMLCh* retPtr = retBuf;
1454 // 'hh:mm:ss'Z' ('.'s+)?
1458 fillString(retPtr, Hour, 2);
1459 if (fValue[Hour] == 24)
1461 *(retPtr - 2) = chDigit_0;
1462 *(retPtr - 1) = chDigit_0;
1464 *retPtr++ = TIME_SEPARATOR;
1465 fillString(retPtr, Minute, 2);
1466 *retPtr++ = TIME_SEPARATOR;
1467 fillString(retPtr, Second, 2);
1471 *retPtr++ = chPeriod;
1472 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1473 retPtr += miliSecondsLen;
1476 *retPtr++ = UTC_STD_CHAR;
1482 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1484 XMLCh strBuffer[16];
1485 assert(expLen < 16);
1486 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1487 int actualLen = (int) XMLString::stringLen(strBuffer);
1489 //append leading zeros
1490 for (i = 0; i < expLen - actualLen; i++)
1495 for (i = 0; i < actualLen; i++)
1497 *ptr++ = strBuffer[i];
1502 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1504 XMLCh strBuffer[16];
1505 // let's hope we get no years of 15 digits...
1506 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1507 int actualLen = (int) XMLString::stringLen(strBuffer);
1508 // don't forget that years can be negative...
1509 int negativeYear = 0;
1510 if(strBuffer[0] == chDash)
1512 *ptr++ = strBuffer[0];
1516 //append leading zeros
1517 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1522 for (i = negativeYear; i < actualLen; i++)
1524 *ptr++ = strBuffer[i];
1533 * .check if the rawData has the mili second component
1534 * .capture the substring
1537 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1539 miliStartPtr = miliEndPtr = 0;
1541 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1545 miliStartPtr = fBuffer + milisec + 1;
1546 miliEndPtr = miliStartPtr;
1549 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1555 //remove trailing zeros
1556 while( *(miliEndPtr - 1) == chDigit_0)
1562 void DateTime::setBuffer(const XMLCh* const aString)
1565 fEnd = (int) xercesc::XMLString::stringLen(aString);
1567 if (fEnd > fBufferMaxLen) {
1569 fBufferMaxLen = fEnd + 8;
1570 fBuffer = new XMLCh[fBufferMaxLen+1];
1572 memcpy(fBuffer, aString, (fEnd+1) * sizeof(XMLCh));
1576 void DateTime::reset()
1578 for ( int i=0; i < xercesc::XMLDateTime::TOTAL_SIZE; i++ )
1583 fTimeZone[hh] = fTimeZone[mm] = 0;
1590 void DateTime::copy(const DateTime& rhs)
1592 for ( int i = 0; i < xercesc::XMLDateTime::TOTAL_SIZE; i++ )
1593 fValue[i] = rhs.fValue[i];
1595 fMiliSecond = rhs.fMiliSecond;
1596 fHasTime = rhs.fHasTime;
1597 fTimeZone[hh] = rhs.fTimeZone[hh];
1598 fTimeZone[mm] = rhs.fTimeZone[mm];
1599 fStart = rhs.fStart;
1603 if (fEnd > fBufferMaxLen) {
1605 fBufferMaxLen = rhs.fBufferMaxLen;
1606 fBuffer = new XMLCh[fBufferMaxLen+1];
1608 memcpy(fBuffer, rhs.fBuffer, (fEnd+1) * sizeof(XMLCh));
1612 void DateTime::initParser()
1614 fStart = 0; // to ensure scan from the very first beginning
1615 // in case the pointer is updated accidentally by someone else.
1618 bool DateTime::isNormalized() const
1620 return (fValue[xercesc::XMLDateTime::utc] == xercesc::XMLDateTime::UTC_STD ? true : false);
1623 int DateTime::getRetVal(int c1, int c2)
1625 if ((c1 == xercesc::XMLDateTime::LESS_THAN && c2 == xercesc::XMLDateTime::GREATER_THAN) ||
1626 (c1 == xercesc::XMLDateTime::GREATER_THAN && c2 == xercesc::XMLDateTime::LESS_THAN))
1627 return xercesc::XMLDateTime::INDETERMINATE;
1629 return (c1 != xercesc::XMLDateTime::INDETERMINATE) ? c1 : c2;