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 }