/*

   Derby - Class org.apache.derby.iapi.types.SQLTime

   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.shared.common.reference.SQLState;

import org.apache.derby.iapi.services.context.ContextService;

import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.io.StoredFormatIds;

import org.apache.derby.shared.common.error.StandardException;

import org.apache.derby.iapi.db.DatabaseContext;

import org.apache.derby.shared.common.i18n.LocaleFinder;
import org.apache.derby.iapi.services.cache.ClassSize;
import org.apache.derby.iapi.util.StringUtil;

import java.sql.Time;
import java.sql.Timestamp;
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.sql.ResultSet;
import java.sql.SQLException;

import java.text.DateFormat;
import java.text.ParseException;

/**
 * This contains an instance of a SQL Time
 * Our current implementation doesn't implement time precision so the fractional
 * seconds portion of the time is always 0.  The default when no time precision
 * is specified is 0 fractional seconds.  A SQL Time without timezone information
 * is assumed to be in the local time zone.  The local time is stored as is
 * and doesn't change if the timezone changes. This is in conformance with the
 * SQL99 standard.  The SQL92 standard indicates that the time is in GMT and
 * changes with the timezone.  The SQL99 standard clarifies this to allow time without
 * timezoned to be stored as the local time.
 * <p>
 * Time is stored as two ints.  The first int represents hour, minute, second 
 * and the second represents fractional seconds (currently 0 since we don't support
 * time precision)
 * 	encodedTime = -1 indicates null
 *
 * PERFORMANCE OPTIMIZATION:
 *	The java.sql.Time object is only instantiated on demand for performance
 * 	reasons.
 */

public final class SQLTime extends DataType
						implements DateTimeDataValue
{

	private int		encodedTime;
	private int		encodedTimeFraction; //currently always 0 since we don't
											 //support time precision

	/*
	** DataValueDescriptor interface
	** (mostly implemented in DataType)
	*/

    private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( SQLTime.class);

    public int estimateMemoryUsage()
    {
        return BASE_MEMORY_USAGE;
    } // end of estimateMemoryUsage

	public String getString()
	{
		if (!isNull())
		{
			return encodedTimeToString(encodedTime);
		}
		else
		{
			return null;
		}
	}

    int getEncodedTime()
    {
        return encodedTime;
    }

	/**
     * Convert a SQL TIME to a JDBC java.sql.Timestamp.
     * 
     * Behaviour is to set the date portion of the Timestamp
     * to the actual current date, which may not match the
     * SQL CURRENT DATE, which remains fixed for the lifetime
     * of a SQL statement. JDBC drivers (especially network client drivers)
     * could not be expected to fetch the CURRENT_DATE SQL value
     * on every query that involved a TIME value, so the current
     * date as seen by the JDBC client was picked as the logical behaviour.
     * See DERBY-1811.
	 */
	public Timestamp getTimestamp( Calendar cal)
	{
		if (isNull())
			return null;
		else
		{
            if( cal == null)
            {
                // Calendar initialized to current date and time.
                cal = new GregorianCalendar(); 
            }
            else
            {
                cal.clear();
                // Set Calendar to current date and time
                // to pick up the current date. Time portion
                // will be overridden by this value's time.
                cal.setTimeInMillis(System.currentTimeMillis());
            }
            
            SQLTime.setTimeInCalendar(cal, encodedTime);
          
            // Derby's resolution for the TIME type is only seconds.
			cal.set(Calendar.MILLISECOND, 0);
            
			return new Timestamp(cal.getTimeInMillis());
		}
	}

	public Object getObject()
	{
		return getTime( (Calendar) null);
	}
		
	public int getLength()
	{
		return 8;
	}

	/* this is for DataType's error generator */
	public String getTypeName()
	{
		return "TIME";
	}


	/*
	 * Storable interface, implies Externalizable, TypedFormat
	 */

	/**
		Return my format identifier.

		@see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId
	*/
	public int getTypeFormatId() {
		return StoredFormatIds.SQL_TIME_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.");

		out.writeInt(encodedTime);
		out.writeInt(encodedTimeFraction);
	}

	/**
	 * @see java.io.Externalizable#readExternal
	 *
	 * @exception IOException	Thrown on error reading the object
	 */
	public void readExternal(ObjectInput in) throws IOException
	{
		encodedTime = in.readInt();
		encodedTimeFraction = in.readInt();
	}

	/*
	 * DataValueDescriptor interface
	 */

	/** @see DataValueDescriptor#cloneValue */
	public DataValueDescriptor cloneValue(boolean forceMaterialization)
	{
		// Call constructor with all of our info
		return new SQLTime(encodedTime, encodedTimeFraction);
	}

	/**
	 * @see DataValueDescriptor#getNewNull
	 */
	public DataValueDescriptor getNewNull()
	{
		return new SQLTime();
	}
	/**
	 * @see org.apache.derby.iapi.services.io.Storable#restoreToNull
	 *
	 */

	public void restoreToNull()
	{
		encodedTime = -1;
		encodedTimeFraction = 0;
	}

	/*
	 * DataValueDescriptor interface
	 */

	/** 
	 * @see DataValueDescriptor#setValueFromResultSet 
	 *
	 * @exception SQLException		Thrown on error
	 */
	public void setValueFromResultSet(ResultSet resultSet, int colNumber,
									  boolean isNullable)
		throws SQLException, StandardException
	{
			restoreToNull();
			encodedTime = computeEncodedTime(resultSet.getTime(colNumber));
			//need to set encodedTimeFraction when we implement time precision
	}

	/**
	 * Orderable interface
	 *
	 *
	 * @see org.apache.derby.iapi.types.Orderable
	 *
	 * @exception StandardException thrown on failure
	 */
	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 time values */
		int otherEncodedTime = 0;

		/* if the argument is another Time look up the value
		 * we have already taken care of Null
		 * ignoring encodedTimeFraction for now since it is always 0
		 * - need to change this when we support TIME(precision)
		 */
		if (other instanceof SQLTime)
		{
			otherEncodedTime=((SQLTime)other).encodedTime;
		}
		else 
		{
			/* O.K. have to do it the hard way and calculate the numeric value
			 * from the value
			 */
			otherEncodedTime = computeEncodedTime(other.getTime( (Calendar) null));
		}
		if (encodedTime < otherEncodedTime)
			comparison = -1;
		else if (encodedTime > otherEncodedTime)
			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() || other.isNull())
				return unknownRV;
		}

		/* Do the comparison */
		return super.compare(op, other, orderedNulls, unknownRV);
	}

	/*
	** Class interface
	*/

	/*
	** Constructors
	*/

	/** no-arg constructor required by Formattable */
	public SQLTime() 
	{ 
		encodedTime = -1;	//null value
	}

	public SQLTime(Time value) throws StandardException
	{
		parseTime(value);
	}

    private void parseTime(java.util.Date value) throws StandardException
	{
		encodedTime = computeEncodedTime(value);
	}

	private SQLTime(int encodedTime, int encodedTimeFraction) {
		this.encodedTime = encodedTime;
		this.encodedTimeFraction = encodedTimeFraction;
	}


    /**
     * Construct a time from a string. The allowed time formats are:
     *<ol>
     *<li>old ISO and IBM European standard: hh.mm[.ss]
     *<li>IBM USA standard: hh[:mm] {AM | PM}
     *<li>JIS and current ISO: hh:mm[:ss]
     *</ol>
     * 
     * @exception StandardException if the syntax is invalid or the value is
     * out of range
     */
    public SQLTime( String timeStr, boolean isJdbcEscape, LocaleFinder localeFinder)
        throws StandardException
    {
        parseTime( timeStr, isJdbcEscape, localeFinder, (Calendar) null);
    }
    
    /**
     * Construct a time from a string. The allowed time formats are:
     *<ol>
     *<li>old ISO and IBM European standard: hh.mm[.ss]
     *<li>IBM USA standard: hh[:mm] {AM | PM}
     *<li>JIS and current ISO: hh:mm[:ss]
     *</ol>
     * 
     * @exception StandardException if the syntax is invalid or the value is
     * out of range
     */
    public SQLTime( String timeStr, boolean isJdbcEscape, LocaleFinder localeFinder, Calendar cal)
        throws StandardException
    {
        parseTime( timeStr, isJdbcEscape, localeFinder, cal);
    }

    private static final char IBM_EUR_SEPARATOR = '.';
    private static final char[] IBM_EUR_SEPARATOR_OR_END = {IBM_EUR_SEPARATOR, (char) 0};
    static final char JIS_SEPARATOR = ':';
    private static final char[] US_OR_JIS_MINUTE_END = {JIS_SEPARATOR, ' ', (char) 0};
    private static final char[] ANY_SEPARATOR = { '.', ':', ' '};
    private static final String[] AM_PM = {"AM", "PM"};
    private static final char[] END_OF_STRING = {(char) 0};
    
    private void parseTime( String timeStr, boolean isJdbcEscape, LocaleFinder localeFinder, Calendar cal)
        throws StandardException
    {
        boolean validSyntax = true;
        DateTimeParser parser = new DateTimeParser( timeStr);
        StandardException thrownSE = null;
        int hour = 0;
        int minute = 0;
        int second = 0;
        int amPm = -1;
        try
        {
            if( parser.nextSeparator() == SQLTimestamp.DATE_SEPARATOR)
            {
                    encodedTime = SQLTimestamp.parseDateOrTimestamp( parser, true)[1];
                    return;
            }
            hour = parser.parseInt( 2, true, ANY_SEPARATOR, false);
            switch( parser.getCurrentSeparator())
            {
            case IBM_EUR_SEPARATOR:
                if( isJdbcEscape)
                {
                    validSyntax = false;
                    break;
                }
                minute = parser.parseInt( 2, false, IBM_EUR_SEPARATOR_OR_END, false);
                if( parser.getCurrentSeparator() == IBM_EUR_SEPARATOR)
                    second = parser.parseInt( 2, false, END_OF_STRING, false);
                break;

            case ':':
                // IBM USA or JIS (new ISO)
                minute = parser.parseInt( 2, false, US_OR_JIS_MINUTE_END, false);
                switch( parser.getCurrentSeparator())
                {
                case ' ':
                    // IBM USA with minutes
                    if( isJdbcEscape)
                    {
                        validSyntax = false;
                        break;
                    }
                    amPm = parser.parseChoice( AM_PM);
                    parser.checkEnd();
                    break;

                case JIS_SEPARATOR:
                    second = parser.parseInt( 2, false, END_OF_STRING, false);
                    break;

                    // default is end of string, meaning that the seconds part is zero.
                }
                break;

            case ' ':
                // IBM USA with minutes omitted
                if( isJdbcEscape)
                {
                    validSyntax = false;
                    break;
                }
                amPm = parser.parseChoice( AM_PM);
                break;

            default:
                validSyntax = false;
            }
        }
        catch( StandardException se)
        {
            validSyntax = false;
            thrownSE = se;
        }
        if( validSyntax)
        {
            if( amPm == 0) // AM
            {
                if( hour == 12)
                {
                    if( minute == 0 && second == 0)
                        hour = 24;
                    else
                        hour = 0;
                }
                else if( hour > 12)
                    throw StandardException.newException( SQLState.LANG_DATE_RANGE_EXCEPTION);
            }
            else if( amPm == 1) // PM
            {
                if( hour < 12)
                    hour += 12;
                else if( hour > 12)
                    throw StandardException.newException( SQLState.LANG_DATE_RANGE_EXCEPTION);
            }
            parser.checkEnd();
            encodedTime = computeEncodedTime( hour, minute, second);
        }
        else
        {
            // See if it is a localized time or timestamp
            timeStr = StringUtil.trimTrailing( timeStr);
            DateFormat timeFormat = null;
            if(localeFinder == null)
                timeFormat = DateFormat.getTimeInstance();
            else if( cal == null)
                timeFormat = localeFinder.getTimeFormat();
            else
                timeFormat = (DateFormat) localeFinder.getTimeFormat().clone();
            if( cal != null)
                timeFormat.setCalendar( cal);
            try
            {
                encodedTime = computeEncodedTime( timeFormat.parse( timeStr), cal);
            }
            catch( ParseException pe)
            {
                // Maybe it is a localized timestamp
                try
                {
                    encodedTime = SQLTimestamp.parseLocalTimestamp( timeStr, localeFinder, cal)[1];
                }
                catch( ParseException pe2)
                {
                    if( thrownSE != null)
                        throw thrownSE;
                    throw StandardException.newException( SQLState.LANG_DATE_SYNTAX_EXCEPTION);
                }
            }
        }
    } // end of parseTime

	/**
	 * Set the value from a correctly typed Time object.
	 * @throws StandardException 
	 */
	void setObject(Object theValue) throws StandardException
	{
		setValue((Time) theValue);
	}
    
	protected void setFrom(DataValueDescriptor theValue) throws StandardException {

		if (theValue instanceof SQLTime) {
			restoreToNull();

			SQLTime tvst = (SQLTime) theValue;
			encodedTime = tvst.encodedTime;
			encodedTimeFraction = tvst.encodedTimeFraction;

		}
        else
        {
            Calendar cal = new GregorianCalendar();
			setValue(theValue.getTime( cal), cal);
        }
	}

	/**
		@see DateTimeDataValue#setValue

		@exception StandardException thrown on failure.
	 */
	public void setValue(Time value, Calendar cal) throws StandardException
	{
		restoreToNull();
		encodedTime = computeEncodedTime(value, cal);
	}

	/**
		@see DateTimeDataValue#setValue

		@exception StandardException thrown on failure.
	 */
	public void setValue(Timestamp value, Calendar cal) throws StandardException
	{
		restoreToNull();
		encodedTime = computeEncodedTime(value, cal);
	}


	public void setValue(String theValue)
	    throws StandardException
	{
		restoreToNull();
		if (theValue != null)
        {
            DatabaseContext databaseContext = (DatabaseContext) DataValueFactoryImpl.getContext(DatabaseContext.CONTEXT_ID);
            parseTime( theValue,
                       false,
                       (databaseContext == null) ? null : databaseContext.getDatabase(),
                       (Calendar) null);
        }
	}

	/*
	** SQL Operators
	*/

    NumberDataValue nullValueInt() {
        return new SQLInteger();
    }

	/**
	 * @see DateTimeDataValue#getYear
	 * 
	 * @exception StandardException		Thrown on error
	 */
	public NumberDataValue getYear(NumberDataValue result)
							throws StandardException
	{
		throw StandardException.newException(SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, 
						"getYear", "Time");
	}

	/**
	 * @see DateTimeDataValue#getMonth
	 * 
	 * @exception StandardException		Thrown on error
	 */
	public NumberDataValue getMonth(NumberDataValue result)
							throws StandardException
	{
		throw StandardException.newException(SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, 
						"getMonth", "Time");
	}

	/**
	 * @see DateTimeDataValue#getDate
	 * 
	 * @exception StandardException		Thrown on error
	 */
	public NumberDataValue getDate(NumberDataValue result)
							throws StandardException
	{
		throw StandardException.newException(SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, 
						"getDate", "Time");
	}

	/**
	 * @see DateTimeDataValue#getHours
	 * 
	 * @exception StandardException		Thrown on error
	 */
	public NumberDataValue getHours(NumberDataValue result)
							throws StandardException
	{
        if (isNull()) {
            return nullValueInt();
        } else {    
            return SQLDate.setSource(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(getMinute(encodedTime), result);
        }
	}

	/**
	 * @see DateTimeDataValue#getSeconds
	 * 
	 * @exception StandardException		Thrown on error
	 */
	public NumberDataValue getSeconds(NumberDataValue result)
							throws StandardException
	{
        if (isNull()) {
            return nullValueInt();
        } else {    
            return SQLDate.setSource(getSecond(encodedTime), result);
        }
	}

	/*
	** String display of value
	*/

	public String toString()
	{
		if (isNull())
		{
			return "NULL";
		}
		else
		{
			return getTime( (Calendar) null).toString();
		}
	}

	/*
	 * Hash code
	 */
	public int hashCode()
	{
		if (isNull())
		{
			return 0;
		}
		// add 1 since 0 represents a valid time
		return encodedTime + encodedTimeFraction + 1;

	}

	/** @see DataValueDescriptor#typePrecedence */
	public int	typePrecedence()
	{
		return TypeId.TIME_PRECEDENCE;
	}

	/**
	 * Check if the value is null.  
	 *
	 * @return Whether or not value is logically null.
	 */
	public final boolean isNull()
	{
		return (encodedTime ==  -1);
	}

	/**
	 * Get the time value 
	 * Since this is a JDBC object we use the JDBC definition
	 * we use the JDBC definition, see JDBC API Tutorial and Reference
	 * section 47.3.12
	 * Date is set to Jan. 1, 1970
	 *
	 * @return	The localized time value.
	 */
	public Time getTime(java.util.Calendar cal)
	{
		if (isNull())
			return null;
        
        // Derby's SQL TIME type only has second resolution
        // so pass in 0 for nano-seconds
        return getTime(cal, encodedTime, 0);
	}
    
    /**
     * Set the time portion of a date-time value into
     * the passed in Calendar object from its encodedTime
     * value. Note that this is only the time down
     * to a resolution of one second. Only the HOUR_OF_DAY,
     * MINUTE and SECOND fields are modified. The remaining
     * state of the Calendar is not modified.
     */
    static void setTimeInCalendar(Calendar cal, int encodedTime)
    {
        cal.set(Calendar.HOUR_OF_DAY, getHour(encodedTime));
        cal.set(Calendar.MINUTE, getMinute(encodedTime));
        cal.set(Calendar.SECOND, getSecond(encodedTime));        
    }
    
    /**
     * Get a java.sql.Time object from an encoded time
     * and nano-second value. As required by JDBC the
     * date component of the Time object will be set to
     * Jan. 1, 1970
     * @param cal Calendar to use for conversion
     * @param encodedTime Derby encoded time value
     * @param nanos number of nano-seconds.
     * @return Valid Time object.
     */
    static Time getTime(Calendar cal, int encodedTime, int nanos)
    {
        if( cal == null)
            cal = new GregorianCalendar();
        
        cal.clear();
        
        cal.set(1970, Calendar.JANUARY, 1);

        SQLTime.setTimeInCalendar(cal, encodedTime);

        cal.set(Calendar.MILLISECOND, nanos/1000000);
        
        return new Time(cal.getTimeInMillis());
    }
    
    
	/**
	 * Get the encoded hour value (may be different than hour value for
	 *  	current timezone if value encoded in a different timezone)
	 *
	 * @return	hour value
	 */
	protected static int getHour(int encodedTime)
	{
		return (encodedTime >>> 16) & 0xff;
	}
	/**
	 * Get the encoded minute value (may be different than the minute value for
	 *  	current timezone if value encoded in a different timezone)
	 *
	 * @return	minute value
	 */
	protected static int getMinute(int encodedTime)
	{
		return ((encodedTime >>> 8) & 0xff);
	}
	/**
	 * Get the encoded second value (may be different than the second value for
	 *  	current timezone if value encoded in a different timezone)
	 *
	 * @return	second value
	 */
	protected static int getSecond(int encodedTime)
	{
		return (encodedTime & 0xff);
	}
	/**
	 *	Calculate the encoded time from a Calendar object
	 *	encoded time is hour &lt;&lt; 16 + min &lt;&lt; 8 + sec
	 *  this function is also used by SQLTimestamp 
	 *
	 * @param	cal calendar with time set
	 * @return	encoded time
     *
     * @exception StandardException if the time is not in the DB2 range
	 */
	static int computeEncodedTime(Calendar cal) throws StandardException
	{
		return computeEncodedTime(cal.get(Calendar.HOUR_OF_DAY),
                                  cal.get(Calendar.MINUTE),
                                  cal.get(Calendar.SECOND));
	}

    static int computeEncodedTime( int hour, int minute, int second) throws StandardException
    {
        if( hour == 24)
        {
            if( minute != 0 || second != 0)
                throw StandardException.newException( SQLState.LANG_DATE_RANGE_EXCEPTION);
        }
        else if( hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59)
            throw StandardException.newException( SQLState.LANG_DATE_RANGE_EXCEPTION);

        return (hour << 16) + (minute << 8) + second;
    }

    /**
     * Convert a time to a JDBC escape format string
     *
     * @param hour
     * @param minute
     * @param second
     * @param sb The resulting string is appended to this StringBuffer
     */
    static void timeToString( int hour, int minute, int second, StringBuffer sb)
    {
		String hourStr = Integer.toString( hour);
		String minStr = Integer.toString( minute);
		String secondStr = Integer.toString( second);
		if (hourStr.length() == 1)
			sb.append("0");
		sb.append( hourStr);
		sb.append( JIS_SEPARATOR);
		if (minStr.length() == 1)
			sb.append("0");
		sb.append(minStr);
		sb.append( JIS_SEPARATOR);
		if (secondStr.length() == 1)
			sb.append("0");
		sb.append(secondStr);
    } // end of timeToString

	/**
	 * Get the String version from the encodedTime.
	 *
	 * @return	 string value.
	 */
	protected static String encodedTimeToString(int encodedTime)
	{
		StringBuffer vstr = new StringBuffer();
        timeToString( SQLTime.getHour(encodedTime), SQLTime.getMinute(encodedTime), SQLTime.getSecond(encodedTime), vstr);
		return vstr.toString();
	}

	/**
	 * Compute encoded time value
	 * Time is represented by hour &lt;&lt; 16 + minute &lt;&lt; 8 + seconds
	 */
	private	int computeEncodedTime(java.util.Date value) throws StandardException
	{
        return computeEncodedTime( value, (Calendar) null);
    }

    static int computeEncodedTime(java.util.Date value, Calendar currentCal) throws StandardException
    {
        if (value == null)
			return -1;
        if( currentCal == null)
            currentCal = new GregorianCalendar();
		currentCal.setTime(value);
		return computeEncodedTime(currentCal);
	}

     /** Adding this method to ensure that super class' setInto method doesn't get called
      * that leads to the violation of JDBC spec( untyped nulls ) when batching is turned on.
      */
    public void setInto(PreparedStatement ps, int position) throws SQLException, StandardException {

		      ps.setTime(position, getTime((Calendar) null));
   }


    /**
     * 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 intervalCount 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 intervalCount,
                                           java.sql.Date currentDate,
                                           DateTimeDataValue resultHolder)
        throws StandardException
    {
        return toTimestamp( currentDate).timestampAdd( intervalType, intervalCount, currentDate, resultHolder);
    }

    private SQLTimestamp toTimestamp(java.sql.Date currentDate) throws StandardException
    {
        return new SQLTimestamp( SQLDate.computeEncodedDate( currentDate, (Calendar) null),
                                 getEncodedTime(),
                                 0 /* nanoseconds */);
    }
    
    /**
     * 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
    {
        return toTimestamp( currentDate ).timestampDiff( intervalType, time1, currentDate, resultHolder);
    }
}

