2 * Copyright 2001-2009 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;
42 using namespace xercesc;
46 // constants used to process raw data (fBuffer)
48 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}['Z']
52 static const XMLCh DURATION_STARTER = chLatin_P; // 'P'
53 static const XMLCh DURATION_Y = chLatin_Y; // 'Y'
54 static const XMLCh DURATION_M = chLatin_M; // 'M'
55 static const XMLCh DURATION_D = chLatin_D; // 'D'
56 static const XMLCh DURATION_H = chLatin_H; // 'H'
57 static const XMLCh DURATION_S = chLatin_S; // 'S'
59 static const XMLCh DATE_SEPARATOR = chDash; // '-'
60 static const XMLCh TIME_SEPARATOR = chColon; // ':'
61 static const XMLCh TIMEZONE_SEPARATOR = chColon; // ':'
62 static const XMLCh DATETIME_SEPARATOR = chLatin_T; // 'T'
63 static const XMLCh MILISECOND_SEPARATOR = chPeriod; // '.'
65 static const XMLCh UTC_STD_CHAR = chLatin_Z; // 'Z'
66 static const XMLCh UTC_POS_CHAR = chPlus; // '+'
67 static const XMLCh UTC_NEG_CHAR = chDash; // '-'
69 static const XMLCh UTC_SET[] = {UTC_STD_CHAR //"Z+-"
74 static const int YMD_MIN_SIZE = 10; // CCYY-MM-DD
75 static const int YMONTH_MIN_SIZE = 7; // CCYY_MM
76 static const int TIME_MIN_SIZE = 8; // hh:mm:ss
77 static const int TIMEZONE_SIZE = 5; // hh:mm
78 static const int DAY_SIZE = 5; // ---DD
79 //static const int MONTH_SIZE = 6; // --MM--
80 static const int MONTHDAY_SIZE = 7; // --MM-DD
81 static const int NOT_FOUND = -1;
83 //define constants to be used in assigning default values for
84 //all date/time excluding duration
85 static const int YEAR_DEFAULT = 2000;
86 static const int MONTH_DEFAULT = 01;
87 static const int DAY_DEFAULT = 15;
89 // order-relation on duration is a partial order. The dates below are used to
90 // for comparison of 2 durations, based on the fact that
91 // duration x and y is x<=y iff s+x<=s+y
92 // see 3.2.6 duration W3C schema datatype specs
94 // the dates are in format: {CCYY,MM,DD, H, S, M, MS, timezone}
95 const int DateTime::DATETIMES[][TOTAL_SIZE] =
97 {1696, 9, 1, 0, 0, 0, 0, UTC_STD},
98 {1697, 2, 1, 0, 0, 0, 0, UTC_STD},
99 {1903, 3, 1, 0, 0, 0, 0, UTC_STD},
100 {1903, 7, 1, 0, 0, 0, 0, UTC_STD}
103 // ---------------------------------------------------------------------------
105 // ---------------------------------------------------------------------------
106 static inline int fQuotient(int a, int b)
108 div_t div_result = div(a, b);
109 return div_result.quot;
112 static inline int fQuotient(int temp, int low, int high)
114 return fQuotient(temp - low, high - low);
117 static inline int mod(int a, int b, int quotient)
119 return (a - quotient*b) ;
122 static inline int modulo (int temp, int low, int high)
124 //modulo(a - low, high - low) + low
127 return (mod (a, b, fQuotient(a, b)) + low) ;
130 static inline bool isLeapYear(int year)
132 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
135 static int maxDayInMonthFor(int year, int month)
138 if ( month == 4 || month == 6 || month == 9 || month == 11 )
144 if ( isLeapYear(year) )
156 // ---------------------------------------------------------------------------
157 // static methods : for duration
158 // ---------------------------------------------------------------------------
160 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration")
162 * 3.2.6.2 Order relation on duration
164 * In general, the order-relation on duration is a partial order since there is no
165 * determinate relationship between certain durations such as one month (P1M) and 30 days (P30D).
166 * The order-relation of two duration values x and y is x < y iff s+x < s+y for each qualified
167 * dateTime s in the list below.
169 * These values for s cause the greatest deviations in the addition of dateTimes and durations
172 int DateTime::compare(const DateTime* const pDate1
173 , const DateTime* const pDate2
176 //REVISIT: this is unoptimazed vs of comparing 2 durations
177 // Algorithm is described in 3.2.6.2 W3C Schema Datatype specs
180 int resultA, resultB = XMLDateTime::INDETERMINATE;
182 //try and see if the objects are equal
183 if ( (resultA = compareOrder(pDate1, pDate2)) == XMLDateTime::EQUAL)
184 return XMLDateTime::EQUAL;
186 //long comparison algorithm is required
187 DateTime tempA, *pTempA = &tempA;
188 DateTime tempB, *pTempB = &tempB;
190 addDuration(pTempA, pDate1, 0);
191 addDuration(pTempB, pDate2, 0);
192 resultA = compareOrder(pTempA, pTempB);
193 if ( resultA == XMLDateTime::INDETERMINATE )
194 return XMLDateTime::INDETERMINATE;
196 addDuration(pTempA, pDate1, 1);
197 addDuration(pTempB, pDate2, 1);
198 resultB = compareOrder(pTempA, pTempB);
199 resultA = compareResult(resultA, resultB, strict);
200 if ( resultA == XMLDateTime::INDETERMINATE )
201 return XMLDateTime::INDETERMINATE;
203 addDuration(pTempA, pDate1, 2);
204 addDuration(pTempB, pDate2, 2);
205 resultB = compareOrder(pTempA, pTempB);
206 resultA = compareResult(resultA, resultB, strict);
207 if ( resultA == XMLDateTime::INDETERMINATE )
208 return XMLDateTime::INDETERMINATE;
210 addDuration(pTempA, pDate1, 3);
211 addDuration(pTempB, pDate2, 3);
212 resultB = compareOrder(pTempA, pTempB);
213 resultA = compareResult(resultA, resultB, strict);
220 // Form a new DateTime with duration and baseDate array
226 void DateTime::addDuration(DateTime* fNewDate
227 , const DateTime* const fDuration
232 //REVISIT: some code could be shared between normalize() and this method,
233 // however is it worth moving it? The structures are different...
237 //add months (may be modified additionaly below)
238 int temp = DATETIMES[index][Month] + fDuration->fValue[Month];
239 fNewDate->fValue[Month] = modulo(temp, 1, 13);
240 int carry = fQuotient(temp, 1, 13);
242 //add years (may be modified additionaly below)
243 fNewDate->fValue[CentYear] =
244 DATETIMES[index][CentYear] + fDuration->fValue[CentYear] + carry;
247 temp = DATETIMES[index][Second] + fDuration->fValue[Second];
248 carry = fQuotient (temp, 60);
249 fNewDate->fValue[Second] = mod(temp, 60, carry);
252 temp = DATETIMES[index][Minute] + fDuration->fValue[Minute] + carry;
253 carry = fQuotient(temp, 60);
254 fNewDate->fValue[Minute] = mod(temp, 60, carry);
257 temp = DATETIMES[index][Hour] + fDuration->fValue[Hour] + carry;
258 carry = fQuotient(temp, 24);
259 fNewDate->fValue[Hour] = mod(temp, 24, carry);
261 fNewDate->fValue[Day] =
262 DATETIMES[index][Day] + fDuration->fValue[Day] + carry;
266 temp = maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]);
267 if ( fNewDate->fValue[Day] < 1 )
268 { //original fNewDate was negative
269 fNewDate->fValue[Day] +=
270 maxDayInMonthFor(fNewDate->fValue[CentYear], fNewDate->fValue[Month]-1);
273 else if ( fNewDate->fValue[Day] > temp )
275 fNewDate->fValue[Day] -= temp;
283 temp = fNewDate->fValue[Month] + carry;
284 fNewDate->fValue[Month] = modulo(temp, 1, 13);
285 fNewDate->fValue[CentYear] += fQuotient(temp, 1, 13);
288 //fNewDate->fValue[utc] = UTC_STD_CHAR;
289 fNewDate->fValue[utc] = UTC_STD;
292 int DateTime::compareResult(int resultA
297 if ( resultB == XMLDateTime::INDETERMINATE )
299 return XMLDateTime::INDETERMINATE;
301 else if ( (resultA != resultB) &&
304 return XMLDateTime::INDETERMINATE;
306 else if ( (resultA != resultB) &&
309 if ( (resultA != XMLDateTime::EQUAL) &&
310 (resultB != XMLDateTime::EQUAL) )
312 return XMLDateTime::INDETERMINATE;
316 return (resultA != XMLDateTime::EQUAL)? resultA : resultB;
324 // ---------------------------------------------------------------------------
325 // static methods : for others
326 // ---------------------------------------------------------------------------
327 int DateTime::compare(const DateTime* const pDate1
328 , const DateTime* const pDate2)
331 if (pDate1->fValue[utc] == pDate2->fValue[utc])
333 return DateTime::compareOrder(pDate1, pDate2);
338 if ( pDate1->isNormalized())
340 c1 = compareResult(pDate1, pDate2, false, UTC_POS);
341 c2 = compareResult(pDate1, pDate2, false, UTC_NEG);
342 return getRetVal(c1, c2);
344 else if ( pDate2->isNormalized())
346 c1 = compareResult(pDate1, pDate2, true, UTC_POS);
347 c2 = compareResult(pDate1, pDate2, true, UTC_NEG);
348 return getRetVal(c1, c2);
351 return XMLDateTime::INDETERMINATE;
354 int DateTime::compareResult(const DateTime* const pDate1
355 , const DateTime* const pDate2
359 DateTime tmpDate = (set2Left ? *pDate1 : *pDate2);
361 tmpDate.fTimeZone[hh] = 14;
362 tmpDate.fTimeZone[mm] = 0;
363 tmpDate.fValue[utc] = utc_type;
366 return (set2Left? DateTime::compareOrder(&tmpDate, pDate2) :
367 DateTime::compareOrder(pDate1, &tmpDate));
370 int DateTime::compareOrder(const DateTime* const lValue
371 , const DateTime* const rValue)
372 //, MemoryManager* const memMgr)
375 // If any of the them is not normalized() yet,
376 // we need to do something here.
378 DateTime lTemp = *lValue;
379 DateTime rTemp = *rValue;
384 for ( int i = 0 ; i < TOTAL_SIZE; i++ )
386 if ( lTemp.fValue[i] < rTemp.fValue[i] )
388 return XMLDateTime::LESS_THAN;
390 else if ( lTemp.fValue[i] > rTemp.fValue[i] )
392 return XMLDateTime::GREATER_THAN;
398 if ( lTemp.fMiliSecond < rTemp.fMiliSecond )
400 return XMLDateTime::LESS_THAN;
402 else if ( lTemp.fMiliSecond > rTemp.fMiliSecond )
404 return XMLDateTime::GREATER_THAN;
408 return XMLDateTime::EQUAL;
411 // ---------------------------------------------------------------------------
413 // ---------------------------------------------------------------------------
414 DateTime::~DateTime()
430 DateTime::DateTime(const XMLCh* const aString)
441 DateTime::DateTime(time_t epoch, bool duration)
455 time_t days = epoch / 86400;
457 time_t hours = epoch / 3600;
459 time_t minutes = epoch / 60;
461 s << "P" << days << "DT" << hours << "H" << minutes << "M" << epoch << "S";
462 auto_ptr_XMLCh timeptr(s.str().c_str());
463 setBuffer(timeptr.get());
466 #ifndef HAVE_GMTIME_R
467 struct tm* ptime=gmtime(&epoch);
470 struct tm* ptime=gmtime_r(&epoch,&res);
473 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
474 auto_ptr_XMLCh timeptr(timebuf);
475 setBuffer(timeptr.get());
479 // -----------------------------------------------------------------------
480 // Copy ctor and Assignment operators
481 // -----------------------------------------------------------------------
483 DateTime::DateTime(const DateTime &toCopy)
490 DateTime& DateTime::operator=(const DateTime& rhs)
499 // -----------------------------------------------------------------------
500 // Implementation of Abstract Interface
501 // -----------------------------------------------------------------------
504 // We may simply return the handle to fBuffer
506 const XMLCh* DateTime::getRawData() const
513 const XMLCh* DateTime::getFormattedString() const
518 int DateTime::getSign() const
523 time_t DateTime::getEpoch(bool duration) const
526 time_t epoch = getSecond() + (60 * getMinute()) + (3600 * getHour()) + (86400 * getDay());
528 epoch += (((365 * 4) + 1)/48 * 86400);
530 epoch += 365.25 * 86400;
531 return getSign()!=UTC_NEG ? epoch : -epoch;
535 t.tm_sec=getSecond();
536 t.tm_min=getMinute();
539 t.tm_mon=getMonth()-1;
540 t.tm_year=getYear()-1900;
542 #if defined(HAVE_TIMEGM)
545 // Windows, and hopefully most others...?
546 return mktime(&t) - timezone;
551 // ---------------------------------------------------------------------------
553 // ---------------------------------------------------------------------------
556 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
558 void DateTime::parseDateTime()
563 //fStart is supposed to point to 'T'
564 if (fBuffer[fStart++] != DATETIME_SEPARATOR)
565 throw XMLParserException("Invalid separator between date and time.");
574 // [-]{CCYY-MM-DD}[TimeZone]
576 void DateTime::parseDate()
585 void DateTime::parseTime()
589 // time initialize to default values
590 fValue[CentYear]= YEAR_DEFAULT;
591 fValue[Month] = MONTH_DEFAULT;
592 fValue[Day] = DAY_DEFAULT;
605 void DateTime::parseDay()
609 if (fBuffer[0] != DATE_SEPARATOR ||
610 fBuffer[1] != DATE_SEPARATOR ||
611 fBuffer[2] != DATE_SEPARATOR )
613 throw XMLParserException("Invalid character in date.");
617 fValue[CentYear] = YEAR_DEFAULT;
618 fValue[Month] = MONTH_DEFAULT;
619 fValue[Day] = parseInt(fStart+3, fStart+5);
621 if ( DAY_SIZE < fEnd )
623 int sign = findUTCSign(DAY_SIZE);
626 throw XMLParserException("Invalid character in date.");
639 // {--MM--}[TimeZone]
643 void DateTime::parseMonth()
647 if (fBuffer[0] != DATE_SEPARATOR ||
648 fBuffer[1] != DATE_SEPARATOR )
650 throw XMLParserException("Invalid character in date.");
654 fValue[CentYear] = YEAR_DEFAULT;
655 fValue[Day] = DAY_DEFAULT;
656 fValue[Month] = parseInt(2, 4);
658 // REVISIT: allow both --MM and --MM-- now.
659 // need to remove the following lines to disallow --MM--
660 // when the errata is officially in the rec.
662 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
668 // parse TimeZone if any
672 int sign = findUTCSign(fStart);
675 throw XMLParserException("Invalid character in date.");
688 //[-]{CCYY}[TimeZone]
691 void DateTime::parseYear()
695 // skip the first '-' and search for timezone
697 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
699 if (sign == NOT_FOUND)
701 fValue[CentYear] = parseIntYear(fEnd);
705 fValue[CentYear] = parseIntYear(sign);
710 fValue[Month] = MONTH_DEFAULT;
711 fValue[Day] = DAY_DEFAULT; //java is 1
718 //{--MM-DD}[TimeZone]
721 void DateTime::parseMonthDay()
725 if (fBuffer[0] != DATE_SEPARATOR ||
726 fBuffer[1] != DATE_SEPARATOR ||
727 fBuffer[4] != DATE_SEPARATOR )
729 throw XMLParserException("Invalid character in date.");
734 fValue[CentYear] = YEAR_DEFAULT;
735 fValue[Month] = parseInt(2, 4);
736 fValue[Day] = parseInt(5, 7);
738 if ( MONTHDAY_SIZE < fEnd )
740 int sign = findUTCSign(MONTHDAY_SIZE);
743 throw XMLParserException("Invalid character in date.");
755 void DateTime::parseYearMonth()
761 fValue[Day] = DAY_DEFAULT;
769 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
771 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
773 // Note: the n above shall be >= 0
774 // if no time element found, 'T' shall be absent
776 void DateTime::parseDuration()
780 // must start with '-' or 'P'
782 XMLCh c = fBuffer[fStart++];
783 if ( (c != DURATION_STARTER) &&
786 throw XMLParserException("Invalid character in time.");
789 // 'P' must ALWAYS be present in either case
790 if ( (c == chDash) &&
791 (fBuffer[fStart++]!= DURATION_STARTER ))
793 throw XMLParserException("Invalid character in time.");
797 //date[utc]=(c=='-')?'-':0;
798 //fValue[utc] = UTC_STD;
799 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
801 int negate = ( fBuffer[0] == chDash ? -1 : 1);
804 // No negative value is allowed after 'P'
806 // eg P-1234, invalid
808 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
810 throw XMLParserException("Invalid character in time.");
813 //at least one number and designator must be seen after P
814 bool designator = false;
816 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
817 if ( endDate == NOT_FOUND )
819 endDate = fEnd; // 'T' absent
823 int end = indexOf(fStart, endDate, DURATION_Y);
824 if ( end != NOT_FOUND )
827 fValue[CentYear] = negate * parseInt(fStart, end);
832 end = indexOf(fStart, endDate, DURATION_M);
833 if ( end != NOT_FOUND )
836 fValue[Month] = negate * parseInt(fStart, end);
841 end = indexOf(fStart, endDate, DURATION_D);
842 if ( end != NOT_FOUND )
845 fValue[Day] = negate * parseInt(fStart,end);
850 if ( (fEnd == endDate) && // 'T' absent
851 (fStart != fEnd) ) // something after Day
853 throw XMLParserException("Invalid character in time.");
856 if ( fEnd != endDate ) // 'T' present
858 //scan hours, minutes, seconds
862 end = indexOf(++fStart, fEnd, DURATION_H);
863 if ( end != NOT_FOUND )
866 fValue[Hour] = negate * parseInt(fStart, end);
871 end = indexOf(fStart, fEnd, DURATION_M);
872 if ( end != NOT_FOUND )
875 fValue[Minute] = negate * parseInt(fStart, end);
880 end = indexOf(fStart, fEnd, DURATION_S);
881 if ( end != NOT_FOUND )
884 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
887 * Schema Errata: E2-23
888 * at least one digit must follow the decimal point if it appears.
889 * That is, the value of the seconds component must conform
890 * to the following pattern: [0-9]+(.[0-9]+)?
892 if ( mlsec != NOT_FOUND )
895 * make usure there is something after the '.' and before the end.
897 if ( mlsec+1 == end )
899 throw XMLParserException("Invalid character in time.");
902 fValue[Second] = negate * parseInt(fStart, mlsec);
903 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
907 fValue[Second] = negate * parseInt(fStart,end);
914 // no additional data should appear after last item
915 // P1Y1M1DT is illigal value as well
916 if ( (fStart != fEnd) ||
917 fBuffer[--fStart] == DATETIME_SEPARATOR )
919 throw XMLParserException("Invalid character in time.");
925 throw XMLParserException("Invalid character in time.");
930 // ---------------------------------------------------------------------------
932 // ---------------------------------------------------------------------------
937 // Note: CCYY could be more than 4 digits
938 // Assuming fStart point to the beginning of the Date Section
939 // fStart updated to point to the position right AFTER the second 'D'
940 // Since the lenght of CCYY might be variable, we can't check format upfront
942 void DateTime::getDate()
945 // Ensure enough chars in buffer
946 if ( (fStart+YMD_MIN_SIZE) > fEnd)
947 throw XMLParserException("Date/time string not complete.");
949 getYearMonth(); // Scan YearMonth and
950 // fStart point to the next '-'
952 if (fBuffer[fStart++] != DATE_SEPARATOR)
954 throw XMLParserException("CCYY-MM must be followed by '-' sign.");
957 fValue[Day] = parseInt(fStart, fStart+2);
958 fStart += 2 ; //fStart points right after the Day
964 // hh:mm:ss[.msssss]['Z']
965 // hh:mm:ss[.msssss][['+'|'-']hh:mm]
968 // Note: Assuming fStart point to the beginning of the Time Section
969 // fStart updated to point to the position right AFTER the second 's'
972 void DateTime::getTime()
975 // Ensure enough chars in buffer
976 if ( (fStart+TIME_MIN_SIZE) > fEnd)
977 throw XMLParserException("Incomplete Time Format.");
979 // check (fixed) format first
980 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
981 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
983 throw XMLParserException("Error in parsing time.");
987 // get hours, minute and second
989 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
990 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
991 fValue[Second] = parseInt(fStart + 6, fStart + 8);
994 // to see if any ms and/or utc part after that
998 //find UTC sign if any
999 int sign = findUTCSign(fStart);
1002 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
1003 if ( milisec != NOT_FOUND )
1005 fStart++; // skip the '.'
1006 // make sure we have some thing between the '.' and fEnd
1009 throw XMLParserException("ms should be present once '.' is present.");
1012 if ( sign == NOT_FOUND )
1014 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
1019 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
1022 else if(sign == 0 || sign != fStart)
1024 throw XMLParserException("Seconds has more than 2 digits.");
1027 //parse UTC time zone (hh:mm)
1037 // Note: CCYY could be more than 4 digits
1038 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1040 void DateTime::getYearMonth()
1043 // Ensure enough chars in buffer
1044 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1045 throw XMLParserException("Incomplete YearMonth Format.");
1047 // skip the first leading '-'
1048 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1051 // search for year separator '-'
1053 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1054 if ( yearSeparator == NOT_FOUND)
1055 throw XMLParserException("Year separator is missing or misplaced.");
1057 fValue[CentYear] = parseIntYear(yearSeparator);
1058 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1061 //gonna check we have enough byte for month
1063 if ((fStart + 2) > fEnd )
1064 throw XMLParserException("No month in buffer.");
1066 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1067 fStart += 2; //fStart points right after the MONTH
1072 void DateTime::parseTimeZone()
1074 if ( fStart < fEnd )
1076 int sign = findUTCSign(fStart);
1079 throw XMLParserException("Error in month parsing.");
1094 // Note: Assuming fStart points to the beginning of TimeZone section
1095 // fStart updated to meet fEnd
1097 void DateTime::getTimeZone(const int sign)
1100 if ( fBuffer[sign] == UTC_STD_CHAR )
1102 if ((sign + 1) != fEnd )
1104 throw XMLParserException("Error in parsing time zone.");
1111 // otherwise, it has to be this format
1116 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1117 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1119 throw XMLParserException("Error in parsing time zone.");
1122 fTimeZone[hh] = parseInt(sign+1, sign+3);
1123 fTimeZone[mm] = parseInt(sign+4, fEnd);
1128 // ---------------------------------------------------------------------------
1129 // Validator and normalizer
1130 // ---------------------------------------------------------------------------
1133 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1135 * @param date CCYY-MM-DDThh:mm:ss+03
1136 * @return CCYY-MM-DDThh:mm:ssZ
1138 void DateTime::normalize()
1141 if ((fValue[utc] == UTC_UNKNOWN) ||
1142 (fValue[utc] == UTC_STD) )
1145 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1148 int temp = fValue[Minute] + negate * fTimeZone[mm];
1149 int carry = fQuotient(temp, 60);
1150 fValue[Minute] = mod(temp, 60, carry);
1153 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1154 carry = fQuotient(temp, 24);
1155 fValue[Hour] = mod(temp, 24, carry);
1157 fValue[Day] += carry;
1161 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1162 if (fValue[Day] < 1)
1164 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1167 else if ( fValue[Day] > temp )
1169 fValue[Day] -= temp;
1177 temp = fValue[Month] + carry;
1178 fValue[Month] = modulo(temp, 1, 13);
1179 fValue[CentYear] += fQuotient(temp, 1, 13);
1182 // set to normalized
1183 fValue[utc] = UTC_STD;
1188 void DateTime::validateDateTime() const
1191 //REVISIT: should we throw an exception for not valid dates
1192 // or reporting an error message should be sufficient?
1193 if ( fValue[CentYear] == 0 )
1195 throw XMLParserException("The year \"0000\" is an illegal year value");
1198 if ( fValue[Month] < 1 ||
1199 fValue[Month] > 12 )
1201 throw XMLParserException("The month must have values 1 to 12");
1205 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1208 throw XMLParserException("The day must have values 1 to 31");
1212 if ((fValue[Hour] < 0) ||
1213 (fValue[Hour] > 24) ||
1214 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1215 (fValue[Second] !=0) ||
1216 (fMiliSecond !=0))))
1218 throw XMLParserException("Hour must have values 0-23");
1222 if ( fValue[Minute] < 0 ||
1223 fValue[Minute] > 59 )
1225 throw XMLParserException("Minute must have values 0-59");
1229 if ( fValue[Second] < 0 ||
1230 fValue[Second] > 60 )
1232 throw XMLParserException("Second must have values 0-60");
1235 //validate time-zone hours
1236 if ( (abs(fTimeZone[hh]) > 14) ||
1237 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1239 throw XMLParserException("Time zone should have range -14..+14");
1242 //validate time-zone minutes
1243 if ( abs(fTimeZone[mm]) > 59 )
1245 throw XMLParserException("Minute must have values 0-59");
1251 // -----------------------------------------------------------------------
1252 // locator and converter
1253 // -----------------------------------------------------------------------
1254 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1256 for ( int i = start; i < end; i++ )
1257 if ( fBuffer[i] == ch )
1263 int DateTime::findUTCSign (const int start)
1266 for ( int index = start; index < fEnd; index++ )
1268 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1269 if ( pos != NOT_FOUND)
1271 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1281 // start: starting point in fBuffer
1282 // end: ending point in fBuffer (exclusive)
1283 // fStart NOT updated
1285 int DateTime::parseInt(const int start, const int end) const
1287 unsigned int retVal = 0;
1288 for (int i=start; i < end; i++) {
1290 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1291 throw XMLParserException("Invalid non-numeric characters.");
1293 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1296 return (int) retVal;
1301 // start: pointing to the first digit after the '.'
1302 // end: pointing to one position after the last digit
1303 // fStart NOT updated
1305 double DateTime::parseMiliSecond(const int start, const int end) const
1308 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1309 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1310 ArrayJanitor<XMLCh> janMili(miliSecData);
1311 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1312 *(miliSecData + miliSecLen) = chNull;
1314 char *nptr = XMLString::transcode(miliSecData);
1315 ArrayJanitor<char> jan(nptr);
1316 size_t strLen = strlen(nptr);
1320 //printf("milisec=<%s>\n", nptr);
1322 double retVal = strtod(nptr, &endptr);
1324 // check if all chars are valid char
1325 if ( (endptr - nptr) != strLen)
1326 throw XMLParserException("Invalid non-numeric characters.");
1328 // we don't check underflow occurs since
1329 // nothing we can do about it.
1336 // Note: start from fStart
1338 // fStart NOT updated
1340 int DateTime::parseIntYear(const int end) const
1342 // skip the first leading '-'
1343 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1345 int length = end - start;
1348 throw XMLParserException("Year must have 'CCYY' format");
1350 else if (length > 4 &&
1351 fBuffer[start] == chDigit_0)
1353 throw XMLParserException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
1356 bool negative = (fBuffer[0] == chDash);
1357 int yearVal = parseInt((negative ? 1 : 0), end);
1358 return ( negative ? (-1) * yearVal : yearVal );
1364 * 3.2.7.2 Canonical representation
1366 * Except for trailing fractional zero digits in the seconds representation,
1367 * '24:00:00' time representations, and timezone (for timezoned values),
1368 * the mapping from literals to values is one-to-one. Where there is more
1369 * than one possible representation, the canonical representation is as follows:
1370 * redundant trailing zero digits in fractional-second literals are prohibited.
1371 * An hour representation of '24' is prohibited. Timezoned values are canonically
1372 * represented by appending 'Z' to the nontimezoned representation. (All
1373 * timezoned dateTime values are UTC.)
1375 * .'24:00:00' -> '00:00:00'
1376 * .milisecond: trailing zeros removed
1380 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1382 XMLCh *miliStartPtr, *miliEndPtr;
1383 searchMiliSeconds(miliStartPtr, miliEndPtr);
1384 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1386 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1387 XMLCh* retPtr = retBuf;
1389 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1392 int additionalLen = fillYearString(retPtr, CentYear);
1393 if(additionalLen != 0)
1395 // very bad luck; have to resize the buffer...
1396 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1397 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1398 retPtr = tmpBuf+(retPtr-retBuf);
1402 *retPtr++ = DATE_SEPARATOR;
1403 fillString(retPtr, Month, 2);
1404 *retPtr++ = DATE_SEPARATOR;
1405 fillString(retPtr, Day, 2);
1406 *retPtr++ = DATETIME_SEPARATOR;
1408 fillString(retPtr, Hour, 2);
1409 if (fValue[Hour] == 24)
1411 *(retPtr - 2) = chDigit_0;
1412 *(retPtr - 1) = chDigit_0;
1414 *retPtr++ = TIME_SEPARATOR;
1415 fillString(retPtr, Minute, 2);
1416 *retPtr++ = TIME_SEPARATOR;
1417 fillString(retPtr, Second, 2);
1421 *retPtr++ = chPeriod;
1422 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1423 retPtr += miliSecondsLen;
1426 *retPtr++ = UTC_STD_CHAR;
1435 * . either the time zone must be omitted or,
1436 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1438 * . Additionally, the canonical representation for midnight is 00:00:00.
1441 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1443 XMLCh *miliStartPtr, *miliEndPtr;
1444 searchMiliSeconds(miliStartPtr, miliEndPtr);
1445 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1447 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1448 XMLCh* retPtr = retBuf;
1450 // 'hh:mm:ss'Z' ('.'s+)?
1454 fillString(retPtr, Hour, 2);
1455 if (fValue[Hour] == 24)
1457 *(retPtr - 2) = chDigit_0;
1458 *(retPtr - 1) = chDigit_0;
1460 *retPtr++ = TIME_SEPARATOR;
1461 fillString(retPtr, Minute, 2);
1462 *retPtr++ = TIME_SEPARATOR;
1463 fillString(retPtr, Second, 2);
1467 *retPtr++ = chPeriod;
1468 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1469 retPtr += miliSecondsLen;
1472 *retPtr++ = UTC_STD_CHAR;
1478 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1480 XMLCh strBuffer[16];
1481 assert(expLen < 16);
1482 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1483 int actualLen = (int) XMLString::stringLen(strBuffer);
1485 //append leading zeros
1486 for (i = 0; i < expLen - actualLen; i++)
1491 for (i = 0; i < actualLen; i++)
1493 *ptr++ = strBuffer[i];
1498 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1500 XMLCh strBuffer[16];
1501 // let's hope we get no years of 15 digits...
1502 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1503 int actualLen = (int) XMLString::stringLen(strBuffer);
1504 // don't forget that years can be negative...
1505 int negativeYear = 0;
1506 if(strBuffer[0] == chDash)
1508 *ptr++ = strBuffer[0];
1512 //append leading zeros
1513 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1518 for (i = negativeYear; i < actualLen; i++)
1520 *ptr++ = strBuffer[i];
1529 * .check if the rawData has the mili second component
1530 * .capture the substring
1533 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1535 miliStartPtr = miliEndPtr = 0;
1537 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1541 miliStartPtr = fBuffer + milisec + 1;
1542 miliEndPtr = miliStartPtr;
1545 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1551 //remove trailing zeros
1552 while( *(miliEndPtr - 1) == chDigit_0)
1558 void DateTime::setBuffer(const XMLCh* const aString)
1561 fEnd = (int) xercesc::XMLString::stringLen(aString);
1563 if (fEnd > fBufferMaxLen) {
1565 fBufferMaxLen = fEnd + 8;
1566 fBuffer = new XMLCh[fBufferMaxLen+1];
1568 memcpy(fBuffer, aString, (fEnd+1) * sizeof(XMLCh));
1572 void DateTime::reset()
1574 for ( int i=0; i < xercesc::XMLDateTime::TOTAL_SIZE; i++ )
1579 fTimeZone[hh] = fTimeZone[mm] = 0;
1586 void DateTime::copy(const DateTime& rhs)
1588 for ( int i = 0; i < xercesc::XMLDateTime::TOTAL_SIZE; i++ )
1589 fValue[i] = rhs.fValue[i];
1591 fMiliSecond = rhs.fMiliSecond;
1592 fHasTime = rhs.fHasTime;
1593 fTimeZone[hh] = rhs.fTimeZone[hh];
1594 fTimeZone[mm] = rhs.fTimeZone[mm];
1595 fStart = rhs.fStart;
1599 if (fEnd > fBufferMaxLen) {
1601 fBufferMaxLen = rhs.fBufferMaxLen;
1602 fBuffer = new XMLCh[fBufferMaxLen+1];
1604 memcpy(fBuffer, rhs.fBuffer, (fEnd+1) * sizeof(XMLCh));
1608 void DateTime::initParser()
1610 fStart = 0; // to ensure scan from the very first beginning
1611 // in case the pointer is updated accidentally by someone else.
1614 bool DateTime::isNormalized() const
1616 return (fValue[xercesc::XMLDateTime::utc] == xercesc::XMLDateTime::UTC_STD ? true : false);
1619 int DateTime::getRetVal(int c1, int c2)
1621 if ((c1 == xercesc::XMLDateTime::LESS_THAN && c2 == xercesc::XMLDateTime::GREATER_THAN) ||
1622 (c1 == xercesc::XMLDateTime::GREATER_THAN && c2 == xercesc::XMLDateTime::LESS_THAN))
1623 return xercesc::XMLDateTime::INDETERMINATE;
1625 return (c1 != xercesc::XMLDateTime::INDETERMINATE) ? c1 : c2;