| /* |
| |
| Derby - Class org.apache.derby.iapi.types.SQLTimestamp |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.iapi.types; |
| |
| import org.apache.derby.iapi.reference.SQLState; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.db.DatabaseContext; |
| |
| import org.apache.derby.iapi.services.io.StoredFormatIds; |
| |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.services.i18n.LocaleFinder; |
| import org.apache.derby.iapi.services.cache.ClassSize; |
| import org.apache.derby.iapi.util.StringUtil; |
| |
| import java.sql.Date; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.PreparedStatement; |
| |
| import java.util.Calendar; |
| import java.util.GregorianCalendar; |
| |
| import java.io.ObjectOutput; |
| import java.io.ObjectInput; |
| import java.io.IOException; |
| |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| |
| /** |
| * This contains an instance of a SQL Timestamp object. |
| * <p> |
| * SQLTimestamp is stored in 3 ints - an encoded date, an encoded time and |
| * nanoseconds |
| * encodedDate = 0 indicates a null WSCTimestamp |
| * |
| * SQLTimestamp is similar to SQLTimestamp, but it does conserves space by not keeping a GregorianCalendar object |
| * |
| * PERFORMANCE OPTIMIZATION: |
| * We only instantiate the value field when required due to the overhead of the |
| * Date methods. |
| * Thus, use isNull() instead of "value == null" and |
| * getTimestamp() instead of using value directly. |
| */ |
| |
| public final class SQLTimestamp extends DataType |
| implements DateTimeDataValue |
| { |
| |
| static final int MAX_FRACTION_DIGITS = 9; // Only nanosecond resolution on conversion to/from strings |
| static final int FRACTION_TO_NANO = 1; // 10**(9 - MAX_FRACTION_DIGITS) |
| |
| static final int ONE_BILLION = 1000000000; |
| |
| private int encodedDate; |
| private int encodedTime; |
| private int nanos; |
| /* |
| ** DataValueDescriptor interface |
| ** (mostly implemented in DataType) |
| */ |
| |
| private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( SQLTimestamp.class); |
| |
| public int estimateMemoryUsage() |
| { |
| int sz = BASE_MEMORY_USAGE; |
| return sz; |
| } // end of estimateMemoryUsage |
| |
| public String getString() |
| { |
| if (!isNull()) |
| { |
| String valueString = getTimestamp((Calendar) null).toString(); |
| /* The java.sql.Timestamp.toString() method is supposed to return a string in |
| * the JDBC escape format. However the JDK 1.3 libraries truncate leading zeros from |
| * the year. This is not acceptable to DB2. So add leading zeros if necessary. |
| */ |
| int separatorIdx = valueString.indexOf('-'); |
| if (separatorIdx >= 0 && separatorIdx < 4) |
| { |
| StringBuffer sb = new StringBuffer(); |
| for( ; separatorIdx < 4; separatorIdx++) |
| sb.append('0'); |
| sb.append(valueString); |
| valueString = sb.toString(); |
| } |
| |
| return valueString; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| getDate returns the date portion of the timestamp |
| Time is set to 00:00:00.0 |
| Since Date is a JDBC object we use the JDBC definition |
| for the time portion. See JDBC API Tutorial, 47.3.12. |
| |
| @exception StandardException thrown on failure |
| */ |
| public Date getDate( Calendar cal) throws StandardException |
| { |
| if (isNull()) |
| return null; |
| |
| if( cal == null) |
| cal = new GregorianCalendar(); |
| |
| // Will clear all the other fields to zero |
| // that we require at zero, specifically |
| // HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND |
| cal.clear(); |
| |
| SQLDate.setDateInCalendar(cal, encodedDate); |
| |
| return new Date(cal.getTimeInMillis()); |
| } |
| |
| /** |
| getTime returns the time portion of the timestamp |
| Date is set to 1970-01-01 |
| Since Time is a JDBC object we use the JDBC definition |
| for the date portion. See JDBC API Tutorial, 47.3.12. |
| @exception StandardException thrown on failure |
| */ |
| public Time getTime( Calendar cal) throws StandardException |
| { |
| if (isNull()) |
| return null; |
| |
| // Derby's SQL TIMESTAMP type supports resolution |
| // to nano-seconds so ensure the Time object |
| // maintains that since it has milli-second |
| // resolutiuon. |
| return SQLTime.getTime(cal, encodedTime, nanos); |
| } |
| |
| public Object getObject() |
| { |
| return getTimestamp((Calendar) null); |
| } |
| |
| /* get storage length */ |
| public int getLength() |
| { |
| return 12; |
| } |
| |
| /* this is for DataType's error generator */ |
| public String getTypeName() |
| { |
| return "TIMESTAMP"; |
| } |
| |
| /* |
| * Storable interface, implies Externalizable, TypedFormat |
| */ |
| |
| /** |
| Return my format identifier. |
| |
| @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId |
| */ |
| public int getTypeFormatId() { |
| return StoredFormatIds.SQL_TIMESTAMP_ID; |
| } |
| |
| /** |
| @exception IOException error writing data |
| |
| */ |
| public void writeExternal(ObjectOutput out) throws IOException { |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(!isNull(), "writeExternal() is not supposed to be called for null values."); |
| |
| /* |
| ** Timestamp is written out 3 ints, encoded date, encoded time, and |
| ** nanoseconds |
| */ |
| out.writeInt(encodedDate); |
| out.writeInt(encodedTime); |
| out.writeInt(nanos); |
| } |
| |
| /** |
| * @see java.io.Externalizable#readExternal |
| * |
| * @exception IOException Thrown on error reading the object |
| */ |
| public void readExternal(ObjectInput in) throws IOException |
| { |
| encodedDate = in.readInt(); |
| encodedTime = in.readInt(); |
| nanos = in.readInt(); |
| } |
| |
| /* |
| * DataValueDescriptor interface |
| */ |
| |
| /** @see DataValueDescriptor#cloneValue */ |
| public DataValueDescriptor cloneValue(boolean forceMaterialization) |
| { |
| // Call constructor with all of our info |
| return new SQLTimestamp(encodedDate, encodedTime, nanos); |
| } |
| |
| /** |
| * @see DataValueDescriptor#getNewNull |
| */ |
| public DataValueDescriptor getNewNull() |
| { |
| return new SQLTimestamp(); |
| } |
| /** |
| * @see org.apache.derby.iapi.services.io.Storable#restoreToNull |
| * |
| */ |
| public void restoreToNull() |
| { |
| // clear numeric representation |
| encodedDate = 0; |
| encodedTime = 0; |
| nanos = 0; |
| |
| } |
| |
| /* |
| * DataValueDescriptor interface |
| */ |
| |
| /** |
| * @see DataValueDescriptor#setValueFromResultSet |
| * |
| * @exception SQLException Thrown on error |
| */ |
| public void setValueFromResultSet(ResultSet resultSet, int colNumber, |
| boolean isNullable) |
| throws SQLException, StandardException |
| { |
| setValue(resultSet.getTimestamp(colNumber), (Calendar) null); |
| } |
| |
| public int compare(DataValueDescriptor other) |
| throws StandardException |
| { |
| /* Use compare method from dominant type, negating result |
| * to reflect flipping of sides. |
| */ |
| if (typePrecedence() < other.typePrecedence()) |
| { |
| return - (other.compare(this)); |
| } |
| |
| boolean thisNull, otherNull; |
| |
| thisNull = this.isNull(); |
| otherNull = other.isNull(); |
| |
| /* |
| * thisNull otherNull return |
| * T T 0 (this == other) |
| * F T -1 (this < other) |
| * T F 1 (this > other) |
| */ |
| if (thisNull || otherNull) |
| { |
| if (!thisNull) // otherNull must be true |
| return -1; |
| if (!otherNull) // thisNull must be true |
| return 1; |
| return 0; |
| } |
| |
| /* |
| Neither are null compare them |
| */ |
| |
| int comparison; |
| /* get the comparison date values */ |
| int otherEncodedDate = 0; |
| int otherEncodedTime = 0; |
| int otherNanos = 0; |
| |
| /* if the argument is another SQLTimestamp, look up the value |
| */ |
| if (other instanceof SQLTimestamp) |
| { |
| SQLTimestamp st = (SQLTimestamp)other; |
| otherEncodedDate= st.encodedDate; |
| otherEncodedTime= st.encodedTime; |
| otherNanos= st.nanos; |
| } |
| else |
| { |
| /* O.K. have to do it the hard way and calculate the numeric value |
| * from the value |
| */ |
| Calendar cal = new GregorianCalendar(); |
| Timestamp otherts = other.getTimestamp(cal); |
| otherEncodedDate = SQLTimestamp.computeEncodedDate(otherts, cal); |
| otherEncodedTime = SQLTimestamp.computeEncodedTime(otherts, cal); |
| otherNanos = otherts.getNanos(); |
| } |
| if (encodedDate < otherEncodedDate) |
| comparison = -1; |
| else if (encodedDate > otherEncodedDate) |
| comparison = 1; |
| else if (encodedTime < otherEncodedTime) |
| comparison = -1; |
| else if (encodedTime > otherEncodedTime) |
| comparison = 1; |
| else if (nanos < otherNanos) |
| comparison = -1; |
| else if (nanos > otherNanos) |
| comparison = 1; |
| else |
| comparison = 0; |
| |
| return comparison; |
| } |
| |
| /** |
| @exception StandardException thrown on error |
| */ |
| public boolean compare(int op, |
| DataValueDescriptor other, |
| boolean orderedNulls, |
| boolean unknownRV) |
| throws StandardException |
| { |
| if (!orderedNulls) // nulls are unordered |
| { |
| if (this.isNull() || ((DataValueDescriptor)other).isNull()) |
| return unknownRV; |
| } |
| |
| /* Do the comparison */ |
| return super.compare(op, other, orderedNulls, unknownRV); |
| } |
| |
| /* |
| ** Class interface |
| */ |
| |
| /* |
| ** Constructors |
| */ |
| |
| /** no-arg constructor required by Formattable */ |
| public SQLTimestamp() { } |
| |
| /** |
| * Create a new SQLTimestamp instance that represents the specified |
| * Timestamp in the time zone of the given Calendar. |
| * @param value the Timestamp value to be represented by this instance |
| * @param cal the time zone of the calendar is used to construct the |
| * database timestamp value |
| * @throws StandardException if an error occurs |
| */ |
| public SQLTimestamp(Timestamp value, Calendar cal) throws StandardException |
| { |
| setValue(value, cal); |
| } |
| |
| /** |
| * Create a new SQLTimestamp instance that represents the specified |
| * Timestamp in the local time zone. |
| * @param value the Timestamp value to be represented by this instance |
| * @throws StandardException if an error occurs |
| */ |
| public SQLTimestamp(Timestamp value) throws StandardException |
| { |
| this(value, (Calendar) null); |
| } |
| |
| SQLTimestamp(int encodedDate, int encodedTime, int nanos) { |
| |
| this.encodedDate = encodedDate; |
| this.encodedTime = encodedTime; |
| this.nanos = nanos; |
| } |
| |
| public SQLTimestamp( DataValueDescriptor date, DataValueDescriptor time) throws StandardException |
| { |
| Calendar cal = null; |
| if( date == null || date.isNull() |
| || time == null || time.isNull()) |
| return; |
| if( date instanceof SQLDate) |
| { |
| SQLDate sqlDate = (SQLDate) date; |
| encodedDate = sqlDate.getEncodedDate(); |
| } |
| else |
| { |
| cal = new GregorianCalendar(); |
| encodedDate = computeEncodedDate( date.getDate( cal), cal); |
| } |
| if( time instanceof SQLTime) |
| { |
| SQLTime sqlTime = (SQLTime) time; |
| encodedTime = sqlTime.getEncodedTime(); |
| } |
| else |
| { |
| if( cal == null) |
| cal = new GregorianCalendar(); |
| encodedTime = computeEncodedTime( time.getTime( cal), cal); |
| } |
| } |
| |
| /** |
| * Construct a timestamp from a string. The allowed formats are: |
| *<ol> |
| *<li>JDBC escape: yyyy-mm-dd hh:mm:ss[.fffff] |
| *<li>IBM: yyyy-mm-dd-hh.mm.ss[.nnnnnn] |
| *</ol> |
| * The format is specified by a parameter to the constructor. Leading zeroes may be omitted from the month, day, |
| * and hour part of the timestamp. The microsecond part may be omitted or truncated. |
| */ |
| public SQLTimestamp( String timestampStr, boolean isJDBCEscape, LocaleFinder localeFinder) |
| throws StandardException |
| { |
| parseTimestamp( timestampStr, isJDBCEscape,localeFinder, (Calendar) null); |
| } |
| |
| /** |
| * Construct a timestamp from a string. The allowed formats are: |
| *<ol> |
| *<li>JDBC escape: yyyy-mm-dd hh:mm:ss[.fffff] |
| *<li>IBM: yyyy-mm-dd-hh.mm.ss[.nnnnnn] |
| *</ol> |
| * The format is specified by a parameter to the constructor. Leading zeroes may be omitted from the month, day, |
| * and hour part of the timestamp. The microsecond part may be omitted or truncated. |
| */ |
| public SQLTimestamp( String timestampStr, boolean isJDBCEscape, LocaleFinder localeFinder, Calendar cal) |
| throws StandardException |
| { |
| parseTimestamp( timestampStr, isJDBCEscape, localeFinder, cal); |
| } |
| |
| static final char DATE_SEPARATOR = '-'; |
| private static final char[] DATE_SEPARATORS = { DATE_SEPARATOR}; |
| private static final char IBM_DATE_TIME_SEPARATOR = '-'; |
| private static final char ODBC_DATE_TIME_SEPARATOR = ' '; |
| private static final char[] DATE_TIME_SEPARATORS = {IBM_DATE_TIME_SEPARATOR, ODBC_DATE_TIME_SEPARATOR}; |
| private static final char[] DATE_TIME_SEPARATORS_OR_END |
| = {IBM_DATE_TIME_SEPARATOR, ODBC_DATE_TIME_SEPARATOR, (char) 0}; |
| private static final char IBM_TIME_SEPARATOR = '.'; |
| private static final char ODBC_TIME_SEPARATOR = ':'; |
| private static final char[] TIME_SEPARATORS = {IBM_TIME_SEPARATOR, ODBC_TIME_SEPARATOR}; |
| private static final char[] TIME_SEPARATORS_OR_END = {IBM_TIME_SEPARATOR, ODBC_TIME_SEPARATOR, (char) 0}; |
| private static final char[] END_OF_STRING = {(char) 0}; |
| |
| private void parseTimestamp( String timestampStr, boolean isJDBCEscape, LocaleFinder localeFinder, Calendar cal) |
| throws StandardException |
| { |
| StandardException thrownSE = null; |
| DateTimeParser parser = new DateTimeParser( timestampStr); |
| try |
| { |
| int[] dateTimeNano = parseDateOrTimestamp( parser, true); |
| encodedDate = dateTimeNano[0]; |
| encodedTime = dateTimeNano[1]; |
| nanos = dateTimeNano[2]; |
| return; |
| } |
| catch( StandardException se) |
| { |
| thrownSE = se; |
| } |
| // see if it is a localized timestamp |
| try |
| { |
| timestampStr = StringUtil.trimTrailing( timestampStr); |
| int[] dateAndTime = parseLocalTimestamp( timestampStr, localeFinder, cal); |
| encodedDate = dateAndTime[0]; |
| encodedTime = dateAndTime[1]; |
| return; |
| } |
| catch( ParseException pe){} |
| catch( StandardException se){} |
| if( thrownSE != null) |
| throw thrownSE; |
| throw StandardException.newException( SQLState.LANG_DATE_SYNTAX_EXCEPTION); |
| } // end of parseTimestamp |
| |
| /** |
| * Parse a localized timestamp. |
| * |
| * @param str the timestamp string, with trailing blanks removed. |
| * @param localeFinder |
| * |
| * @return a {encodedDate, encodedTime} array. |
| * |
| * @exception ParseException If the string is not a valid timestamp. |
| */ |
| static int[] parseLocalTimestamp( String str, LocaleFinder localeFinder, Calendar cal) |
| throws StandardException, ParseException |
| { |
| DateFormat timestampFormat = null; |
| if(localeFinder == null) |
| timestampFormat = DateFormat.getDateTimeInstance(); |
| else if( cal == null) |
| timestampFormat = localeFinder.getTimestampFormat(); |
| else |
| timestampFormat = (DateFormat) localeFinder.getTimestampFormat().clone(); |
| if( cal == null) |
| cal = new GregorianCalendar(); |
| else |
| timestampFormat.setCalendar( cal); |
| java.util.Date date = timestampFormat.parse( str); |
| |
| return new int[] { computeEncodedDate( date, cal), computeEncodedTime( date, cal)}; |
| } // end of parseLocalTimestamp |
| |
| /** |
| * Parse a timestamp or a date. DB2 allows timestamps to be used as dates or times. So |
| * date('2004-04-15-16.15.32') is valid, as is date('2004-04-15'). |
| * |
| * This method does not handle localized timestamps. |
| * |
| * @param parser a DateTimeParser initialized with a string. |
| * @param timeRequired If true then an error will be thrown if the time is missing. If false then the time may |
| * be omitted. |
| * |
| * @return {encodedDate, encodedTime, nanosecond} array. |
| * |
| * @exception StandardException if the syntax is incorrect for an IBM standard timestamp. |
| */ |
| static int[] parseDateOrTimestamp( DateTimeParser parser, boolean timeRequired) |
| throws StandardException |
| { |
| int year = parser.parseInt( 4, false, DATE_SEPARATORS, false); |
| int month = parser.parseInt( 2, true, DATE_SEPARATORS, false); |
| int day = parser.parseInt( 2, true, timeRequired ? DATE_TIME_SEPARATORS : DATE_TIME_SEPARATORS_OR_END, false); |
| int hour = 0; |
| int minute = 0; |
| int second = 0; |
| int nano = 0; |
| if( parser.getCurrentSeparator() != 0) |
| { |
| char timeSeparator = (parser.getCurrentSeparator() == ODBC_DATE_TIME_SEPARATOR) |
| ? ODBC_TIME_SEPARATOR : IBM_TIME_SEPARATOR; |
| hour = parser.parseInt( 2, true, TIME_SEPARATORS, false); |
| if( timeSeparator == parser.getCurrentSeparator()) |
| { |
| minute = parser.parseInt( 2, false, TIME_SEPARATORS, false); |
| if( timeSeparator == parser.getCurrentSeparator()) |
| { |
| second = parser.parseInt( 2, false, TIME_SEPARATORS_OR_END, false); |
| if( parser.getCurrentSeparator() == '.') |
| nano = parser.parseInt( MAX_FRACTION_DIGITS, true, END_OF_STRING, true)*FRACTION_TO_NANO; |
| } |
| } |
| } |
| parser.checkEnd(); |
| return new int[] { SQLDate.computeEncodedDate( year, month, day), |
| SQLTime.computeEncodedTime( hour,minute,second), |
| nano}; |
| } // end of parseDateOrTimestamp |
| |
| /** |
| * Set the value from a correctly typed Timestamp object. |
| * @throws StandardException |
| */ |
| void setObject(Object theValue) throws StandardException |
| { |
| setValue((Timestamp) theValue); |
| } |
| |
| protected void setFrom(DataValueDescriptor theValue) throws StandardException { |
| |
| if (theValue instanceof SQLTimestamp) { |
| restoreToNull(); |
| SQLTimestamp tvst = (SQLTimestamp) theValue; |
| encodedDate = tvst.encodedDate; |
| encodedTime = tvst.encodedTime; |
| nanos = tvst.nanos; |
| } |
| else |
| { |
| Calendar cal = new GregorianCalendar(); |
| setValue(theValue.getTimestamp( cal), cal); |
| } |
| } |
| |
| /** |
| @see DateTimeDataValue#setValue |
| When converting from a date to a timestamp, time is set to 00:00:00.0 |
| |
| */ |
| public void setValue(Date value, Calendar cal) throws StandardException |
| { |
| restoreToNull(); |
| if( value != null) |
| { |
| if( cal == null) |
| cal = new GregorianCalendar(); |
| encodedDate = computeEncodedDate(value, cal); |
| } |
| /* encodedTime and nanos are already set to zero by restoreToNull() */ |
| } |
| |
| |
| /** |
| @see DateTimeDataValue#setValue |
| |
| */ |
| public void setValue(Timestamp value, Calendar cal) |
| throws StandardException |
| { |
| restoreToNull(); |
| setNumericTimestamp(value, cal); |
| } |
| |
| |
| public void setValue(String theValue) |
| throws StandardException |
| { |
| restoreToNull(); |
| |
| if (theValue != null) |
| { |
| DatabaseContext databaseContext = (DatabaseContext) DataValueFactoryImpl.getContext(DatabaseContext.CONTEXT_ID); |
| parseTimestamp( theValue, |
| false, |
| (databaseContext == null) ? null : databaseContext.getDatabase(), |
| (Calendar) null); |
| } |
| /* restoreToNull will have already set the encoded date to 0 (null value) */ |
| } |
| |
| /* |
| ** SQL Operators |
| */ |
| |
| NumberDataValue nullValueInt() { |
| return new SQLInteger(); |
| } |
| |
| NumberDataValue nullValueDouble() { |
| return new SQLDouble(); |
| } |
| |
| /** |
| * @see DateTimeDataValue#getYear |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getYear(NumberDataValue result) |
| throws StandardException |
| { |
| if (isNull()) { |
| return nullValueInt(); |
| } else { |
| return SQLDate.setSource(SQLDate.getYear(encodedDate), result); |
| } |
| } |
| |
| /** |
| * @see DateTimeDataValue#getMonth |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getMonth(NumberDataValue result) |
| throws StandardException |
| { |
| if (isNull()) { |
| return nullValueInt(); |
| } else { |
| return SQLDate.setSource(SQLDate.getMonth(encodedDate), result); |
| } |
| } |
| |
| /** |
| * @see DateTimeDataValue#getDate |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getDate(NumberDataValue result) |
| throws StandardException |
| { |
| if (isNull()) { |
| return nullValueInt(); |
| } else { |
| return SQLDate.setSource(SQLDate.getDay(encodedDate), result); |
| } |
| } |
| |
| /** |
| * @see DateTimeDataValue#getHours |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getHours(NumberDataValue result) |
| throws StandardException |
| { |
| if (isNull()) { |
| return nullValueInt(); |
| } else { |
| return SQLDate.setSource(SQLTime.getHour(encodedTime), result); |
| } |
| } |
| |
| /** |
| * @see DateTimeDataValue#getMinutes |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getMinutes(NumberDataValue result) |
| throws StandardException |
| { |
| if (isNull()) { |
| return nullValueInt(); |
| } else { |
| return SQLDate.setSource(SQLTime.getMinute(encodedTime), result); |
| } |
| } |
| |
| /** |
| * @see DateTimeDataValue#getSeconds |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public NumberDataValue getSeconds(NumberDataValue source) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(source == null || source instanceof SQLDouble, |
| "getSeconds for a timestamp was given a source other than a SQLDouble"); |
| } |
| NumberDataValue result; |
| |
| if (isNull()) { |
| return nullValueDouble(); |
| } |
| |
| if (source != null) |
| result = source; |
| else |
| result = new SQLDouble(); |
| |
| result.setValue((double)(SQLTime.getSecond(encodedTime)) |
| + ((double)nanos)/1.0e9); |
| |
| return result; |
| } |
| |
| /* |
| ** String display of value |
| */ |
| |
| public String toString() |
| { |
| if (isNull()) |
| { |
| return "NULL"; |
| } |
| else |
| { |
| return getTimestamp( (Calendar) null).toString(); |
| } |
| } |
| |
| /* |
| * Hash code |
| */ |
| public int hashCode() |
| { |
| if (isNull()) |
| { |
| return 0; |
| } |
| |
| return encodedDate + encodedTime + nanos; //since 0 is null |
| |
| } |
| |
| /** @see DataValueDescriptor#typePrecedence */ |
| public int typePrecedence() |
| { |
| return TypeId.TIMESTAMP_PRECEDENCE; |
| } |
| |
| /** |
| * Check if the value is null. encodedDate value of 0 is null |
| * |
| * @return Whether or not value is logically null. |
| */ |
| public final boolean isNull() |
| { |
| return (encodedDate == 0); |
| } |
| |
| /** |
| * Get the value field. We instantiate the field |
| * on demand. |
| * |
| * @return The value field. |
| */ |
| public Timestamp getTimestamp(java.util.Calendar cal) |
| { |
| if (isNull()) |
| return null; |
| |
| if (cal == null) |
| cal = new GregorianCalendar(); |
| setCalendar(cal); |
| Timestamp t = new Timestamp(cal.getTimeInMillis()); |
| t.setNanos(nanos); |
| return t; |
| } |
| |
| private void setCalendar(Calendar cal) |
| { |
| cal.clear(); |
| |
| SQLDate.setDateInCalendar(cal, encodedDate); |
| |
| SQLTime.setTimeInCalendar(cal, encodedTime); |
| |
| cal.set(Calendar.MILLISECOND, 0); |
| } // end of setCalendar |
| |
| /** |
| * Set the encoded values for the timestamp |
| * |
| */ |
| private void setNumericTimestamp(Timestamp value, Calendar cal) throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isNull(), "setNumericTimestamp called when already set"); |
| } |
| if (value != null) |
| { |
| if( cal == null) |
| cal = new GregorianCalendar(); |
| encodedDate = computeEncodedDate(value, cal); |
| encodedTime = computeEncodedTime(value, cal); |
| nanos = value.getNanos(); |
| } |
| /* encoded date should already be 0 for null */ |
| } |
| |
| /** |
| computeEncodedDate sets the date in a Calendar object |
| and then uses the SQLDate function to compute an encoded date |
| The encoded date is |
| year << 16 + month << 8 + date |
| @param value the value to convert |
| @return the encodedDate |
| |
| */ |
| private static int computeEncodedDate(java.util.Date value, Calendar currentCal) throws StandardException |
| { |
| if (value == null) |
| return 0; |
| |
| currentCal.setTime(value); |
| return SQLDate.computeEncodedDate(currentCal); |
| } |
| /** |
| computeEncodedTime extracts the hour, minute and seconds from |
| a java.util.Date value and encodes them as |
| hour << 16 + minute << 8 + second |
| using the SQLTime function for encoding the data |
| @param value the value to convert |
| @return the encodedTime |
| |
| */ |
| private static int computeEncodedTime(java.util.Date value, Calendar currentCal) throws StandardException |
| { |
| currentCal.setTime(value); |
| return SQLTime.computeEncodedTime(currentCal); |
| } |
| |
| |
| public void setInto(PreparedStatement ps, int position) throws SQLException, StandardException { |
| |
| ps.setTimestamp(position, getTimestamp((Calendar) null)); |
| } |
| |
| /** |
| * Compute the SQL timestamp function. |
| * |
| * @exception StandardException |
| */ |
| public static DateTimeDataValue computeTimestampFunction( DataValueDescriptor operand, |
| DataValueFactory dvf) throws StandardException |
| { |
| try |
| { |
| if( operand.isNull()) |
| return new SQLTimestamp(); |
| if( operand instanceof SQLTimestamp) |
| return (SQLTimestamp) operand.cloneValue(false); |
| |
| String str = operand.getString(); |
| if( str.length() == 14) |
| { |
| int year = parseDateTimeInteger( str, 0, 4); |
| int month = parseDateTimeInteger( str, 4, 2); |
| int day = parseDateTimeInteger( str, 6, 2); |
| int hour = parseDateTimeInteger( str, 8, 2); |
| int minute = parseDateTimeInteger( str, 10, 2); |
| int second = parseDateTimeInteger( str, 12, 2); |
| return new SQLTimestamp( SQLDate.computeEncodedDate( year, month, day), |
| SQLTime.computeEncodedTime( hour,minute,second), |
| 0); |
| } |
| // else use the standard cast |
| return dvf.getTimestampValue( str, false); |
| } |
| catch( StandardException se) |
| { |
| if( SQLState.LANG_DATE_SYNTAX_EXCEPTION.startsWith( se.getSQLState())) |
| throw StandardException.newException( SQLState.LANG_INVALID_FUNCTION_ARGUMENT, |
| operand.getString(), "timestamp"); |
| throw se; |
| } |
| } // end of computeTimestampFunction |
| |
| static int parseDateTimeInteger( String str, int start, int ndigits) throws StandardException |
| { |
| int end = start + ndigits; |
| int retVal = 0; |
| for( int i = start; i < end; i++) |
| { |
| char c = str.charAt( i); |
| if( !Character.isDigit( c)) |
| throw StandardException.newException( SQLState.LANG_DATE_SYNTAX_EXCEPTION); |
| retVal = 10*retVal + Character.digit( c, 10); |
| } |
| return retVal; |
| } // end of parseDateTimeInteger |
| |
| /** |
| * Add a number of intervals to a datetime value. Implements the JDBC escape TIMESTAMPADD function. |
| * |
| * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL, |
| * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL |
| * @param count The number of intervals to add |
| * @param currentDate Used to convert time to timestamp |
| * @param resultHolder If non-null a DateTimeDataValue that can be used to hold the result. If null then |
| * generate a new holder |
| * |
| * @return startTime + intervalCount intervals, as a timestamp |
| * |
| * @exception StandardException |
| */ |
| public DateTimeDataValue timestampAdd( int intervalType, |
| NumberDataValue count, |
| java.sql.Date currentDate, |
| DateTimeDataValue resultHolder) |
| throws StandardException |
| { |
| if( resultHolder == null) |
| resultHolder = new SQLTimestamp(); |
| SQLTimestamp tsResult = (SQLTimestamp) resultHolder; |
| if( isNull() || count.isNull()) |
| { |
| tsResult.restoreToNull(); |
| return resultHolder; |
| } |
| tsResult.setFrom( this); |
| int intervalCount = count.getInt(); |
| |
| switch( intervalType) |
| { |
| case FRAC_SECOND_INTERVAL: |
| // The interval is nanoseconds. Do the computation in long to avoid overflow. |
| long nanos = this.nanos + intervalCount; |
| if( nanos >= 0 && nanos < ONE_BILLION) |
| tsResult.nanos = (int) nanos; |
| else |
| { |
| int secondsInc = (int)(nanos/ONE_BILLION); |
| if( nanos >= 0) |
| tsResult.nanos = (int) (nanos % ONE_BILLION); |
| else |
| { |
| secondsInc--; |
| nanos -= secondsInc * (long)ONE_BILLION; // 0 <= nanos < ONE_BILLION |
| tsResult.nanos = (int) nanos; |
| } |
| addInternal( Calendar.SECOND, secondsInc, tsResult); |
| } |
| break; |
| |
| case SECOND_INTERVAL: |
| addInternal( Calendar.SECOND, intervalCount, tsResult); |
| break; |
| |
| case MINUTE_INTERVAL: |
| addInternal( Calendar.MINUTE, intervalCount, tsResult); |
| break; |
| |
| case HOUR_INTERVAL: |
| addInternal( Calendar.HOUR, intervalCount, tsResult); |
| break; |
| |
| case DAY_INTERVAL: |
| addInternal( Calendar.DATE, intervalCount, tsResult); |
| break; |
| |
| case WEEK_INTERVAL: |
| addInternal( Calendar.DATE, intervalCount*7, tsResult); |
| break; |
| |
| case MONTH_INTERVAL: |
| addInternal( Calendar.MONTH, intervalCount, tsResult); |
| break; |
| |
| case QUARTER_INTERVAL: |
| addInternal( Calendar.MONTH, intervalCount*3, tsResult); |
| break; |
| |
| case YEAR_INTERVAL: |
| addInternal( Calendar.YEAR, intervalCount, tsResult); |
| break; |
| |
| default: |
| throw StandardException.newException(SQLState.LANG_INVALID_FUNCTION_ARGUMENT, intervalType, |
| "TIMESTAMPADD"); |
| } |
| return tsResult; |
| } // end of timestampAdd |
| |
| private void addInternal( int calIntervalType, int count, SQLTimestamp tsResult) throws StandardException |
| { |
| Calendar cal = new GregorianCalendar(); |
| setCalendar( cal); |
| try |
| { |
| cal.add( calIntervalType, count); |
| tsResult.encodedTime = SQLTime.computeEncodedTime( cal); |
| tsResult.encodedDate = SQLDate.computeEncodedDate( cal); |
| } |
| catch( StandardException se) |
| { |
| String state = se.getSQLState(); |
| if( state != null && state.length() > 0 && SQLState.LANG_DATE_RANGE_EXCEPTION.startsWith( state)) |
| { |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TIMESTAMP"); |
| } |
| throw se; |
| } |
| } // end of addInternal |
| |
| /** |
| * Finds the difference between two datetime values as a number of intervals. Implements the JDBC |
| * TIMESTAMPDIFF escape function. |
| * |
| * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL, |
| * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL |
| * @param time1 |
| * @param currentDate Used to convert time to timestamp |
| * @param resultHolder If non-null a NumberDataValue that can be used to hold the result. If null then |
| * generate a new holder |
| * |
| * @return the number of intervals by which this datetime is greater than time1 |
| * |
| * @exception StandardException |
| */ |
| public NumberDataValue timestampDiff( int intervalType, |
| DateTimeDataValue time1, |
| java.sql.Date currentDate, |
| NumberDataValue resultHolder) |
| throws StandardException |
| { |
| if( resultHolder == null) |
| resultHolder = new SQLLongint(); |
| |
| if( isNull() || time1.isNull()) |
| { |
| resultHolder.setToNull(); |
| return resultHolder; |
| } |
| |
| SQLTimestamp ts1 = promote( time1, currentDate); |
| |
| /* Years, months, and quarters are difficult because their lengths are not constant. |
| * The other intervals are relatively easy (because we ignore leap seconds). |
| */ |
| Calendar cal = new GregorianCalendar(); |
| setCalendar( cal); |
| long thisInSeconds = cal.getTime().getTime()/1000; |
| ts1.setCalendar( cal); |
| long ts1InSeconds = cal.getTime().getTime()/1000; |
| long secondsDiff = thisInSeconds - ts1InSeconds; |
| int nanosDiff = nanos - ts1.nanos; |
| // Normalize secondsDiff and nanosDiff so that they are both <= 0 or both >= 0. |
| if( nanosDiff < 0 && secondsDiff > 0) |
| { |
| secondsDiff--; |
| nanosDiff += ONE_BILLION; |
| } |
| else if( nanosDiff > 0 && secondsDiff < 0) |
| { |
| secondsDiff++; |
| nanosDiff -= ONE_BILLION; |
| } |
| long ldiff = 0; |
| |
| switch( intervalType) |
| { |
| case FRAC_SECOND_INTERVAL: |
| ldiff = secondsDiff*ONE_BILLION + nanosDiff; |
| break; |
| |
| case SECOND_INTERVAL: |
| ldiff = secondsDiff; |
| break; |
| |
| case MINUTE_INTERVAL: |
| ldiff = secondsDiff/60; |
| break; |
| |
| case HOUR_INTERVAL: |
| ldiff = secondsDiff/(60*60); |
| break; |
| |
| case DAY_INTERVAL: |
| ldiff = secondsDiff/(24*60*60); |
| break; |
| |
| case WEEK_INTERVAL: |
| ldiff = secondsDiff/(7*24*60*60); |
| break; |
| |
| case QUARTER_INTERVAL: |
| case MONTH_INTERVAL: |
| // Make a conservative guess and increment until we overshoot. |
| if( Math.abs( secondsDiff) > 366*24*60*60) // Certainly more than a year |
| ldiff = 12*(secondsDiff/(366*24*60*60)); |
| else |
| ldiff = secondsDiff/(31*24*60*60); |
| if( secondsDiff >= 0) |
| { |
| if (ldiff >= Integer.MAX_VALUE) |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); |
| // cal holds the time for time1 |
| cal.add( Calendar.MONTH, (int) (ldiff + 1)); |
| for(;;) |
| { |
| if( cal.getTime().getTime()/1000 > thisInSeconds) |
| break; |
| cal.add( Calendar.MONTH, 1); |
| ldiff++; |
| } |
| } |
| else |
| { |
| if (ldiff <= Integer.MIN_VALUE) |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); |
| // cal holds the time for time1 |
| cal.add( Calendar.MONTH, (int) (ldiff - 1)); |
| for(;;) |
| { |
| if( cal.getTime().getTime()/1000 < thisInSeconds) |
| break; |
| cal.add( Calendar.MONTH, -1); |
| ldiff--; |
| } |
| } |
| if( intervalType == QUARTER_INTERVAL) |
| ldiff = ldiff/3; |
| break; |
| |
| case YEAR_INTERVAL: |
| // Make a conservative guess and increment until we overshoot. |
| ldiff = secondsDiff/(366*24*60*60); |
| if( secondsDiff >= 0) |
| { |
| if (ldiff >= Integer.MAX_VALUE) |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); |
| // cal holds the time for time1 |
| cal.add( Calendar.YEAR, (int) (ldiff + 1)); |
| for(;;) |
| { |
| if( cal.getTime().getTime()/1000 > thisInSeconds) |
| break; |
| cal.add( Calendar.YEAR, 1); |
| ldiff++; |
| } |
| } |
| else |
| { |
| if (ldiff <= Integer.MIN_VALUE) |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); |
| // cal holds the time for time1 |
| cal.add( Calendar.YEAR, (int) (ldiff - 1)); |
| for(;;) |
| { |
| if( cal.getTime().getTime()/1000 < thisInSeconds) |
| break; |
| cal.add( Calendar.YEAR, -1); |
| ldiff--; |
| } |
| } |
| break; |
| |
| default: |
| throw StandardException.newException(SQLState.LANG_INVALID_FUNCTION_ARGUMENT, intervalType, |
| "TIMESTAMPDIFF"); |
| } |
| resultHolder.setValue(ldiff); |
| return resultHolder; |
| } // end of timestampDiff |
| |
| /** |
| * Promotes a DateTimeDataValue to a timestamp. |
| * |
| * |
| * @return the corresponding timestamp, using the current date if datetime is a time, |
| * or time 00:00:00 if datetime is a date. |
| * |
| * @exception StandardException |
| */ |
| static SQLTimestamp promote( DateTimeDataValue dateTime, java.sql.Date currentDate) throws StandardException |
| { |
| if( dateTime instanceof SQLTimestamp) |
| return (SQLTimestamp) dateTime; |
| else if( dateTime instanceof SQLTime) |
| return new SQLTimestamp( SQLDate.computeEncodedDate( currentDate, (Calendar) null), |
| ((SQLTime) dateTime).getEncodedTime(), |
| 0 /* nanoseconds */); |
| else if( dateTime instanceof SQLDate) |
| return new SQLTimestamp( ((SQLDate) dateTime).getEncodedDate(), 0, 0); |
| else |
| return new SQLTimestamp( dateTime.getTimestamp( new GregorianCalendar())); |
| } // end of promote |
| } |