jaxp/src/com/sun/org/apache/xerces/internal/impl/dv/xs/AbstractDateTimeDV.java

Print this page




  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 
  57         //define shared variables for date/time
  58 
  59 
  60         //define constants to be used in assigning default values for
  61         //all date/time excluding duration
  62         protected final static int YEAR=2000;
  63         protected final static int MONTH=01;
  64         protected final static int DAY = 01;
  65 
  66     protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl();
  67 
  68         public short getAllowedFacets(){
  69                 return ( XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION |XSSimpleTypeDecl.FACET_MAXINCLUSIVE |XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE  | XSSimpleTypeDecl.FACET_MINEXCLUSIVE  );

  70         }//getAllowedFacets()
  71 
  72 
  73         // distinguishes between identity and equality for date/time values
  74         // ie: two values representing the same "moment in time" but with different
  75         // remembered timezones are now equal but not identical.
  76         public boolean isIdentical (Object value1, Object value2) {

  77                 if (!(value1 instanceof DateTimeData) || !(value2 instanceof DateTimeData)) {
  78                         return false;
  79                 }
  80 
  81                 DateTimeData v1 = (DateTimeData)value1;
  82                 DateTimeData v2 = (DateTimeData)value2;
  83 
  84                 // original timezones must be the same in addition to date/time values
  85                 // being 'equal'
  86                 if ((v1.timezoneHr == v2.timezoneHr) && (v1.timezoneMin == v2.timezoneMin)) {
  87                         return v1.equals(v2);
  88                 }
  89 
  90                 return false;
  91         }//isIdentical()
  92 
  93         // the parameters are in compiled form (from getActualValue)
  94         public int compare (Object value1, Object value2) {
  95                 return compareDates(((DateTimeData)value1),
  96                                 ((DateTimeData)value2), true);

  97         }//compare()
  98 
  99         /**
 100          * Compare algorithm described in dateDime (3.2.7).
 101          * Duration datatype overwrites this method
 102          *
 103          * @param date1  normalized date representation of the first value
 104          * @param date2  normalized date representation of the second value
 105          * @param strict
 106          * @return less, greater, less_equal, greater_equal, equal
 107          */
 108         protected short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) {
 109                 if (date1.utc == date2.utc) {
 110                         return compareOrder(date1, date2);
 111                 }
 112                 short c1, c2;
 113 
 114                 DateTimeData tempDate = new DateTimeData(null, this);
 115 
 116                 if ( date1.utc=='Z' ) {
 117 
 118                         //compare date1<=date1<=(date2 with time zone -14)
 119                         //
 120                         cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate
 121                         tempDate.timezoneHr=14;
 122                         tempDate.timezoneMin = 0;
 123                         tempDate.utc='+';
 124                         normalize(tempDate);
 125                         c1 = compareOrder(date1, tempDate);
 126                         if (c1 == LESS_THAN)
 127                                 return c1;

 128 
 129                         //compare date1>=(date2 with time zone +14)
 130                         //
 131                         cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate
 132                         tempDate.timezoneHr = -14;
 133                         tempDate.timezoneMin = 0;
 134                         tempDate.utc='-';
 135                         normalize(tempDate);
 136                         c2 = compareOrder(date1, tempDate);
 137                         if (c2 == GREATER_THAN)
 138                                 return c2;

 139 
 140                         return INDETERMINATE;
 141                 }
 142                 else if ( date2.utc=='Z' ) {
 143 
 144                         //compare (date1 with time zone -14)<=date2
 145                         //
 146                         cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
 147                         tempDate.timezoneHr = -14;
 148                         tempDate.timezoneMin = 0;
 149                         tempDate.utc='-';
 150                         if (DEBUG) {
 151                                 System.out.println("tempDate=" + dateToString(tempDate));
 152                         }
 153                         normalize(tempDate);
 154                         c1 = compareOrder(tempDate, date2);
 155                         if (DEBUG) {
 156                                 System.out.println("date=" + dateToString(date2));
 157                                 System.out.println("tempDate=" + dateToString(tempDate));
 158                         }
 159                         if (c1 == LESS_THAN)
 160                                 return c1;

 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                         return INDETERMINATE;
 177                 }
 178                 return INDETERMINATE;
 179 
 180         }
 181 
 182         /**
 183          * Given normalized values, determines order-relation
 184          * between give date/time objects.
 185          *
 186          * @param date1  date/time object
 187          * @param date2  date/time object
 188          * @return 0 if date1 and date2 are equal, a value less than 0 if date1 is less than date2, a value greater than 0 if date1 is greater than date2

 189          */
 190         protected short compareOrder(DateTimeData date1, DateTimeData date2) {
 191                 if(date1.position < 1) {
 192                         if (date1.year < date2.year)
 193                                 return -1;
 194                         if (date1.year > date2.year)

 195                                 return 1;
 196                 }
 197                 if(date1.position < 2) {
 198                         if (date1.month < date2.month)

 199                                 return -1;
 200                         if (date1.month > date2.month)

 201                                 return 1;
 202                 }
 203                 if (date1.day < date2.day)

 204                         return -1;
 205                 if (date1.day > date2.day)

 206                         return 1;
 207                 if (date1.hour < date2.hour)

 208                         return -1;
 209                 if (date1.hour > date2.hour)

 210                         return 1;
 211                 if (date1.minute < date2.minute)

 212                         return -1;
 213                 if (date1.minute > date2.minute)

 214                         return 1;
 215                 if (date1.second < date2.second)

 216                         return -1;
 217                 if (date1.second > date2.second)

 218                         return 1;
 219                 if (date1.utc < date2.utc)

 220                         return -1;
 221                 if (date1.utc > date2.utc)

 222                         return 1;

 223                 return 0;
 224         }
 225 
 226         /**
 227          * Parses time hh:mm:ss.sss and time zone if any
 228          *
 229          * @param start
 230          * @param end
 231          * @param data
 232          * @exception RuntimeException
 233          */
 234         protected  void getTime (String buffer, int start, int end, DateTimeData data) throws RuntimeException{
 235 
 236                 int stop = start+2;
 237 
 238                 //get hours (hh)
 239                 data.hour=parseInt(buffer, start,stop);
 240 
 241                 //get minutes (mm)
 242 
 243                 if (buffer.charAt(stop++)!=':') {
 244                         throw new RuntimeException("Error in parsing time zone" );
 245                 }
 246                 start = stop;
 247                 stop = stop+2;
 248                 data.minute=parseInt(buffer, start,stop);
 249 
 250                 //get seconds (ss)
 251                 if (buffer.charAt(stop++)!=':') {
 252                         throw new RuntimeException("Error in parsing time zone" );
 253                 }
 254 
 255                 //find UTC sign if any
 256                 int sign = findUTCSign(buffer, start, end);
 257 
 258                 //get seconds (ms)
 259                 start = stop;
 260                 stop = sign < 0 ? end : sign;
 261                 data.second = parseSecond(buffer, start, stop);
 262 
 263                 //parse UTC time zone (hh:mm)
 264                 if (sign > 0) {
 265                         getTimeZone(buffer, data, sign, end);
 266                 }
 267         }
 268 
 269         /**
 270          * Parses date CCYY-MM-DD
 271          *
 272          * @param buffer
 273          * @param start start position
 274          * @param end end position
 275          * @param date
 276          * @exception RuntimeException
 277          */
 278         protected int getDate (String buffer, int start, int end, DateTimeData date) throws RuntimeException{
 279 
 280                 start = getYearMonth(buffer, start, end, date);
 281 
 282                 if (buffer.charAt(start++) !='-') {
 283                         throw new RuntimeException("CCYY-MM must be followed by '-' sign");
 284                 }
 285                 int stop = start + 2;
 286                 date.day=parseInt(buffer, start, stop);
 287                 return stop;
 288         }
 289 
 290         /**
 291          * Parses date CCYY-MM
 292          *
 293          * @param buffer
 294          * @param start start position
 295          * @param end end position
 296          * @param date
 297          * @exception RuntimeException
 298          */
 299         protected int getYearMonth (String buffer, int start, int end, DateTimeData date) throws RuntimeException{
 300 
 301                 if ( buffer.charAt(0)=='-' ) {
 302                         // REVISIT: date starts with preceding '-' sign
 303                         //          do we have to do anything with it?
 304                         //
 305                         start++;
 306                 }
 307                 int i = indexOf(buffer, start, end, '-');
 308                 if ( i==-1 ) throw new RuntimeException("Year separator is missing or misplaced");
 309                 int length = i-start;
 310                 if (length<4) {
 311                         throw new RuntimeException("Year must have 'CCYY' format");
 312                 }
 313                 else if (length > 4 && buffer.charAt(start)=='0'){



 314                         throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
 315                 }
 316                 date.year= parseIntYear(buffer, i);
 317                 if (buffer.charAt(i)!='-') {
 318                         throw new RuntimeException("CCYY must be followed by '-' sign");
 319                 }
 320                 start = ++i;
 321                 i = start +2;
 322                 date.month=parseInt(buffer, start, i);
 323                 return i; //fStart points right after the MONTH
 324         }
 325 
 326         /**
 327          * Shared code from Date and YearMonth datatypes.
 328          * Finds if time zone sign is present
 329          *
 330          * @param end
 331          * @param date
 332          * @exception RuntimeException
 333          */
 334         protected void parseTimeZone (String buffer, int start, int end, DateTimeData date) throws RuntimeException{
 335 
 336                 //fStart points right after the date
 337 
 338                 if ( start < end ) {
 339                         if (!isNextCharUTCSign(buffer, start, end)) {
 340                                 throw new RuntimeException ("Error in month parsing");
 341                         }
 342                         else {
 343                                 getTimeZone(buffer, date, start, end);
 344                         }
 345                 }
 346         }
 347 
 348         /**
 349          * Parses time zone: 'Z' or {+,-} followed by  hh:mm
 350          *
 351          * @param data
 352          * @param sign
 353          * @exception RuntimeException
 354          */
 355         protected void getTimeZone (String buffer, DateTimeData data, int sign, int end) throws RuntimeException{
 356                 data.utc=buffer.charAt(sign);
 357 
 358                 if ( buffer.charAt(sign) == 'Z' ) {
 359                         if (end>(++sign)) {
 360                                 throw new RuntimeException("Error in parsing time zone");
 361                         }
 362                         return;
 363                 }
 364                 if ( sign<=(end-6) ) {
 365 
 366                         int negate = buffer.charAt(sign) == '-'?-1:1;
 367                         //parse hr
 368                         int stop = ++sign+2;
 369                         data.timezoneHr = negate*parseInt(buffer, sign, stop);
 370                         if (buffer.charAt(stop++)!=':') {
 371                                 throw new RuntimeException("Error in parsing time zone" );
 372                         }
 373 
 374                         //parse min
 375                         data.timezoneMin = negate*parseInt(buffer, stop, stop+2);
 376 
 377                         if ( stop+2!=end ) {
 378                                 throw new RuntimeException("Error in parsing time zone");
 379                         }
 380             if(data.timezoneHr != 0 || data.timezoneMin != 0)
 381                 data.normalized = false;
 382                 }
 383                 else {
 384                         throw new RuntimeException("Error in parsing time zone");
 385                 }
 386                 if ( DEBUG ) {
 387                         System.out.println("time[hh]="+data.timezoneHr + " time[mm]=" +data.timezoneMin);
 388                 }
 389         }
 390 
 391         /**
 392          * Computes index of given char within StringBuffer
 393          *
 394          * @param start
 395          * @param end
 396          * @param ch     character to look for in StringBuffer
 397          * @return index of ch within StringBuffer
 398          */
 399         protected  int indexOf (String buffer, int start, int end, char ch) {
 400                 for ( int i=start;i<end;i++ ) {
 401                         if ( buffer.charAt(i) == ch ) {
 402                                 return i;
 403                         }
 404                 }
 405                 return -1;
 406         }
 407 
 408         /**
 409          * Validates given date/time object accoring to W3C PR Schema
 410          * [D.1 ISO 8601 Conventions]
 411          *
 412          * @param data
 413          */
 414         protected void validateDateTime (DateTimeData data) {
 415 
 416                 //REVISIT: should we throw an exception for not valid dates
 417                 //          or reporting an error message should be sufficient?
 418 
 419                 /**
 420                  * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types.
 421                  */
 422                 if (!Constants.SCHEMA_1_1_SUPPORT && data.year==0 ) {
 423                         throw new RuntimeException("The year \"0000\" is an illegal year value");
 424 
 425                 }
 426 
 427                 if ( data.month<1 || data.month>12 ) {
 428                         throw new RuntimeException("The month must have values 1 to 12");
 429 
 430                 }
 431 
 432                 //validate days
 433                 if ( data.day>maxDayInMonthFor(data.year, data.month) || data.day<1 ) {
 434                         throw new RuntimeException("The day must have values 1 to 31");
 435                 }
 436 
 437                 //validate hours
 438                 if ( data.hour>23 || data.hour<0 ) {
 439                         if (data.hour == 24 && data.minute == 0 && data.second == 0) {
 440                                 data.hour = 0;
 441                                 if (++data.day > maxDayInMonthFor(data.year, data.month)) {
 442                                         data.day = 1;
 443                                         if (++data.month > 12) {
 444                                                 data.month = 1;
 445                                                 if (Constants.SCHEMA_1_1_SUPPORT) {
 446                                                         ++data.year;
 447                                                 }
 448                                                 else if (++data.year == 0) {
 449                                                         data.year = 1;
 450                                                 }
 451                                         }
 452                                 }
 453                         }
 454                         else {
 455                                 throw new RuntimeException("Hour must have values 0-23, unless 24:00:00");
 456                         }
 457                 }
 458 
 459                 //validate
 460                 if ( data.minute>59 || data.minute<0 ) {
 461                         throw new RuntimeException("Minute must have values 0-59");
 462                 }
 463 
 464                 //validate
 465                 if ( data.second>=60 || data.second<0 ) {
 466                         throw new RuntimeException("Second must have values 0-59");
 467 
 468                 }
 469 
 470                 //validate
 471                 if ( data.timezoneHr>14 || data.timezoneHr<-14 ) {
 472                         throw new RuntimeException("Time zone should have range -14:00 to +14:00");
 473                 }
 474                 else {
 475                         if((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0)
 476                                 throw new RuntimeException("Time zone should have range -14:00 to +14:00");
 477                         else if(data.timezoneMin > 59 || data.timezoneMin < -59)
 478                                 throw new RuntimeException("Minute must have values 0-59");
 479                 }

 480 
 481         }
 482 
 483         /**
 484          * Return index of UTC char: 'Z', '+', '-'
 485          *
 486          * @param start
 487          * @param end
 488          * @return index of the UTC character that was found
 489          */
 490         protected int findUTCSign (String buffer, int start, int end) {
 491                 int c;
 492                 for ( int i=start;i<end;i++ ) {
 493                         c=buffer.charAt(i);
 494                         if ( c == 'Z' || c=='+' || c=='-' ) {
 495                                 return i;
 496                         }
 497 
 498                 }
 499                 return -1;
 500         }
 501 
 502     /**
 503      * Returns <code>true</code> if the character at start is 'Z', '+' or '-'.

 504      */
 505     protected final boolean isNextCharUTCSign(String buffer, int start, int end) {
 506         if (start < end) {
 507             char c = buffer.charAt(start);
 508             return (c == 'Z' || c == '+' || c == '-');
 509         }
 510         return false;
 511     }
 512 
 513         /**
 514          * Given start and end position, parses string value
 515          *
 516          * @param buffer string to parse
 517          * @param start  start position
 518          * @param end    end position
 519          * @return  return integer representation of characters
 520          */
 521         protected  int parseInt (String buffer, int start, int end)
 522         throws NumberFormatException{
 523                 //REVISIT: more testing on this parsing needs to be done.
 524                 int radix=10;
 525                 int result = 0;
 526                 int digit=0;
 527                 int limit = -Integer.MAX_VALUE;
 528                 int multmin = limit / radix;
 529                 int i = start;
 530                 do {
 531                         digit = getDigit(buffer.charAt(i));
 532                         if ( digit < 0 ) throw new NumberFormatException("'" + buffer + "' has wrong format");
 533                         if ( result < multmin ) throw new NumberFormatException("'" + buffer + "' has wrong format");




 534                         result *= radix;
 535                         if ( result < limit + digit ) throw new NumberFormatException("'" + buffer + "' has wrong format");


 536                         result -= digit;
 537 
 538                 }while ( ++i < end );
 539                 return -result;
 540         }
 541 
 542         // parse Year differently to support negative value.
 543         protected int parseIntYear (String buffer, int end){
 544                 int radix=10;
 545                 int result = 0;
 546                 boolean negative = false;
 547                 int i=0;
 548                 int limit;
 549                 int multmin;
 550                 int digit=0;
 551 
 552                 if (buffer.charAt(0) == '-'){
 553                         negative = true;
 554                         limit = Integer.MIN_VALUE;
 555                         i++;
 556 
 557                 }
 558                 else{
 559                         limit = -Integer.MAX_VALUE;
 560                 }
 561                 multmin = limit / radix;
 562                 while (i < end)
 563                 {
 564                         digit = getDigit(buffer.charAt(i++));
 565                         if (digit < 0) throw new NumberFormatException("'" + buffer + "' has wrong format");
 566                         if (result < multmin) throw new NumberFormatException("'" + buffer + "' has wrong format");




 567                         result *= radix;
 568                         if (result < limit + digit) throw new NumberFormatException("'" + buffer + "' has wrong format");


 569                         result -= digit;
 570                 }
 571 
 572                 if (negative)
 573                 {
 574                         if (i > 1) return result;
 575                         else throw new NumberFormatException("'" + buffer + "' has wrong format");


 576                 }
 577                 return -result;
 578 
 579         }
 580 
 581         /**
 582          * If timezone present - normalize dateTime  [E Adding durations to dateTimes]

 583          *
 584          * @param date   CCYY-MM-DDThh:mm:ss+03
 585          */
 586         protected void normalize(DateTimeData date) {
 587 
 588                 // REVISIT: we have common code in addDuration() for durations
 589                 //          should consider reorganizing it.
 590                 //
 591 
 592                 //add minutes (from time zone)
 593                 int negate = -1;
 594 
 595                 if ( DEBUG ) {
 596                         System.out.println("==>date.minute"+date.minute);
 597                         System.out.println("==>date.timezoneMin" +date.timezoneMin);
 598                 }
 599                 int temp = date.minute + negate * date.timezoneMin;
 600                 int carry = fQuotient (temp, 60);
 601                 date.minute= mod(temp, 60, carry);
 602 
 603                 if ( DEBUG ) {
 604                         System.out.println("==>carry: " + carry);
 605                 }
 606                 //add hours
 607                 temp = date.hour + negate * date.timezoneHr + carry;
 608                 carry = fQuotient(temp, 24);
 609                 date.hour=mod(temp, 24, carry);
 610                 if ( DEBUG ) {
 611                         System.out.println("==>date.hour"+date.hour);
 612                         System.out.println("==>carry: " + carry);
 613                 }
 614 
 615                 date.day=date.day+carry;
 616 
 617                 while ( true ) {
 618                         temp=maxDayInMonthFor(date.year, date.month);
 619                         if (date.day<1) {
 620                                 date.day = date.day + maxDayInMonthFor(date.year, date.month-1);
 621                                 carry=-1;
 622                         }
 623                         else if ( date.day>temp ) {
 624                                 date.day=date.day-temp;
 625                                 carry=1;
 626                         }
 627                         else {
 628                                 break;
 629                         }
 630                         temp=date.month+carry;
 631                         date.month=modulo(temp, 1, 13);
 632                         date.year=date.year+fQuotient(temp, 1, 13);
 633             if(date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) {
 634                 date.year = (date.timezoneHr < 0 || date.timezoneMin < 0)?1:-1;
 635             }
 636                 }
 637                 date.utc='Z';
 638         }
 639 
 640 
 641         /**
 642      * @param date
 643      */
 644     protected void saveUnnormalized(DateTimeData date) {
 645         date.unNormYear = date.year;
 646         date.unNormMonth = date.month;
 647         date.unNormDay = date.day;
 648         date.unNormHour = date.hour;
 649         date.unNormMinute = date.minute;
 650         date.unNormSecond = date.second;
 651     }
 652 
 653     /**
 654          * Resets object representation of date/time
 655          *
 656          * @param data   date/time object
 657          */
 658         protected void resetDateObj(DateTimeData data) {
 659                 data.year = 0;
 660                 data.month = 0;
 661                 data.day = 0;
 662                 data.hour = 0;
 663                 data.minute = 0;
 664                 data.second = 0;
 665                 data.utc = 0;
 666                 data.timezoneHr = 0;
 667                 data.timezoneMin = 0;
 668         }
 669 
 670         /**
 671          * Given {year,month} computes maximum
 672          * number of days for given month
 673          *
 674          * @param year
 675          * @param month
 676          * @return integer containg the number of days in a given month
 677          */
 678         protected int maxDayInMonthFor(int year, int month) {
 679                 //validate days
 680                 if ( month==4 || month==6 || month==9 || month==11 ) {
 681                         return 30;
 682                 }
 683                 else if ( month==2 ) {
 684                         if ( isLeapYear(year) ) {
 685                                 return 29;
 686                         }
 687                         else {
 688                                 return 28;
 689                         }
 690                 }
 691                 else {
 692                         return 31;
 693                 }
 694         }
 695 
 696         private boolean isLeapYear(int year) {
 697 
 698                 //REVISIT: should we take care about Julian calendar?
 699                 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
 700         }
 701 
 702         //
 703         // help function described in W3C PR Schema [E Adding durations to dateTimes]
 704         //
 705         protected int mod (int a, int b, int quotient) {
 706                 //modulo(a, b) = a - fQuotient(a,b)*b
 707                 return (a - quotient*b) ;
 708         }
 709 
 710         //
 711         // help function described in W3C PR Schema [E Adding durations to dateTimes]
 712         //
 713         protected int fQuotient (int a, int b) {
 714 
 715                 //fQuotient(a, b) = the greatest integer less than or equal to a/b
 716                 return (int)Math.floor((float)a/b);
 717         }
 718 
 719         //
 720         // help function described in W3C PR Schema [E Adding durations to dateTimes]
 721         //
 722         protected int modulo (int temp, int low, int high) {
 723                 //modulo(a - low, high - low) + low
 724                 int a = temp - low;
 725                 int b = high - low;
 726                 return (mod (a, b, fQuotient(a, b)) + low) ;
 727         }
 728 
 729         //
 730         // help function described in W3C PR Schema [E Adding durations to dateTimes]
 731         //
 732         protected int fQuotient (int temp, int low, int high) {
 733                 //fQuotient(a - low, high - low)
 734 
 735                 return fQuotient(temp - low, high - low);
 736         }
 737 
 738 
 739         protected String dateToString(DateTimeData date) {
 740                 StringBuffer message = new StringBuffer(25);
 741                 append(message, date.year, 4);
 742                 message.append('-');
 743                 append(message, date.month, 2);
 744                 message.append('-');
 745                 append(message, date.day, 2);
 746                 message.append('T');
 747                 append(message, date.hour, 2);
 748                 message.append(':');
 749                 append(message, date.minute, 2);
 750                 message.append(':');
 751                 append(message, date.second);
 752                 append(message, (char)date.utc, 0);
 753                 return message.toString();
 754         }
 755 
 756         protected final void append(StringBuffer message, int value, int nch) {
 757         if (value == Integer.MIN_VALUE) {
 758             message.append(value);
 759             return;
 760         }
 761                 if (value < 0) {
 762                         message.append('-');
 763                         value = -value;
 764                 }
 765                 if (nch == 4) {
 766                         if (value < 10)
 767                                 message.append("000");
 768                         else if (value < 100)
 769                                 message.append("00");
 770                         else if (value < 1000)
 771                                 message.append('0');
 772                         message.append(value);
 773                 }
 774                 else if (nch == 2) {
 775                         if (value < 10)

 776                                 message.append('0');

 777                         message.append(value);



 778                 }
 779                 else {
 780                         if (value != 0)
 781                                 message.append((char)value);
 782                 }
 783         }
 784 
 785         protected final void append(StringBuffer message, double value) {
 786             if (value < 0) {
 787                 message.append('-');
 788                 value = -value;
 789             }
 790             if (value < 10) {
 791                 message.append('0');
 792             }
 793             append2(message, value);
 794         }
 795 
 796     protected final void append2(StringBuffer message, double value) {
 797         final int intValue = (int) value;
 798         if (value == intValue) {
 799             message.append(intValue);
 800         }
 801         else {
 802             append3(message, value);
 803         }
 804     }
 805 
 806     private void append3(StringBuffer message, double value) {
 807         String d = String.valueOf(value);
 808         int eIndex = d.indexOf('E');
 809         if (eIndex == -1) {
 810             message.append(d);
 811             return;
 812         }
 813         int exp;
 814         if (value < 1) {
 815             // Need to convert from scientific notation of the form
 816             // n.nnn...E-N (N >= 4) to a normal decimal value.
 817             try {
 818                 exp = parseInt(d, eIndex+2, d.length());
 819             }
 820             // This should never happen.
 821             // It's only possible if String.valueOf(double) is broken.
 822             catch (Exception e) {
 823                 message.append(d);
 824                 return;
 825             }
 826             message.append("0.");
 827             for (int i = 1; i < exp; ++i) {
 828                 message.append('0');
 829             }
 830             // Remove trailing zeros.
 831             int end = eIndex - 1;
 832             while (end > 0) {
 833                 char c = d.charAt(end);
 834                 if (c != '0') {
 835                     break;
 836                 }
 837                 --end;
 838             }
 839             // Now append the digits to the end. Skip over the decimal point.
 840             for (int i = 0; i <= end; ++i) {
 841                 char c = d.charAt(i);
 842                 if (c != '.') {
 843                     message.append(c);
 844                 }
 845             }
 846         }
 847         else {
 848             // Need to convert from scientific notation of the form
 849             // n.nnn...EN (N >= 7) to a normal decimal value.
 850             try {
 851                 exp = parseInt(d, eIndex+1, d.length());
 852             }
 853             // This should never happen.
 854             // It's only possible if String.valueOf(double) is broken.
 855             catch (Exception e) {
 856                 message.append(d);
 857                 return;
 858             }
 859             final int integerEnd = exp + 2;
 860             for (int i = 0; i < eIndex; ++i) {
 861                 char c = d.charAt(i);
 862                 if (c != '.') {
 863                     if (i == integerEnd) {
 864                         message.append('.');
 865                     }
 866                     message.append(c);
 867                 }
 868             }
 869             // Append trailing zeroes if necessary.
 870             for (int i = integerEnd - eIndex; i > 0; --i) {
 871                 message.append('0');
 872             }
 873         }
 874     }
 875 
 876         protected double parseSecond(String buffer, int start, int end)
 877         throws NumberFormatException {
 878                 int dot = -1;
 879                 for (int i = start; i < end; i++) {
 880                         char ch = buffer.charAt(i);
 881                         if (ch == '.')
 882                                 dot = i;
 883                         else if (ch > '9' || ch < '0')
 884                                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 885                 }

 886                 if (dot == -1) {
 887                         if (start+2 != end)
 888                                 throw new NumberFormatException("'" + buffer + "' has wrong format");
 889                 }
 890                 else if (start+2 != dot || dot+1 == end) {
 891                         throw new NumberFormatException("'" + buffer + "' has wrong format");
 892                 }
 893                 return Double.parseDouble(buffer.substring(start, end));
 894         }
 895 
 896         //
 897         //Private help functions
 898         //
 899 
 900         private void cloneDate (DateTimeData finalValue, DateTimeData tempDate) {
 901                 tempDate.year = finalValue.year;
 902                 tempDate.month = finalValue.month;
 903                 tempDate.day = finalValue.day;
 904                 tempDate.hour = finalValue.hour;
 905                 tempDate.minute = finalValue.minute;
 906                 tempDate.second = finalValue.second;
 907                 tempDate.utc = finalValue.utc;
 908                 tempDate.timezoneHr = finalValue.timezoneHr;
 909                 tempDate.timezoneMin = finalValue.timezoneMin;
 910         }
 911 
 912         /**
 913          * Represents date time data
 914          */
 915         static final class DateTimeData implements XSDateTime {

 916                 int year, month, day, hour, minute, utc;
 917                 double second;
 918                 int timezoneHr, timezoneMin;
 919         private String originalValue;
 920         boolean normalized = true;
 921 
 922         int unNormYear;
 923         int unNormMonth;
 924         int unNormDay;
 925         int unNormHour;
 926         int unNormMinute;
 927         double unNormSecond;
 928 
 929                 // used for comparisons - to decide the 'interesting' portions of
 930                 // a date/time based data type.
 931                 int position;
 932                 // a pointer to the type that was used go generate this data
 933                 // note that this is not the actual simple type, but one of the
 934                 // statically created XXXDV objects, so this won't cause any GC problem.
 935                 final AbstractDateTimeDV type;
 936                 private String canonical;

 937                 public DateTimeData(String originalValue, AbstractDateTimeDV type) {
 938             this.originalValue = originalValue;
 939                         this.type = type;
 940                 }

 941                 public DateTimeData(int year, int month, int day, int hour, int minute,
 942                                 double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) {
 943                         this.year = year;
 944                         this.month = month;
 945                         this.day = day;
 946                         this.hour = hour;
 947                         this.minute = minute;
 948                         this.second = second;
 949                         this.utc = utc;
 950                         this.type = type;
 951             this.originalValue = originalValue;
 952                 }


 953                 public boolean equals(Object obj) {
 954                         if (!(obj instanceof DateTimeData))
 955                                 return false;
 956                         return type.compareDates(this, (DateTimeData)obj, true)==0;
 957                 }
 958                 public synchronized String toString() {


















 959                         if (canonical == null) {
 960                                 canonical = type.dateToString(this);
 961                         }
 962                         return canonical;
 963                 }
 964                 /* (non-Javadoc)
 965                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear()
 966                  */


 967                 public int getYears() {
 968             if(type instanceof DurationDV)
 969                 return 0;
 970                         return normalized?year:unNormYear;

 971                 }
 972                 /* (non-Javadoc)
 973                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth()
 974                  */


 975                 public int getMonths() {
 976             if(type instanceof DurationDV) {
 977                 return year*12 + month;
 978             }
 979                         return normalized?month:unNormMonth;
 980                 }
 981                 /* (non-Javadoc)
 982                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay()
 983                  */


 984                 public int getDays() {
 985             if(type instanceof DurationDV)
 986                 return 0;
 987                         return normalized?day:unNormDay;

 988                 }
 989                 /* (non-Javadoc)
 990                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour()
 991                  */


 992                 public int getHours() {
 993             if(type instanceof DurationDV)
 994                 return 0;
 995                         return normalized?hour:unNormHour;

 996                 }
 997                 /* (non-Javadoc)
 998                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes()
 999                  */


1000                 public int getMinutes() {
1001             if(type instanceof DurationDV)
1002                 return 0;
1003                         return normalized?minute:unNormMinute;

1004                 }
1005                 /* (non-Javadoc)
1006                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds()
1007                  */


1008                 public double getSeconds() {
1009             if(type instanceof DurationDV) {
1010                 return day*24*60*60 + hour*60*60 + minute*60 + second;
1011             }
1012                         return normalized?second:unNormSecond;
1013                 }
1014                 /* (non-Javadoc)
1015                  * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone()
1016                  */


1017                 public boolean hasTimeZone() {
1018                         return utc != 0;
1019                 }
1020                 /* (non-Javadoc)
1021                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours()
1022                  */


1023                 public int getTimeZoneHours() {
1024                         return timezoneHr;
1025                 }
1026                 /* (non-Javadoc)
1027                  * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes()
1028                  */


1029                 public int getTimeZoneMinutes() {
1030                         return timezoneMin;
1031                 }
1032         /* (non-Javadoc)
1033          * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue()
1034          */


1035         public String getLexicalValue() {
1036             return originalValue;
1037         }
1038         /* (non-Javadoc)
1039          * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize()
1040          */


1041         public XSDateTime normalize() {
1042             if(!normalized) {
1043                 DateTimeData dt = (DateTimeData)this.clone();
1044                 dt.normalized = true;
1045                 return dt;
1046             }
1047             return this;
1048         }
1049         /* (non-Javadoc)
1050          * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized()
1051          */


1052         public boolean isNormalized() {
1053             return normalized;
1054         }
1055 

1056         public Object clone() {
1057             DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour,
1058                         this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type);
1059             dt.canonical = this.canonical;
1060             dt.position = position;
1061             dt.timezoneHr = this.timezoneHr;
1062             dt.timezoneMin = this.timezoneMin;
1063             dt.unNormYear = this.unNormYear;
1064             dt.unNormMonth = this.unNormMonth;
1065             dt.unNormDay = this.unNormDay;
1066             dt.unNormHour = this.unNormHour;
1067             dt.unNormMinute = this.unNormMinute;
1068             dt.unNormSecond = this.unNormSecond;
1069             return dt;
1070         }
1071 
1072         /* (non-Javadoc)
1073          * @see org.apache.xerces.xs.datatypes.XSDateTime#getXMLGregorianCalendar()
1074          */

1075         public XMLGregorianCalendar getXMLGregorianCalendar() {
1076             return type.getXMLGregorianCalendar(this);
1077         }
1078         /* (non-Javadoc)
1079          * @see org.apache.xerces.xs.datatypes.XSDateTime#getDuration()
1080          */


1081         public Duration getDuration() {
1082             return type.getDuration(this);
1083         }
1084         }
1085 
1086     protected XMLGregorianCalendar getXMLGregorianCalendar(DateTimeData data) {
1087         return null;
1088     }
1089 
1090     protected Duration getDuration(DateTimeData data) {
1091         return null;
1092     }
1093 
1094     protected final BigDecimal getFractionalSecondsAsBigDecimal(DateTimeData data) {
1095         final StringBuffer buf = new StringBuffer();
1096         append3(buf, data.unNormSecond);
1097         String value = buf.toString();
1098         final int index = value.indexOf('.');
1099         if (index == -1) {
1100             return null;


  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;