/*
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * @(#)Timestamp.java	1.8 03/10/21
 */

package java.sql;

import java.text.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * <P>A thin wrapper around <code>java.util.Date</code> that allows the JDBC API to identify this as an SQL <code>TIMESTAMP</code> value. It adds the ability to hold the SQL <code>TIMESTAMP</code> nanos value and provides formatting and parsing operations to support the JDBC escape syntax for timestamp values. <P><B>Note:</B> This type is a composite of a <code>java.util.Date</code> and a separate nanoseconds value. Only integral seconds are stored in the <code>java.util.Date</code> component. The fractional seconds - the nanos - are separate. The <code>getTime</code> method will return only integral seconds. If a time value that includes the fractional seconds is desired, you must convert nanos to milliseconds (nanos/1000000) and add this to the <code>getTime</code> value.  The <code>Timestamp.equals(Object)</code> method never returns  <code>true</code> when passed a value of type <code>java.util.Date</code> because the nanos component of a date is unknown. As a result, the <code>Timestamp.equals(Object)</code> method is not symmetric with respect to the <code>java.util.Date.equals(Object)</code> method.  Also, the <code>hashcode</code> method uses the underlying  <code>java.util.Data</code>  implementation and therefore does not include nanos in its computation.   Due to the differences between the <code>Timestamp</code> class and the <code>java.util.Date</code> class mentioned above, it is recommended that code not view <code>Timestamp</code> values generically as an instance of <code>java.util.Date</code>.  The inheritance relationship between <code>Timestamp</code> and <code>java.util.Date</code> really  denotes implementation inheritance, and not type inheritance.
 */
public class Timestamp extends java.util.Date {

    /**
     * Constructs a <code>Timestamp</code> object 
     * using a milliseconds time value. The
     * integral seconds are stored in the underlying date value; the
     * fractional seconds are stored in the <code>nanos</code> field of
     * the <code>Timestamp</code> object.
     *
     * @param time milliseconds since January 1, 1970, 00:00:00 GMT.
     *        A negative number is the number of milliseconds before
     *         January 1, 1970, 00:00:00 GMT.
     * @see java.util.Calendar for more information
     */
    public Timestamp(long time) {
	super((time/1000)*1000);
	nanos = (int)((time%1000) * 1000000);
	if (nanos < 0) {
	    nanos = 1000000000 + nanos;	    
	    super.setTime(((time/1000)-1)*1000);
	}
    }

    /**
     * Sets this <code>Timestamp</code> object to represent a point in time that is 
     * <tt>time</tt> milliseconds after January 1, 1970 00:00:00 GMT. 
     *
     * @param time   the number of milliseconds.
     * @see #getTime
     * @see #Timestamp(long time)
     * @see java.util.Calendar for more information
     */
    public void setTime(long time) {
	super.setTime((time/1000)*1000);
	nanos = (int)((time%1000) * 1000000);
	if (nanos < 0) {
	    nanos = 1000000000 + nanos;	    
	    super.setTime(((time/1000)-1)*1000);
	}
    }

    /**
     * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * represented by this <code>Timestamp</code> object.
     *
     * @return  the number of milliseconds since January 1, 1970, 00:00:00 GMT
     *          represented by this date.
     * @see #setTime
     */
    public long getTime() {
        long time = super.getTime();
        return (time + (nanos / 1000000));
    }
            

    /**
     * @serial
     */
    private int nanos;

    /**
     * Converts a <code>String</code> object in JDBC timestamp escape format to a
     * <code>Timestamp</code> value.
     *
     * @param s timestamp in format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
     * @return corresponding <code>Timestamp</code> value
     * @exception java.lang.IllegalArgumentException if the given argument
     * does not have the format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
     */
    public static Timestamp valueOf(String s) {
	String date_s;
	String time_s;
	String nanos_s;
	int year;
	int month;
	int day;
	int hour;
	int minute;
	int second;
	int a_nanos = 0;
	int firstDash;
	int secondDash;
	int dividingSpace;
	int firstColon = 0;
	int secondColon = 0;
	int period = 0;
	String formatError = "Timestamp format must be yyyy-mm-dd hh:mm:ss.fffffffff";
	String zeros = "000000000";

	if (s == null) throw new java.lang.IllegalArgumentException("null string");

	// Split the string into date and time components
	s = s.trim();
	dividingSpace = s.indexOf(' ');
	if (dividingSpace > 0) {
	    date_s = s.substring(0,dividingSpace);
	    time_s = s.substring(dividingSpace+1);
	} else {
	    throw new java.lang.IllegalArgumentException(formatError);
	}


	// Parse the date
	firstDash = date_s.indexOf('-');
	secondDash = date_s.indexOf('-', firstDash+1);

	// Parse the time
	if (time_s == null) 
	    throw new java.lang.IllegalArgumentException(formatError);
	firstColon = time_s.indexOf(':');
	secondColon = time_s.indexOf(':', firstColon+1);
	period = time_s.indexOf('.', secondColon+1);

	// Convert the date
	if ((firstDash > 0) & (secondDash > 0) & 
	    (secondDash < date_s.length()-1)) {
	    year = Integer.parseInt(date_s.substring(0, firstDash));
	    month = 
		Integer.parseInt(date_s.substring
				 (firstDash+1, secondDash)) - 1;
	    day = Integer.parseInt(date_s.substring(secondDash+1));
	} else {		
	    throw new java.lang.IllegalArgumentException(formatError);
	}

	// Convert the time; default missing nanos
	if ((firstColon > 0) & (secondColon > 0) & 
	    (secondColon < time_s.length()-1)) {
	    hour = Integer.parseInt(time_s.substring(0, firstColon));
	    minute = 
		Integer.parseInt(time_s.substring(firstColon+1, secondColon));
	    if ((period > 0) & (period < time_s.length()-1)) {
		second = 
		    Integer.parseInt(time_s.substring(secondColon+1, period));
		nanos_s = time_s.substring(period+1);
		if (nanos_s.length() > 9) 
		    throw new java.lang.IllegalArgumentException(formatError);
		if (!Character.isDigit(nanos_s.charAt(0)))
		    throw new java.lang.IllegalArgumentException(formatError);
		nanos_s = nanos_s + zeros.substring(0,9-nanos_s.length());
		a_nanos = Integer.parseInt(nanos_s);
	    } else if (period > 0) {
		throw new java.lang.IllegalArgumentException(formatError);
	    } else {
		second = Integer.parseInt(time_s.substring(secondColon+1));
	    }
	} else {
	    throw new java.lang.IllegalArgumentException();
	}

	Timestamp ts = new Timestamp(new GregorianCalendar(
	    year, month, day, hour, minute, second).getTime().getTime());
	ts.setNanos(a_nanos);
	
	return ts;
    }

    /**
     * Formats a timestamp in JDBC timestamp escape format.
     *
     * NOTE: To specify a timestamp format for the class 
     * <code>SimpleDateFormat</code>, use "yyyy.MM.dd" rather than 
     * "yyyy-mm-dd". In the context of <code>SimpleDateFormat</code>, "mm"
     * indicates minutes rather than the month. Note that 
     * <code>SimpleDateFormat<code> does not allw for the nanoseconds 
     * components of a <code>Timestamp</code> object.
     * For example:
     * <PRE>
     *
     *  Format Pattern				Result
     *  --------------                  	------
`    *  "yyyy.MM.dd G 'at' hh:mm:ss z"     -->	2002.07.10 AD at 15:08:56 PDT
     *
     * </PRE>
     * @return a <code>String</code> object in
     *           <code>yyyy-mm-dd hh:mm:ss.fffffffff</code> format
     * @overrides <code>toString</code> in class <code>java.util.Date</code>
     */
    public String toString() {
	Calendar gc = new GregorianCalendar();
	gc.setTime(this);
	int year = gc.get(Calendar.YEAR);
	int month = gc.get(Calendar.MONTH) + 1;
	int day = gc.get(Calendar.DAY_OF_MONTH);
	int hour = gc.get(Calendar.HOUR_OF_DAY);
	int minute = gc.get(Calendar.MINUTE);
	int second = gc.get(Calendar.SECOND);
	String yearString;
	String monthString;
	String dayString;
	String hourString;
	String minuteString;
	String secondString;
	String nanosString;
	String zeros = "000000000";
	String yearZeros = "0000";
	StringBuffer timestampBuf;

	if (year < 1000) {
	    // Add leading zeros 
	    yearString = "" + year;
	    yearString = yearZeros.substring(0, (4-yearString.length())) + 
	   	yearString;
	} else {
	    yearString = "" + year;
	}
	if (month < 10) {
	    monthString = "0" + month;
	} else {
	    monthString = Integer.toString(month);
	} 
	if (day < 10) {
	    dayString = "0" + day;
	} else {
	    dayString = Integer.toString(day);
	}
	if (hour < 10) {
	    hourString = "0" + hour;
	} else {
	    hourString = Integer.toString(hour);
	}
	if (minute < 10) {
	    minuteString = "0" + minute;
	} else {
	    minuteString = Integer.toString(minute);
	}
	if (second < 10) {
	    secondString = "0" + second;
	} else {
	    secondString = Integer.toString(second);
	}
	
    	if (nanos == 0) {
	    nanosString = "0";
    	} else {
	    nanosString = Integer.toString(nanos);
	    
	    // Add leading zeros
	    nanosString = zeros.substring(0, (9-nanosString.length())) +
		nanosString; 
	    // Truncate trailing zeros
	    char[] nanosChar = new char[nanosString.length()];
    	    nanosString.getChars(0, nanosString.length(), nanosChar, 0);
    	    int truncIndex = 8;
    	    while (nanosChar[truncIndex] == '0') {
		truncIndex--;
	    }
	    
	    nanosString = new String(nanosChar, 0, truncIndex + 1);
	}

	// do a string buffer here instead.
	timestampBuf = new StringBuffer();
	timestampBuf.append(yearString);
	timestampBuf.append("-");
	timestampBuf.append(monthString);
	timestampBuf.append("-");
	timestampBuf.append(dayString);
	timestampBuf.append(" ");
	timestampBuf.append(hourString);
	timestampBuf.append(":");
	timestampBuf.append(minuteString);
	timestampBuf.append(":");
	timestampBuf.append(secondString);
	timestampBuf.append(".");
	timestampBuf.append(nanosString);
	
	return (timestampBuf.toString());
    }
    
    /**
	 * Gets this <code>Timestamp</code> object's <code>nanos</code> value.
	 * @return  this <code>Timestamp</code> object's fractional seconds component
	 * @see  #setNanos
	 * @uml.property  name="nanos"
	 */
    public int getNanos() {
	return nanos;
    }

    /**
	 * Sets this <code>Timestamp</code> object's <code>nanos</code> field to the given value.
	 * @param n  the new fractional seconds component
	 * @exception java.lang.IllegalArgumentException  if the given argument  is greater than 999999999 or less than 0
	 * @see  #getNanos
	 * @uml.property  name="nanos"
	 */
    public void setNanos(int n) {
	if (n > 999999999 || n < 0) {
	    throw new IllegalArgumentException("nanos > 999999999 or < 0");
	}
	nanos = n;
    }

    /**
     * Tests to see if this <code>Timestamp</code> object is
     * equal to the given <code>Timestamp</code> object.
     *
     * @param ts the <code>Timestamp</code> value to compare with
     * @return <code>true</code> if the given <code>Timestamp</code>
     *         object is equal to this <code>Timestamp</code> object;
     *         <code>false</code> otherwise
     */
    public boolean equals(Timestamp ts) {
	if (super.equals(ts)) {
	    if  (nanos == ts.nanos) {
		return true;
	    } else {
		return false;
	    }
	} else {
	    return false;
	}
    }

    /**
     * Tests to see if this <code>Timestamp</code> object is
     * equal to the given object.
     *
     * This version of the method <code>equals</code> has been added
     * to fix the incorrect 
     * signature of <code>Timestamp.equals(Timestamp)</code> and to preserve backward 
     * compatibility with existing class files.
     *
     * Note: This method is not symmetric with respect to the 
     * <code>equals(Object)</code> method in the base class.
     *
     * @param ts the <code>Object</code> value to compare with
     * @return <code>true</code> if the given <code>Object</code>
     *         instance is equal to this <code>Timestamp</code> object;
     *         <code>false</code> otherwise
     */
    public boolean equals(java.lang.Object ts) {
      if (ts instanceof Timestamp) {
	return this.equals((Timestamp)ts);
      } else {
	return false;
      }
    }

    /**
     * Indicates whether this <code>Timestamp</code> object is
     * earlier than the given <code>Timestamp</code> object.
     *
     * @param ts the <code>Timestamp</code> value to compare with
     * @return <code>true</code> if this <code>Timestamp</code> object is earlier;
     *        <code>false</code> otherwise
     */
    public boolean before(Timestamp ts) {
	if (super.before(ts)) {
	    return true;
	} else {
	    if (super.equals(ts)) {
		if (nanos < ts.nanos) {
		    return true;
		} else {
		    return false;
		}
	    } else {
		return false;
	    }
	}
    }

    /**
     * Indicates whether this <code>Timestamp</code> object is
     * later than the given <code>Timestamp</code> object.
     *
     * @param ts the <code>Timestamp</code> value to compare with
     * @return <code>true</code> if this <code>Timestamp</code> object is later;
     *        <code>false</code> otherwise
     */
    public boolean after(Timestamp ts) {
	if (super.after(ts)) {
	    return true;
	} else {
	    if (super.equals(ts)) {
		if (nanos > ts.nanos) {
		    return true;
		} else {
		    return false;
		}
	    } else {
		return false;
	    }
	}
    }

    /**
     * Compares two Timestamps for ordering.
     *
     * @param   ts   the <code>Timestamp</code> to be compared.
     * @return  the value <code>0</code> if the argument Timestamp is equal to
     *          this Timestamp; a value less than <code>0</code> if this 
     *          Timestamp is before the Date argument; and a value greater than
     *      <code>0</code> if this Timestamp is after the Timestamp argument.
     * @since   1.2
     */
    public int compareTo(Timestamp ts) {
        int i = super.compareTo(ts);
        if (i == 0) {
            if (nanos > ts.nanos) {
		    return 1;
            } else if (nanos < ts.nanos) {
                return -1;
            }
        }
        return i;

    }

    /**
     * Compares this Timestamp to another Object. If the Object is 
     * a Timestamp, this function behaves like compareTo(Timestamp). 
     * Otherwise, it throws aClassCastException (as Timestamps are 
     * comparable only to other Timestamps)
     *
     * @param o the <code>object</code> to be compared
     *
     * @return the value <code>0</code> if the argument is a Timestamp 
     * equal to this Timestamp; a value less than <code>0</code> if the 
     * argument is a Timestamp after this Timestamp; and a value 
     * greater than <code>0</code> if the argument is a Timestamp 
     * before this Timestamp.
     *
     * @exception ClassCastException if the argument is not a
     *        <code>Timestamp</code>.
     */
    public int compareTo(Object o) {
        return compareTo((Timestamp)o);
    }
            
            
    

    static final long serialVersionUID = 2745179027874758501L;

}

