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