0001: /*
0002:
0003: Derby - Class org.apache.derby.iapi.types.SQLTimestamp
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.iapi.types;
0023:
0024: import org.apache.derby.iapi.reference.SQLState;
0025:
0026: import org.apache.derby.iapi.services.io.ArrayInputStream;
0027:
0028: import org.apache.derby.iapi.error.StandardException;
0029: import org.apache.derby.iapi.db.DatabaseContext;
0030:
0031: import org.apache.derby.iapi.types.DataValueDescriptor;
0032: import org.apache.derby.iapi.types.TypeId;
0033:
0034: import org.apache.derby.iapi.types.NumberDataValue;
0035: import org.apache.derby.iapi.types.DateTimeDataValue;
0036:
0037: import org.apache.derby.iapi.services.io.StoredFormatIds;
0038: import org.apache.derby.iapi.services.context.ContextService;
0039:
0040: import org.apache.derby.iapi.services.sanity.SanityManager;
0041: import org.apache.derby.iapi.types.DataType;
0042: import org.apache.derby.iapi.services.i18n.LocaleFinder;
0043: import org.apache.derby.iapi.services.cache.ClassSize;
0044: import org.apache.derby.iapi.util.StringUtil;
0045: import org.apache.derby.iapi.util.ReuseFactory;
0046:
0047: import org.apache.derby.iapi.types.SQLDouble;
0048: import org.apache.derby.iapi.types.SQLTime;
0049:
0050: import java.sql.Date;
0051: import java.sql.Time;
0052: import java.sql.Timestamp;
0053: import java.sql.Types;
0054: import java.sql.ResultSet;
0055: import java.sql.SQLException;
0056: import java.sql.PreparedStatement;
0057:
0058: import java.util.Calendar;
0059: import java.util.GregorianCalendar;
0060:
0061: import java.io.ObjectOutput;
0062: import java.io.ObjectInput;
0063: import java.io.IOException;
0064:
0065: import java.text.DateFormat;
0066: import java.text.ParseException;
0067:
0068: /**
0069: * This contains an instance of a SQL Timestamp object.
0070: * <p>
0071: * SQLTimestamp is stored in 3 ints - an encoded date, an encoded time and
0072: * nanoseconds
0073: * encodedDate = 0 indicates a null WSCTimestamp
0074: *
0075: * SQLTimestamp is similar to SQLTimestamp, but it does conserves space by not keeping a GregorianCalendar object
0076: *
0077: * PERFORMANCE OPTIMIZATION:
0078: * We only instantiate the value field when required due to the overhead of the
0079: * Date methods.
0080: * Thus, use isNull() instead of "value == null" and
0081: * getTimestamp() instead of using value directly.
0082: */
0083:
0084: public final class SQLTimestamp extends DataType implements
0085: DateTimeDataValue {
0086:
0087: static final int MAX_FRACTION_DIGITS = 6; // Only microsecond resolution on conversion to/from strings
0088: static final int FRACTION_TO_NANO = 1000; // 10**(9 - MAX_FRACTION_DIGITS)
0089:
0090: static final int ONE_BILLION = 1000000000;
0091:
0092: private int encodedDate;
0093: private int encodedTime;
0094: private int nanos;
0095:
0096: // The cached value.toString()
0097: private String valueString;
0098:
0099: /*
0100: ** DataValueDescriptor interface
0101: ** (mostly implemented in DataType)
0102: */
0103:
0104: private static final int BASE_MEMORY_USAGE = ClassSize
0105: .estimateBaseFromCatalog(SQLTimestamp.class);
0106:
0107: public int estimateMemoryUsage() {
0108: int sz = BASE_MEMORY_USAGE
0109: + ClassSize.estimateMemoryUsage(valueString);
0110: return sz;
0111: } // end of estimateMemoryUsage
0112:
0113: public String getString() {
0114: if (!isNull()) {
0115: if (valueString == null) {
0116: valueString = getTimestamp((Calendar) null).toString();
0117: /* The java.sql.Timestamp.toString() method is supposed to return a string in
0118: * the JDBC escape format. However the JDK 1.3 libraries truncate leading zeros from
0119: * the year. This is not acceptable to DB2. So add leading zeros if necessary.
0120: */
0121: int separatorIdx = valueString.indexOf('-');
0122: if (separatorIdx >= 0 && separatorIdx < 4) {
0123: StringBuffer sb = new StringBuffer();
0124: for (; separatorIdx < 4; separatorIdx++)
0125: sb.append('0');
0126: sb.append(valueString);
0127: valueString = sb.toString();
0128: }
0129: }
0130:
0131: return valueString;
0132: } else {
0133: if (SanityManager.DEBUG) {
0134: if (valueString != null) {
0135: SanityManager
0136: .THROWASSERT("valueString expected to be null, not "
0137: + valueString);
0138: }
0139: }
0140: return null;
0141: }
0142: }
0143:
0144: /**
0145: getDate returns the date portion of the timestamp
0146: Time is set to 00:00:00.0
0147: Since Date is a JDBC object we use the JDBC definition
0148: for the time portion. See JDBC API Tutorial, 47.3.12.
0149:
0150: @exception StandardException thrown on failure
0151: */
0152: public Date getDate(Calendar cal) throws StandardException {
0153: if (isNull())
0154: return null;
0155: return newDate(cal);
0156: }
0157:
0158: private Date newDate(java.util.Calendar cal)
0159: throws StandardException {
0160: if (cal == null)
0161: cal = new GregorianCalendar();
0162: cal.clear();
0163: cal.set(Calendar.YEAR, SQLDate.getYear(encodedDate));
0164: cal.set(Calendar.MONTH, SQLDate.getMonth(encodedDate) - 1);
0165: cal.set(Calendar.DATE, SQLDate.getDay(encodedDate));
0166: cal.set(Calendar.HOUR_OF_DAY, 0);
0167: cal.set(Calendar.MINUTE, 0);
0168: cal.set(Calendar.SECOND, 0);
0169: cal.set(Calendar.MILLISECOND, 0);
0170: return new Date(cal.getTime().getTime());
0171: }
0172:
0173: /**
0174: getTime returns the time portion of the timestamp
0175: Date is set to 1970-01-01
0176: Since Time is a JDBC object we use the JDBC definition
0177: for the date portion. See JDBC API Tutorial, 47.3.12.
0178: @exception StandardException thrown on failure
0179: */
0180: public Time getTime(Calendar cal) throws StandardException {
0181: if (isNull())
0182: return null;
0183: return newTime(cal);
0184: }
0185:
0186: private Time newTime(java.util.Calendar cal)
0187: throws StandardException {
0188: if (cal == null)
0189: cal = new GregorianCalendar();
0190: cal.clear();
0191: cal.set(Calendar.YEAR, 1970);
0192: cal.set(Calendar.MONTH, Calendar.JANUARY);
0193: cal.set(Calendar.DATE, 1);
0194: cal.set(Calendar.HOUR_OF_DAY, SQLTime.getHour(encodedTime));
0195: cal.set(Calendar.MINUTE, SQLTime.getMinute(encodedTime));
0196: cal.set(Calendar.SECOND, SQLTime.getSecond(encodedTime));
0197: cal.set(Calendar.MILLISECOND, (int) (nanos / 1000000));
0198: return new Time(cal.getTime().getTime());
0199: }
0200:
0201: public Object getObject() {
0202: return getTimestamp((Calendar) null);
0203: }
0204:
0205: /* get storage length */
0206: public int getLength() {
0207: return 12;
0208: }
0209:
0210: /* this is for DataType's error generator */
0211: public String getTypeName() {
0212: return "TIMESTAMP";
0213: }
0214:
0215: /*
0216: * Storable interface, implies Externalizable, TypedFormat
0217: */
0218:
0219: /**
0220: Return my format identifier.
0221:
0222: @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId
0223: */
0224: public int getTypeFormatId() {
0225: return StoredFormatIds.SQL_TIMESTAMP_ID;
0226: }
0227:
0228: /**
0229: @exception IOException error writing data
0230:
0231: */
0232: public void writeExternal(ObjectOutput out) throws IOException {
0233:
0234: if (SanityManager.DEBUG)
0235: SanityManager
0236: .ASSERT(!isNull(),
0237: "writeExternal() is not supposed to be called for null values.");
0238:
0239: /*
0240: ** Timestamp is written out 3 ints, encoded date, encoded time, and
0241: ** nanoseconds
0242: */
0243: out.writeInt(encodedDate);
0244: out.writeInt(encodedTime);
0245: out.writeInt(nanos);
0246: }
0247:
0248: /**
0249: * @see java.io.Externalizable#readExternal
0250: *
0251: * @exception IOException Thrown on error reading the object
0252: */
0253: public void readExternal(ObjectInput in) throws IOException {
0254: encodedDate = in.readInt();
0255: encodedTime = in.readInt();
0256: nanos = in.readInt();
0257: // reset cached values
0258: valueString = null;
0259: }
0260:
0261: public void readExternalFromArray(ArrayInputStream in)
0262: throws IOException {
0263: encodedDate = in.readInt();
0264: encodedTime = in.readInt();
0265: nanos = in.readInt();
0266: // reset cached values
0267: valueString = null;
0268: }
0269:
0270: /*
0271: * DataValueDescriptor interface
0272: */
0273:
0274: /** @see DataValueDescriptor#getClone */
0275: public DataValueDescriptor getClone() {
0276: // Call constructor with all of our info
0277: return new SQLTimestamp(encodedDate, encodedTime, nanos);
0278: }
0279:
0280: /**
0281: * @see DataValueDescriptor#getNewNull
0282: */
0283: public DataValueDescriptor getNewNull() {
0284: return new SQLTimestamp();
0285: }
0286:
0287: /**
0288: * @see org.apache.derby.iapi.services.io.Storable#restoreToNull
0289: *
0290: */
0291: public void restoreToNull() {
0292: // clear numeric representation
0293: encodedDate = 0;
0294: encodedTime = 0;
0295: nanos = 0;
0296:
0297: // clear cached valueString
0298: valueString = null;
0299: }
0300:
0301: /*
0302: * DataValueDescriptor interface
0303: */
0304:
0305: /**
0306: * @see DataValueDescriptor#setValueFromResultSet
0307: *
0308: * @exception SQLException Thrown on error
0309: */
0310: public void setValueFromResultSet(ResultSet resultSet,
0311: int colNumber, boolean isNullable) throws SQLException,
0312: StandardException {
0313: setValue(resultSet.getTimestamp(colNumber), (Calendar) null);
0314: }
0315:
0316: public int compare(DataValueDescriptor other)
0317: throws StandardException {
0318: /* Use compare method from dominant type, negating result
0319: * to reflect flipping of sides.
0320: */
0321: if (typePrecedence() < other.typePrecedence()) {
0322: return -(other.compare(this ));
0323: }
0324:
0325: boolean this Null, otherNull;
0326:
0327: this Null = this .isNull();
0328: otherNull = other.isNull();
0329:
0330: /*
0331: * thisNull otherNull return
0332: * T T 0 (this == other)
0333: * F T -1 (this < other)
0334: * T F 1 (this > other)
0335: */
0336: if (this Null || otherNull) {
0337: if (!this Null) // otherNull must be true
0338: return -1;
0339: if (!otherNull) // thisNull must be true
0340: return 1;
0341: return 0;
0342: }
0343:
0344: /*
0345: Neither are null compare them
0346: */
0347:
0348: int comparison;
0349: /* get the comparison date values */
0350: int otherEncodedDate = 0;
0351: int otherEncodedTime = 0;
0352: int otherNanos = 0;
0353:
0354: /* if the argument is another SQLTimestamp, look up the value
0355: */
0356: if (other instanceof SQLTimestamp) {
0357: SQLTimestamp st = (SQLTimestamp) other;
0358: otherEncodedDate = st.encodedDate;
0359: otherEncodedTime = st.encodedTime;
0360: otherNanos = st.nanos;
0361: } else {
0362: /* O.K. have to do it the hard way and calculate the numeric value
0363: * from the value
0364: */
0365: Calendar cal = new GregorianCalendar();
0366: Timestamp otherts = other.getTimestamp(cal);
0367: otherEncodedDate = SQLTimestamp.computeEncodedDate(otherts,
0368: cal);
0369: otherEncodedTime = SQLTimestamp.computeEncodedTime(otherts,
0370: cal);
0371: otherNanos = otherts.getNanos();
0372: }
0373: if (encodedDate < otherEncodedDate)
0374: comparison = -1;
0375: else if (encodedDate > otherEncodedDate)
0376: comparison = 1;
0377: else if (encodedTime < otherEncodedTime)
0378: comparison = -1;
0379: else if (encodedTime > otherEncodedTime)
0380: comparison = 1;
0381: else if (nanos < otherNanos)
0382: comparison = -1;
0383: else if (nanos > otherNanos)
0384: comparison = 1;
0385: else
0386: comparison = 0;
0387:
0388: return comparison;
0389: }
0390:
0391: /**
0392: @exception StandardException thrown on error
0393: */
0394: public boolean compare(int op, DataValueDescriptor other,
0395: boolean orderedNulls, boolean unknownRV)
0396: throws StandardException {
0397: if (!orderedNulls) // nulls are unordered
0398: {
0399: if (this .isNull() || ((DataValueDescriptor) other).isNull())
0400: return unknownRV;
0401: }
0402:
0403: /* Do the comparison */
0404: return super .compare(op, other, orderedNulls, unknownRV);
0405: }
0406:
0407: /*
0408: ** Class interface
0409: */
0410:
0411: /*
0412: ** Constructors
0413: */
0414:
0415: /** no-arg constructor required by Formattable */
0416: public SQLTimestamp() {
0417: }
0418:
0419: public SQLTimestamp(Timestamp value) throws StandardException {
0420: setValue(value, (Calendar) null);
0421: }
0422:
0423: SQLTimestamp(int encodedDate, int encodedTime, int nanos) {
0424:
0425: this .encodedDate = encodedDate;
0426: this .encodedTime = encodedTime;
0427: this .nanos = nanos;
0428: }
0429:
0430: public SQLTimestamp(DataValueDescriptor date,
0431: DataValueDescriptor time) throws StandardException {
0432: Calendar cal = null;
0433: if (date == null || date.isNull() || time == null
0434: || time.isNull())
0435: return;
0436: if (date instanceof SQLDate) {
0437: SQLDate sqlDate = (SQLDate) date;
0438: encodedDate = sqlDate.getEncodedDate();
0439: } else {
0440: cal = new GregorianCalendar();
0441: encodedDate = computeEncodedDate(date.getDate(cal), cal);
0442: }
0443: if (time instanceof SQLTime) {
0444: SQLTime sqlTime = (SQLTime) time;
0445: encodedTime = sqlTime.getEncodedTime();
0446: } else {
0447: if (cal == null)
0448: cal = new GregorianCalendar();
0449: encodedTime = computeEncodedTime(time.getTime(cal), cal);
0450: }
0451: }
0452:
0453: /**
0454: * Construct a timestamp from a string. The allowed formats are:
0455: *<ol>
0456: *<li>JDBC escape: yyyy-mm-dd hh:mm:ss[.fffff]
0457: *<li>IBM: yyyy-mm-dd-hh.mm.ss[.nnnnnn]
0458: *</ol>
0459: * The format is specified by a parameter to the constructor. Leading zeroes may be omitted from the month, day,
0460: * and hour part of the timestamp. The microsecond part may be omitted or truncated.
0461: */
0462: public SQLTimestamp(String timestampStr, boolean isJDBCEscape,
0463: LocaleFinder localeFinder) throws StandardException {
0464: parseTimestamp(timestampStr, isJDBCEscape, localeFinder,
0465: (Calendar) null);
0466: }
0467:
0468: /**
0469: * Construct a timestamp from a string. The allowed formats are:
0470: *<ol>
0471: *<li>JDBC escape: yyyy-mm-dd hh:mm:ss[.fffff]
0472: *<li>IBM: yyyy-mm-dd-hh.mm.ss[.nnnnnn]
0473: *</ol>
0474: * The format is specified by a parameter to the constructor. Leading zeroes may be omitted from the month, day,
0475: * and hour part of the timestamp. The microsecond part may be omitted or truncated.
0476: */
0477: public SQLTimestamp(String timestampStr, boolean isJDBCEscape,
0478: LocaleFinder localeFinder, Calendar cal)
0479: throws StandardException {
0480: parseTimestamp(timestampStr, isJDBCEscape, localeFinder, cal);
0481: }
0482:
0483: static final char DATE_SEPARATOR = '-';
0484: private static final char[] DATE_SEPARATORS = { DATE_SEPARATOR };
0485: private static final char IBM_DATE_TIME_SEPARATOR = '-';
0486: private static final char ODBC_DATE_TIME_SEPARATOR = ' ';
0487: private static final char[] DATE_TIME_SEPARATORS = {
0488: IBM_DATE_TIME_SEPARATOR, ODBC_DATE_TIME_SEPARATOR };
0489: private static final char[] DATE_TIME_SEPARATORS_OR_END = {
0490: IBM_DATE_TIME_SEPARATOR, ODBC_DATE_TIME_SEPARATOR, (char) 0 };
0491: private static final char IBM_TIME_SEPARATOR = '.';
0492: private static final char ODBC_TIME_SEPARATOR = ':';
0493: private static final char[] TIME_SEPARATORS = { IBM_TIME_SEPARATOR,
0494: ODBC_TIME_SEPARATOR };
0495: private static final char[] TIME_SEPARATORS_OR_END = {
0496: IBM_TIME_SEPARATOR, ODBC_TIME_SEPARATOR, (char) 0 };
0497: private static final char[] END_OF_STRING = { (char) 0 };
0498:
0499: private void parseTimestamp(String timestampStr,
0500: boolean isJDBCEscape, LocaleFinder localeFinder,
0501: Calendar cal) throws StandardException {
0502: StandardException thrownSE = null;
0503: DateTimeParser parser = new DateTimeParser(timestampStr);
0504: try {
0505: int[] dateTimeNano = parseDateOrTimestamp(parser, true);
0506: encodedDate = dateTimeNano[0];
0507: encodedTime = dateTimeNano[1];
0508: nanos = dateTimeNano[2];
0509: valueString = parser.getTrimmedString();
0510: return;
0511: } catch (StandardException se) {
0512: thrownSE = se;
0513: }
0514: // see if it is a localized timestamp
0515: try {
0516: timestampStr = StringUtil.trimTrailing(timestampStr);
0517: int[] dateAndTime = parseLocalTimestamp(timestampStr,
0518: localeFinder, cal);
0519: encodedDate = dateAndTime[0];
0520: encodedTime = dateAndTime[1];
0521: valueString = timestampStr;
0522: return;
0523: } catch (ParseException pe) {
0524: } catch (StandardException se) {
0525: }
0526: if (thrownSE != null)
0527: throw thrownSE;
0528: throw StandardException
0529: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
0530: } // end of parseTimestamp
0531:
0532: /**
0533: * Parse a localized timestamp.
0534: *
0535: * @param str the timestamp string, with trailing blanks removed.
0536: * @param localeFinder
0537: *
0538: * @return a {encodedDate, encodedTime} array.
0539: *
0540: * @exception ParseException If the string is not a valid timestamp.
0541: */
0542: static int[] parseLocalTimestamp(String str,
0543: LocaleFinder localeFinder, Calendar cal)
0544: throws StandardException, ParseException {
0545: DateFormat timestampFormat = null;
0546: if (localeFinder == null)
0547: timestampFormat = DateFormat.getDateTimeInstance();
0548: else if (cal == null)
0549: timestampFormat = localeFinder.getTimestampFormat();
0550: else
0551: timestampFormat = (DateFormat) localeFinder
0552: .getTimestampFormat().clone();
0553: if (cal == null)
0554: cal = new GregorianCalendar();
0555: else
0556: timestampFormat.setCalendar(cal);
0557: java.util.Date date = timestampFormat.parse(str);
0558:
0559: return new int[] { computeEncodedDate(date, cal),
0560: computeEncodedTime(date, cal) };
0561: } // end of parseLocalTimestamp
0562:
0563: /**
0564: * Parse a timestamp or a date. DB2 allows timestamps to be used as dates or times. So
0565: * date('2004-04-15-16.15.32') is valid, as is date('2004-04-15').
0566: *
0567: * This method does not handle localized timestamps.
0568: *
0569: * @param parser a DateTimeParser initialized with a string.
0570: * @param timeRequired If true then an error will be thrown if the time is missing. If false then the time may
0571: * be omitted.
0572: *
0573: * @return {encodedDate, encodedTime, nanosecond} array.
0574: *
0575: * @exception StandardException if the syntax is incorrect for an IBM standard timestamp.
0576: */
0577: static int[] parseDateOrTimestamp(DateTimeParser parser,
0578: boolean timeRequired) throws StandardException {
0579: int year = parser.parseInt(4, false, DATE_SEPARATORS, false);
0580: int month = parser.parseInt(2, true, DATE_SEPARATORS, false);
0581: int day = parser.parseInt(2, true,
0582: timeRequired ? DATE_TIME_SEPARATORS
0583: : DATE_TIME_SEPARATORS_OR_END, false);
0584: int hour = 0;
0585: int minute = 0;
0586: int second = 0;
0587: int nano = 0;
0588: if (parser.getCurrentSeparator() != 0) {
0589: char timeSeparator = (parser.getCurrentSeparator() == ODBC_DATE_TIME_SEPARATOR) ? ODBC_TIME_SEPARATOR
0590: : IBM_TIME_SEPARATOR;
0591: hour = parser.parseInt(2, true, TIME_SEPARATORS, false);
0592: if (timeSeparator == parser.getCurrentSeparator()) {
0593: minute = parser.parseInt(2, false, TIME_SEPARATORS,
0594: false);
0595: if (timeSeparator == parser.getCurrentSeparator()) {
0596: second = parser.parseInt(2, false,
0597: TIME_SEPARATORS_OR_END, false);
0598: if (parser.getCurrentSeparator() == '.')
0599: nano = parser.parseInt(MAX_FRACTION_DIGITS,
0600: true, END_OF_STRING, true)
0601: * FRACTION_TO_NANO;
0602: }
0603: }
0604: }
0605: parser.checkEnd();
0606: return new int[] {
0607: SQLDate.computeEncodedDate(year, month, day),
0608: SQLTime.computeEncodedTime(hour, minute, second), nano };
0609: } // end of parseDateOrTimestamp
0610:
0611: /**
0612: * Set the value from a correctly typed Timestamp object.
0613: * @throws StandardException
0614: */
0615: void setObject(Object theValue) throws StandardException {
0616: setValue((Timestamp) theValue);
0617: }
0618:
0619: protected void setFrom(DataValueDescriptor theValue)
0620: throws StandardException {
0621:
0622: if (theValue instanceof SQLTimestamp) {
0623: restoreToNull();
0624: SQLTimestamp tvst = (SQLTimestamp) theValue;
0625: encodedDate = tvst.encodedDate;
0626: encodedTime = tvst.encodedTime;
0627: nanos = tvst.nanos;
0628: } else {
0629: Calendar cal = new GregorianCalendar();
0630: setValue(theValue.getTimestamp(cal), cal);
0631: }
0632: }
0633:
0634: /**
0635: @see DateTimeDataValue#setValue
0636: When converting from a date to a timestamp, time is set to 00:00:00.0
0637:
0638: */
0639: public void setValue(Date value, Calendar cal)
0640: throws StandardException {
0641: restoreToNull();
0642: if (value != null) {
0643: if (cal == null)
0644: cal = new GregorianCalendar();
0645: encodedDate = computeEncodedDate(value, cal);
0646: }
0647: /* encodedTime and nanos are already set to zero by restoreToNull() */
0648: }
0649:
0650: /**
0651: @see DateTimeDataValue#setValue
0652:
0653: */
0654: public void setValue(Time value, Calendar cal)
0655: throws StandardException {
0656: restoreToNull();
0657: if (value != null) {
0658: /*
0659: ** Create a new timestamp with today's date,
0660: ** and 'value' time.
0661: **
0662: ** We create a new calendar to get today's date
0663: */
0664: Calendar today = GregorianCalendar.getInstance();
0665: encodedDate = SQLDate.computeEncodedDate(today);
0666: if (cal == null)
0667: cal = today;
0668: encodedTime = computeEncodedTime(value, cal);
0669: }
0670: }
0671:
0672: /**
0673: @see DateTimeDataValue#setValue
0674:
0675: */
0676: public void setValue(Timestamp value, Calendar cal)
0677: throws StandardException {
0678: restoreToNull();
0679: setNumericTimestamp(value, cal);
0680: }
0681:
0682: public void setValue(String theValue) throws StandardException {
0683: restoreToNull();
0684:
0685: if (theValue != null) {
0686: DatabaseContext databaseContext = (DatabaseContext) ContextService
0687: .getContext(DatabaseContext.CONTEXT_ID);
0688: parseTimestamp(theValue, false,
0689: (databaseContext == null) ? null : databaseContext
0690: .getDatabase(), (Calendar) null);
0691: }
0692: /* restoreToNull will have already set the encoded date to 0 (null value) */
0693: }
0694:
0695: /*
0696: ** SQL Operators
0697: */
0698:
0699: /**
0700: * @see DateTimeDataValue#getYear
0701: *
0702: * @exception StandardException Thrown on error
0703: */
0704: public NumberDataValue getYear(NumberDataValue result)
0705: throws StandardException {
0706: if (SanityManager.DEBUG) {
0707: SanityManager.ASSERT(!isNull(), "getYear called on a null");
0708: }
0709: return SQLDate.setSource(SQLDate.getYear(encodedDate), result);
0710: }
0711:
0712: /**
0713: * @see DateTimeDataValue#getMonth
0714: *
0715: * @exception StandardException Thrown on error
0716: */
0717: public NumberDataValue getMonth(NumberDataValue result)
0718: throws StandardException {
0719: if (SanityManager.DEBUG) {
0720: SanityManager
0721: .ASSERT(!isNull(), "getMonth called on a null");
0722: }
0723: return SQLDate.setSource(SQLDate.getMonth(encodedDate), result);
0724: }
0725:
0726: /**
0727: * @see DateTimeDataValue#getDate
0728: *
0729: * @exception StandardException Thrown on error
0730: */
0731: public NumberDataValue getDate(NumberDataValue result)
0732: throws StandardException {
0733: if (SanityManager.DEBUG) {
0734: SanityManager.ASSERT(!isNull(), "getDate called on a null");
0735: }
0736: return SQLDate.setSource(SQLDate.getDay(encodedDate), result);
0737: }
0738:
0739: /**
0740: * @see DateTimeDataValue#getHours
0741: *
0742: * @exception StandardException Thrown on error
0743: */
0744: public NumberDataValue getHours(NumberDataValue result)
0745: throws StandardException {
0746: if (SanityManager.DEBUG) {
0747: SanityManager
0748: .ASSERT(!isNull(), "getHours called on a null");
0749: }
0750: return SQLDate.setSource(SQLTime.getHour(encodedTime), result);
0751: }
0752:
0753: /**
0754: * @see DateTimeDataValue#getMinutes
0755: *
0756: * @exception StandardException Thrown on error
0757: */
0758: public NumberDataValue getMinutes(NumberDataValue result)
0759: throws StandardException {
0760: if (SanityManager.DEBUG) {
0761: SanityManager.ASSERT(!isNull(),
0762: "getMinute called on a null");
0763: }
0764: return SQLDate
0765: .setSource(SQLTime.getMinute(encodedTime), result);
0766: }
0767:
0768: /**
0769: * @see DateTimeDataValue#getSeconds
0770: *
0771: * @exception StandardException Thrown on error
0772: */
0773: public NumberDataValue getSeconds(NumberDataValue source)
0774: throws StandardException {
0775: if (SanityManager.DEBUG) {
0776: SanityManager.ASSERT(!isNull(),
0777: "getSeconds called on a null");
0778: SanityManager
0779: .ASSERT(source == null
0780: || source instanceof SQLDouble,
0781: "getSeconds for a timestamp was given a source other than a SQLDouble");
0782: }
0783: NumberDataValue result;
0784:
0785: if (source != null)
0786: result = source;
0787: else
0788: result = new SQLDouble();
0789:
0790: result.setValue((double) (SQLTime.getSecond(encodedTime))
0791: + ((double) nanos) / 1.0e9);
0792:
0793: return result;
0794: }
0795:
0796: /*
0797: ** String display of value
0798: */
0799:
0800: public String toString() {
0801: if (isNull()) {
0802: return "NULL";
0803: } else {
0804: return getTimestamp((Calendar) null).toString();
0805: }
0806: }
0807:
0808: /*
0809: * Hash code
0810: */
0811: public int hashCode() {
0812: if (isNull()) {
0813: return 0;
0814: }
0815:
0816: return encodedDate + encodedTime + nanos; //since 0 is null
0817:
0818: }
0819:
0820: /** @see DataValueDescriptor#typePrecedence */
0821: public int typePrecedence() {
0822: return TypeId.TIMESTAMP_PRECEDENCE;
0823: }
0824:
0825: /**
0826: * Check if the value is null. encodedDate value of 0 is null
0827: *
0828: * @return Whether or not value is logically null.
0829: */
0830: public final boolean isNull() {
0831: return (encodedDate == 0);
0832: }
0833:
0834: /**
0835: * Get the value field. We instantiate the field
0836: * on demand.
0837: *
0838: * @return The value field.
0839: */
0840: public Timestamp getTimestamp(java.util.Calendar cal) {
0841: if (isNull())
0842: return null;
0843: return newTimestamp(cal);
0844: }
0845:
0846: private Timestamp newTimestamp(Calendar currentCal) {
0847: if (currentCal == null)
0848: currentCal = new GregorianCalendar();
0849: setCalendar(currentCal);
0850: Timestamp t = new Timestamp(currentCal.getTime().getTime());
0851: t.setNanos(nanos);
0852: return t;
0853: }
0854:
0855: private void setCalendar(Calendar cal) {
0856: cal.clear();
0857: cal.set(Calendar.YEAR, SQLDate.getYear(encodedDate));
0858: /* Note calendar month is zero based so we subtract 1*/
0859: cal.set(Calendar.MONTH, (SQLDate.getMonth(encodedDate) - 1));
0860: cal.set(Calendar.DATE, SQLDate.getDay(encodedDate));
0861: cal.set(Calendar.HOUR_OF_DAY, SQLTime.getHour(encodedTime));
0862: cal.set(Calendar.MINUTE, SQLTime.getMinute(encodedTime));
0863: cal.set(Calendar.SECOND, SQLTime.getSecond(encodedTime));
0864: cal.set(Calendar.MILLISECOND, 0);
0865: } // end of setCalendar
0866:
0867: /**
0868: * Set the encoded values for the timestamp
0869: *
0870: */
0871: private void setNumericTimestamp(Timestamp value, Calendar cal)
0872: throws StandardException {
0873: if (SanityManager.DEBUG) {
0874: SanityManager.ASSERT(isNull(),
0875: "setNumericTimestamp called when already set");
0876: }
0877: if (value != null) {
0878: if (cal == null)
0879: cal = new GregorianCalendar();
0880: encodedDate = computeEncodedDate(value, cal);
0881: encodedTime = computeEncodedTime(value, cal);
0882: nanos = value.getNanos();
0883: }
0884: /* encoded date should already be 0 for null */
0885: }
0886:
0887: // International Support
0888:
0889: /**
0890: * International version of getString(). Overrides getNationalString
0891: * in DataType for date, time, and timestamp.
0892: *
0893: * @exception StandardException Thrown on error
0894: */
0895: protected String getNationalString(LocaleFinder localeFinder)
0896: throws StandardException {
0897: if (isNull()) {
0898: return getString();
0899: }
0900:
0901: return localeFinder.getTimestampFormat().format(
0902: getTimestamp((Calendar) null));
0903: }
0904:
0905: /**
0906: computeEncodedDate sets the date in a Calendar object
0907: and then uses the SQLDate function to compute an encoded date
0908: The encoded date is
0909: year << 16 + month << 8 + date
0910: @param value the value to convert
0911: @return the encodedDate
0912:
0913: */
0914: private static int computeEncodedDate(java.util.Date value,
0915: Calendar currentCal) throws StandardException {
0916: if (value == null)
0917: return 0;
0918:
0919: currentCal.setTime(value);
0920: return SQLDate.computeEncodedDate(currentCal);
0921: }
0922:
0923: /**
0924: computeEncodedTime extracts the hour, minute and seconds from
0925: a java.util.Date value and encodes them as
0926: hour << 16 + minute << 8 + second
0927: using the SQLTime function for encoding the data
0928: @param value the value to convert
0929: @return the encodedTime
0930:
0931: */
0932: private static int computeEncodedTime(java.util.Date value,
0933: Calendar currentCal) throws StandardException {
0934: currentCal.setTime(value);
0935: return SQLTime.computeEncodedTime(currentCal);
0936: }
0937:
0938: public void setInto(PreparedStatement ps, int position)
0939: throws SQLException, StandardException {
0940:
0941: ps.setTimestamp(position, getTimestamp((Calendar) null));
0942: }
0943:
0944: /**
0945: * Compute the SQL timestamp function.
0946: *
0947: * @exception StandardException
0948: */
0949: public static DateTimeDataValue computeTimestampFunction(
0950: DataValueDescriptor operand, DataValueFactory dvf)
0951: throws StandardException {
0952: try {
0953: if (operand.isNull())
0954: return new SQLTimestamp();
0955: if (operand instanceof SQLTimestamp)
0956: return (SQLTimestamp) operand.getClone();
0957:
0958: String str = operand.getString();
0959: if (str.length() == 14) {
0960: int year = parseDateTimeInteger(str, 0, 4);
0961: int month = parseDateTimeInteger(str, 4, 2);
0962: int day = parseDateTimeInteger(str, 6, 2);
0963: int hour = parseDateTimeInteger(str, 8, 2);
0964: int minute = parseDateTimeInteger(str, 10, 2);
0965: int second = parseDateTimeInteger(str, 12, 2);
0966: return new SQLTimestamp(SQLDate.computeEncodedDate(
0967: year, month, day), SQLTime.computeEncodedTime(
0968: hour, minute, second), 0);
0969: }
0970: // else use the standard cast
0971: return dvf.getTimestampValue(str, false);
0972: } catch (StandardException se) {
0973: if (SQLState.LANG_DATE_SYNTAX_EXCEPTION.startsWith(se
0974: .getSQLState()))
0975: throw StandardException.newException(
0976: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
0977: operand.getString(), "timestamp");
0978: throw se;
0979: }
0980: } // end of computeTimestampFunction
0981:
0982: static int parseDateTimeInteger(String str, int start, int ndigits)
0983: throws StandardException {
0984: int end = start + ndigits;
0985: int retVal = 0;
0986: for (int i = start; i < end; i++) {
0987: char c = str.charAt(i);
0988: if (!Character.isDigit(c))
0989: throw StandardException
0990: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
0991: retVal = 10 * retVal + Character.digit(c, 10);
0992: }
0993: return retVal;
0994: } // end of parseDateTimeInteger
0995:
0996: /**
0997: * Add a number of intervals to a datetime value. Implements the JDBC escape TIMESTAMPADD function.
0998: *
0999: * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL,
1000: * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL
1001: * @param count The number of intervals to add
1002: * @param currentDate Used to convert time to timestamp
1003: * @param resultHolder If non-null a DateTimeDataValue that can be used to hold the result. If null then
1004: * generate a new holder
1005: *
1006: * @return startTime + intervalCount intervals, as a timestamp
1007: *
1008: * @exception StandardException
1009: */
1010: public DateTimeDataValue timestampAdd(int intervalType,
1011: NumberDataValue count, java.sql.Date currentDate,
1012: DateTimeDataValue resultHolder) throws StandardException {
1013: if (resultHolder == null)
1014: resultHolder = new SQLTimestamp();
1015: SQLTimestamp tsResult = (SQLTimestamp) resultHolder;
1016: if (isNull() || count.isNull()) {
1017: tsResult.restoreToNull();
1018: return resultHolder;
1019: }
1020: tsResult.setFrom(this );
1021: int intervalCount = count.getInt();
1022:
1023: switch (intervalType) {
1024: case FRAC_SECOND_INTERVAL:
1025: // The interval is nanoseconds. Do the computation in long to avoid overflow.
1026: long nanos = this .nanos + intervalCount;
1027: if (nanos >= 0 && nanos < ONE_BILLION)
1028: tsResult.nanos = (int) nanos;
1029: else {
1030: int secondsInc = (int) (nanos / ONE_BILLION);
1031: if (nanos >= 0)
1032: tsResult.nanos = (int) (nanos % ONE_BILLION);
1033: else {
1034: secondsInc--;
1035: nanos -= secondsInc * (long) ONE_BILLION; // 0 <= nanos < ONE_BILLION
1036: tsResult.nanos = (int) nanos;
1037: }
1038: addInternal(Calendar.SECOND, secondsInc, tsResult);
1039: }
1040: break;
1041:
1042: case SECOND_INTERVAL:
1043: addInternal(Calendar.SECOND, intervalCount, tsResult);
1044: break;
1045:
1046: case MINUTE_INTERVAL:
1047: addInternal(Calendar.MINUTE, intervalCount, tsResult);
1048: break;
1049:
1050: case HOUR_INTERVAL:
1051: addInternal(Calendar.HOUR, intervalCount, tsResult);
1052: break;
1053:
1054: case DAY_INTERVAL:
1055: addInternal(Calendar.DATE, intervalCount, tsResult);
1056: break;
1057:
1058: case WEEK_INTERVAL:
1059: addInternal(Calendar.DATE, intervalCount * 7, tsResult);
1060: break;
1061:
1062: case MONTH_INTERVAL:
1063: addInternal(Calendar.MONTH, intervalCount, tsResult);
1064: break;
1065:
1066: case QUARTER_INTERVAL:
1067: addInternal(Calendar.MONTH, intervalCount * 3, tsResult);
1068: break;
1069:
1070: case YEAR_INTERVAL:
1071: addInternal(Calendar.YEAR, intervalCount, tsResult);
1072: break;
1073:
1074: default:
1075: throw StandardException.newException(
1076: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
1077: ReuseFactory.getInteger(intervalType),
1078: "TIMESTAMPADD");
1079: }
1080: return tsResult;
1081: } // end of timestampAdd
1082:
1083: private void addInternal(int calIntervalType, int count,
1084: SQLTimestamp tsResult) throws StandardException {
1085: Calendar cal = new GregorianCalendar();
1086: setCalendar(cal);
1087: try {
1088: cal.add(calIntervalType, count);
1089: tsResult.encodedTime = SQLTime.computeEncodedTime(cal);
1090: tsResult.encodedDate = SQLDate.computeEncodedDate(cal);
1091: } catch (StandardException se) {
1092: String state = se.getSQLState();
1093: if (state != null
1094: && state.length() > 0
1095: && SQLState.LANG_DATE_RANGE_EXCEPTION
1096: .startsWith(state)) {
1097: throw StandardException.newException(
1098: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1099: "TIMESTAMP");
1100: }
1101: throw se;
1102: }
1103: } // end of addInternal
1104:
1105: /**
1106: * Finds the difference between two datetime values as a number of intervals. Implements the JDBC
1107: * TIMESTAMPDIFF escape function.
1108: *
1109: * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL,
1110: * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL
1111: * @param time1
1112: * @param currentDate Used to convert time to timestamp
1113: * @param resultHolder If non-null a NumberDataValue that can be used to hold the result. If null then
1114: * generate a new holder
1115: *
1116: * @return the number of intervals by which this datetime is greater than time1
1117: *
1118: * @exception StandardException
1119: */
1120: public NumberDataValue timestampDiff(int intervalType,
1121: DateTimeDataValue time1, java.sql.Date currentDate,
1122: NumberDataValue resultHolder) throws StandardException {
1123: if (resultHolder == null)
1124: resultHolder = new SQLInteger();
1125:
1126: if (isNull() || time1.isNull()) {
1127: resultHolder.setToNull();
1128: return resultHolder;
1129: }
1130:
1131: SQLTimestamp ts1 = promote(time1, currentDate);
1132:
1133: /* Years, months, and quarters are difficult because their lengths are not constant.
1134: * The other intervals are relatively easy (because we ignore leap seconds).
1135: */
1136: Calendar cal = new GregorianCalendar();
1137: setCalendar(cal);
1138: long this InSeconds = cal.getTime().getTime() / 1000;
1139: ts1.setCalendar(cal);
1140: long ts1InSeconds = cal.getTime().getTime() / 1000;
1141: long secondsDiff = this InSeconds - ts1InSeconds;
1142: int nanosDiff = nanos - ts1.nanos;
1143: // Normalize secondsDiff and nanosDiff so that they are both <= 0 or both >= 0.
1144: if (nanosDiff < 0 && secondsDiff > 0) {
1145: secondsDiff--;
1146: nanosDiff += ONE_BILLION;
1147: } else if (nanosDiff > 0 && secondsDiff < 0) {
1148: secondsDiff++;
1149: nanosDiff -= ONE_BILLION;
1150: }
1151: long ldiff = 0;
1152:
1153: switch (intervalType) {
1154: case FRAC_SECOND_INTERVAL:
1155: if (secondsDiff > Integer.MAX_VALUE / ONE_BILLION
1156: || secondsDiff < Integer.MIN_VALUE / ONE_BILLION)
1157: throw StandardException.newException(
1158: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1159: "INTEGER");
1160: ldiff = secondsDiff * ONE_BILLION + nanosDiff;
1161: break;
1162:
1163: case SECOND_INTERVAL:
1164: ldiff = secondsDiff;
1165: break;
1166:
1167: case MINUTE_INTERVAL:
1168: ldiff = secondsDiff / 60;
1169: break;
1170:
1171: case HOUR_INTERVAL:
1172: ldiff = secondsDiff / (60 * 60);
1173: break;
1174:
1175: case DAY_INTERVAL:
1176: ldiff = secondsDiff / (24 * 60 * 60);
1177: break;
1178:
1179: case WEEK_INTERVAL:
1180: ldiff = secondsDiff / (7 * 24 * 60 * 60);
1181: break;
1182:
1183: case QUARTER_INTERVAL:
1184: case MONTH_INTERVAL:
1185: // Make a conservative guess and increment until we overshoot.
1186: if (Math.abs(secondsDiff) > 366 * 24 * 60 * 60) // Certainly more than a year
1187: ldiff = 12 * (secondsDiff / (366 * 24 * 60 * 60));
1188: else
1189: ldiff = secondsDiff / (31 * 24 * 60 * 60);
1190: if (secondsDiff >= 0) {
1191: if (ldiff >= Integer.MAX_VALUE)
1192: throw StandardException.newException(
1193: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1194: "INTEGER");
1195: // cal holds the time for time1
1196: cal.add(Calendar.MONTH, (int) (ldiff + 1));
1197: for (;;) {
1198: if (cal.getTime().getTime() / 1000 > this InSeconds)
1199: break;
1200: cal.add(Calendar.MONTH, 1);
1201: ldiff++;
1202: }
1203: } else {
1204: if (ldiff <= Integer.MIN_VALUE)
1205: throw StandardException.newException(
1206: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1207: "INTEGER");
1208: // cal holds the time for time1
1209: cal.add(Calendar.MONTH, (int) (ldiff - 1));
1210: for (;;) {
1211: if (cal.getTime().getTime() / 1000 < this InSeconds)
1212: break;
1213: cal.add(Calendar.MONTH, -1);
1214: ldiff--;
1215: }
1216: }
1217: if (intervalType == QUARTER_INTERVAL)
1218: ldiff = ldiff / 3;
1219: break;
1220:
1221: case YEAR_INTERVAL:
1222: // Make a conservative guess and increment until we overshoot.
1223: ldiff = secondsDiff / (366 * 24 * 60 * 60);
1224: if (secondsDiff >= 0) {
1225: if (ldiff >= Integer.MAX_VALUE)
1226: throw StandardException.newException(
1227: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1228: "INTEGER");
1229: // cal holds the time for time1
1230: cal.add(Calendar.YEAR, (int) (ldiff + 1));
1231: for (;;) {
1232: if (cal.getTime().getTime() / 1000 > this InSeconds)
1233: break;
1234: cal.add(Calendar.YEAR, 1);
1235: ldiff++;
1236: }
1237: } else {
1238: if (ldiff <= Integer.MIN_VALUE)
1239: throw StandardException.newException(
1240: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1241: "INTEGER");
1242: // cal holds the time for time1
1243: cal.add(Calendar.YEAR, (int) (ldiff - 1));
1244: for (;;) {
1245: if (cal.getTime().getTime() / 1000 < this InSeconds)
1246: break;
1247: cal.add(Calendar.YEAR, -1);
1248: ldiff--;
1249: }
1250: }
1251: break;
1252:
1253: default:
1254: throw StandardException.newException(
1255: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
1256: ReuseFactory.getInteger(intervalType),
1257: "TIMESTAMPDIFF");
1258: }
1259: if (ldiff > Integer.MAX_VALUE || ldiff < Integer.MIN_VALUE)
1260: throw StandardException
1261: .newException(
1262: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1263: "INTEGER");
1264: resultHolder.setValue((int) ldiff);
1265: return resultHolder;
1266: } // end of timestampDiff
1267:
1268: /**
1269: * Promotes a DateTimeDataValue to a timestamp.
1270: *
1271: *
1272: * @return the corresponding timestamp, using the current date if datetime is a time,
1273: * or time 00:00:00 if datetime is a date.
1274: *
1275: * @exception StandardException
1276: */
1277: static SQLTimestamp promote(DateTimeDataValue dateTime,
1278: java.sql.Date currentDate) throws StandardException {
1279: if (dateTime instanceof SQLTimestamp)
1280: return (SQLTimestamp) dateTime;
1281: else if (dateTime instanceof SQLTime)
1282: return new SQLTimestamp(SQLDate.computeEncodedDate(
1283: currentDate, (Calendar) null), ((SQLTime) dateTime)
1284: .getEncodedTime(), 0 /* nanoseconds */);
1285: else if (dateTime instanceof SQLDate)
1286: return new SQLTimestamp(((SQLDate) dateTime)
1287: .getEncodedDate(), 0, 0);
1288: else
1289: return new SQLTimestamp(dateTime
1290: .getTimestamp(new GregorianCalendar()));
1291: } // end of promote
1292: }
|