1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.sql;
19
20import java.text.ParsePosition;
21import java.text.SimpleDateFormat;
22import java.util.Date;
23import java.util.regex.Pattern;
24
25import org.apache.harmony.sql.internal.nls.Messages;
26
27/**
28 * A Java representation of the SQL {@code TIMESTAMP} type. It provides the
29 * capability of representing the SQL {@code TIMESTAMP} nanosecond value, in
30 * addition to the regular date/time value which has millisecond resolution.
31 * <p>
32 * The {@code Timestamp} class consists of a regular date/time value, where only
33 * the integral seconds value is stored, plus a nanoseconds value where the
34 * fractional seconds are stored.
35 * <p>
36 * The addition of the nanosecond value field to the {@code Timestamp} object
37 * makes it significantly different from the {@code java.util.Date} object which
38 * it extends. Users should be aware that {@code Timestamp} objects are not
39 * interchangable with {@code java.util.Date} objects when used outside the
40 * confines of the {@code java.sql} package.
41 *
42 * @see Date
43 * @see Time
44 * @see java.util.Date
45 */
46public class Timestamp extends Date {
47
48    private static final long serialVersionUID = 2745179027874758501L;
49
50    // The nanoseconds time value of the Timestamp
51    private int nanos;
52
53    // The regex pattern of yyyy-mm-dd hh:mm:ss
54    private static final String TIME_FORMAT_REGEX = "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.*"; //$NON-NLS-1$
55
56    /**
57     * Returns a {@code Timestamp} corresponding to the time specified by the
58     * supplied values for <i>Year</i>, <i>Month</i>, <i>Date</i>, <i>Hour</i>,
59     * <i>Minutes</i>, <i>Seconds</i> and <i>Nanoseconds</i>.
60     *
61     * @deprecated Use the constructor {@link #Timestamp(long)}.
62     * @param theYear
63     *            specified as the year minus 1900.
64     * @param theMonth
65     *            specified as an integer in the range [0,11].
66     * @param theDate
67     *            specified as an integer in the range [1,31].
68     * @param theHour
69     *            specified as an integer in the range [0,23].
70     * @param theMinute
71     *            specified as an integer in the range [0,59].
72     * @param theSecond
73     *            specified as an integer in the range [0,59].
74     * @param theNano
75     *            which defines the nanosecond value of the timestamp specified
76     *            as an integer in the range [0,999'999'999]
77     * @throws IllegalArgumentException
78     *             if any of the parameters is out of range.
79     */
80    @SuppressWarnings("deprecation")
81    @Deprecated
82    public Timestamp(int theYear, int theMonth, int theDate, int theHour,
83            int theMinute, int theSecond, int theNano)
84            throws IllegalArgumentException {
85        super(theYear, theMonth, theDate, theHour, theMinute, theSecond);
86        if (theNano < 0 || theNano > 999999999) {
87            throw new IllegalArgumentException();
88        }
89        nanos = theNano;
90    }
91
92    /**
93     * Returns a {@code Timestamp} object corresponding to the time represented
94     * by a supplied time value.
95     *
96     * @param theTime
97     *            a time value in the format of milliseconds since the Epoch
98     *            (January 1 1970 00:00:00.000 GMT).
99     */
100    public Timestamp(long theTime) {
101        super(theTime);
102        /*
103         * Now set the time for this Timestamp object - which deals with the
104         * nanosecond value as well as the base time
105         */
106        setTimeImpl(theTime);
107    }
108
109    /**
110     * Returns {@code true} if this timestamp object is later than the supplied
111     * timestamp, otherwise returns {@code false}.
112     *
113     * @param theTimestamp
114     *            the timestamp to compare with this timestamp object.
115     * @return {@code true} if this {@code Timestamp} object is later than the
116     *         supplied timestamp, {@code false} otherwise.
117     */
118    public boolean after(Timestamp theTimestamp) {
119        long thisTime = this.getTime();
120        long compareTime = theTimestamp.getTime();
121
122        // If the time value is later, the timestamp is later
123        if (thisTime > compareTime) {
124            return true;
125        }
126        // If the time value is earlier, the timestamp is not later
127        else if (thisTime < compareTime) {
128            return false;
129        }
130        /*
131         * Otherwise the time values are equal in which case the nanoseconds
132         * value determines whether this timestamp is later...
133         */
134        else if (this.getNanos() > theTimestamp.getNanos()) {
135            return true;
136        } else {
137            return false;
138        }
139    }
140
141    /**
142     * Returns {@code true} if this {@code Timestamp} object is earlier than the
143     * supplied timestamp, otherwise returns {@code false}.
144     *
145     * @param theTimestamp
146     *            the timestamp to compare with this {@code Timestamp} object.
147     * @return {@code true} if this {@code Timestamp} object is earlier than the
148     *         supplied timestamp, {@code false} otherwise.
149     */
150    public boolean before(Timestamp theTimestamp) {
151        long thisTime = this.getTime();
152        long compareTime = theTimestamp.getTime();
153
154        // If the time value is later, the timestamp is later
155        if (thisTime < compareTime) {
156            return true;
157        }
158        // If the time value is earlier, the timestamp is not later
159        else if (thisTime > compareTime) {
160            return false;
161        }
162        /*
163         * Otherwise the time values are equal in which case the nanoseconds
164         * value determines whether this timestamp is later...
165         */
166        else if (this.getNanos() < theTimestamp.getNanos()) {
167            return true;
168        } else {
169            return false;
170        }
171    }
172
173    /**
174     * Compares this {@code Timestamp} object with a supplied {@code Timestamp}
175     * object.
176     *
177     * @param theObject
178     *            the timestamp to compare with this {@code Timestamp} object,
179     *            passed as an {@code Object}.
180     * @return <dd>
181     *         <dl>
182     *         {@code 0} if the two {@code Timestamp} objects are equal in time
183     *         </dl>
184     *         <dl>
185     *         a value {@code < 0} if this {@code Timestamp} object is before
186     *         the supplied {@code Timestamp} and a value
187     *         </dl>
188     *         <dl>
189     *         {@code > 0} if this {@code Timestamp} object is after the
190     *         supplied {@code Timestamp}
191     *         </dl>
192     *         </dd>
193     * @throws ClassCastException
194     *             if the supplied object is not a {@code Timestamp} object.
195     */
196    @Override
197    public int compareTo(Date theObject) throws ClassCastException {
198        return this.compareTo((Timestamp) theObject);
199    }
200
201    /**
202     * Compares this {@code Timestamp} object with a supplied {@code Timestamp}
203     * object.
204     *
205     * @param theTimestamp
206     *            the timestamp to compare with this {@code Timestamp} object,
207     *            passed in as a {@code Timestamp}.
208     * @return one of the following:
209     *         <ul>
210     *         <li>{@code 0}, if the two {@code Timestamp} objects are
211     *         equal in time</li>
212     *         <li>{@code < 0}, if this {@code Timestamp} object is before the
213     *         supplied {@code Timestamp}</li>
214     *         <li> {@code > 0}, if this {@code Timestamp} object is after the
215     *         supplied {@code Timestamp}</li>
216     *         </ul>
217     */
218    public int compareTo(Timestamp theTimestamp) {
219        int result = super.compareTo(theTimestamp);
220        if (result == 0) {
221            int thisNano = this.getNanos();
222            int thatNano = theTimestamp.getNanos();
223            if (thisNano > thatNano) {
224                return 1;
225            } else if (thisNano == thatNano) {
226                return 0;
227            } else {
228                return -1;
229            }
230        }
231        return result;
232    }
233
234    /**
235     * Tests to see if this timestamp is equal to a supplied object.
236     *
237     * @param theObject
238     *            the object to which this timestamp is compared.
239     * @return {@code true} if this {@code Timestamp} object is equal to the
240     *         supplied {@code Timestamp} object<br>{@code false} if the object
241     *         is not a {@code Timestamp} object or if the object is a {@code
242     *         Timestamp} but represents a different instant in time.
243     */
244    @Override
245    public boolean equals(Object theObject) {
246        if (theObject instanceof Timestamp) {
247            return equals((Timestamp) theObject);
248        }
249        return false;
250    }
251
252    /**
253     * Tests to see if this timestamp is equal to a supplied timestamp.
254     *
255     * @param theTimestamp
256     *            the timestamp to compare with this {@code Timestamp} object,
257     *            passed as an {@code Object}.
258     * @return {@code true} if this {@code Timestamp} object is equal to the
259     *         supplied {@code Timestamp} object, {@code false} otherwise.
260     */
261    public boolean equals(Timestamp theTimestamp) {
262        if (theTimestamp == null) {
263            return false;
264        }
265        return (this.getTime() == theTimestamp.getTime())
266                && (this.getNanos() == theTimestamp.getNanos());
267    }
268
269    /**
270     * Gets this {@code Timestamp}'s nanosecond value
271     *
272     * @return The timestamp's nanosecond value, an integer between 0 and
273     *         999,999,999.
274     */
275    public int getNanos() {
276        return nanos;
277    }
278
279    /**
280     * Returns the time represented by this {@code Timestamp} object, as a long
281     * value containing the number of milliseconds since the Epoch (January 1
282     * 1970, 00:00:00.000 GMT).
283     *
284     * @return the number of milliseconds that have passed since January 1 1970,
285     *         00:00:00.000 GMT.
286     */
287    @Override
288    public long getTime() {
289        long theTime = super.getTime();
290        theTime = theTime + (nanos / 1000000);
291        return theTime;
292    }
293
294    /**
295     * Sets the nanosecond value for this {@code Timestamp}.
296     *
297     * @param n
298     *            number of nanoseconds.
299     * @throws IllegalArgumentException
300     *             if number of nanoseconds smaller than 0 or greater than
301     *             999,999,999.
302     */
303    public void setNanos(int n) throws IllegalArgumentException {
304        if ((n < 0) || (n > 999999999)) {
305            // sql.0=Value out of range
306            throw new IllegalArgumentException(Messages.getString("sql.0")); //$NON-NLS-1$
307        }
308        nanos = n;
309    }
310
311    /**
312     * Sets the time represented by this {@code Timestamp} object to the
313     * supplied time, defined as the number of milliseconds since the Epoch
314     * (January 1 1970, 00:00:00.000 GMT).
315     *
316     * @param theTime
317     *            number of milliseconds since the Epoch (January 1 1970,
318     *            00:00:00.000 GMT).
319     */
320    @Override
321    public void setTime(long theTime) {
322        setTimeImpl(theTime);
323    }
324
325    private void setTimeImpl(long theTime) {
326        /*
327         * Deal with the nanoseconds value. The supplied time is in milliseconds -
328         * so we must extract the milliseconds value and multiply by 1000000 to
329         * get nanoseconds. Things are more complex if theTime value is
330         * negative, since then the time value is the time before the Epoch but
331         * the nanoseconds value of the Timestamp must be positive - so we must
332         * take the "raw" milliseconds value and subtract it from 1000 to get to
333         * the true nanoseconds value Simultaneously, recalculate the time value
334         * to the exact nearest second and reset the Date time value
335         */
336        int milliseconds = (int) (theTime % 1000);
337        theTime = theTime - milliseconds;
338        if (milliseconds < 0) {
339            theTime = theTime - 1000;
340            milliseconds = 1000 + milliseconds;
341        }
342        super.setTime(theTime);
343        setNanos(milliseconds * 1000000);
344    }
345
346    /**
347     * Returns the timestamp formatted as a String in the JDBC Timestamp Escape
348     * format, which is {@code "yyyy-mm-dd hh:mm:ss.nnnnnnnnn"}.
349     *
350     * @return A string representing the instant defined by the {@code
351     *         Timestamp}, in JDBC Timestamp escape format.
352     */
353    @SuppressWarnings("deprecation")
354    @Override
355    public String toString() {
356        StringBuilder sb = new StringBuilder(29);
357
358        format((getYear() + 1900), 4, sb);
359        sb.append('-');
360        format((getMonth() + 1), 2, sb);
361        sb.append('-');
362        format(getDate(), 2, sb);
363        sb.append(' ');
364        format(getHours(), 2, sb);
365        sb.append(':');
366        format(getMinutes(), 2, sb);
367        sb.append(':');
368        format(getSeconds(), 2, sb);
369        sb.append('.');
370        if (nanos == 0) {
371            sb.append('0');
372        } else {
373            format(nanos, 9, sb);
374            while (sb.charAt(sb.length() - 1) == '0') {
375                sb.setLength(sb.length() - 1);
376            }
377        }
378
379        return sb.toString();
380    }
381
382    private static final String PADDING = "000000000";  //$NON-NLS-1$
383
384    /*
385    * Private method to format the time
386    */
387    private void format(int date, int digits, StringBuilder sb) {
388        String str = String.valueOf(date);
389        if (digits - str.length() > 0) {
390            sb.append(PADDING.substring(0, digits - str.length()));
391        }
392        sb.append(str);
393    }
394
395    /**
396     * Creates a {@code Timestamp} object with a time value equal to the time
397     * specified by a supplied String holding the time in JDBC timestamp escape
398     * format, which is {@code "yyyy-mm-dd hh:mm:ss.nnnnnnnnn}"
399     *
400     * @param s
401     *            the {@code String} containing a time in JDBC timestamp escape
402     *            format.
403     * @return A {@code Timestamp} object with time value as defined by the
404     *         supplied {@code String}.
405     * @throws IllegalArgumentException
406     *             if the provided string is {@code null}.
407     */
408    public static Timestamp valueOf(String s) throws IllegalArgumentException {
409        if (s == null) {
410            // sql.3=Argument cannot be null
411            throw new IllegalArgumentException(Messages.getString("sql.3")); //$NON-NLS-1$
412        }
413
414        // omit trailing whitespaces
415        s = s.trim();
416        if (!Pattern.matches(TIME_FORMAT_REGEX, s)) {
417            throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
418        }
419
420        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$
421        ParsePosition pp = new ParsePosition(0);
422
423        /*
424         * First parse out the yyyy-MM-dd HH:mm:ss component of the String into
425         * a Date object using the SimpleDateFormat. This should stop after the
426         * seconds value, according to the definition of SimpleDateFormat.parse,
427         * with the ParsePosition indicating the index of the "." which should
428         * precede the nanoseconds value
429         */
430        Date theDate;
431        try {
432            theDate = df.parse(s, pp);
433        } catch (Exception e) {
434            throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
435        }
436
437        if (theDate == null) {
438            throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
439        }
440
441        /*
442         * If we get here, the Date part of the string was OK - now for the
443         * nanoseconds value. Strictly, this requires the remaining part of the
444         * String to look like ".nnnnnnnnn". However, we accept anything with a
445         * '.' followed by 1 to 9 digits - we also accept nothing (no fractions
446         * of a second). Anything else is interpreted as incorrect format which
447         * will generate an IllegalArgumentException
448         */
449        int position = pp.getIndex();
450        int remaining = s.length() - position;
451        int theNanos;
452
453        if (remaining == 0) {
454            // First, allow for the case where no fraction of a second is given:
455            theNanos = 0;
456        } else {
457            /*
458             * Case where fraction of a second is specified: Require 1 character
459             * plus the "." in the remaining part of the string...
460             */
461            if ((s.length() - position) < ".n".length()) { //$NON-NLS-1$
462                throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
463            }
464
465            /*
466             * If we're strict, we should not allow any EXTRA characters after
467             * the 9 digits
468             */
469            if ((s.length() - position) > ".nnnnnnnnn".length()) { //$NON-NLS-1$
470                throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
471            }
472
473            // Require the next character to be a "."
474            if (s.charAt(position) != '.') {
475                // sql.4=Bad input string format: expected '.' not {0}
476                throw new NumberFormatException(Messages.getString(
477                        "sql.4", s.charAt(position))); //$NON-NLS-1$
478            }
479            // Get the length of the number string - need to account for the '.'
480            int nanoLength = s.length() - position - 1;
481
482            // Get the 9 characters following the "." as an integer
483            String theNanoString = s.substring(position + 1, position + 1
484                    + nanoLength);
485            /*
486             * We must adjust for the cases where the nanos String was not 9
487             * characters long by padding out with zeros
488             */
489            theNanoString = theNanoString + "000000000"; //$NON-NLS-1$
490            theNanoString = theNanoString.substring(0, 9);
491
492            try {
493                theNanos = Integer.parseInt(theNanoString);
494            } catch (Exception e) {
495                // If we get here, the string was not a number
496                throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
497            }
498        }
499
500        if (theNanos < 0 || theNanos > 999999999) {
501            throw new IllegalArgumentException(Messages.getString("sql.2")); //$NON-NLS-1$
502        }
503
504        Timestamp theTimestamp = new Timestamp(theDate.getTime());
505        theTimestamp.setNanos(theNanos);
506
507        return theTimestamp;
508    }
509}
510