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 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; 1101 } 1102 value = value.substring(index); 1103 final BigDecimal _val = new BigDecimal(value); 1104 if (_val.compareTo(BigDecimal.valueOf(0)) == 0) { 1105 return null; 1106 } 1107 return _val; 1108 } 1109 }