0001:/*
0002: * The Apache Software License, Version 1.1
0003: *
0004: *
0005: * Copyright (c) 1999, 2001 The Apache Software Foundation. All rights
0006: * reserved.
0007: *
0008: * Redistribution and use in source and binary forms, with or without
0009: * modification, are permitted provided that the following conditions
0010: * are met:
0011: *
0012: * 1. Redistributions of source code must retain the above copyright
0013: * notice, this list of conditions and the following disclaimer.
0014: *
0015: * 2. Redistributions in binary form must reproduce the above copyright
0016: * notice, this list of conditions and the following disclaimer in
0017: * the documentation and/or other materials provided with the
0018: * distribution.
0019: *
0020: * 3. The end-user documentation included with the redistribution,
0021: * if any, must include the following acknowledgment:
0022: * "This product includes software developed by the
0023: * Apache Software Foundation (http://www.apache.org/)."
0024: * Alternately, this acknowledgment may appear in the software itself,
0025: * if and wherever such third-party acknowledgments normally appear.
0026: *
0027: * 4. The names "Xerces" and "Apache Software Foundation" must
0028: * not be used to endorse or promote products derived from this
0029: * software without prior written permission. For written
0030: * permission, please contact apache@apache.org.
0031: *
0032: * 5. Products derived from this software may not be called "Apache",
0033: * nor may "Apache" appear in their name, without prior written
0034: * permission of the Apache Software Foundation.
0035: *
0036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0047: * SUCH DAMAGE.
0048: * ====================================================================
0049: *
0050: * This software consists of voluntary contributions made by many
0051: * individuals on behalf of the Apache Software Foundation and was
0052: * originally based on software copyright (c) 2001, International
0053: * Business Machines, Inc., http://www.apache.org. For more
0054: * information on the Apache Software Foundation, please see
0055: * <http://www.apache.org/>.
0056: */
0057:
0058:package org.apache.xerces.validators.datatype;
0059:
0060:import java.util.Vector;
0061:import java.util.Enumeration;
0062:import java.util.Hashtable;
0063:import java.lang.Math;
0064:import org.apache.xerces.utils.regex.RegularExpression;
0065:import org.apache.xerces.validators.schema.SchemaSymbols;
0066:
0067:
0068:/**
0069: * This is the base class of all date/time datatype validators.
0070: * It implements common code for parsing, validating and comparing datatypes.
0071: * Classes that extend this class, must implement parse() method.
0072: *
0073: * @author Elena Litani
0074: * @author Len Berman
0075: *
0076: * @version $Id: DateTimeValidator.java,v 1.14.2.2 2001/11/05 13:10:19 elena Exp $
0077: */
0078:
0079:public abstract class DateTimeValidator extends AbstractNumericFacetValidator {
0080:
0081: //debugging
0082: private static final boolean DEBUG=false;
0083:
0084: //define shared variables for date/time
0085:
0086: //define constants
0087: protected final static int CY = 0, M = 1, D = 2, h = 3,
0088: m = 4, s = 5, ms = 6, utc=7, hh=0, mm=1;
0089:
0090: //comparison
0091: protected static final short LESS_THAN=-1;
0092: protected static final short EQUAL=0;
0093: protected static final short GREATER_THAN=1;
0094:
0095: //size for all objects must have the same fields:
0096: //CCYY, MM, DD, h, m, s, ms + timeZone
0097: protected final static int TOTAL_SIZE = 8;
0098:
0099: //date obj size for gMonth datatype (without time zone): --09--
0100: protected final static int MONTH_SIZE = 6;
0101:
0102: //date obj must have at least 6 chars after year (without time zone): "-MM-DD"
0103: private final static int YEARMONTH_SIZE = 7;
0104:
0105: //define constants to be used in assigning default values for
0106: //all date/time excluding duration
0107: protected final static int YEAR=2000;
0108: protected final static int MONTH=01;
0109: protected final static int DAY = 15;
0110:
0111: //obj to store timeZone for date/time object excluding duration
0112: protected int[] timeZone;
0113:
0114: //size of enumeration if any
0115: protected int fEnumSize;
0116:
0117: //size of string buffer
0118: protected int fEnd;
0119: protected int fStart;
0120:
0121: //storage for string value of date/time object
0122: protected StringBuffer fBuffer;
0123:
0124: //obj to store all date/time objects with fields:
0125: // {CY, M, D, h, m, s, ms, utc}
0126: protected int[] fDateValue;
0127: private int[] fTempDate;
0128:
0129: //error message buffer
0130: protected StringBuffer message;
0131: //
0132: //REVISIT: more error checking, general debuging/common code clean up
0133: //
0134:
0135:
0136: //default constractor
0137:
0138: public DateTimeValidator () throws InvalidDatatypeFacetException {
0139: super ( null, null, false ); // Native, No Facets defined, Restriction
0140:
0141: }
0142:
0143: public DateTimeValidator (DatatypeValidator base, Hashtable facets, boolean derivedByList )
0144: throws InvalidDatatypeFacetException {
0145: super (base, facets, derivedByList);
0146: }
0147:
0148: protected void initializeValues(){
0149: fDateValue = new int[TOTAL_SIZE];
0150: fTempDate = new int[TOTAL_SIZE];
0151: fEnd = 30;
0152: fStart = 0;
0153: message = new StringBuffer(TOTAL_SIZE);
0154: fBuffer = new StringBuffer(fEnd);
0155: timeZone = new int[2];
0156: }
0157:
0158: protected void assignAdditionalFacets(String key, Hashtable facets ) throws InvalidDatatypeFacetException{
0159: throw new InvalidDatatypeFacetException( getErrorString(DatatypeMessageProvider.ILLEGAL_DATETIME_FACET,
0160: DatatypeMessageProvider.MSG_NONE, new Object[] { key }));
0161: }
0162:
0163: protected int compareValues (Object value1, Object value2) {
0164: return compareDates((int[])value1, (int[])value2, true);
0165: }
0166:
0167: protected void setMaxInclusive (String value) {
0168: fMaxInclusive = parse(value, null);
0169: }
0170: protected void setMinInclusive (String value) {
0171: fMinInclusive = parse(value, null);
0172: }
0173:
0174: protected void setMaxExclusive (String value) {
0175: fMaxExclusive = parse(value, null);
0176:
0177: }
0178: protected void setMinExclusive (String value) {
0179: fMinExclusive = parse(value, null);
0180:
0181: }
0182: protected void setEnumeration (Vector enumeration) throws InvalidDatatypeValueException{
0183:
0184: if ( enumeration != null ) {
0185:
0186: fEnumSize = enumeration.size();
0187: fEnumeration = new int[fEnumSize][];
0188: for ( int i=0; i<fEnumSize; i++ ) {
0189: try {
0190: fEnumeration[i] = parse((String)enumeration.elementAt(i), null);
0191: }
0192: catch ( RuntimeException e ) {
0193: throw new InvalidDatatypeValueException(e.getMessage());
0194: }
0195: }
0196: }
0197:}
0198:
0199:
0200: protected String getMaxInclusive (boolean isBase) {
0201: return (isBase)?(dateToString((int[]) ((DateTimeValidator)fBaseValidator).fMaxInclusive))
0202: :dateToString((int[])fMaxInclusive);
0203: }
0204: protected String getMinInclusive (boolean isBase) {
0205: return (isBase)?(dateToString((int[]) ((DateTimeValidator)fBaseValidator).fMinInclusive))
0206: :dateToString((int[])fMinInclusive);
0207: }
0208: protected String getMaxExclusive (boolean isBase) {
0209: return (isBase)?(dateToString((int[]) ((DateTimeValidator)fBaseValidator).fMaxExclusive))
0210: :dateToString((int[])fMaxExclusive);
0211: }
0212: protected String getMinExclusive (boolean isBase) {
0213: return (isBase)?(dateToString((int[]) ((DateTimeValidator)fBaseValidator).fMinExclusive))
0214: :dateToString((int[])fMinExclusive);
0215: }
0216:
0217: protected void checkContent( String content, Object State, Vector enum, boolean asBase)
0218: throws InvalidDatatypeValueException{
0219: }
0220:
0221: /**
0222: * Implemented by each subtype, calling appropriate function to parse
0223: * given date/time
0224: *
0225: * @param content String value of the date/time
0226: * @param date Storage to represent date/time object.
0227: * If null - new object will be created, otherwise
0228: * date will be reset and reused
0229: * @return updated date/time object
0230: * @exception Exception
0231: */
0232: abstract protected int[] parse (String content, int[] date) throws SchemaDateTimeException;
0233:
0234: /**
0235: * Validate that a string is a W3C date/time type
0236: *
0237: * @param content string value of date/time
0238: * @param state
0239: * @return
0240: * @exception InvalidDatatypeValueException
0241: */
0242: public Object validate(String content, Object state) throws InvalidDatatypeValueException{
0243:
0244: try {
0245: resetDateObj(fDateValue);
0246: parse(content, fDateValue);
0247: }
0248: catch ( RuntimeException e ) {
0249: throw new InvalidDatatypeValueException("Value '"+content+
0250: "' is not legal value for current datatype. " +e.getMessage() );
0251: }
0252: validateDate (fDateValue, content);
0253: return null;
0254: }
0255:
0256: /**
0257: * Validates date object against facet and base datatype
0258: *
0259: * @param date represents date/time obj
0260: * @param content lexical representation of date/time obj
0261: * @exception InvalidDatatypeValueException
0262: */
0263: protected void validateDate (int[] date, String content) throws InvalidDatatypeValueException{
0264:
0265: if ( this .fBaseValidator != null ) {//validate against parent type if any
0266: if ( (fFacetsDefined & DatatypeValidator.FACET_PATTERN ) != 0 ) {
0267: if ( fRegex == null || fRegex.matches( content) == false )
0268: throw new InvalidDatatypeValueException("Value'"+content+
0269: "' does not match regular expression facet " + fRegex.getPattern() );
0270: }
0271: //validate against base type
0272: ((DateTimeValidator)this .fBaseValidator).validateDate( date, content);
0273: if ( (fFacetsDefined & DatatypeValidator.FACET_ENUMERATION ) != 0 ) {
0274: int count=0;
0275: boolean valid = false;
0276: while ( count < fEnumSize ) {
0277: if ( compareDates(date, (int[])fEnumeration[count], false) == EQUAL ) {
0278: valid = true;
0279: break;
0280: }
0281: count++;
0282: }
0283: if ( !valid ) {
0284: throw new InvalidDatatypeValueException("Value'"+content+
0285: "' does not match enumeration values" );
0286: }
0287: }
0288:
0289: // REVISIT: output values for facets in error message
0290: short c;
0291: if ( fMinInclusive != null ) {
0292:
0293: c = compareDates(date, (int[])fMinInclusive, false);
0294: if ( c == LESS_THAN || c == INDETERMINATE ) {
0295: throw new InvalidDatatypeValueException("Value '"+content+
0296: "' is less than minInclusive: " +dateToString((int[])fMinInclusive) );
0297: }
0298: }
0299: if ( fMinExclusive != null ) {
0300:
0301: if ( compareDates(date, (int[])fMinExclusive, true) != GREATER_THAN ) {
0302: throw new InvalidDatatypeValueException("Value '"+content+
0303: "' is less than or equal to minExclusive: " +dateToString((int[])fMinExclusive));
0304: }
0305: }
0306:
0307: if ( fMaxInclusive != null ) {
0308:
0309: c = compareDates(date, (int[])fMaxInclusive, false );
0310: if ( c == GREATER_THAN || c == INDETERMINATE ) {
0311: throw new InvalidDatatypeValueException("Value '"+content+
0312: "' is greater than maxInclusive: " +dateToString((int[])fMaxInclusive) );
0313: }
0314: }
0315:
0316: if ( fMaxExclusive != null ) {
0317:
0318: if ( compareDates(date, (int[])fMaxExclusive, true ) != LESS_THAN ) {
0319: throw new InvalidDatatypeValueException("Value '"+content+
0320: "' is greater than or equal to maxExlusive: " +dateToString((int[])fMaxExclusive) );
0321: }
0322: }
0323: }
0324: else {
0325: return;
0326: }
0327:
0328: }
0329: public int compare( String content1, String content2) {
0330: //implement compareDates using the compare() method
0331: try{
0332: parse(content1, fDateValue);
0333: parse(content2,fTempDate);
0334: int result = compareDates(fDateValue, fTempDate, true);
0335: return (result==INDETERMINATE)?-1:result;
0336: }
0337: catch ( RuntimeException e ) {
0338: return -1;
0339:
0340: }
0341: }
0342:
0343:
0344: public Object clone() throws CloneNotSupportedException {
0345: throw new CloneNotSupportedException("clone() is not supported in "+this .getClass().getName());
0346: }
0347:
0348:
0349:
0350: /**
0351: * Compare algorithm described in dateDime (3.2.7).
0352: * Duration datatype overwrites this method
0353: *
0354: * @param date1 normalized date representation of the first value
0355: * @param date2 normalized date representation of the second value
0356: * @param strict
0357: * @return less, greater, less_equal, greater_equal, equal
0358: */
0359: protected short compareDates(int[] date1, int[] date2, boolean strict) {
0360: if ( date1[utc]==date2[utc] ) {
0361: return compareOrder(date1, date2);
0362: }
0363: short c1, c2;
0364:
0365: if ( date1[utc]=='Z' ) {
0366:
0367: //compare date1<=date1<=(date2 with time zone -14)
0368: //
0369: cloneDate(date2); //clones date1 value to global temporary storage: fTempDate
0370: timeZone[hh]=14;
0371: timeZone[mm]=0;
0372: fTempDate[utc]='+';
0373: normalize(fTempDate);
0374: c1 = compareOrder(date1, fTempDate);
0375:
0376: //compare date1>=(date2 with time zone +14)
0377: //
0378: cloneDate(date2); //clones date1 value to global temporary storage: fTempDate
0379: timeZone[hh]=14;
0380: timeZone[mm]=0;
0381: fTempDate[utc]='-';
0382: normalize(fTempDate);
0383: c2 = compareOrder(date1, fTempDate);
0384:
0385: if ( (c1==LESS_THAN && c2==GREATER_THAN) ||
0386: (c1==GREATER_THAN && c2==LESS_THAN) ) {
0387: return INDETERMINATE;
0388: }
0389: //REVISIT: wait for clarification on this case from schema
0390: return(c1!=INDETERMINATE)?c1:c2;
0391: }
0392: else if ( date2[utc]=='Z' ) {
0393:
0394: //compare (date1 with time zone -14)<=date2
0395: //
0396: cloneDate(date1); //clones date1 value to global temporary storage: fTempDate
0397: timeZone[hh]=14;
0398: timeZone[mm]=0;
0399:
0400: fTempDate[utc]='-';
0401: if (DEBUG) {
0402: System.out.println("fTempDate=" + dateToString(fTempDate));
0403: }
0404: normalize(fTempDate);
0405: c1 = compareOrder(fTempDate, date2);
0406: if (DEBUG) {
0407: System.out.println("date=" + dateToString(date2));
0408: System.out.println("fTempDate=" + dateToString(fTempDate));
0409: }
0410: //compare (date1 with time zone +14)<=date2
0411: //
0412: cloneDate(date1); //clones date1 value to global temporary storage: fTempDate
0413: timeZone[hh]=14;
0414: timeZone[mm]=0;
0415: fTempDate[utc]='+';
0416: normalize(fTempDate);
0417: c2 = compareOrder(fTempDate, date2);
0418: if (DEBUG) {
0419: System.out.println("fTempDate=" + dateToString(fTempDate));
0420: }
0421: if ( (c1==LESS_THAN && c2==GREATER_THAN) ||
0422: (c1==GREATER_THAN && c2==LESS_THAN) ) {
0423: return INDETERMINATE;
0424: }
0425: //REVISIT: wait for clarification on this case from schema
0426: return(c1!=INDETERMINATE)?c1:c2;
0427: }
0428: return INDETERMINATE;
0429:
0430: }
0431:
0432:
0433: /**
0434: * Given normalized values, determines order-relation
0435: * between give date/time objects.
0436: *
0437: * @param date1 date/time object
0438: * @param date2 date/time object
0439: * @return
0440: */
0441: protected short compareOrder (int[] date1, int[] date2) {
0442:
0443: for ( int i=0;i<TOTAL_SIZE;i++ ) {
0444: if ( date1[i]<date2[i] ) {
0445: return LESS_THAN;
0446: }
0447: else if ( date1[i]>date2[i] ) {
0448: return GREATER_THAN;
0449: }
0450: }
0451: return EQUAL;
0452: }
0453:
0454:
0455: /**
0456: * Parses time hh:mm:ss.sss and time zone if any
0457: *
0458: * @param start
0459: * @param end
0460: * @param data
0461: * @return
0462: * @exception Exception
0463: */
0464: protected void getTime (int start, int end, int[] data) throws RuntimeException{
0465:
0466: int stop = start+2;
0467:
0468: //get hours (hh)
0469: data[h]=parseInt(start,stop);
0470:
0471: //get minutes (mm)
0472:
0473: if (fBuffer.charAt(stop++)!=':') {
0474: throw new RuntimeException("Error in parsing time zone" );
0475: }
0476: start = stop;
0477: stop = stop+2;
0478: data[m]=parseInt(start,stop);
0479:
0480: //get seconds (ss)
0481: if (fBuffer.charAt(stop++)!=':') {
0482: throw new RuntimeException("Error in parsing time zone" );
0483: }
0484: start = stop;
0485: stop = stop+2;
0486: data[s]=parseInt(start,stop);
0487:
0488: //get miliseconds (ms)
0489: int milisec = indexOf(start, end, '.');
0490:
0491: //find UTC sign if any
0492: int sign = findUTCSign((milisec!=-1)?milisec:start, end);
0493:
0494: //parse miliseconds
0495: if ( milisec != -1 ) {
0496:
0497: if ( sign<0 ) {
0498:
0499: //get all digits after "."
0500: data[ms]=parseInt(milisec+1,fEnd);
0501: }
0502: else {
0503:
0504: //get ms before UTC sign
0505: data[ms]=parseInt(milisec+1,sign);
0506: }
0507:
0508: }
0509:
0510: //parse UTC time zone (hh:mm)
0511: if ( sign>0 ) {
0512: getTimeZone(data,sign);
0513: }
0514: }
0515:
0516:
0517: /**
0518: * Parses date CCYY-MM-DD
0519: *
0520: * @param start
0521: * @param end
0522: * @param data
0523: * @return
0524: * @exception Exception
0525: */
0526: protected void getDate (int start, int end, int[] date) throws RuntimeException{
0527:
0528: getYearMonth(start, end, date);
0529:
0530: if (fBuffer.charAt(fStart++) !='-') {
0531: throw new RuntimeException("CCYY-MM must be followed by '-' sign");
0532: }
0533: int stop = fStart + 2;
0534: date[D]=parseInt(fStart, stop);
0535: fStart = stop; //fStart points right after the Day
0536: }
0537:
0538: /**
0539: * Parses date CCYY-MM
0540: *
0541: * @param start
0542: * @param end
0543: * @param data
0544: * @return
0545: * @exception Exception
0546: */
0547: protected void getYearMonth (int start, int end, int[] date) throws RuntimeException{
0548:
0549: if ( fBuffer.charAt(0)=='-' ) {
0550: // REVISIT: date starts with preceding '-' sign
0551: // do we have to do anything with it?
0552: //
0553: start++;
0554: }
0555: int i = indexOf(start, end, '-');
0556: if ( i==-1 ) throw new RuntimeException("Year separator is missing or misplaced");
0557: date[CY]= parseIntYear(i);
0558: if (fBuffer.charAt(i)!='-') {
0559: throw new RuntimeException("CCYY must be followed by '-' sign");
0560: }
0561: start = ++i;
0562: i = start +2;
0563: date[M]=parseInt(start, i);
0564: fStart = i; //fStart points right after the MONTH
0565: }
0566:
0567:
0568:
0569: /**
0570: * Shared code from Date and YearMonth datatypes.
0571: * Finds if time zone sign is present
0572: *
0573: * @param end
0574: * @param date
0575: * @return
0576: * @exception Exception
0577: */
0578: protected void parseTimeZone (int end, int[] date) throws RuntimeException{
0579:
0580: //fStart points right after the date
0581:
0582: if ( fStart<fEnd ) {
0583: int sign = findUTCSign(fStart, fEnd);
0584: if ( sign<0 ) {
0585: throw new RuntimeException ("Error in month parsing");
0586: }
0587: else {
0588: getTimeZone(date, sign);
0589: }
0590: }
0591: }
0592:
0593: /**
0594: * Parses time zone: 'Z' or {+,-} followed by hh:mm
0595: *
0596: * @param data
0597: * @param sign
0598: * @return
0599: */
0600: protected void getTimeZone (int[] data, int sign) throws RuntimeException{
0601: data[utc]=fBuffer.charAt(sign);
0602:
0603: if ( fBuffer.charAt(sign) == 'Z' ) {
0604: if (fEnd>(++sign)) {
0605: throw new RuntimeException("Error in parsing time zone");
0606: }
0607: return;
0608: }
0609: if ( sign<=(fEnd-6) ) {
0610:
0611: //parse [hh]
0612: int stop = ++sign+2;
0613: timeZone[hh]=parseInt(sign, stop);
0614: if (fBuffer.charAt(stop++)!=':') {
0615: throw new RuntimeException("Error in parsing time zone" );
0616: }
0617:
0618: //parse [ss]
0619: timeZone[mm]=parseInt(stop, stop+2);
0620:
0621: if ( stop+2!=fEnd ) {
0622: throw new RuntimeException("Error in parsing time zone");
0623: }
0624:
0625: }
0626: else {
0627: throw new RuntimeException("Error in parsing time zone");
0628: }
0629: if ( DEBUG ) {
0630: System.out.println("time[hh]="+timeZone[hh] + " time[mm]=" +timeZone[mm]);
0631: }
0632: }
0633:
0634:
0635:
0636: /**
0637: * Computes index of given char within StringBuffer
0638: *
0639: * @param start
0640: * @param end
0641: * @param ch character to look for in StringBuffer
0642: * @return index of ch within StringBuffer
0643: */
0644: protected int indexOf (int start, int end, char ch) {
0645: for ( int i=start;i<end;i++ ) {
0646: if ( fBuffer.charAt(i) == ch ) {
0647: return i;
0648: }
0649: }
0650: return -1;
0651: }
0652:
0653:
0654: /**
0655: * Validates given date/time object accoring to W3C PR Schema
0656: * [D.1 ISO 8601 Conventions]
0657: *
0658: * @param data
0659: * @return
0660: */
0661: protected void validateDateTime (int[] data) {
0662:
0663: //REVISIT: should we throw an exception for not valid dates
0664: // or reporting an error message should be sufficient?
0665: if ( data[CY]==0 ) {
0666: throw new RuntimeException("The year \"0000\" is an illegal year value");
0667:
0668: }
0669:
0670: if ( data[M]<1 || data[M]>12 ) {
0671: throw new RuntimeException("The month must have values 1 to 12");
0672:
0673: }
0674:
0675: //validate days
0676: if ( data[D]>maxDayInMonthFor(data[CY], data[M]) || data[D]==0 ) {
0677: throw new RuntimeException("The day must have values 1 to 31");
0678: }
0679:
0680: //validate hours
0681: if ( data[h]>24 || data[h]<0 ||
0682: (data[h] == 24 && (data[m]!=0 || data[s]!=0 || data[ms]!=0)) ) {
0683: throw new RuntimeException("Hour must have values 0-24. If hour is 24, minutes and seconds must both have the value 0.");
0684: }
0685:
0686: //validate minutes
0687: if ( data[m]>59 || data[m]<0 ) {
0688: throw new RuntimeException("Minute must have values 0-59");
0689: }
0690:
0691: //validate seconds
0692: if ( data[s]>60 || data[s]<0 ) {
0693: throw new RuntimeException("Second must have values 0-60");
0694:
0695: }
0696:
0697: //validate time-zone hours
0698: if ( Math.abs(timeZone[hh])>14 ||
0699: (Math.abs(timeZone[hh]) == 14 && timeZone[mm] != 0) ) {
0700: throw new RuntimeException("Time zone should have range -14..+14");
0701: }
0702:
0703: //validate time-zone minutes
0704: if ( Math.abs(timeZone[mm]) > 59 ) {
0705: throw new RuntimeException("Minute must have values 0-59");
0706: }
0707: }
0708:
0709:
0710: /**
0711: * Return index of UTC char: 'Z', '+', '-'
0712: *
0713: * @param start
0714: * @param end
0715: * @return
0716: */
0717: protected int findUTCSign (int start, int end) {
0718: int c;
0719: for ( int i=start;i<end;i++ ) {
0720: c=fBuffer.charAt(i);
0721: if ( c == 'Z' || c=='+' || c=='-' ) {
0722: return i;
0723: }
0724:
0725: }
0726: return -1;
0727: }
0728:
0729:
0730: /**
0731: * Given start and end position, parses string value
0732: *
0733: * @param value string to parse
0734: * @param start Start position
0735: * @param end end position
0736: * @return return integer representation of characters
0737: */
0738: protected int parseInt (int start, int end)
0739: throws NumberFormatException{
0740: //REVISIT: more testing on this parsing needs to be done.
0741: int radix=10;
0742: int result = 0;
0743: int digit=0;
0744: int limit = -Integer.MAX_VALUE;
0745: int multmin = limit / radix;
0746: int i = start;
0747: do {
0748: digit = Character.digit(fBuffer.charAt(i),radix);
0749: if ( digit < 0 ) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0750: if ( result < multmin ) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0751: result *= radix;
0752: if ( result < limit + digit ) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0753: result -= digit;
0754:
0755: }while ( ++i < end );
0756: return -result;
0757: }
0758:
0759: // parse Year differently to support negative value.
0760: protected int parseIntYear (int end){
0761: int radix=10;
0762: int result = 0;
0763: boolean negative = false;
0764: int i=0;
0765: int limit;
0766: int multmin;
0767: int digit=0;
0768:
0769: if (fBuffer.charAt(0) == '-'){
0770: negative = true;
0771: limit = Integer.MIN_VALUE;
0772: i++;
0773: }
0774: else{
0775: limit = -Integer.MAX_VALUE;
0776: }
0777:
0778: int length = end-i;
0779: if (length<4 ||
0780: (length > 4 && fBuffer.charAt(i)=='0')) {
0781: throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden.");
0782: }
0783:
0784: multmin = limit / radix;
0785: while (i < end)
0786: {
0787: digit = Character.digit(fBuffer.charAt(i++),radix);
0788: if (digit < 0) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0789: if (result < multmin) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0790: result *= radix;
0791: if (result < limit + digit) throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0792: result -= digit;
0793: }
0794:
0795: if (negative)
0796: {
0797: if (i > 1) return result;
0798: else throw new NumberFormatException("'"+fBuffer.toString()+"' has wrong format");
0799: }
0800: return -result;
0801:
0802:
0803:
0804: }
0805:
0806: /**
0807: * normalize dateTime [E Adding durations to dateTimes]
0808: * If timezone present - normalize to UTC
0809: * If hour is 24 - normalize to start of following day
0810: *
0811: * @param date CCYY-MM-DDThh:mm:ss+03
0812: * @return CCYY-MM-DDThh:mm:ssZ
0813: */
0814: protected void normalize (int[] date) {
0815:
0816: // REVISIT: we have common code in addDuration() for durations
0817: // should consider reorganizing it.
0818: //
0819:
0820: //add minutes (from time zone)
0821: int negate = 1;
0822: if (date[utc]=='+') {
0823: negate = -1;
0824: }
0825: if ( DEBUG ) {
0826: System.out.println("==>date[m]"+date[m]);
0827: System.out.println("==>timeZone[mm]" +timeZone[mm]);
0828: }
0829: int temp = date[m] + negate*timeZone[mm];
0830: int carry = fQuotient (temp, 60);
0831: date[m]= mod(temp, 60, carry);
0832:
0833: if ( DEBUG ) {
0834: System.out.println("==>carry: " + carry);
0835: }
0836: //add hours
0837: temp = date[h] + negate*timeZone[hh] + carry;
0838: carry = fQuotient(temp, 24);
0839: date[h]=mod(temp, 24, carry);
0840: if ( DEBUG ) {
0841: System.out.println("==>date[h]"+date[h]);
0842: System.out.println("==>carry: " + carry);
0843: }
0844:
0845: date[D]=date[D]+carry;
0846:
0847: while ( true ) {
0848: temp=maxDayInMonthFor(date[CY], date[M]);
0849: if (date[D]<1) {
0850: date[D] = date[D] + maxDayInMonthFor(date[CY], date[M]-1);
0851: carry=-1;
0852: }
0853: else if ( date[D]>temp ) {
0854: date[D]=date[D]-temp;
0855: carry=1;
0856: }
0857: else {
0858: break;
0859: }
0860: temp=date[M]+carry;
0861: date[M]=modulo(temp, 1, 13);
0862: date[CY]=date[CY]+fQuotient(temp, 1, 13);
0863: }
0864:
0865: if (date[utc] != 0) {
0866: date[utc]='Z';
0867: }
0868: }
0869:
0870:
0871: /**
0872: * Resets fBuffer to store string representation of
0873: * date/time
0874: *
0875: * @param str Lexical representation of date/time
0876: */
0877: protected void resetBuffer (String str) {
0878: fBuffer.setLength(0);
0879: fStart=fEnd=0;
0880: timeZone[hh]=timeZone[mm]=0;
0881: fBuffer.append(str);
0882: fEnd = fBuffer.length();
0883:
0884: }
0885:
0886:
0887: /**
0888: * Resets object representation of date/time
0889: *
0890: * @param data date/time object
0891: */
0892: protected void resetDateObj (int[] data) {
0893: for ( int i=0;i<TOTAL_SIZE;i++ ) {
0894: data[i]=0;
0895: }
0896: }
0897:
0898:
0899: /**
0900: * Given {year,month} computes maximum
0901: * number of days for given month
0902: *
0903: * @param year
0904: * @param month
0905: * @return
0906: */
0907: protected int maxDayInMonthFor(int year, int month) {
0908: //validate days
0909: if ( month==4 || month==6 || month==9 || month==11 ) {
0910: return 30;
0911: }
0912: else if ( month==2 ) {
0913: if ( isLeapYear(year) ) {
0914: return 29;
0915: }
0916: else {
0917: return 28;
0918: }
0919: }
0920: else {
0921: return 31;
0922: }
0923: }
0924:
0925:
0926: private boolean isLeapYear(int year) {
0927: return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
0928: }
0929:
0930: //
0931: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0932: //
0933: protected int mod (int a, int b, int quotient) {
0934: //modulo(a, b) = a - fQuotient(a,b)*b
0935: return (a - quotient*b) ;
0936: }
0937:
0938: //
0939: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0940: //
0941: protected int fQuotient (int a, int b) {
0942:
0943: //fQuotient(a, b) = the greatest integer less than or equal to a/b
0944: return (int)Math.floor((float)a/b);
0945: }
0946:
0947: //
0948: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0949: //
0950: protected int modulo (int temp, int low, int high) {
0951: //modulo(a - low, high - low) + low
0952: int a = temp - low;
0953: int b = high - low;
0954: return (mod (a, b, fQuotient(a, b)) + low) ;
0955: }
0956:
0957: //
0958: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0959: //
0960: protected int fQuotient (int temp, int low, int high) {
0961: //fQuotient(a - low, high - low)
0962:
0963: return fQuotient(temp - low, high - low);
0964: }
0965:
0966:
0967: protected String dateToString(int[] date) {
0968: message.setLength(0);
0969: message.append(date[CY]);
0970: message.append('-');
0971: message.append(date[M]);
0972: message.append('-');
0973: message.append(date[D]);
0974: message.append('T');
0975: message.append(date[h]);
0976: message.append(':');
0977: message.append(date[m]);
0978: message.append(':');
0979: message.append(date[s]);
0980: message.append('.');
0981: message.append(date[ms]);
0982: message.append((char)date[utc]);
0983: return message.toString();
0984: }
0985:
0986:
0987: /**
0988: * Use this function to report errors in constructor
0989: *
0990: * @param msg
0991: * @param value
0992: */
0993: protected void reportError(String msg, String value) {
0994: System.err.println("[Error]: " +msg+": Value '"+value+"' is not legal for current datatype");
0995: }
0996:
0997:
0998: //
0999: //Private help functions
1000: //
1001:
1002: private void cloneDate (int[] finalValue) {
1003: resetDateObj(fTempDate);
1004: for ( int i=0;i<TOTAL_SIZE;i++ ) {
1005: fTempDate[i]=finalValue[i];
1006: }
1007: }
1008:
1009:}
|