0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.xerces.impl.dv.xs;
0019:
0020: import javax.xml.datatype.DatatypeFactory;
0021: import javax.xml.datatype.Duration;
0022: import javax.xml.datatype.XMLGregorianCalendar;
0023:
0024: import org.apache.xerces.impl.Constants;
0025: import org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl;
0026: import org.apache.xerces.xs.datatypes.XSDateTime;
0027:
0028: /**
0029: * This is the base class of all date/time datatype validators.
0030: * It implements common code for parsing, validating and comparing datatypes.
0031: * Classes that extend this class, must implement parse() method.
0032: *
0033: * REVISIT: There are many instance variables, which would cause problems
0034: * when we support grammar caching. A grammar is possibly used by
0035: * two parser instances at the same time, then the same simple type
0036: * decl object can be used to validate two strings at the same time.
0037: * -SG
0038: *
0039: * @xerces.internal
0040: *
0041: * @author Elena Litani
0042: * @author Len Berman
0043: * @author Gopal Sharma, SUN Microsystems Inc.
0044: *
0045: * @version $Id: AbstractDateTimeDV.java 572095 2007-09-02 18:32:43Z mrglavas $
0046: */
0047: public abstract class AbstractDateTimeDV extends TypeValidator {
0048:
0049: //debugging
0050: private static final boolean DEBUG = false;
0051:
0052: //define shared variables for date/time
0053:
0054: //define constants to be used in assigning default values for
0055: //all date/time excluding duration
0056: protected final static int YEAR = 2000;
0057: protected final static int MONTH = 01;
0058: protected final static int DAY = 01;
0059:
0060: protected final DatatypeFactory factory = new DatatypeFactoryImpl();
0061:
0062: public short getAllowedFacets() {
0063: return (XSSimpleTypeDecl.FACET_PATTERN
0064: | XSSimpleTypeDecl.FACET_WHITESPACE
0065: | XSSimpleTypeDecl.FACET_ENUMERATION
0066: | XSSimpleTypeDecl.FACET_MAXINCLUSIVE
0067: | XSSimpleTypeDecl.FACET_MININCLUSIVE
0068: | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE);
0069: }//getAllowedFacets()
0070:
0071: // distinguishes between identity and equality for date/time values
0072: // ie: two values representing the same "moment in time" but with different
0073: // remembered timezones are now equal but not identical.
0074: public boolean isIdentical(Object value1, Object value2) {
0075: if (!(value1 instanceof DateTimeData)
0076: || !(value2 instanceof DateTimeData)) {
0077: return false;
0078: }
0079:
0080: DateTimeData v1 = (DateTimeData) value1;
0081: DateTimeData v2 = (DateTimeData) value2;
0082:
0083: // original timezones must be the same in addition to date/time values
0084: // being 'equal'
0085: if ((v1.timezoneHr == v2.timezoneHr)
0086: && (v1.timezoneMin == v2.timezoneMin)) {
0087: return v1.equals(v2);
0088: }
0089:
0090: return false;
0091: }//isIdentical()
0092:
0093: // the parameters are in compiled form (from getActualValue)
0094: public int compare(Object value1, Object value2) {
0095: return compareDates(((DateTimeData) value1),
0096: ((DateTimeData) value2), true);
0097: }//compare()
0098:
0099: /**
0100: * Compare algorithm described in dateDime (3.2.7).
0101: * Duration datatype overwrites this method
0102: *
0103: * @param date1 normalized date representation of the first value
0104: * @param date2 normalized date representation of the second value
0105: * @param strict
0106: * @return less, greater, less_equal, greater_equal, equal
0107: */
0108: protected short compareDates(DateTimeData date1,
0109: DateTimeData date2, boolean strict) {
0110: if (date1.utc == date2.utc) {
0111: return compareOrder(date1, date2);
0112: }
0113: short c1, c2;
0114:
0115: DateTimeData tempDate = new DateTimeData(null, this );
0116:
0117: if (date1.utc == 'Z') {
0118:
0119: //compare date1<=date1<=(date2 with time zone -14)
0120: //
0121: cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate
0122: tempDate.timezoneHr = 14;
0123: tempDate.timezoneMin = 0;
0124: tempDate.utc = '+';
0125: normalize(tempDate);
0126: c1 = compareOrder(date1, tempDate);
0127: if (c1 == LESS_THAN)
0128: return c1;
0129:
0130: //compare date1>=(date2 with time zone +14)
0131: //
0132: cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate
0133: tempDate.timezoneHr = -14;
0134: tempDate.timezoneMin = 0;
0135: tempDate.utc = '-';
0136: normalize(tempDate);
0137: c2 = compareOrder(date1, tempDate);
0138: if (c2 == GREATER_THAN)
0139: return c2;
0140:
0141: return INDETERMINATE;
0142: } else if (date2.utc == 'Z') {
0143:
0144: //compare (date1 with time zone -14)<=date2
0145: //
0146: cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
0147: tempDate.timezoneHr = -14;
0148: tempDate.timezoneMin = 0;
0149: tempDate.utc = '-';
0150: if (DEBUG) {
0151: System.out
0152: .println("tempDate=" + dateToString(tempDate));
0153: }
0154: normalize(tempDate);
0155: c1 = compareOrder(tempDate, date2);
0156: if (DEBUG) {
0157: System.out.println("date=" + dateToString(date2));
0158: System.out
0159: .println("tempDate=" + dateToString(tempDate));
0160: }
0161: if (c1 == LESS_THAN)
0162: return c1;
0163:
0164: //compare (date1 with time zone +14)<=date2
0165: //
0166: cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate
0167: tempDate.timezoneHr = 14;
0168: tempDate.timezoneMin = 0;
0169: tempDate.utc = '+';
0170: normalize(tempDate);
0171: c2 = compareOrder(tempDate, date2);
0172: if (DEBUG) {
0173: System.out
0174: .println("tempDate=" + dateToString(tempDate));
0175: }
0176: if (c2 == GREATER_THAN)
0177: return c2;
0178:
0179: return INDETERMINATE;
0180: }
0181: return INDETERMINATE;
0182:
0183: }
0184:
0185: /**
0186: * Given normalized values, determines order-relation
0187: * between give date/time objects.
0188: *
0189: * @param date1 date/time object
0190: * @param date2 date/time object
0191: * @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
0192: */
0193: protected short compareOrder(DateTimeData date1, DateTimeData date2) {
0194: if (date1.position < 1) {
0195: if (date1.year < date2.year)
0196: return -1;
0197: if (date1.year > date2.year)
0198: return 1;
0199: }
0200: if (date1.position < 2) {
0201: if (date1.month < date2.month)
0202: return -1;
0203: if (date1.month > date2.month)
0204: return 1;
0205: }
0206: if (date1.day < date2.day)
0207: return -1;
0208: if (date1.day > date2.day)
0209: return 1;
0210: if (date1.hour < date2.hour)
0211: return -1;
0212: if (date1.hour > date2.hour)
0213: return 1;
0214: if (date1.minute < date2.minute)
0215: return -1;
0216: if (date1.minute > date2.minute)
0217: return 1;
0218: if (date1.second < date2.second)
0219: return -1;
0220: if (date1.second > date2.second)
0221: return 1;
0222: if (date1.utc < date2.utc)
0223: return -1;
0224: if (date1.utc > date2.utc)
0225: return 1;
0226: return 0;
0227: }
0228:
0229: /**
0230: * Parses time hh:mm:ss.sss and time zone if any
0231: *
0232: * @param start
0233: * @param end
0234: * @param data
0235: * @exception RuntimeException
0236: */
0237: protected void getTime(String buffer, int start, int end,
0238: DateTimeData data) throws RuntimeException {
0239:
0240: int stop = start + 2;
0241:
0242: //get hours (hh)
0243: data.hour = parseInt(buffer, start, stop);
0244:
0245: //get minutes (mm)
0246:
0247: if (buffer.charAt(stop++) != ':') {
0248: throw new RuntimeException("Error in parsing time zone");
0249: }
0250: start = stop;
0251: stop = stop + 2;
0252: data.minute = parseInt(buffer, start, stop);
0253:
0254: //get seconds (ss)
0255: if (buffer.charAt(stop++) != ':') {
0256: throw new RuntimeException("Error in parsing time zone");
0257: }
0258:
0259: //find UTC sign if any
0260: int sign = findUTCSign(buffer, start, end);
0261:
0262: //get seconds (ms)
0263: start = stop;
0264: stop = sign < 0 ? end : sign;
0265: data.second = parseSecond(buffer, start, stop);
0266:
0267: //parse UTC time zone (hh:mm)
0268: if (sign > 0) {
0269: getTimeZone(buffer, data, sign, end);
0270: }
0271: }
0272:
0273: /**
0274: * Parses date CCYY-MM-DD
0275: *
0276: * @param buffer
0277: * @param start start position
0278: * @param end end position
0279: * @param date
0280: * @exception RuntimeException
0281: */
0282: protected int getDate(String buffer, int start, int end,
0283: DateTimeData date) throws RuntimeException {
0284:
0285: start = getYearMonth(buffer, start, end, date);
0286:
0287: if (buffer.charAt(start++) != '-') {
0288: throw new RuntimeException(
0289: "CCYY-MM must be followed by '-' sign");
0290: }
0291: int stop = start + 2;
0292: date.day = parseInt(buffer, start, stop);
0293: return stop;
0294: }
0295:
0296: /**
0297: * Parses date CCYY-MM
0298: *
0299: * @param buffer
0300: * @param start start position
0301: * @param end end position
0302: * @param date
0303: * @exception RuntimeException
0304: */
0305: protected int getYearMonth(String buffer, int start, int end,
0306: DateTimeData date) throws RuntimeException {
0307:
0308: if (buffer.charAt(0) == '-') {
0309: // REVISIT: date starts with preceding '-' sign
0310: // do we have to do anything with it?
0311: //
0312: start++;
0313: }
0314: int i = indexOf(buffer, start, end, '-');
0315: if (i == -1)
0316: throw new RuntimeException(
0317: "Year separator is missing or misplaced");
0318: int length = i - start;
0319: if (length < 4) {
0320: throw new RuntimeException("Year must have 'CCYY' format");
0321: } else if (length > 4 && buffer.charAt(start) == '0') {
0322: throw new RuntimeException(
0323: "Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
0324: }
0325: date.year = parseIntYear(buffer, i);
0326: if (buffer.charAt(i) != '-') {
0327: throw new RuntimeException(
0328: "CCYY must be followed by '-' sign");
0329: }
0330: start = ++i;
0331: i = start + 2;
0332: date.month = parseInt(buffer, start, i);
0333: return i; //fStart points right after the MONTH
0334: }
0335:
0336: /**
0337: * Shared code from Date and YearMonth datatypes.
0338: * Finds if time zone sign is present
0339: *
0340: * @param end
0341: * @param date
0342: * @exception RuntimeException
0343: */
0344: protected void parseTimeZone(String buffer, int start, int end,
0345: DateTimeData date) throws RuntimeException {
0346:
0347: //fStart points right after the date
0348:
0349: if (start < end) {
0350: if (!isNextCharUTCSign(buffer, start, end)) {
0351: throw new RuntimeException("Error in month parsing");
0352: } else {
0353: getTimeZone(buffer, date, start, end);
0354: }
0355: }
0356: }
0357:
0358: /**
0359: * Parses time zone: 'Z' or {+,-} followed by hh:mm
0360: *
0361: * @param data
0362: * @param sign
0363: * @exception RuntimeException
0364: */
0365: protected void getTimeZone(String buffer, DateTimeData data,
0366: int sign, int end) throws RuntimeException {
0367: data.utc = buffer.charAt(sign);
0368:
0369: if (buffer.charAt(sign) == 'Z') {
0370: if (end > (++sign)) {
0371: throw new RuntimeException("Error in parsing time zone");
0372: }
0373: return;
0374: }
0375: if (sign <= (end - 6)) {
0376:
0377: int negate = buffer.charAt(sign) == '-' ? -1 : 1;
0378: //parse hr
0379: int stop = ++sign + 2;
0380: data.timezoneHr = negate * parseInt(buffer, sign, stop);
0381: if (buffer.charAt(stop++) != ':') {
0382: throw new RuntimeException("Error in parsing time zone");
0383: }
0384:
0385: //parse min
0386: data.timezoneMin = negate
0387: * parseInt(buffer, stop, stop + 2);
0388:
0389: if (stop + 2 != end) {
0390: throw new RuntimeException("Error in parsing time zone");
0391: }
0392: if (data.timezoneHr != 0 || data.timezoneMin != 0)
0393: data.normalized = false;
0394: } else {
0395: throw new RuntimeException("Error in parsing time zone");
0396: }
0397: if (DEBUG) {
0398: System.out.println("time[hh]=" + data.timezoneHr
0399: + " time[mm]=" + data.timezoneMin);
0400: }
0401: }
0402:
0403: /**
0404: * Computes index of given char within StringBuffer
0405: *
0406: * @param start
0407: * @param end
0408: * @param ch character to look for in StringBuffer
0409: * @return index of ch within StringBuffer
0410: */
0411: protected int indexOf(String buffer, int start, int end, char ch) {
0412: for (int i = start; i < end; i++) {
0413: if (buffer.charAt(i) == ch) {
0414: return i;
0415: }
0416: }
0417: return -1;
0418: }
0419:
0420: /**
0421: * Validates given date/time object accoring to W3C PR Schema
0422: * [D.1 ISO 8601 Conventions]
0423: *
0424: * @param data
0425: */
0426: protected void validateDateTime(DateTimeData data) {
0427:
0428: //REVISIT: should we throw an exception for not valid dates
0429: // or reporting an error message should be sufficient?
0430:
0431: /**
0432: * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types.
0433: */
0434: if (!Constants.SCHEMA_1_1_SUPPORT && data.year == 0) {
0435: throw new RuntimeException(
0436: "The year \"0000\" is an illegal year value");
0437:
0438: }
0439:
0440: if (data.month < 1 || data.month > 12) {
0441: throw new RuntimeException(
0442: "The month must have values 1 to 12");
0443:
0444: }
0445:
0446: //validate days
0447: if (data.day > maxDayInMonthFor(data.year, data.month)
0448: || data.day < 1) {
0449: throw new RuntimeException(
0450: "The day must have values 1 to 31");
0451: }
0452:
0453: //validate hours
0454: if (data.hour > 23 || data.hour < 0) {
0455: if (data.hour == 24 && data.minute == 0 && data.second == 0) {
0456: data.hour = 0;
0457: if (++data.day > maxDayInMonthFor(data.year, data.month)) {
0458: data.day = 1;
0459: if (++data.month > 12) {
0460: data.month = 1;
0461: if (Constants.SCHEMA_1_1_SUPPORT) {
0462: ++data.year;
0463: } else if (++data.year == 0) {
0464: data.year = 1;
0465: }
0466: }
0467: }
0468: } else {
0469: throw new RuntimeException(
0470: "Hour must have values 0-23, unless 24:00:00");
0471: }
0472: }
0473:
0474: //validate
0475: if (data.minute > 59 || data.minute < 0) {
0476: throw new RuntimeException("Minute must have values 0-59");
0477: }
0478:
0479: //validate
0480: if (data.second >= 60 || data.second < 0) {
0481: throw new RuntimeException("Second must have values 0-59");
0482:
0483: }
0484:
0485: //validate
0486: if (data.timezoneHr > 14 || data.timezoneHr < -14) {
0487: throw new RuntimeException(
0488: "Time zone should have range -14:00 to +14:00");
0489: } else {
0490: if ((data.timezoneHr == 14 || data.timezoneHr == -14)
0491: && data.timezoneMin != 0)
0492: throw new RuntimeException(
0493: "Time zone should have range -14:00 to +14:00");
0494: else if (data.timezoneMin > 59 || data.timezoneMin < -59)
0495: throw new RuntimeException(
0496: "Minute must have values 0-59");
0497: }
0498:
0499: }
0500:
0501: /**
0502: * Return index of UTC char: 'Z', '+', '-'
0503: *
0504: * @param start
0505: * @param end
0506: * @return index of the UTC character that was found
0507: */
0508: protected int findUTCSign(String buffer, int start, int end) {
0509: int c;
0510: for (int i = start; i < end; i++) {
0511: c = buffer.charAt(i);
0512: if (c == 'Z' || c == '+' || c == '-') {
0513: return i;
0514: }
0515:
0516: }
0517: return -1;
0518: }
0519:
0520: /**
0521: * Returns <code>true</code> if the character at start is 'Z', '+' or '-'.
0522: */
0523: protected final boolean isNextCharUTCSign(String buffer, int start,
0524: int end) {
0525: if (start < end) {
0526: char c = buffer.charAt(start);
0527: return (c == 'Z' || c == '+' || c == '-');
0528: }
0529: return false;
0530: }
0531:
0532: /**
0533: * Given start and end position, parses string value
0534: *
0535: * @param buffer string to parse
0536: * @param start start position
0537: * @param end end position
0538: * @return return integer representation of characters
0539: */
0540: protected int parseInt(String buffer, int start, int end)
0541: throws NumberFormatException {
0542: //REVISIT: more testing on this parsing needs to be done.
0543: int radix = 10;
0544: int result = 0;
0545: int digit = 0;
0546: int limit = -Integer.MAX_VALUE;
0547: int multmin = limit / radix;
0548: int i = start;
0549: do {
0550: digit = getDigit(buffer.charAt(i));
0551: if (digit < 0)
0552: throw new NumberFormatException("'" + buffer
0553: + "' has wrong format");
0554: if (result < multmin)
0555: throw new NumberFormatException("'" + buffer
0556: + "' has wrong format");
0557: result *= radix;
0558: if (result < limit + digit)
0559: throw new NumberFormatException("'" + buffer
0560: + "' has wrong format");
0561: result -= digit;
0562:
0563: } while (++i < end);
0564: return -result;
0565: }
0566:
0567: // parse Year differently to support negative value.
0568: protected int parseIntYear(String buffer, int end) {
0569: int radix = 10;
0570: int result = 0;
0571: boolean negative = false;
0572: int i = 0;
0573: int limit;
0574: int multmin;
0575: int digit = 0;
0576:
0577: if (buffer.charAt(0) == '-') {
0578: negative = true;
0579: limit = Integer.MIN_VALUE;
0580: i++;
0581:
0582: } else {
0583: limit = -Integer.MAX_VALUE;
0584: }
0585: multmin = limit / radix;
0586: while (i < end) {
0587: digit = getDigit(buffer.charAt(i++));
0588: if (digit < 0)
0589: throw new NumberFormatException("'" + buffer
0590: + "' has wrong format");
0591: if (result < multmin)
0592: throw new NumberFormatException("'" + buffer
0593: + "' has wrong format");
0594: result *= radix;
0595: if (result < limit + digit)
0596: throw new NumberFormatException("'" + buffer
0597: + "' has wrong format");
0598: result -= digit;
0599: }
0600:
0601: if (negative) {
0602: if (i > 1)
0603: return result;
0604: else
0605: throw new NumberFormatException("'" + buffer
0606: + "' has wrong format");
0607: }
0608: return -result;
0609:
0610: }
0611:
0612: /**
0613: * If timezone present - normalize dateTime [E Adding durations to dateTimes]
0614: *
0615: * @param date CCYY-MM-DDThh:mm:ss+03
0616: */
0617: protected void normalize(DateTimeData date) {
0618:
0619: // REVISIT: we have common code in addDuration() for durations
0620: // should consider reorganizing it.
0621: //
0622:
0623: //add minutes (from time zone)
0624: int negate = -1;
0625:
0626: if (DEBUG) {
0627: System.out.println("==>date.minute" + date.minute);
0628: System.out
0629: .println("==>date.timezoneMin" + date.timezoneMin);
0630: }
0631: int temp = date.minute + negate * date.timezoneMin;
0632: int carry = fQuotient(temp, 60);
0633: date.minute = mod(temp, 60, carry);
0634:
0635: if (DEBUG) {
0636: System.out.println("==>carry: " + carry);
0637: }
0638: //add hours
0639: temp = date.hour + negate * date.timezoneHr + carry;
0640: carry = fQuotient(temp, 24);
0641: date.hour = mod(temp, 24, carry);
0642: if (DEBUG) {
0643: System.out.println("==>date.hour" + date.hour);
0644: System.out.println("==>carry: " + carry);
0645: }
0646:
0647: date.day = date.day + carry;
0648:
0649: while (true) {
0650: temp = maxDayInMonthFor(date.year, date.month);
0651: if (date.day < 1) {
0652: date.day = date.day
0653: + maxDayInMonthFor(date.year, date.month - 1);
0654: carry = -1;
0655: } else if (date.day > temp) {
0656: date.day = date.day - temp;
0657: carry = 1;
0658: } else {
0659: break;
0660: }
0661: temp = date.month + carry;
0662: date.month = modulo(temp, 1, 13);
0663: date.year = date.year + fQuotient(temp, 1, 13);
0664: if (date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) {
0665: date.year = (date.timezoneHr < 0 || date.timezoneMin < 0) ? 1
0666: : -1;
0667: }
0668: }
0669: date.utc = 'Z';
0670: }
0671:
0672: /**
0673: * @param date
0674: */
0675: protected void saveUnnormalized(DateTimeData date) {
0676: date.unNormYear = date.year;
0677: date.unNormMonth = date.month;
0678: date.unNormDay = date.day;
0679: date.unNormHour = date.hour;
0680: date.unNormMinute = date.minute;
0681: date.unNormSecond = date.second;
0682: }
0683:
0684: /**
0685: * Resets object representation of date/time
0686: *
0687: * @param data date/time object
0688: */
0689: protected void resetDateObj(DateTimeData data) {
0690: data.year = 0;
0691: data.month = 0;
0692: data.day = 0;
0693: data.hour = 0;
0694: data.minute = 0;
0695: data.second = 0;
0696: data.utc = 0;
0697: data.timezoneHr = 0;
0698: data.timezoneMin = 0;
0699: }
0700:
0701: /**
0702: * Given {year,month} computes maximum
0703: * number of days for given month
0704: *
0705: * @param year
0706: * @param month
0707: * @return integer containg the number of days in a given month
0708: */
0709: protected int maxDayInMonthFor(int year, int month) {
0710: //validate days
0711: if (month == 4 || month == 6 || month == 9 || month == 11) {
0712: return 30;
0713: } else if (month == 2) {
0714: if (isLeapYear(year)) {
0715: return 29;
0716: } else {
0717: return 28;
0718: }
0719: } else {
0720: return 31;
0721: }
0722: }
0723:
0724: private boolean isLeapYear(int year) {
0725:
0726: //REVISIT: should we take care about Julian calendar?
0727: return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
0728: }
0729:
0730: //
0731: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0732: //
0733: protected int mod(int a, int b, int quotient) {
0734: //modulo(a, b) = a - fQuotient(a,b)*b
0735: return (a - quotient * b);
0736: }
0737:
0738: //
0739: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0740: //
0741: protected int fQuotient(int a, int b) {
0742:
0743: //fQuotient(a, b) = the greatest integer less than or equal to a/b
0744: return (int) Math.floor((float) a / b);
0745: }
0746:
0747: //
0748: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0749: //
0750: protected int modulo(int temp, int low, int high) {
0751: //modulo(a - low, high - low) + low
0752: int a = temp - low;
0753: int b = high - low;
0754: return (mod(a, b, fQuotient(a, b)) + low);
0755: }
0756:
0757: //
0758: // help function described in W3C PR Schema [E Adding durations to dateTimes]
0759: //
0760: protected int fQuotient(int temp, int low, int high) {
0761: //fQuotient(a - low, high - low)
0762:
0763: return fQuotient(temp - low, high - low);
0764: }
0765:
0766: protected String dateToString(DateTimeData date) {
0767: StringBuffer message = new StringBuffer(25);
0768: append(message, date.year, 4);
0769: message.append('-');
0770: append(message, date.month, 2);
0771: message.append('-');
0772: append(message, date.day, 2);
0773: message.append('T');
0774: append(message, date.hour, 2);
0775: message.append(':');
0776: append(message, date.minute, 2);
0777: message.append(':');
0778: append(message, date.second);
0779: append(message, (char) date.utc, 0);
0780: return message.toString();
0781: }
0782:
0783: protected final void append(StringBuffer message, int value, int nch) {
0784: if (value == Integer.MIN_VALUE) {
0785: message.append(value);
0786: return;
0787: }
0788: if (value < 0) {
0789: message.append('-');
0790: value = -value;
0791: }
0792: if (nch == 4) {
0793: if (value < 10)
0794: message.append("000");
0795: else if (value < 100)
0796: message.append("00");
0797: else if (value < 1000)
0798: message.append("0");
0799: message.append(value);
0800: } else if (nch == 2) {
0801: if (value < 10)
0802: message.append('0');
0803: message.append(value);
0804: } else {
0805: if (value != 0)
0806: message.append((char) value);
0807: }
0808: }
0809:
0810: protected final void append(StringBuffer message, double value) {
0811: if (value < 0) {
0812: message.append('-');
0813: value = -value;
0814: }
0815: if (value < 10) {
0816: message.append('0');
0817: }
0818: append2(message, value);
0819: }
0820:
0821: protected final void append2(StringBuffer message, double value) {
0822: final int intValue = (int) value;
0823: if (value == intValue) {
0824: message.append(intValue);
0825: } else {
0826: append3(message, value);
0827: }
0828: }
0829:
0830: private void append3(StringBuffer message, double value) {
0831: String d = String.valueOf(value);
0832: int eIndex = d.indexOf('E');
0833: if (eIndex == -1) {
0834: message.append(d);
0835: return;
0836: }
0837: int exp;
0838: if (value < 1) {
0839: // Need to convert from scientific notation of the form
0840: // n.nnn...E-N (N >= 4) to a normal decimal value.
0841: try {
0842: exp = parseInt(d, eIndex + 2, d.length());
0843: }
0844: // This should never happen.
0845: // It's only possible if String.valueOf(double) is broken.
0846: catch (Exception e) {
0847: message.append(d);
0848: return;
0849: }
0850: message.append("0.");
0851: for (int i = 1; i < exp; ++i) {
0852: message.append('0');
0853: }
0854: // Remove trailing zeros.
0855: int end = eIndex - 1;
0856: while (end > 0) {
0857: char c = d.charAt(end);
0858: if (c != '0') {
0859: break;
0860: }
0861: --end;
0862: }
0863: // Now append the digits to the end. Skip over the decimal point.
0864: for (int i = 0; i <= end; ++i) {
0865: char c = d.charAt(i);
0866: if (c != '.') {
0867: message.append(c);
0868: }
0869: }
0870: } else {
0871: // Need to convert from scientific notation of the form
0872: // n.nnn...EN (N >= 7) to a normal decimal value.
0873: try {
0874: exp = parseInt(d, eIndex + 1, d.length());
0875: }
0876: // This should never happen.
0877: // It's only possible if String.valueOf(double) is broken.
0878: catch (Exception e) {
0879: message.append(d);
0880: return;
0881: }
0882: final int integerEnd = exp + 2;
0883: for (int i = 0; i < eIndex; ++i) {
0884: char c = d.charAt(i);
0885: if (c != '.') {
0886: if (i == integerEnd) {
0887: message.append('.');
0888: }
0889: message.append(c);
0890: }
0891: }
0892: // Append trailing zeroes if necessary.
0893: for (int i = integerEnd - eIndex; i > 0; --i) {
0894: message.append('0');
0895: }
0896: }
0897: }
0898:
0899: protected double parseSecond(String buffer, int start, int end)
0900: throws NumberFormatException {
0901: int dot = -1;
0902: for (int i = start; i < end; i++) {
0903: char ch = buffer.charAt(i);
0904: if (ch == '.')
0905: dot = i;
0906: else if (ch > '9' || ch < '0')
0907: throw new NumberFormatException("'" + buffer
0908: + "' has wrong format");
0909: }
0910: if (dot == -1) {
0911: if (start + 2 != end)
0912: throw new NumberFormatException("'" + buffer
0913: + "' has wrong format");
0914: } else if (start + 2 != dot || dot + 1 == end) {
0915: throw new NumberFormatException("'" + buffer
0916: + "' has wrong format");
0917: }
0918: return Double.parseDouble(buffer.substring(start, end));
0919: }
0920:
0921: //
0922: //Private help functions
0923: //
0924:
0925: private void cloneDate(DateTimeData finalValue,
0926: DateTimeData tempDate) {
0927: tempDate.year = finalValue.year;
0928: tempDate.month = finalValue.month;
0929: tempDate.day = finalValue.day;
0930: tempDate.hour = finalValue.hour;
0931: tempDate.minute = finalValue.minute;
0932: tempDate.second = finalValue.second;
0933: tempDate.utc = finalValue.utc;
0934: tempDate.timezoneHr = finalValue.timezoneHr;
0935: tempDate.timezoneMin = finalValue.timezoneMin;
0936: }
0937:
0938: /**
0939: * Represents date time data
0940: */
0941: static final class DateTimeData implements XSDateTime {
0942: int year, month, day, hour, minute, utc;
0943: double second;
0944: int timezoneHr, timezoneMin;
0945: private String originalValue;
0946: boolean normalized = true;
0947:
0948: int unNormYear;
0949: int unNormMonth;
0950: int unNormDay;
0951: int unNormHour;
0952: int unNormMinute;
0953: double unNormSecond;
0954:
0955: // used for comparisons - to decide the 'interesting' portions of
0956: // a date/time based data type.
0957: int position;
0958: // a pointer to the type that was used go generate this data
0959: // note that this is not the actual simple type, but one of the
0960: // statically created XXXDV objects, so this won't cause any GC problem.
0961: final AbstractDateTimeDV type;
0962: private String canonical;
0963:
0964: public DateTimeData(String originalValue,
0965: AbstractDateTimeDV type) {
0966: this .originalValue = originalValue;
0967: this .type = type;
0968: }
0969:
0970: public DateTimeData(int year, int month, int day, int hour,
0971: int minute, double second, int utc,
0972: String originalValue, boolean normalized,
0973: AbstractDateTimeDV type) {
0974: this .year = year;
0975: this .month = month;
0976: this .day = day;
0977: this .hour = hour;
0978: this .minute = minute;
0979: this .second = second;
0980: this .utc = utc;
0981: this .type = type;
0982: this .originalValue = originalValue;
0983: }
0984:
0985: public boolean equals(Object obj) {
0986: if (!(obj instanceof DateTimeData))
0987: return false;
0988: return type.compareDates(this , (DateTimeData) obj, true) == 0;
0989: }
0990:
0991: public synchronized String toString() {
0992: if (canonical == null) {
0993: canonical = type.dateToString(this );
0994: }
0995: return canonical;
0996: }
0997:
0998: /* (non-Javadoc)
0999: * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear()
1000: */
1001: public int getYears() {
1002: if (type instanceof DurationDV)
1003: return 0;
1004: return normalized ? year : unNormYear;
1005: }
1006:
1007: /* (non-Javadoc)
1008: * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth()
1009: */
1010: public int getMonths() {
1011: if (type instanceof DurationDV) {
1012: return year * 12 + month;
1013: }
1014: return normalized ? month : unNormMonth;
1015: }
1016:
1017: /* (non-Javadoc)
1018: * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay()
1019: */
1020: public int getDays() {
1021: if (type instanceof DurationDV)
1022: return 0;
1023: return normalized ? day : unNormDay;
1024: }
1025:
1026: /* (non-Javadoc)
1027: * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour()
1028: */
1029: public int getHours() {
1030: if (type instanceof DurationDV)
1031: return 0;
1032: return normalized ? hour : unNormHour;
1033: }
1034:
1035: /* (non-Javadoc)
1036: * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes()
1037: */
1038: public int getMinutes() {
1039: if (type instanceof DurationDV)
1040: return 0;
1041: return normalized ? minute : unNormMinute;
1042: }
1043:
1044: /* (non-Javadoc)
1045: * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds()
1046: */
1047: public double getSeconds() {
1048: if (type instanceof DurationDV) {
1049: return day * 24 * 60 * 60 + hour * 60 * 60 + minute
1050: * 60 + second;
1051: }
1052: return normalized ? second : unNormSecond;
1053: }
1054:
1055: /* (non-Javadoc)
1056: * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone()
1057: */
1058: public boolean hasTimeZone() {
1059: return utc != 0;
1060: }
1061:
1062: /* (non-Javadoc)
1063: * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours()
1064: */
1065: public int getTimeZoneHours() {
1066: return timezoneHr;
1067: }
1068:
1069: /* (non-Javadoc)
1070: * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes()
1071: */
1072: public int getTimeZoneMinutes() {
1073: return timezoneMin;
1074: }
1075:
1076: /* (non-Javadoc)
1077: * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue()
1078: */
1079: public String getLexicalValue() {
1080: return originalValue;
1081: }
1082:
1083: /* (non-Javadoc)
1084: * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize()
1085: */
1086: public XSDateTime normalize() {
1087: if (!normalized) {
1088: DateTimeData dt = (DateTimeData) this .clone();
1089: dt.normalized = true;
1090: return dt;
1091: }
1092: return this ;
1093: }
1094:
1095: /* (non-Javadoc)
1096: * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized()
1097: */
1098: public boolean isNormalized() {
1099: return normalized;
1100: }
1101:
1102: public Object clone() {
1103: DateTimeData dt = new DateTimeData(this .year, this .month,
1104: this .day, this .hour, this .minute, this .second,
1105: this .utc, this .originalValue, this .normalized,
1106: this .type);
1107: dt.canonical = this .canonical;
1108: dt.position = position;
1109: dt.timezoneHr = this .timezoneHr;
1110: dt.timezoneMin = this .timezoneMin;
1111: dt.unNormYear = this .unNormYear;
1112: dt.unNormMonth = this .unNormMonth;
1113: dt.unNormDay = this .unNormDay;
1114: dt.unNormHour = this .unNormHour;
1115: dt.unNormMinute = this .unNormMinute;
1116: dt.unNormSecond = this .unNormSecond;
1117: return dt;
1118: }
1119:
1120: /* (non-Javadoc)
1121: * @see org.apache.xerces.xs.datatypes.XSDateTime#getXMLGregorianCalendar()
1122: */
1123: public XMLGregorianCalendar getXMLGregorianCalendar() {
1124: return type.getXMLGregorianCalendar(this );
1125: }
1126:
1127: /* (non-Javadoc)
1128: * @see org.apache.xerces.xs.datatypes.XSDateTime#getDuration()
1129: */
1130: public Duration getDuration() {
1131: return type.getDuration(this );
1132: }
1133: }
1134:
1135: protected XMLGregorianCalendar getXMLGregorianCalendar(
1136: DateTimeData data) {
1137: return null;
1138: }
1139:
1140: protected Duration getDuration(DateTimeData data) {
1141: return null;
1142: }
1143: }
|