- /*
- * The Apache Software License, Version 1.1
- *
- *
- * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- * if any, must include the following acknowledgment:
- * "This product includes software developed by the
- * Apache Software Foundation (http://www.apache.org/)."
- * Alternately, this acknowledgment may appear in the software itself,
- * if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Xerces" and "Apache Software Foundation" must
- * not be used to endorse or promote products derived from this
- * software without prior written permission. For written
- * permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- * nor may "Apache" appear in their name, without prior written
- * permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation and was
- * originally based on software copyright (c) 2001, International
- * Business Machines, Inc., http://www.apache.org. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- */
-
- package com.sun.org.apache.xerces.internal.impl.dv.xs;
-
- /**
- * This is the base class of all date/time datatype validators.
- * It implements common code for parsing, validating and comparing datatypes.
- * Classes that extend this class, must implement parse() method.
- *
- * REVISIT: There are many instance variables, which would cause problems
- * when we support grammar caching. A grammar is possibly used by
- * two parser instances at the same time, then the same simple type
- * decl object can be used to validate two strings at the same time.
- * -SG
- *
- * @author Elena Litani
- * @author Len Berman
- * @author Gopal Sharma, SUN Microsystems Inc.
- *
- * @version $Id: AbstractDateTimeDV.java,v 1.12 2003/06/16 18:15:51 sandygao Exp $
- */
- public abstract class AbstractDateTimeDV extends TypeValidator {
-
- //debugging
- private static final boolean DEBUG=false;
-
- //define shared variables for date/time
-
- //define constants
- protected final static int CY = 0, M = 1, D = 2, h = 3,
- m = 4, s = 5, ms = 6, utc=7, hh=0, mm=1;
-
- //size for all objects must have the same fields:
- //CCYY, MM, DD, h, m, s, ms + timeZone
- protected final static int TOTAL_SIZE = 8;
-
- //define constants to be used in assigning default values for
- //all date/time excluding duration
- protected final static int YEAR=2000;
- protected final static int MONTH=01;
- protected final static int DAY = 15;
-
- public short getAllowedFacets(){
- return ( XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION |XSSimpleTypeDecl.FACET_MAXINCLUSIVE |XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE );
- }//getAllowedFacets()
-
- // the parameters are in compiled form (from getActualValue)
- public int compare (Object value1, Object value2) {
- return compareDates(((DateTimeData)value1).data,
- ((DateTimeData)value2).data, true);
- }//compare()
-
- /**
- * Compare algorithm described in dateDime (3.2.7).
- * Duration datatype overwrites this method
- *
- * @param date1 normalized date representation of the first value
- * @param date2 normalized date representation of the second value
- * @param strict
- * @return less, greater, less_equal, greater_equal, equal
- */
- protected short compareDates(int[] date1, int[] date2, boolean strict) {
- if ( date1[utc]==date2[utc] ) {
- return compareOrder(date1, date2);
- }
- short c1, c2;
-
- int[] tempDate = new int[TOTAL_SIZE];
- int[] timeZone = new int[2];
-
- if ( date1[utc]=='Z' ) {
-
- //compare date1<=date1<=(date2 with time zone -14)
- //
- cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate
- timeZone[hh]=14;
- timeZone[mm]=0;
- tempDate[utc]='+';
- normalize(tempDate, timeZone);
- c1 = compareOrder(date1, tempDate);
- if (c1 == LESS_THAN)
- return c1;
-
- //compare date1>=(date2 with time zone +14)
- //
- cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate
- timeZone[hh]=14;
- timeZone[mm]=0;
- tempDate[utc]='-';
- normalize(tempDate, timeZone);
- c2 = compareOrder(date1, tempDate);
- if (c2 == GREATER_THAN)
- return c2;
-
- return INDETERMINATE;
- }
- else if ( date2[utc]=='Z' ) {
-
- //compare (date1 with time zone -14)<=date2
- //
- cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
- timeZone[hh]=14;
- timeZone[mm]=0;
- tempDate[utc]='-';
- if (DEBUG) {
- System.out.println("tempDate=" + dateToString(tempDate));
- }
- normalize(tempDate, timeZone);
- c1 = compareOrder(tempDate, date2);
- if (DEBUG) {
- System.out.println("date=" + dateToString(date2));
- System.out.println("tempDate=" + dateToString(tempDate));
- }
- if (c1 == LESS_THAN)
- return c1;
-
- //compare (date1 with time zone +14)<=date2
- //
- cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
- timeZone[hh]=14;
- timeZone[mm]=0;
- tempDate[utc]='+';
- normalize(tempDate, timeZone);
- c2 = compareOrder(tempDate, date2);
- if (DEBUG) {
- System.out.println("tempDate=" + dateToString(tempDate));
- }
- if (c2 == GREATER_THAN)
- return c2;
-
- return INDETERMINATE;
- }
- return INDETERMINATE;
-
- }
-
- /**
- * Given normalized values, determines order-relation
- * between give date/time objects.
- *
- * @param date1 date/time object
- * @param date2 date/time object
- * @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
- */
- protected short compareOrder (int[] date1, int[] date2) {
-
- for ( int i=0;i<TOTAL_SIZE;i++ ) {
- if ( date1[i]<date2[i] ) {
- return -1;
- }
- else if ( date1[i]>date2[i] ) {
- return 1;
- }
- }
- return 0;
- }
-
- /**
- * Parses time hh:mm:ss.sss and time zone if any
- *
- * @param start
- * @param end
- * @param data
- * @exception RuntimeException
- */
- protected void getTime (String buffer, int start, int end, int[] data, int[] timeZone) throws RuntimeException{
-
- int stop = start+2;
-
- //get hours (hh)
- data[h]=parseInt(buffer, start,stop);
-
- //get minutes (mm)
-
- if (buffer.charAt(stop++)!=':') {
- throw new RuntimeException("Error in parsing time zone" );
- }
- start = stop;
- stop = stop+2;
- data[m]=parseInt(buffer, start,stop);
-
- //get seconds (ss)
- if (buffer.charAt(stop++)!=':') {
- throw new RuntimeException("Error in parsing time zone" );
- }
- start = stop;
- stop = stop+2;
- data[s]=parseInt(buffer, start,stop);
-
- if (stop == end)
- return;
-
- //get miliseconds (ms)
- start = stop;
- int milisec = buffer.charAt(start) == '.' ? start : -1;
-
- //find UTC sign if any
- int sign = findUTCSign(buffer, start, end);
-
- //parse miliseconds
- if ( milisec != -1 ) {
- // The end of millisecond part is between . and
- // either the end of the UTC sign
- start = sign < 0 ? end : sign;
- data[ms]=parseInt(buffer, milisec+1, start);
- }
-
- //parse UTC time zone (hh:mm)
- if ( sign>0 ) {
- if (start != sign)
- throw new RuntimeException("Error in parsing time zone" );
- getTimeZone(buffer, data, sign, end, timeZone);
- }
- else if (start != end) {
- throw new RuntimeException("Error in parsing time zone" );
- }
- }
-
- /**
- * Parses date CCYY-MM-DD
- *
- * @param start
- * @param end
- * @param data
- * @exception RuntimeException
- */
- protected int getDate (String buffer, int start, int end, int[] date) throws RuntimeException{
-
- start = getYearMonth(buffer, start, end, date);
-
- if (buffer.charAt(start++) !='-') {
- throw new RuntimeException("CCYY-MM must be followed by '-' sign");
- }
- int stop = start + 2;
- date[D]=parseInt(buffer, start, stop);
- return stop;
- }
-
- /**
- * Parses date CCYY-MM
- *
- * @param start
- * @param end
- * @param data
- * @exception RuntimeException
- */
- protected int getYearMonth (String buffer, int start, int end, int[] date) throws RuntimeException{
-
- if ( buffer.charAt(0)=='-' ) {
- // REVISIT: date starts with preceding '-' sign
- // do we have to do anything with it?
- //
- start++;
- }
- int i = indexOf(buffer, start, end, '-');
- if ( i==-1 ) throw new RuntimeException("Year separator is missing or misplaced");
- int length = i-start;
- if (length<4) {
- throw new RuntimeException("Year must have 'CCYY' format");
- }
- else if (length > 4 && buffer.charAt(start)=='0'){
- throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
- }
- date[CY]= parseIntYear(buffer, i);
- if (buffer.charAt(i)!='-') {
- throw new RuntimeException("CCYY must be followed by '-' sign");
- }
- start = ++i;
- i = start +2;
- date[M]=parseInt(buffer, start, i);
- return i; //fStart points right after the MONTH
- }
-
- /**
- * Shared code from Date and YearMonth datatypes.
- * Finds if time zone sign is present
- *
- * @param end
- * @param date
- * @exception RuntimeException
- */
- protected void parseTimeZone (String buffer, int start, int end, int[] date, int[] timeZone) throws RuntimeException{
-
- //fStart points right after the date
-
- if ( start<end ) {
- int sign = findUTCSign(buffer, start, end);
- if ( sign<0 ) {
- throw new RuntimeException ("Error in month parsing");
- }
- else {
- getTimeZone(buffer, date, sign, end, timeZone);
- }
- }
- }
-
- /**
- * Parses time zone: 'Z' or {+,-} followed by hh:mm
- *
- * @param data
- * @param sign
- * @exception RuntimeException
- */
- protected void getTimeZone (String buffer, int[] data, int sign, int end, int[] timeZone) throws RuntimeException{
- data[utc]=buffer.charAt(sign);
-
- if ( buffer.charAt(sign) == 'Z' ) {
- if (end>(++sign)) {
- throw new RuntimeException("Error in parsing time zone");
- }
- return;
- }
- if ( sign<=(end-6) ) {
-
- //parse [hh]
- int stop = ++sign+2;
- timeZone[hh]=parseInt(buffer, sign, stop);
- if (buffer.charAt(stop++)!=':') {
- throw new RuntimeException("Error in parsing time zone" );
- }
-
- //parse [ss]
- timeZone[mm]=parseInt(buffer, stop, stop+2);
-
- if ( stop+2!=end ) {
- throw new RuntimeException("Error in parsing time zone");
- }
-
- }
- else {
- throw new RuntimeException("Error in parsing time zone");
- }
- if ( DEBUG ) {
- System.out.println("time[hh]="+timeZone[hh] + " time[mm]=" +timeZone[mm]);
- }
- }
-
- /**
- * Computes index of given char within StringBuffer
- *
- * @param start
- * @param end
- * @param ch character to look for in StringBuffer
- * @return index of ch within StringBuffer
- */
- protected int indexOf (String buffer, int start, int end, char ch) {
- for ( int i=start;i<end;i++ ) {
- if ( buffer.charAt(i) == ch ) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Validates given date/time object accoring to W3C PR Schema
- * [D.1 ISO 8601 Conventions]
- *
- * @param data
- */
- protected void validateDateTime (int[] data, int[] timeZone) {
-
- //REVISIT: should we throw an exception for not valid dates
- // or reporting an error message should be sufficient?
- if ( data[CY]==0 ) {
- throw new RuntimeException("The year \"0000\" is an illegal year value");
-
- }
-
- if ( data[M]<1 || data[M]>12 ) {
- throw new RuntimeException("The month must have values 1 to 12");
-
- }
-
- //validate days
- if ( data[D]>maxDayInMonthFor(data[CY], data[M]) || data[D]<1 ) {
- throw new RuntimeException("The day must have values 1 to 31");
- }
-
- //validate hours
- if ( data[h]>23 || data[h]<0 ) {
- if (data[h] == 24 && data[m] == 0 && data[s] == 0 && data[ms] == 0) {
- data[h] = 0;
- if (++data[D] > maxDayInMonthFor(data[CY], data[M])) {
- data[D] = 1;
- if (++data[M] > 12) {
- data[M] = 1;
- if (++data[CY] == 0)
- data[CY] = 1;
- }
- }
- }
- else {
- throw new RuntimeException("Hour must have values 0-23, unless 24:00:00");
- }
- }
-
- //validate
- if ( data[m]>59 || data[m]<0 ) {
- throw new RuntimeException("Minute must have values 0-59");
- }
-
- //validate
- if ( data[s]>60 || data[s]<0 ) {
- throw new RuntimeException("Second must have values 0-60");
-
- }
-
- //validate
- if ( timeZone[hh]>14 || timeZone[hh]<-14 ) {
- throw new RuntimeException("Time zone should have range -14..+14");
- }
-
- //validate
- if ( timeZone[mm]>59 || timeZone[mm]<-59 ) {
- throw new RuntimeException("Minute must have values 0-59");
- }
- }
-
- /**
- * Return index of UTC char: 'Z', '+', '-'
- *
- * @param start
- * @param end
- * @return index of the UTC character that was found
- */
- protected int findUTCSign (String buffer, int start, int end) {
- int c;
- for ( int i=start;i<end;i++ ) {
- c=buffer.charAt(i);
- if ( c == 'Z' || c=='+' || c=='-' ) {
- return i;
- }
-
- }
- return -1;
- }
-
- /**
- * Given start and end position, parses string value
- *
- * @param value string to parse
- * @param start Start position
- * @param end end position
- * @return return integer representation of characters
- */
- protected int parseInt (String buffer, int start, int end)
- throws NumberFormatException{
- //REVISIT: more testing on this parsing needs to be done.
- int radix=10;
- int result = 0;
- int digit=0;
- int limit = -Integer.MAX_VALUE;
- int multmin = limit / radix;
- int i = start;
- do {
- digit = getDigit(buffer.charAt(i));
- if ( digit < 0 ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- if ( result < multmin ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- result *= radix;
- if ( result < limit + digit ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- result -= digit;
-
- }while ( ++i < end );
- return -result;
- }
-
- // parse Year differently to support negative value.
- protected int parseIntYear (String buffer, int end){
- int radix=10;
- int result = 0;
- boolean negative = false;
- int i=0;
- int limit;
- int multmin;
- int digit=0;
-
- if (buffer.charAt(0) == '-'){
- negative = true;
- limit = Integer.MIN_VALUE;
- i++;
-
- }
- else{
- limit = -Integer.MAX_VALUE;
- }
- multmin = limit / radix;
- while (i < end)
- {
- digit = getDigit(buffer.charAt(i++));
- if (digit < 0) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- if (result < multmin) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- result *= radix;
- if (result < limit + digit) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- result -= digit;
- }
-
- if (negative)
- {
- if (i > 1) return result;
- else throw new NumberFormatException("'"+buffer.toString()+"' has wrong format");
- }
- return -result;
-
- }
-
- /**
- * If timezone present - normalize dateTime [E Adding durations to dateTimes]
- *
- * @param date CCYY-MM-DDThh:mm:ss+03
- * @return CCYY-MM-DDThh:mm:ssZ
- */
- protected void normalize (int[] date, int[] timeZone) {
-
- // REVISIT: we have common code in addDuration() for durations
- // should consider reorganizing it.
- //
-
- //add minutes (from time zone)
- int negate = 1;
- if (date[utc]=='+') {
- negate = -1;
- }
- if ( DEBUG ) {
- System.out.println("==>date[m]"+date[m]);
- System.out.println("==>timeZone[mm]" +timeZone[mm]);
- }
- int temp = date[m] + negate*timeZone[mm];
- int carry = fQuotient (temp, 60);
- date[m]= mod(temp, 60, carry);
-
- if ( DEBUG ) {
- System.out.println("==>carry: " + carry);
- }
- //add hours
- temp = date[h] + negate*timeZone[hh] + carry;
- carry = fQuotient(temp, 24);
- date[h]=mod(temp, 24, carry);
- if ( DEBUG ) {
- System.out.println("==>date[h]"+date[h]);
- System.out.println("==>carry: " + carry);
- }
-
- date[D]=date[D]+carry;
-
- while ( true ) {
- temp=maxDayInMonthFor(date[CY], date[M]);
- if (date[D]<1) {
- date[D] = date[D] + maxDayInMonthFor(date[CY], date[M]-1);
- carry=-1;
- }
- else if ( date[D]>temp ) {
- date[D]=date[D]-temp;
- carry=1;
- }
- else {
- break;
- }
- temp=date[M]+carry;
- date[M]=modulo(temp, 1, 13);
- date[CY]=date[CY]+fQuotient(temp, 1, 13);
- }
- date[utc]='Z';
- }
-
-
- /**
- * Resets object representation of date/time
- *
- * @param data date/time object
- */
- protected void resetDateObj (int[] data) {
- for ( int i=0;i<TOTAL_SIZE;i++ ) {
- data[i]=0;
- }
- }
-
- /**
- * Given {year,month} computes maximum
- * number of days for given month
- *
- * @param year
- * @param month
- * @return integer containg the number of days in a given month
- */
- protected int maxDayInMonthFor(int year, int month) {
- //validate days
- if ( month==4 || month==6 || month==9 || month==11 ) {
- return 30;
- }
- else if ( month==2 ) {
- if ( isLeapYear(year) ) {
- return 29;
- }
- else {
- return 28;
- }
- }
- else {
- return 31;
- }
- }
-
- private boolean isLeapYear(int year) {
-
- //REVISIT: should we take care about Julian calendar?
- return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
- }
-
- //
- // help function described in W3C PR Schema [E Adding durations to dateTimes]
- //
- protected int mod (int a, int b, int quotient) {
- //modulo(a, b) = a - fQuotient(a,b)*b
- return (a - quotient*b) ;
- }
-
- //
- // help function described in W3C PR Schema [E Adding durations to dateTimes]
- //
- protected int fQuotient (int a, int b) {
-
- //fQuotient(a, b) = the greatest integer less than or equal to a/b
- return (int)Math.floor((float)ab);
- }
-
- //
- // help function described in W3C PR Schema [E Adding durations to dateTimes]
- //
- protected int modulo (int temp, int low, int high) {
- //modulo(a - low, high - low) + low
- int a = temp - low;
- int b = high - low;
- return (mod (a, b, fQuotient(a, b)) + low) ;
- }
-
- //
- // help function described in W3C PR Schema [E Adding durations to dateTimes]
- //
- protected int fQuotient (int temp, int low, int high) {
- //fQuotient(a - low, high - low)
-
- return fQuotient(temp - low, high - low);
- }
-
-
- protected String dateToString(int[] date) {
- StringBuffer message = new StringBuffer(25);
- append(message, date[CY], 4);
- message.append('-');
- append(message, date[M], 2);
- message.append('-');
- append(message, date[D], 2);
- message.append('T');
- append(message, date[h], 2);
- message.append(':');
- append(message, date[m], 2);
- message.append(':');
- append(message, date[s], 2);
- message.append('.');
- message.append(date[ms]);
- append(message, (char)date[utc], 0);
- return message.toString();
- }
-
- protected void append(StringBuffer message, int value, int nch) {
- if (value < 0) {
- message.append('-');
- value = -value;
- }
- if (nch == 4) {
- if (value < 10)
- message.append("000");
- else if (value < 100)
- message.append("00");
- else if (value < 1000)
- message.append("0");
- message.append(value);
- }
- else if (nch == 2) {
- if (value < 10)
- message.append('0');
- message.append(value);
- }
- else {
- if (value != 0)
- message.append((char)value);
- }
- }
-
- //
- //Private help functions
- //
-
- private void cloneDate (int[] finalValue, int[] tempDate) {
- System.arraycopy(finalValue, 0, tempDate, 0, TOTAL_SIZE);
- }
-
- /**
- * Represents date time data
- */
- static final class DateTimeData {
- // actual data stored in an int array
- final int[] data;
- // a pointer to the type that was used go generate this data
- // note that this is not the actual simple type, but one of the
- // statically created XXXDV objects, so this won't cause any GC problem.
- final AbstractDateTimeDV type;
- private String canonical;
- public DateTimeData(int[] data, AbstractDateTimeDV type) {
- this.data = data;
- this.type = type;
- }
- public boolean equals(Object obj) {
- if (!(obj instanceof DateTimeData))
- return false;
- int[] odata = ((DateTimeData)obj).data;
- return type.compareDates(data, odata, true)==0;
- }
- public synchronized String toString() {
- if (canonical == null) {
- canonical = type.dateToString(data);
- }
- return canonical;
- }
- }
- }