2 * Copyright 2001-2007 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Manipulation of XML date/time data.
24 * This is mostly copied from Xerces-C, but they don't seem inclined to produce a usable
25 * class, so I had to incorporate my own version of it for now. I can't inherit it
26 * since the fields I need are private.
30 #include "util/DateTime.h"
38 #include <xercesc/util/Janitor.hpp>
39 #include <xercesc/util/NumberFormatException.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)
448 #ifndef HAVE_GMTIME_R
449 struct tm* ptime=gmtime(&epoch);
452 struct tm* ptime=gmtime_r(&epoch,&res);
455 strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
456 auto_ptr_XMLCh timeptr(timebuf);
457 setBuffer(timeptr.get());
460 // -----------------------------------------------------------------------
461 // Copy ctor and Assignment operators
462 // -----------------------------------------------------------------------
464 DateTime::DateTime(const DateTime &toCopy)
471 DateTime& DateTime::operator=(const DateTime& rhs)
480 // -----------------------------------------------------------------------
481 // Implementation of Abstract Interface
482 // -----------------------------------------------------------------------
485 // We may simply return the handle to fBuffer
487 const XMLCh* DateTime::getRawData() const
494 const XMLCh* DateTime::getFormattedString() const
499 int DateTime::getSign() const
504 time_t DateTime::getEpoch() const
507 t.tm_sec=getSecond();
508 t.tm_min=getMinute();
511 t.tm_mon=getMonth()-1;
512 t.tm_year=getYear()-1900;
514 #if defined(HAVE_TIMEGM)
517 // Windows, and hopefully most others...?
518 return mktime(&t) - timezone;
522 // ---------------------------------------------------------------------------
524 // ---------------------------------------------------------------------------
527 // [-]{CCYY-MM-DD}'T'{HH:MM:SS.MS}[TimeZone]
529 void DateTime::parseDateTime()
534 //fStart is supposed to point to 'T'
535 if (fBuffer[fStart++] != DATETIME_SEPARATOR)
536 ThrowXML1(SchemaDateTimeException
537 , XMLExcepts::DateTime_gDay_invalid
547 // [-]{CCYY-MM-DD}[TimeZone]
549 void DateTime::parseDate()
558 void DateTime::parseTime()
562 // time initialize to default values
563 fValue[CentYear]= YEAR_DEFAULT;
564 fValue[Month] = MONTH_DEFAULT;
565 fValue[Day] = DAY_DEFAULT;
578 void DateTime::parseDay()
582 if (fBuffer[0] != DATE_SEPARATOR ||
583 fBuffer[1] != DATE_SEPARATOR ||
584 fBuffer[2] != DATE_SEPARATOR )
586 ThrowXML1(SchemaDateTimeException
587 , XMLExcepts::DateTime_gDay_invalid
592 fValue[CentYear] = YEAR_DEFAULT;
593 fValue[Month] = MONTH_DEFAULT;
594 fValue[Day] = parseInt(fStart+3, fStart+5);
596 if ( DAY_SIZE < fEnd )
598 int sign = findUTCSign(DAY_SIZE);
601 ThrowXML1(SchemaDateTimeException
602 , XMLExcepts::DateTime_gDay_invalid
616 // {--MM--}[TimeZone]
620 void DateTime::parseMonth()
624 if (fBuffer[0] != DATE_SEPARATOR ||
625 fBuffer[1] != DATE_SEPARATOR )
627 ThrowXML1(SchemaDateTimeException
628 , XMLExcepts::DateTime_gMth_invalid
633 fValue[CentYear] = YEAR_DEFAULT;
634 fValue[Day] = DAY_DEFAULT;
635 fValue[Month] = parseInt(2, 4);
637 // REVISIT: allow both --MM and --MM-- now.
638 // need to remove the following lines to disallow --MM--
639 // when the errata is officially in the rec.
641 if ( fEnd >= fStart+2 && fBuffer[fStart] == DATE_SEPARATOR && fBuffer[fStart+1] == DATE_SEPARATOR )
647 // parse TimeZone if any
651 int sign = findUTCSign(fStart);
654 ThrowXML1(SchemaDateTimeException
655 , XMLExcepts::DateTime_gMth_invalid
669 //[-]{CCYY}[TimeZone]
672 void DateTime::parseYear()
676 // skip the first '-' and search for timezone
678 int sign = findUTCSign((fBuffer[0] == chDash) ? 1 : 0);
680 if (sign == NOT_FOUND)
682 fValue[CentYear] = parseIntYear(fEnd);
686 fValue[CentYear] = parseIntYear(sign);
691 fValue[Month] = MONTH_DEFAULT;
692 fValue[Day] = DAY_DEFAULT; //java is 1
699 //{--MM-DD}[TimeZone]
702 void DateTime::parseMonthDay()
706 if (fBuffer[0] != DATE_SEPARATOR ||
707 fBuffer[1] != DATE_SEPARATOR ||
708 fBuffer[4] != DATE_SEPARATOR )
710 ThrowXML1(SchemaDateTimeException
711 , XMLExcepts::DateTime_gMthDay_invalid
717 fValue[CentYear] = YEAR_DEFAULT;
718 fValue[Month] = parseInt(2, 4);
719 fValue[Day] = parseInt(5, 7);
721 if ( MONTHDAY_SIZE < fEnd )
723 int sign = findUTCSign(MONTHDAY_SIZE);
726 ThrowXML1(SchemaDateTimeException
727 , XMLExcepts::DateTime_gMthDay_invalid
740 void DateTime::parseYearMonth()
746 fValue[Day] = DAY_DEFAULT;
754 //PnYn MnDTnH nMnS: -P1Y2M3DT10H30M
756 // [-]{'P'{[n'Y'][n'M'][n'D']['T'][n'H'][n'M'][n'S']}}
758 // Note: the n above shall be >= 0
759 // if no time element found, 'T' shall be absent
761 void DateTime::parseDuration()
765 // must start with '-' or 'P'
767 XMLCh c = fBuffer[fStart++];
768 if ( (c != DURATION_STARTER) &&
771 ThrowXML1(SchemaDateTimeException
772 , XMLExcepts::DateTime_dur_Start_dashP
776 // 'P' must ALWAYS be present in either case
777 if ( (c == chDash) &&
778 (fBuffer[fStart++]!= DURATION_STARTER ))
780 ThrowXML1(SchemaDateTimeException
781 , XMLExcepts::DateTime_dur_noP
786 //date[utc]=(c=='-')?'-':0;
787 //fValue[utc] = UTC_STD;
788 fValue[utc] = (fBuffer[0] == chDash? UTC_NEG : UTC_STD);
790 int negate = ( fBuffer[0] == chDash ? -1 : 1);
793 // No negative value is allowed after 'P'
795 // eg P-1234, invalid
797 if (indexOf(fStart, fEnd, chDash) != NOT_FOUND)
799 ThrowXML1(SchemaDateTimeException
800 , XMLExcepts::DateTime_dur_DashNotFirst
804 //at least one number and designator must be seen after P
805 bool designator = false;
807 int endDate = indexOf(fStart, fEnd, DATETIME_SEPARATOR);
808 if ( endDate == NOT_FOUND )
810 endDate = fEnd; // 'T' absent
814 int end = indexOf(fStart, endDate, DURATION_Y);
815 if ( end != NOT_FOUND )
818 fValue[CentYear] = negate * parseInt(fStart, end);
823 end = indexOf(fStart, endDate, DURATION_M);
824 if ( end != NOT_FOUND )
827 fValue[Month] = negate * parseInt(fStart, end);
832 end = indexOf(fStart, endDate, DURATION_D);
833 if ( end != NOT_FOUND )
836 fValue[Day] = negate * parseInt(fStart,end);
841 if ( (fEnd == endDate) && // 'T' absent
842 (fStart != fEnd) ) // something after Day
844 ThrowXML1(SchemaDateTimeException
845 , XMLExcepts::DateTime_dur_inv_b4T
849 if ( fEnd != endDate ) // 'T' present
851 //scan hours, minutes, seconds
855 end = indexOf(++fStart, fEnd, DURATION_H);
856 if ( end != NOT_FOUND )
859 fValue[Hour] = negate * parseInt(fStart, end);
864 end = indexOf(fStart, fEnd, DURATION_M);
865 if ( end != NOT_FOUND )
868 fValue[Minute] = negate * parseInt(fStart, end);
873 end = indexOf(fStart, fEnd, DURATION_S);
874 if ( end != NOT_FOUND )
877 int mlsec = indexOf (fStart, end, MILISECOND_SEPARATOR);
880 * Schema Errata: E2-23
881 * at least one digit must follow the decimal point if it appears.
882 * That is, the value of the seconds component must conform
883 * to the following pattern: [0-9]+(.[0-9]+)?
885 if ( mlsec != NOT_FOUND )
888 * make usure there is something after the '.' and before the end.
890 if ( mlsec+1 == end )
892 ThrowXML1(SchemaDateTimeException
893 , XMLExcepts::DateTime_dur_inv_seconds
897 fValue[Second] = negate * parseInt(fStart, mlsec);
898 fMiliSecond = negate * parseMiliSecond(mlsec+1, end);
902 fValue[Second] = negate * parseInt(fStart,end);
909 // no additional data should appear after last item
910 // P1Y1M1DT is illigal value as well
911 if ( (fStart != fEnd) ||
912 fBuffer[--fStart] == DATETIME_SEPARATOR )
914 ThrowXML1(SchemaDateTimeException
915 , XMLExcepts::DateTime_dur_NoTimeAfterT
922 ThrowXML1(SchemaDateTimeException
923 , XMLExcepts::DateTime_dur_NoElementAtAll
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 ThrowXML1(SchemaDateTimeException
947 , XMLExcepts::DateTime_date_incomplete
950 getYearMonth(); // Scan YearMonth and
951 // fStart point to the next '-'
953 if (fBuffer[fStart++] != DATE_SEPARATOR)
955 ThrowXML1(SchemaDateTimeException
956 , XMLExcepts::DateTime_date_invalid
958 //("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 ThrowXML1(SchemaDateTimeException
982 , XMLExcepts::DateTime_time_incomplete
984 //"Imcomplete Time Format"
986 // check (fixed) format first
987 if ((fBuffer[fStart + 2] != TIME_SEPARATOR) ||
988 (fBuffer[fStart + 5] != TIME_SEPARATOR) )
990 ThrowXML1(SchemaDateTimeException
991 , XMLExcepts::DateTime_time_invalid
993 //("Error in parsing time" );
997 // get hours, minute and second
999 fValue[Hour] = parseInt(fStart + 0, fStart + 2);
1000 fValue[Minute] = parseInt(fStart + 3, fStart + 5);
1001 fValue[Second] = parseInt(fStart + 6, fStart + 8);
1004 // to see if any ms and/or utc part after that
1008 //find UTC sign if any
1009 int sign = findUTCSign(fStart);
1012 int milisec = (fBuffer[fStart] == MILISECOND_SEPARATOR)? fStart : NOT_FOUND;
1013 if ( milisec != NOT_FOUND )
1015 fStart++; // skip the '.'
1016 // make sure we have some thing between the '.' and fEnd
1019 ThrowXML1(SchemaDateTimeException
1020 , XMLExcepts::DateTime_ms_noDigit
1022 //("ms shall be present once '.' is present" );
1025 if ( sign == NOT_FOUND )
1027 fMiliSecond = parseMiliSecond(fStart, fEnd); //get ms between '.' and fEnd
1032 fMiliSecond = parseMiliSecond(fStart, sign); //get ms between UTC sign and fEnd
1035 else if(sign == 0 || sign != fStart)
1037 // seconds has more than 2 digits
1038 ThrowXML1(SchemaDateTimeException
1039 , XMLExcepts::DateTime_min_invalid
1043 //parse UTC time zone (hh:mm)
1053 // Note: CCYY could be more than 4 digits
1054 // fStart updated to point AFTER the second 'M' (probably meet the fEnd)
1056 void DateTime::getYearMonth()
1059 // Ensure enough chars in buffer
1060 if ( (fStart+YMONTH_MIN_SIZE) > fEnd)
1061 ThrowXML1(SchemaDateTimeException
1062 , XMLExcepts::DateTime_ym_incomplete
1064 //"Imcomplete YearMonth Format";
1066 // skip the first leading '-'
1067 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1070 // search for year separator '-'
1072 int yearSeparator = indexOf(start, fEnd, DATE_SEPARATOR);
1073 if ( yearSeparator == NOT_FOUND)
1074 ThrowXML1(SchemaDateTimeException
1075 , XMLExcepts::DateTime_ym_invalid
1077 //("Year separator is missing or misplaced");
1079 fValue[CentYear] = parseIntYear(yearSeparator);
1080 fStart = yearSeparator + 1; //skip the '-' and point to the first M
1083 //gonna check we have enough byte for month
1085 if ((fStart + 2) > fEnd )
1086 ThrowXML1(SchemaDateTimeException
1087 , XMLExcepts::DateTime_ym_noMonth
1089 //"no month in buffer"
1091 fValue[Month] = parseInt(fStart, yearSeparator + 3);
1092 fStart += 2; //fStart points right after the MONTH
1097 void DateTime::parseTimeZone()
1099 if ( fStart < fEnd )
1101 int sign = findUTCSign(fStart);
1104 ThrowXML1(SchemaDateTimeException
1105 , XMLExcepts::DateTime_tz_noUTCsign
1107 //("Error in month parsing");
1122 // Note: Assuming fStart points to the beginning of TimeZone section
1123 // fStart updated to meet fEnd
1125 void DateTime::getTimeZone(const int sign)
1128 if ( fBuffer[sign] == UTC_STD_CHAR )
1130 if ((sign + 1) != fEnd )
1132 ThrowXML1(SchemaDateTimeException
1133 , XMLExcepts::DateTime_tz_stuffAfterZ
1135 //"Error in parsing time zone");
1142 // otherwise, it has to be this format
1147 if ( ( ( sign + TIMEZONE_SIZE + 1) != fEnd ) ||
1148 ( fBuffer[sign + 3] != TIMEZONE_SEPARATOR ) )
1150 ThrowXML1(SchemaDateTimeException
1151 , XMLExcepts::DateTime_tz_invalid
1153 //("Error in parsing time zone");
1156 fTimeZone[hh] = parseInt(sign+1, sign+3);
1157 fTimeZone[mm] = parseInt(sign+4, fEnd);
1162 // ---------------------------------------------------------------------------
1163 // Validator and normalizer
1164 // ---------------------------------------------------------------------------
1167 * If timezone present - normalize dateTime [E Adding durations to dateTimes]
1169 * @param date CCYY-MM-DDThh:mm:ss+03
1170 * @return CCYY-MM-DDThh:mm:ssZ
1172 void DateTime::normalize()
1175 if ((fValue[utc] == UTC_UNKNOWN) ||
1176 (fValue[utc] == UTC_STD) )
1179 int negate = (fValue[utc] == UTC_POS)? -1: 1;
1182 int temp = fValue[Minute] + negate * fTimeZone[mm];
1183 int carry = fQuotient(temp, 60);
1184 fValue[Minute] = mod(temp, 60, carry);
1187 temp = fValue[Hour] + negate * fTimeZone[hh] + carry;
1188 carry = fQuotient(temp, 24);
1189 fValue[Hour] = mod(temp, 24, carry);
1191 fValue[Day] += carry;
1195 temp = maxDayInMonthFor(fValue[CentYear], fValue[Month]);
1196 if (fValue[Day] < 1)
1198 fValue[Day] += maxDayInMonthFor(fValue[CentYear], fValue[Month] - 1);
1201 else if ( fValue[Day] > temp )
1203 fValue[Day] -= temp;
1211 temp = fValue[Month] + carry;
1212 fValue[Month] = modulo(temp, 1, 13);
1213 fValue[CentYear] += fQuotient(temp, 1, 13);
1216 // set to normalized
1217 fValue[utc] = UTC_STD;
1222 void DateTime::validateDateTime() const
1225 //REVISIT: should we throw an exception for not valid dates
1226 // or reporting an error message should be sufficient?
1227 if ( fValue[CentYear] == 0 )
1229 ThrowXML1(SchemaDateTimeException
1230 , XMLExcepts::DateTime_year_zero
1232 //"The year \"0000\" is an illegal year value");
1235 if ( fValue[Month] < 1 ||
1236 fValue[Month] > 12 )
1238 ThrowXML1(SchemaDateTimeException
1239 , XMLExcepts::DateTime_mth_invalid
1241 //"The month must have values 1 to 12");
1245 if ( fValue[Day] > maxDayInMonthFor( fValue[CentYear], fValue[Month]) ||
1248 ThrowXML1(SchemaDateTimeException
1249 , XMLExcepts::DateTime_day_invalid
1251 //"The day must have values 1 to 31");
1255 if ((fValue[Hour] < 0) ||
1256 (fValue[Hour] > 24) ||
1257 ((fValue[Hour] == 24) && ((fValue[Minute] !=0) ||
1258 (fValue[Second] !=0) ||
1259 (fMiliSecond !=0))))
1261 ThrowXML1(SchemaDateTimeException
1262 , XMLExcepts::DateTime_hour_invalid
1264 //("Hour must have values 0-23");
1268 if ( fValue[Minute] < 0 ||
1269 fValue[Minute] > 59 )
1271 ThrowXML1(SchemaDateTimeException
1272 , XMLExcepts::DateTime_min_invalid
1274 //"Minute must have values 0-59");
1278 if ( fValue[Second] < 0 ||
1279 fValue[Second] > 60 )
1281 ThrowXML1(SchemaDateTimeException
1282 , XMLExcepts::DateTime_second_invalid
1284 //"Second must have values 0-60");
1287 //validate time-zone hours
1288 if ( (abs(fTimeZone[hh]) > 14) ||
1289 ((abs(fTimeZone[hh]) == 14) && (fTimeZone[mm] != 0)) )
1291 ThrowXML1(SchemaDateTimeException
1292 , XMLExcepts::DateTime_tz_hh_invalid
1294 //"Time zone should have range -14..+14");
1297 //validate time-zone minutes
1298 if ( abs(fTimeZone[mm]) > 59 )
1300 ThrowXML1(SchemaDateTimeException
1301 , XMLExcepts::DateTime_min_invalid
1303 //("Minute must have values 0-59");
1309 // -----------------------------------------------------------------------
1310 // locator and converter
1311 // -----------------------------------------------------------------------
1312 int DateTime::indexOf(const int start, const int end, const XMLCh ch) const
1314 for ( int i = start; i < end; i++ )
1315 if ( fBuffer[i] == ch )
1321 int DateTime::findUTCSign (const int start)
1324 for ( int index = start; index < fEnd; index++ )
1326 pos = XMLString::indexOf(UTC_SET, fBuffer[index]);
1327 if ( pos != NOT_FOUND)
1329 fValue[utc] = pos+1; // refer to utcType, there is 1 diff
1339 // start: starting point in fBuffer
1340 // end: ending point in fBuffer (exclusive)
1341 // fStart NOT updated
1343 int DateTime::parseInt(const int start, const int end) const
1345 unsigned int retVal = 0;
1346 for (int i=start; i < end; i++) {
1348 if (fBuffer[i] < chDigit_0 || fBuffer[i] > chDigit_9)
1349 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
1351 retVal = (retVal * 10) + (unsigned int) (fBuffer[i] - chDigit_0);
1354 return (int) retVal;
1359 // start: pointing to the first digit after the '.'
1360 // end: pointing to one position after the last digit
1361 // fStart NOT updated
1363 double DateTime::parseMiliSecond(const int start, const int end) const
1366 unsigned int miliSecLen = (end-1) - (start-1) + 1; //to include the '.'
1367 XMLCh* miliSecData = new XMLCh[miliSecLen + 1];
1368 ArrayJanitor<XMLCh> janMili(miliSecData);
1369 XMLString::copyNString(miliSecData, &(fBuffer[start-1]), miliSecLen);
1370 *(miliSecData + miliSecLen) = chNull;
1372 char *nptr = XMLString::transcode(miliSecData);
1373 ArrayJanitor<char> jan(nptr);
1374 size_t strLen = strlen(nptr);
1378 //printf("milisec=<%s>\n", nptr);
1380 double retVal = strtod(nptr, &endptr);
1382 // check if all chars are valid char
1383 if ( (endptr - nptr) != strLen)
1384 ThrowXML(NumberFormatException, XMLExcepts::XMLNUM_Inv_chars);
1386 // we don't check underflow occurs since
1387 // nothing we can do about it.
1394 // Note: start from fStart
1396 // fStart NOT updated
1398 int DateTime::parseIntYear(const int end) const
1400 // skip the first leading '-'
1401 int start = ( fBuffer[0] == chDash ) ? fStart + 1 : fStart;
1403 int length = end - start;
1406 ThrowXML1(SchemaDateTimeException
1407 , XMLExcepts::DateTime_year_tooShort
1409 //"Year must have 'CCYY' format");
1411 else if (length > 4 &&
1412 fBuffer[start] == chDigit_0)
1414 ThrowXML1(SchemaDateTimeException
1415 , XMLExcepts::DateTime_year_leadingZero
1417 //"Leading zeros are required if the year value would otherwise have fewer than four digits;
1418 // otherwise they are forbidden");
1421 bool negative = (fBuffer[0] == chDash);
1422 int yearVal = parseInt((negative ? 1 : 0), end);
1423 return ( negative ? (-1) * yearVal : yearVal );
1429 * 3.2.7.2 Canonical representation
1431 * Except for trailing fractional zero digits in the seconds representation,
1432 * '24:00:00' time representations, and timezone (for timezoned values),
1433 * the mapping from literals to values is one-to-one. Where there is more
1434 * than one possible representation, the canonical representation is as follows:
1435 * redundant trailing zero digits in fractional-second literals are prohibited.
1436 * An hour representation of '24' is prohibited. Timezoned values are canonically
1437 * represented by appending 'Z' to the nontimezoned representation. (All
1438 * timezoned dateTime values are UTC.)
1440 * .'24:00:00' -> '00:00:00'
1441 * .milisecond: trailing zeros removed
1445 XMLCh* DateTime::getDateTimeCanonicalRepresentation() const
1447 XMLCh *miliStartPtr, *miliEndPtr;
1448 searchMiliSeconds(miliStartPtr, miliEndPtr);
1449 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1451 XMLCh* retBuf = new XMLCh[21 + miliSecondsLen + 2];
1452 XMLCh* retPtr = retBuf;
1454 // (-?) cc+yy-mm-dd'T'hh:mm:ss'Z' ('.'s+)?
1457 int additionalLen = fillYearString(retPtr, CentYear);
1458 if(additionalLen != 0)
1460 // very bad luck; have to resize the buffer...
1461 XMLCh *tmpBuf = new XMLCh[additionalLen+21+miliSecondsLen +2];
1462 XMLString::moveChars(tmpBuf, retBuf, 4+additionalLen);
1463 retPtr = tmpBuf+(retPtr-retBuf);
1467 *retPtr++ = DATE_SEPARATOR;
1468 fillString(retPtr, Month, 2);
1469 *retPtr++ = DATE_SEPARATOR;
1470 fillString(retPtr, Day, 2);
1471 *retPtr++ = DATETIME_SEPARATOR;
1473 fillString(retPtr, Hour, 2);
1474 if (fValue[Hour] == 24)
1476 *(retPtr - 2) = chDigit_0;
1477 *(retPtr - 1) = chDigit_0;
1479 *retPtr++ = TIME_SEPARATOR;
1480 fillString(retPtr, Minute, 2);
1481 *retPtr++ = TIME_SEPARATOR;
1482 fillString(retPtr, Second, 2);
1486 *retPtr++ = chPeriod;
1487 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1488 retPtr += miliSecondsLen;
1491 *retPtr++ = UTC_STD_CHAR;
1500 * . either the time zone must be omitted or,
1501 * if present, the time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
1503 * . Additionally, the canonical representation for midnight is 00:00:00.
1506 XMLCh* DateTime::getTimeCanonicalRepresentation() const
1508 XMLCh *miliStartPtr, *miliEndPtr;
1509 searchMiliSeconds(miliStartPtr, miliEndPtr);
1510 size_t miliSecondsLen = miliEndPtr - miliStartPtr;
1512 XMLCh* retBuf = new XMLCh[10 + miliSecondsLen + 2];
1513 XMLCh* retPtr = retBuf;
1515 // 'hh:mm:ss'Z' ('.'s+)?
1519 fillString(retPtr, Hour, 2);
1520 if (fValue[Hour] == 24)
1522 *(retPtr - 2) = chDigit_0;
1523 *(retPtr - 1) = chDigit_0;
1525 *retPtr++ = TIME_SEPARATOR;
1526 fillString(retPtr, Minute, 2);
1527 *retPtr++ = TIME_SEPARATOR;
1528 fillString(retPtr, Second, 2);
1532 *retPtr++ = chPeriod;
1533 XMLString::copyNString(retPtr, miliStartPtr, miliSecondsLen);
1534 retPtr += miliSecondsLen;
1537 *retPtr++ = UTC_STD_CHAR;
1543 void DateTime::fillString(XMLCh*& ptr, valueIndex ind, int expLen) const
1545 XMLCh strBuffer[16];
1546 assert(expLen < 16);
1547 XMLString::binToText(fValue[ind], strBuffer, expLen, 10);
1548 int actualLen = XMLString::stringLen(strBuffer);
1550 //append leading zeros
1551 for (i = 0; i < expLen - actualLen; i++)
1556 for (i = 0; i < actualLen; i++)
1558 *ptr++ = strBuffer[i];
1563 int DateTime::fillYearString(XMLCh*& ptr, valueIndex ind) const
1565 XMLCh strBuffer[16];
1566 // let's hope we get no years of 15 digits...
1567 XMLString::binToText(fValue[ind], strBuffer, 15, 10);
1568 int actualLen = XMLString::stringLen(strBuffer);
1569 // don't forget that years can be negative...
1570 int negativeYear = 0;
1571 if(strBuffer[0] == chDash)
1573 *ptr++ = strBuffer[0];
1577 //append leading zeros
1578 for (i = 0; i < 4 - actualLen+negativeYear; i++)
1583 for (i = negativeYear; i < actualLen; i++)
1585 *ptr++ = strBuffer[i];
1594 * .check if the rawData has the mili second component
1595 * .capture the substring
1598 void DateTime::searchMiliSeconds(XMLCh*& miliStartPtr, XMLCh*& miliEndPtr) const
1600 miliStartPtr = miliEndPtr = 0;
1602 int milisec = XMLString::indexOf(fBuffer, MILISECOND_SEPARATOR);
1606 miliStartPtr = fBuffer + milisec + 1;
1607 miliEndPtr = miliStartPtr;
1610 if ((*miliEndPtr < chDigit_0) || (*miliEndPtr > chDigit_9))
1616 //remove trailing zeros
1617 while( *(miliEndPtr - 1) == chDigit_0)