1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 1999-2005 The Apache Software Foundation.
   7  *
   8  * Licensed under the Apache License, Version 2.0 (the "License");
   9  * you may not use this file except in compliance with the License.
  10  * You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.impl.dv.xs;
  22 
  23 import java.math.BigDecimal;
  24 
  25 import javax.xml.datatype.DatatypeFactory;
  26 import javax.xml.datatype.Duration;
  27 import javax.xml.datatype.XMLGregorianCalendar;
  28 
  29 import com.sun.org.apache.xerces.internal.impl.Constants;
  30 import com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl;
  31 import com.sun.org.apache.xerces.internal.xs.datatypes.XSDateTime;
  32 
  33 /**
  34  * This is the base class of all date/time datatype validators.
  35  * It implements common code for parsing, validating and comparing datatypes.
  36  * Classes that extend this class, must implement parse() method.
  37  *
  38  * REVISIT: There are many instance variables, which would cause problems
  39  *          when we support grammar caching. A grammar is possibly used by
  40  *          two parser instances at the same time, then the same simple type
  41  *          decl object can be used to validate two strings at the same time.
  42  *          -SG
  43  *
  44  * @xerces.internal
  45  *
  46  * @author Elena Litani
  47  * @author Len Berman
  48  * @author Gopal Sharma, SUN Microsystems Inc.
  49  *
  50  * @version $Id: AbstractDateTimeDV.java,v 1.7 2010-11-01 04:39:46 joehw Exp $
  51  */
  52 public abstract class AbstractDateTimeDV extends TypeValidator {
  53 
  54     //debugging
  55     private static final boolean DEBUG = false;
  56     //define shared variables for date/time
  57     //define constants to be used in assigning default values for
  58     //all date/time excluding duration
  59     protected final static int YEAR = 2000;
  60     protected final static int MONTH = 01;
  61     protected final static int DAY = 01;
  62     protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl();
  63 
  64     @Override
  65     public short getAllowedFacets() {
  66         return (XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION | XSSimpleTypeDecl.FACET_MAXINCLUSIVE | XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE);
  67     }//getAllowedFacets()
  68 
  69     // distinguishes between identity and equality for date/time values
  70     // ie: two values representing the same "moment in time" but with different
  71     // remembered timezones are now equal but not identical.
  72     @Override
  73     public boolean isIdentical(Object value1, Object value2) {
  74         if (!(value1 instanceof DateTimeData) || !(value2 instanceof DateTimeData)) {
  75             return false;
  76         }
  77 
  78         DateTimeData v1 = (DateTimeData) value1;
  79         DateTimeData v2 = (DateTimeData) value2;
  80 
  81         // original timezones must be the same in addition to date/time values
  82         // being 'equal'
  83         if ((v1.timezoneHr == v2.timezoneHr) && (v1.timezoneMin == v2.timezoneMin)) {
  84             return v1.equals(v2);
  85         }
  86 
  87         return false;
  88     }//isIdentical()
  89 
  90     // the parameters are in compiled form (from getActualValue)
  91     @Override
  92     public int compare(Object value1, Object value2) {
  93         return compareDates(((DateTimeData) value1),
  94                 ((DateTimeData) value2), true);
  95     }//compare()
  96 
  97     /**
  98      * Compare algorithm described in dateDime (3.2.7). Duration datatype
  99      * overwrites this method
 100      *
 101      * @param date1 normalized date representation of the first value
 102      * @param date2 normalized date representation of the second value
 103      * @param strict
 104      * @return less, greater, less_equal, greater_equal, equal
 105      */
 106     protected short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) {
 107         if (date1.utc == date2.utc) {
 108             return compareOrder(date1, date2);
 109         }
 110         short c1, c2;
 111 
 112         DateTimeData tempDate = new DateTimeData(null, this);
 113 
 114         if (date1.utc == 'Z') {
 115 
 116             //compare date1<=date1<=(date2 with time zone -14)
 117             //
 118             cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate
 119             tempDate.timezoneHr = 14;
 120             tempDate.timezoneMin = 0;
 121             tempDate.utc = '+';
 122             normalize(tempDate);
 123             c1 = compareOrder(date1, tempDate);
 124             if (c1 == LESS_THAN) {
 125                 return c1;
 126             }
 127 
 128             //compare date1>=(date2 with time zone +14)
 129             //
 130             cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate
 131             tempDate.timezoneHr = -14;
 132             tempDate.timezoneMin = 0;
 133             tempDate.utc = '-';
 134             normalize(tempDate);
 135             c2 = compareOrder(date1, tempDate);
 136             if (c2 == GREATER_THAN) {
 137                 return c2;
 138             }
 139 
 140             return INDETERMINATE;
 141         } else if (date2.utc == 'Z') {
 142 
 143             //compare (date1 with time zone -14)<=date2
 144             //
 145             cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
 146             tempDate.timezoneHr = -14;
 147             tempDate.timezoneMin = 0;
 148             tempDate.utc = '-';
 149             if (DEBUG) {
 150                 System.out.println("tempDate=" + dateToString(tempDate));
 151             }
 152             normalize(tempDate);
 153             c1 = compareOrder(tempDate, date2);
 154             if (DEBUG) {
 155                 System.out.println("date=" + dateToString(date2));
 156                 System.out.println("tempDate=" + dateToString(tempDate));
 157             }
 158             if (c1 == LESS_THAN) {
 159                 return c1;
 160             }
 161 
 162             //compare (date1 with time zone +14)<=date2
 163             //
 164             cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
 165             tempDate.timezoneHr = 14;
 166             tempDate.timezoneMin = 0;
 167             tempDate.utc = '+';
 168             normalize(tempDate);
 169             c2 = compareOrder(tempDate, date2);
 170             if (DEBUG) {
 171                 System.out.println("tempDate=" + dateToString(tempDate));
 172             }
 173             if (c2 == GREATER_THAN) {
 174                 return c2;
 175             }
 176 
 177             return INDETERMINATE;
 178         }
 179         return INDETERMINATE;
 180 
 181     }
 182 
 183     /**
 184      * Given normalized values, determines order-relation between give date/time
 185      * objects.
 186      *
 187      * @param date1 date/time object
 188      * @param date2 date/time object
 189      * @return 0 if date1 and date2 are equal, a value less than 0 if date1 is
 190      * less than date2, a value greater than 0 if date1 is greater than date2
 191      */
 192     protected short compareOrder(DateTimeData date1, DateTimeData date2) {
 193         if (date1.position < 1) {
 194             if (date1.year < date2.year) {
 195                 return -1;
 196             }
 197             if (date1.year > date2.year) {
 198                 return 1;
 199             }
 200         }
 201         if (date1.position < 2) {
 202             if (date1.month < date2.month) {
 203                 return -1;
 204             }
 205             if (date1.month > date2.month) {
 206                 return 1;
 207             }
 208         }
 209         if (date1.day < date2.day) {
 210             return -1;
 211         }
 212         if (date1.day > date2.day) {
 213             return 1;
 214         }
 215         if (date1.hour < date2.hour) {
 216             return -1;
 217         }
 218         if (date1.hour > date2.hour) {
 219             return 1;
 220         }
 221         if (date1.minute < date2.minute) {
 222             return -1;
 223         }
 224         if (date1.minute > date2.minute) {
 225             return 1;
 226         }
 227         if (date1.second < date2.second) {
 228             return -1;
 229         }
 230         if (date1.second > date2.second) {
 231             return 1;
 232         }
 233         if (date1.utc < date2.utc) {
 234             return -1;
 235         }
 236         if (date1.utc > date2.utc) {
 237             return 1;
 238         }
 239         return 0;
 240     }
 241 
 242     /**
 243      * Parses time hh:mm:ss.sss and time zone if any
 244      *
 245      * @param start
 246      * @param end
 247      * @param data
 248      * @exception RuntimeException
 249      */
 250     protected void getTime(String buffer, int start, int end, DateTimeData data) throws RuntimeException {
 251 
 252         int stop = start + 2;
 253 
 254         //get hours (hh)
 255         data.hour = parseInt(buffer, start, stop);
 256 
 257         //get minutes (mm)
 258 
 259         if (buffer.charAt(stop++) != ':') {
 260             throw new RuntimeException("Error in parsing time zone");
 261         }
 262         start = stop;
 263         stop = stop + 2;
 264         data.minute = parseInt(buffer, start, stop);
 265 
 266         //get seconds (ss)
 267         if (buffer.charAt(stop++) != ':') {
 268             throw new RuntimeException("Error in parsing time zone");
 269         }
 270 
 271         //find UTC sign if any
 272         int sign = findUTCSign(buffer, start, end);
 273 
 274         //get seconds (ms)
 275         start = stop;
 276         stop = sign < 0 ? end : sign;
 277         data.second = parseSecond(buffer, start, stop);
 278 
 279         //parse UTC time zone (hh:mm)
 280         if (sign > 0) {
 281             getTimeZone(buffer, data, sign, end);
 282         }
 283     }
 284 
 285     /**
 286      * Parses date CCYY-MM-DD
 287      *
 288      * @param buffer
 289      * @param start start position
 290      * @param end end position
 291      * @param date
 292      * @exception RuntimeException
 293      */
 294     protected int getDate(String buffer, int start, int end, DateTimeData date) throws RuntimeException {
 295 
 296         start = getYearMonth(buffer, start, end, date);
 297 
 298         if (buffer.charAt(start++) != '-') {
 299             throw new RuntimeException("CCYY-MM must be followed by '-' sign");
 300         }
 301         int stop = start + 2;
 302         date.day = parseInt(buffer, start, stop);
 303         return stop;
 304     }
 305 
 306     /**
 307      * Parses date CCYY-MM
 308      *
 309      * @param buffer
 310      * @param start start position
 311      * @param end end position
 312      * @param date
 313      * @exception RuntimeException
 314      */
 315     protected int getYearMonth(String buffer, int start, int end, DateTimeData date) throws RuntimeException {
 316 
 317         if (buffer.charAt(0) == '-') {
 318             // REVISIT: date starts with preceding '-' sign
 319             //          do we have to do anything with it?
 320             //
 321             start++;
 322         }
 323         int i = indexOf(buffer, start, end, '-');
 324         if (i == -1) {
 325             throw new RuntimeException("Year separator is missing or misplaced");
 326         }
 327         int length = i - start;
 328         if (length < 4) {
 329             throw new RuntimeException("Year must have 'CCYY' format");
 330         } else if (length > 4 && buffer.charAt(start) == '0') {
 331             throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
 332         }
 333         date.year = parseIntYear(buffer, i);
 334         if (buffer.charAt(i) != '-') {
 335             throw new RuntimeException("CCYY must be followed by '-' sign");
 336         }
 337         start = ++i;
 338         i = start + 2;
 339         date.month = parseInt(buffer, start, i);
 340         return i; //fStart points right after the MONTH
 341     }
 342 
 343     /**
 344      * Shared code from Date and YearMonth datatypes. Finds if time zone sign is
 345      * present
 346      *
 347      * @param end
 348      * @param date
 349      * @exception RuntimeException
 350      */
 351     protected void parseTimeZone(String buffer, int start, int end, DateTimeData date) throws RuntimeException {
 352 
 353         //fStart points right after the date
 354 
 355         if (start < end) {
 356             if (!isNextCharUTCSign(buffer, start, end)) {
 357                 throw new RuntimeException("Error in month parsing");
 358             } else {
 359                 getTimeZone(buffer, date, start, end);
 360             }
 361         }
 362     }
 363 
 364     /**
 365      * Parses time zone: 'Z' or {+,-} followed by hh:mm
 366      *
 367      * @param data
 368      * @param sign
 369      * @exception RuntimeException
 370      */
 371     protected void getTimeZone(String buffer, DateTimeData data, int sign, int end) throws RuntimeException {
 372         data.utc = buffer.charAt(sign);
 373 
 374         if (buffer.charAt(sign) == 'Z') {
 375             if (end > (++sign)) {
 376                 throw new RuntimeException("Error in parsing time zone");
 377             }
 378             return;
 379         }
 380         if (sign <= (end - 6)) {
 381 
 382             int negate = buffer.charAt(sign) == '-' ? -1 : 1;
 383             //parse hr
 384             int stop = ++sign + 2;
 385             data.timezoneHr = negate * parseInt(buffer, sign, stop);
 386             if (buffer.charAt(stop++) != ':') {
 387                 throw new RuntimeException("Error in parsing time zone");
 388             }
 389 
 390             //parse min
 391             data.timezoneMin = negate * parseInt(buffer, stop, stop + 2);
 392 
 393             if (stop + 2 != end) {
 394                 throw new RuntimeException("Error in parsing time zone");
 395             }
 396             if (data.timezoneHr != 0 || data.timezoneMin != 0) {
 397                 data.normalized = false;
 398             }
 399         } else {
 400             throw new RuntimeException("Error in parsing time zone");
 401         }
 402         if (DEBUG) {
 403             System.out.println("time[hh]=" + data.timezoneHr + " time[mm]=" + data.timezoneMin);
 404         }
 405     }
 406 
 407     /**
 408      * Computes index of given char within StringBuffer
 409      *
 410      * @param start
 411      * @param end
 412      * @param ch character to look for in StringBuffer
 413      * @return index of ch within StringBuffer
 414      */
 415     protected int indexOf(String buffer, int start, int end, char ch) {
 416         for (int i = start; i < end; i++) {
 417             if (buffer.charAt(i) == ch) {
 418                 return i;
 419             }
 420         }
 421         return -1;
 422     }
 423 
 424     /**
 425      * Validates given date/time object accoring to W3C PR Schema [D.1 ISO 8601
 426      * Conventions]
 427      *
 428      * @param data
 429      */
 430     protected void validateDateTime(DateTimeData data) {
 431 
 432         //REVISIT: should we throw an exception for not valid dates
 433         //          or reporting an error message should be sufficient?
 434 
 435         /**
 436          * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types.
 437          */
 438         if (!Constants.SCHEMA_1_1_SUPPORT && data.year == 0) {
 439             throw new RuntimeException("The year \"0000\" is an illegal year value");
 440 
 441         }
 442 
 443         if (data.month < 1 || data.month > 12) {
 444             throw new RuntimeException("The month must have values 1 to 12");
 445 
 446         }
 447 
 448         //validate days
 449         if (data.day > maxDayInMonthFor(data.year, data.month) || data.day < 1) {
 450             throw new RuntimeException("The day must have values 1 to 31");
 451         }
 452 
 453         //validate hours
 454         if (data.hour > 23 || data.hour < 0) {
 455             if (data.hour == 24 && data.minute == 0 && data.second == 0) {
 456                 data.hour = 0;
 457                 if (++data.day > maxDayInMonthFor(data.year, data.month)) {
 458                     data.day = 1;
 459                     if (++data.month > 12) {
 460                         data.month = 1;
 461                         if (Constants.SCHEMA_1_1_SUPPORT) {
 462                             ++data.year;
 463                         } else if (++data.year == 0) {
 464                             data.year = 1;
 465                         }
 466                     }
 467                 }
 468             } else {
 469                 throw new RuntimeException("Hour must have values 0-23, unless 24:00:00");
 470             }
 471         }
 472 
 473         //validate
 474         if (data.minute > 59 || data.minute < 0) {
 475             throw new RuntimeException("Minute must have values 0-59");
 476         }
 477 
 478         //validate
 479         if (data.second >= 60 || data.second < 0) {
 480             throw new RuntimeException("Second must have values 0-59");
 481 
 482         }
 483 
 484         //validate
 485         if (data.timezoneHr > 14 || data.timezoneHr < -14) {
 486             throw new RuntimeException("Time zone should have range -14:00 to +14:00");
 487         } else {
 488             if ((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0) {
 489                 throw new RuntimeException("Time zone should have range -14:00 to +14:00");
 490             } else if (data.timezoneMin > 59 || data.timezoneMin < -59) {
 491                 throw new RuntimeException("Minute must have values 0-59");
 492             }
 493         }
 494 
 495     }
 496 
 497     /**
 498      * Return index of UTC char: 'Z', '+', '-'
 499      *
 500      * @param start
 501      * @param end
 502      * @return index of the UTC character that was found
 503      */
 504     protected int findUTCSign(String buffer, int start, int end) {
 505         int c;
 506         for (int i = start; i < end; i++) {
 507             c = buffer.charAt(i);
 508             if (c == 'Z' || c == '+' || c == '-') {
 509                 return i;
 510             }
 511 
 512         }
 513         return -1;
 514     }
 515 
 516     /**
 517      * Returns
 518      * <code>true</code> if the character at start is 'Z', '+' or '-'.
 519      */
 520     protected final boolean isNextCharUTCSign(String buffer, int start, int end) {
 521         if (start < end) {
 522             char c = buffer.charAt(start);
 523             return (c == 'Z' || c == '+' || c == '-');
 524         }
 525         return false;
 526     }
 527 
 528     /**
 529      * Given start and end position, parses string value
 530      *
 531      * @param buffer string to parse
 532      * @param start start position
 533      * @param end end position
 534      * @return return integer representation of characters
 535      */
 536     protected int parseInt(String buffer, int start, int end)
 537             throws NumberFormatException {
 538         //REVISIT: more testing on this parsing needs to be done.
 539         int radix = 10;
 540         int result = 0;
 541         int digit = 0;
 542         int limit = -Integer.MAX_VALUE;
 543         int multmin = limit / radix;
 544         int i = start;
 545         do {
 546             digit = getDigit(buffer.charAt(i));
 547             if (digit < 0) {
 548                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 549             }
 550             if (result < multmin) {
 551                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 552             }
 553             result *= radix;
 554             if (result < limit + digit) {
 555                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 556             }
 557             result -= digit;
 558 
 559         } while (++i < end);
 560         return -result;
 561     }
 562 
 563     // parse Year differently to support negative value.
 564     protected int parseIntYear(String buffer, int end) {
 565         int radix = 10;
 566         int result = 0;
 567         boolean negative = false;
 568         int i = 0;
 569         int limit;
 570         int multmin;
 571         int digit = 0;
 572 
 573         if (buffer.charAt(0) == '-') {
 574             negative = true;
 575             limit = Integer.MIN_VALUE;
 576             i++;
 577 
 578         } else {
 579             limit = -Integer.MAX_VALUE;
 580         }
 581         multmin = limit / radix;
 582         while (i < end) {
 583             digit = getDigit(buffer.charAt(i++));
 584             if (digit < 0) {
 585                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 586             }
 587             if (result < multmin) {
 588                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 589             }
 590             result *= radix;
 591             if (result < limit + digit) {
 592                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 593             }
 594             result -= digit;
 595         }
 596 
 597         if (negative) {
 598             if (i > 1) {
 599                 return result;
 600             } else {
 601                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 602             }
 603         }
 604         return -result;
 605 
 606     }
 607 
 608     /**
 609      * If timezone present - normalize dateTime [E Adding durations to
 610      * dateTimes]
 611      *
 612      * @param date CCYY-MM-DDThh:mm:ss+03
 613      */
 614     protected void normalize(DateTimeData date) {
 615 
 616         // REVISIT: we have common code in addDuration() for durations
 617         //          should consider reorganizing it.
 618         //
 619 
 620         //add minutes (from time zone)
 621         int negate = -1;
 622 
 623         if (DEBUG) {
 624             System.out.println("==>date.minute" + date.minute);
 625             System.out.println("==>date.timezoneMin" + date.timezoneMin);
 626         }
 627         int temp = date.minute + negate * date.timezoneMin;
 628         int carry = fQuotient(temp, 60);
 629         date.minute = mod(temp, 60, carry);
 630 
 631         if (DEBUG) {
 632             System.out.println("==>carry: " + carry);
 633         }
 634         //add hours
 635         temp = date.hour + negate * date.timezoneHr + carry;
 636         carry = fQuotient(temp, 24);
 637         date.hour = mod(temp, 24, carry);
 638         if (DEBUG) {
 639             System.out.println("==>date.hour" + date.hour);
 640             System.out.println("==>carry: " + carry);
 641         }
 642 
 643         date.day = date.day + carry;
 644 
 645         while (true) {
 646             temp = maxDayInMonthFor(date.year, date.month);
 647             if (date.day < 1) {
 648                 date.day = date.day + maxDayInMonthFor(date.year, date.month - 1);
 649                 carry = -1;
 650             } else if (date.day > temp) {
 651                 date.day = date.day - temp;
 652                 carry = 1;
 653             } else {
 654                 break;
 655             }
 656             temp = date.month + carry;
 657             date.month = modulo(temp, 1, 13);
 658             date.year = date.year + fQuotient(temp, 1, 13);
 659             if (date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) {
 660                 date.year = (date.timezoneHr < 0 || date.timezoneMin < 0) ? 1 : -1;
 661             }
 662         }
 663         date.utc = 'Z';
 664     }
 665 
 666     /**
 667      * @param date
 668      */
 669     protected void saveUnnormalized(DateTimeData date) {
 670         date.unNormYear = date.year;
 671         date.unNormMonth = date.month;
 672         date.unNormDay = date.day;
 673         date.unNormHour = date.hour;
 674         date.unNormMinute = date.minute;
 675         date.unNormSecond = date.second;
 676     }
 677 
 678     /**
 679      * Resets object representation of date/time
 680      *
 681      * @param data date/time object
 682      */
 683     protected void resetDateObj(DateTimeData data) {
 684         data.year = 0;
 685         data.month = 0;
 686         data.day = 0;
 687         data.hour = 0;
 688         data.minute = 0;
 689         data.second = 0;
 690         data.utc = 0;
 691         data.timezoneHr = 0;
 692         data.timezoneMin = 0;
 693     }
 694 
 695     /**
 696      * Given {year,month} computes maximum number of days for given month
 697      *
 698      * @param year
 699      * @param month
 700      * @return integer containg the number of days in a given month
 701      */
 702     protected int maxDayInMonthFor(int year, int month) {
 703         //validate days
 704         if (month == 4 || month == 6 || month == 9 || month == 11) {
 705             return 30;
 706         } else if (month == 2) {
 707             if (isLeapYear(year)) {
 708                 return 29;
 709             } else {
 710                 return 28;
 711             }
 712         } else {
 713             return 31;
 714         }
 715     }
 716 
 717     private boolean isLeapYear(int year) {
 718 
 719         //REVISIT: should we take care about Julian calendar?
 720         return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
 721     }
 722 
 723     //
 724     // help function described in W3C PR Schema [E Adding durations to dateTimes]
 725     //
 726     protected int mod(int a, int b, int quotient) {
 727         //modulo(a, b) = a - fQuotient(a,b)*b
 728         return (a - quotient * b);
 729     }
 730 
 731     //
 732     // help function described in W3C PR Schema [E Adding durations to dateTimes]
 733     //
 734     protected int fQuotient(int a, int b) {
 735 
 736         //fQuotient(a, b) = the greatest integer less than or equal to a/b
 737         return (int) Math.floor((float) a / b);
 738     }
 739 
 740     //
 741     // help function described in W3C PR Schema [E Adding durations to dateTimes]
 742     //
 743     protected int modulo(int temp, int low, int high) {
 744         //modulo(a - low, high - low) + low
 745         int a = temp - low;
 746         int b = high - low;
 747         return (mod(a, b, fQuotient(a, b)) + low);
 748     }
 749 
 750     //
 751     // help function described in W3C PR Schema [E Adding durations to dateTimes]
 752     //
 753     protected int fQuotient(int temp, int low, int high) {
 754         //fQuotient(a - low, high - low)
 755 
 756         return fQuotient(temp - low, high - low);
 757     }
 758 
 759     protected String dateToString(DateTimeData date) {
 760         StringBuffer message = new StringBuffer(25);
 761         append(message, date.year, 4);
 762         message.append('-');
 763         append(message, date.month, 2);
 764         message.append('-');
 765         append(message, date.day, 2);
 766         message.append('T');
 767         append(message, date.hour, 2);
 768         message.append(':');
 769         append(message, date.minute, 2);
 770         message.append(':');
 771         append(message, date.second);
 772         append(message, (char) date.utc, 0);
 773         return message.toString();
 774     }
 775 
 776     protected final void append(StringBuffer message, int value, int nch) {
 777         if (value == Integer.MIN_VALUE) {
 778             message.append(value);
 779             return;
 780         }
 781         if (value < 0) {
 782             message.append('-');
 783             value = -value;
 784         }
 785         if (nch == 4) {
 786             if (value < 10) {
 787                 message.append("000");
 788             } else if (value < 100) {
 789                 message.append("00");
 790             } else if (value < 1000) {
 791                 message.append('0');
 792             }
 793             message.append(value);
 794         } else if (nch == 2) {
 795             if (value < 10) {
 796                 message.append('0');
 797             }
 798             message.append(value);
 799         } else {
 800             if (value != 0) {
 801                 message.append((char) value);
 802             }
 803         }
 804     }
 805 
 806     protected final void append(StringBuffer message, double value) {
 807         if (value < 0) {
 808             message.append('-');
 809             value = -value;
 810         }
 811         if (value < 10) {
 812             message.append('0');
 813         }
 814         append2(message, value);
 815     }
 816 
 817     protected final void append2(StringBuffer message, double value) {
 818         final int intValue = (int) value;
 819         if (value == intValue) {
 820             message.append(intValue);
 821         } else {
 822             append3(message, value);
 823         }
 824     }
 825 
 826     private void append3(StringBuffer message, double value) {
 827         String d = String.valueOf(value);
 828         int eIndex = d.indexOf('E');
 829         if (eIndex == -1) {
 830             message.append(d);
 831             return;
 832         }
 833         int exp;
 834         if (value < 1) {
 835             // Need to convert from scientific notation of the form
 836             // n.nnn...E-N (N >= 4) to a normal decimal value.
 837             try {
 838                 exp = parseInt(d, eIndex + 2, d.length());
 839             } // This should never happen.
 840             // It's only possible if String.valueOf(double) is broken.
 841             catch (Exception e) {
 842                 message.append(d);
 843                 return;
 844             }
 845             message.append("0.");
 846             for (int i = 1; i < exp; ++i) {
 847                 message.append('0');
 848             }
 849             // Remove trailing zeros.
 850             int end = eIndex - 1;
 851             while (end > 0) {
 852                 char c = d.charAt(end);
 853                 if (c != '0') {
 854                     break;
 855                 }
 856                 --end;
 857             }
 858             // Now append the digits to the end. Skip over the decimal point.
 859             for (int i = 0; i <= end; ++i) {
 860                 char c = d.charAt(i);
 861                 if (c != '.') {
 862                     message.append(c);
 863                 }
 864             }
 865         } else {
 866             // Need to convert from scientific notation of the form
 867             // n.nnn...EN (N >= 7) to a normal decimal value.
 868             try {
 869                 exp = parseInt(d, eIndex + 1, d.length());
 870             } // This should never happen.
 871             // It's only possible if String.valueOf(double) is broken.
 872             catch (Exception e) {
 873                 message.append(d);
 874                 return;
 875             }
 876             final int integerEnd = exp + 2;
 877             for (int i = 0; i < eIndex; ++i) {
 878                 char c = d.charAt(i);
 879                 if (c != '.') {
 880                     if (i == integerEnd) {
 881                         message.append('.');
 882                     }
 883                     message.append(c);
 884                 }
 885             }
 886             // Append trailing zeroes if necessary.
 887             for (int i = integerEnd - eIndex; i > 0; --i) {
 888                 message.append('0');
 889             }
 890         }
 891     }
 892 
 893     protected double parseSecond(String buffer, int start, int end)
 894             throws NumberFormatException {
 895         int dot = -1;
 896         for (int i = start; i < end; i++) {
 897             char ch = buffer.charAt(i);
 898             if (ch == '.') {
 899                 dot = i;
 900             } else if (ch > '9' || ch < '0') {
 901                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 902             }
 903         }
 904         if (dot == -1) {
 905             if (start + 2 != end) {
 906                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 907             }
 908         } else if (start + 2 != dot || dot + 1 == end) {
 909             throw new NumberFormatException("'" + buffer + "' has wrong format");
 910         }
 911         return Double.parseDouble(buffer.substring(start, end));
 912     }
 913 
 914     //
 915     //Private help functions
 916     //
 917     private void cloneDate(DateTimeData finalValue, DateTimeData tempDate) {
 918         tempDate.year = finalValue.year;
 919         tempDate.month = finalValue.month;
 920         tempDate.day = finalValue.day;
 921         tempDate.hour = finalValue.hour;
 922         tempDate.minute = finalValue.minute;
 923         tempDate.second = finalValue.second;
 924         tempDate.utc = finalValue.utc;
 925         tempDate.timezoneHr = finalValue.timezoneHr;
 926         tempDate.timezoneMin = finalValue.timezoneMin;
 927     }
 928 
 929     /**
 930      * Represents date time data
 931      */
 932     static final class DateTimeData implements XSDateTime {
 933 
 934         int year, month, day, hour, minute, utc;
 935         double second;
 936         int timezoneHr, timezoneMin;
 937         private String originalValue;
 938         boolean normalized = true;
 939         int unNormYear;
 940         int unNormMonth;
 941         int unNormDay;
 942         int unNormHour;
 943         int unNormMinute;
 944         double unNormSecond;
 945         // used for comparisons - to decide the 'interesting' portions of
 946         // a date/time based data type.
 947         int position;
 948         // a pointer to the type that was used go generate this data
 949         // note that this is not the actual simple type, but one of the
 950         // statically created XXXDV objects, so this won't cause any GC problem.
 951         final AbstractDateTimeDV type;
 952         private volatile String canonical;
 953 
 954         public DateTimeData(String originalValue, AbstractDateTimeDV type) {
 955             this.originalValue = originalValue;
 956             this.type = type;
 957         }
 958 
 959         public DateTimeData(int year, int month, int day, int hour, int minute,
 960                 double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) {
 961             this.year = year;
 962             this.month = month;
 963             this.day = day;
 964             this.hour = hour;
 965             this.minute = minute;
 966             this.second = second;
 967             this.utc = utc;
 968             this.type = type;
 969             this.originalValue = originalValue;
 970         }
 971 
 972         @Override
 973         public boolean equals(Object obj) {
 974             if (!(obj instanceof DateTimeData)) {
 975                 return false;
 976             }
 977             return type.compareDates(this, (DateTimeData) obj, true) == 0;
 978         }
 979 
 980         // If two DateTimeData are equals - then they should have the same
 981         // hashcode. This means we need to convert the date to UTC before
 982         // we return its hashcode.
 983         // The DateTimeData is unfortunately mutable - so we cannot
 984         // cache the result of the conversion...
 985         //
 986         @Override
 987         public int hashCode() {
 988             final DateTimeData tempDate = new DateTimeData(null, type);
 989             type.cloneDate(this, tempDate);
 990             type.normalize(tempDate);
 991             return type.dateToString(tempDate).hashCode();
 992         }
 993 
 994         @Override
 995         public String toString() {
 996             if (canonical == null) {
 997                 canonical = type.dateToString(this);
 998             }
 999             return canonical;
1000         }
1001         /* (non-Javadoc)
1002          * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear()
1003          */
1004 
1005         @Override
1006         public int getYears() {
1007             if (type instanceof DurationDV) {
1008                 return 0;
1009             }
1010             return normalized ? year : unNormYear;
1011         }
1012         /* (non-Javadoc)
1013          * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth()
1014          */
1015 
1016         @Override
1017         public int getMonths() {
1018             if (type instanceof DurationDV) {
1019                 return year * 12 + month;
1020             }
1021             return normalized ? month : unNormMonth;
1022         }
1023         /* (non-Javadoc)
1024          * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay()
1025          */
1026 
1027         @Override
1028         public int getDays() {
1029             if (type instanceof DurationDV) {
1030                 return 0;
1031             }
1032             return normalized ? day : unNormDay;
1033         }
1034         /* (non-Javadoc)
1035          * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour()
1036          */
1037 
1038         @Override
1039         public int getHours() {
1040             if (type instanceof DurationDV) {
1041                 return 0;
1042             }
1043             return normalized ? hour : unNormHour;
1044         }
1045         /* (non-Javadoc)
1046          * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes()
1047          */
1048 
1049         @Override
1050         public int getMinutes() {
1051             if (type instanceof DurationDV) {
1052                 return 0;
1053             }
1054             return normalized ? minute : unNormMinute;
1055         }
1056         /* (non-Javadoc)
1057          * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds()
1058          */
1059 
1060         @Override
1061         public double getSeconds() {
1062             if (type instanceof DurationDV) {
1063                 return day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second;
1064             }
1065             return normalized ? second : unNormSecond;
1066         }
1067         /* (non-Javadoc)
1068          * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone()
1069          */
1070 
1071         @Override
1072         public boolean hasTimeZone() {
1073             return utc != 0;
1074         }
1075         /* (non-Javadoc)
1076          * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours()
1077          */
1078 
1079         @Override
1080         public int getTimeZoneHours() {
1081             return timezoneHr;
1082         }
1083         /* (non-Javadoc)
1084          * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes()
1085          */
1086 
1087         @Override
1088         public int getTimeZoneMinutes() {
1089             return timezoneMin;
1090         }
1091         /* (non-Javadoc)
1092          * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue()
1093          */
1094 
1095         @Override
1096         public String getLexicalValue() {
1097             return originalValue;
1098         }
1099         /* (non-Javadoc)
1100          * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize()
1101          */
1102 
1103         @Override
1104         public XSDateTime normalize() {
1105             if (!normalized) {
1106                 DateTimeData dt = (DateTimeData) this.clone();
1107                 dt.normalized = true;
1108                 return dt;
1109             }
1110             return this;
1111         }
1112         /* (non-Javadoc)
1113          * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized()
1114          */
1115 
1116         @Override
1117         public boolean isNormalized() {
1118             return normalized;
1119         }
1120 
1121         @Override
1122         public Object clone() {
1123             DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour,
1124                     this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type);
1125             dt.canonical = this.canonical;
1126             dt.position = position;
1127             dt.timezoneHr = this.timezoneHr;
1128             dt.timezoneMin = this.timezoneMin;
1129             dt.unNormYear = this.unNormYear;
1130             dt.unNormMonth = this.unNormMonth;
1131             dt.unNormDay = this.unNormDay;
1132             dt.unNormHour = this.unNormHour;
1133             dt.unNormMinute = this.unNormMinute;
1134             dt.unNormSecond = this.unNormSecond;
1135             return dt;
1136         }
1137 
1138         /* (non-Javadoc)
1139          * @see org.apache.xerces.xs.datatypes.XSDateTime#getXMLGregorianCalendar()
1140          */
1141         @Override
1142         public XMLGregorianCalendar getXMLGregorianCalendar() {
1143             return type.getXMLGregorianCalendar(this);
1144         }
1145         /* (non-Javadoc)
1146          * @see org.apache.xerces.xs.datatypes.XSDateTime#getDuration()
1147          */
1148 
1149         @Override
1150         public Duration getDuration() {
1151             return type.getDuration(this);
1152         }
1153     }
1154 
1155     protected XMLGregorianCalendar getXMLGregorianCalendar(DateTimeData data) {
1156         return null;
1157     }
1158 
1159     protected Duration getDuration(DateTimeData data) {
1160         return null;
1161     }
1162 
1163     protected final BigDecimal getFractionalSecondsAsBigDecimal(DateTimeData data) {
1164         final StringBuffer buf = new StringBuffer();
1165         append3(buf, data.unNormSecond);
1166         String value = buf.toString();
1167         final int index = value.indexOf('.');
1168         if (index == -1) {
1169             return null;
1170         }
1171         value = value.substring(index);
1172         final BigDecimal _val = new BigDecimal(value);
1173         if (_val.compareTo(BigDecimal.valueOf(0)) == 0) {
1174             return null;
1175         }
1176         return _val;
1177     }
1178 }