All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.ion.Timestamp Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazon.ion;

import static com.amazon.ion.impl._Private_Utils.safeEquals;
import static com.amazon.ion.util.IonTextUtils.printCodePointAsString;

import com.amazon.ion.impl._Private_Utils;
import com.amazon.ion.system.IonTextWriterBuilder;
import com.amazon.ion.util.IonTextUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * An immutable representation of a point in time. Ion defines a simple
 * representation of time based on Coordinated Universal Time (UTC).
 * In practice the use of time could be more accurately described as
 * UTC-SLS (UTC Smoothed Leap Seconds) as there is no representation for the
 * leap second discontinuities that UTC has added.
 * 

* Timestamps preserve precision, meaning the fields that are included, and the * significant digits of any fractional second. Only common break * points in the values are supported. Any unspecified fields are handled * as the start of the new year/month/day. * * *

Equality and Comparison

* * As with {@link IonValue} classes, the {@link #equals equals} methods on this class * perform a strict equivalence that observes the precision and local offset * of each timestamp. * This means that it's possible to have two {@link Timestamp} instances that * represent the same point in time but are not {@code equals}. *

* On the other hand, the {@link #compareTo} methods perform point in time * comparison, ignoring precision and local offset. * Thus the natural comparison method of this class is not * consistent with equals. See the documentation of {@link Comparable} for * further discussion. *

* To illustrate this distinction, consider the following timestamps. None are * {@link #equals} to each other, but any pair will return a zero result from * {@link #compareTo}. *

    *
  • {@code 2009T}
  • *
  • {@code 2009-01T}
  • *
  • {@code 2009-01-01T}
  • *
  • {@code 2009-01-01T00:00Z}
  • *
  • {@code 2009-01-01T00:00:00Z}
  • *
  • {@code 2009-01-01T00:00:00.0Z}
  • *
  • {@code 2009-01-01T00:00:00.00Z}
  • *
  • {@code 2009-01-01T00:00:00.000Z} etc.
  • *
* * @see #equals(Timestamp) * @see #compareTo(Timestamp) */ public final class Timestamp implements Comparable, Cloneable { private static final boolean APPLY_OFFSET_YES = true; private static final boolean APPLY_OFFSET_NO = false; private static final boolean CHECK_FRACTION_YES = true; private static final boolean CHECK_FRACTION_NO = false; private static final int NO_MONTH = 0; private static final int NO_DAY = 0; private static final int NO_HOURS = 0; private static final int NO_MINUTES = 0; private static final int NO_SECONDS = 0; private static final BigDecimal NO_FRACTIONAL_SECONDS = null; /** * 0001-01-01T00:00:00.0Z in millis. */ static final long MINIMUM_TIMESTAMP_IN_MILLIS = -62135769600000L; /** * 0001-01-01T00:00:00.0Z in millis. */ static final BigDecimal MINIMUM_TIMESTAMP_IN_MILLIS_DECIMAL = new BigDecimal(MINIMUM_TIMESTAMP_IN_MILLIS); /** * 10000T in millis, upper bound exclusive. */ static final long MAXIMUM_TIMESTAMP_IN_MILLIS = 253402300800000L; /** * 10000T in millis, upper bound exclusive. */ static final BigDecimal MAXIMUM_ALLOWED_TIMESTAMP_IN_MILLIS_DECIMAL = new BigDecimal(MAXIMUM_TIMESTAMP_IN_MILLIS); /** * 0001-01-01T00:00:00.0Z epoch seconds. */ static final long MINIMUM_TIMESTAMP_IN_EPOCH_SECONDS = MINIMUM_TIMESTAMP_IN_MILLIS / 1000; /** * 10000T in epoch seconds, upper bound exclusive. */ static final long MAXIMUM_TIMESTAMP_IN_EPOCH_SECONDS = MAXIMUM_TIMESTAMP_IN_MILLIS / 1000; /** * Unknown local offset from UTC. */ public static final Integer UNKNOWN_OFFSET = null; /** * Local offset of zero hours from UTC. */ public static final Integer UTC_OFFSET = Integer.valueOf(0); private static final int FLAG_YEAR = 0x01; private static final int FLAG_MONTH = 0x02; private static final int FLAG_DAY = 0x04; private static final int FLAG_MINUTE = 0x08; private static final int FLAG_SECOND = 0x10; /** * The default maximum number of digits in the fractional component that will be written when a text representation * of a timestamp is requested. If more precision is needed, timestamps can be serialized using a text IonWriter * configured with {@link IonTextWriterBuilder#withMaximumTimestampPrecisionDigits(int)}. * * @see #toString() * @see #toZString() * @see #print(Appendable) * @see #printZ(Appendable) */ public static final int DEFAULT_MAXIMUM_DIGITS_TEXT = 10000; /** * The precision of the Timestamp. */ public static enum Precision { YEAR (FLAG_YEAR), MONTH (FLAG_YEAR | FLAG_MONTH), DAY (FLAG_YEAR | FLAG_MONTH | FLAG_DAY), // HOUR is not a supported precision per https://www.w3.org/TR/NOTE-datetime MINUTE (FLAG_YEAR | FLAG_MONTH | FLAG_DAY | FLAG_MINUTE), SECOND (FLAG_YEAR | FLAG_MONTH | FLAG_DAY | FLAG_MINUTE | FLAG_SECOND), /** * DEPRECATED! Treating the fractional part of seconds separate from * the integer part has led to countless bugs. We intend to combine * the two under the SECOND precision. * */ @Deprecated FRACTION(FLAG_YEAR | FLAG_MONTH | FLAG_DAY | FLAG_MINUTE | FLAG_SECOND); /** Bit flags for the precision. */ private final int flags; private Precision(int flags) { this.flags = flags; } public boolean includes(Precision isIncluded) { switch (isIncluded) { case FRACTION: case SECOND: return (flags & FLAG_SECOND) != 0; case MINUTE: return (flags & FLAG_MINUTE) != 0; case DAY: return (flags & FLAG_DAY) != 0; case MONTH: return (flags & FLAG_MONTH) != 0; case YEAR: return (flags & FLAG_YEAR) != 0; default: break; } throw new IllegalStateException("unrecognized precision" + isIncluded); } private boolean alwaysUnknownOffset() { return this.ordinal() <= DAY.ordinal(); } } private static final int HASH_SIGNATURE = "INTERNAL TIMESTAMP".hashCode(); /** * The precision of the Timestamp. The fractional seconds component is * defined by a BigDecimal. *

* During construction of all Timestamps, they will have a * date value (i.e. Year, Month, Day) but with reduced precision they may * exclude any time values that are more precise than the precision that is * being defined. */ private Precision _precision; // These are the time field values for the Timestamp. // _month and _day are 1-based (0 is an invalid value for // these in a non-null Timestamp). private short _year; private byte _month = 1; // Initialized to valid default private byte _day = 1; // Initialized to valid default private byte _hour; private byte _minute; private byte _second; private BigDecimal _fraction; // fractional seconds, must be within range [0, 1) /** * Minutes offset from UTC; zero means UTC proper, * null means that the offset is unknown. */ private Integer _offset; // jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec // the first 0 is to make these arrays 1 based (since month values are 1-12) private static final int[] LEAP_DAYS_IN_MONTH = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; private static final int[] NORMAL_DAYS_IN_MONTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; private static byte last_day_in_month(int year, int month) { boolean is_leap; if ((year % 4) == 0) { // divisible by 4 (lower 2 bits are zero) - may be a leap year if ((year % 100) == 0) { // and divisible by 100 - not a leap year if ((year % 400) == 0) { // but divisible by 400 - then it is a leap year is_leap = true; } else { is_leap = false; } } else { is_leap = true; } } else { is_leap = false; } return (byte) (is_leap ? LEAP_DAYS_IN_MONTH[month] : NORMAL_DAYS_IN_MONTH[month]); } /** * Applies the local time zone offset from UTC to the applicable time field * values. Depending on the local time zone offset, adjustments * (i.e. rollover) will be made to the Year, Day, Hour, Minute time field * values. * * @param offset the local offset, in minutes from UTC. */ private void apply_offset(int offset) { if (offset == 0) return; if (offset < -24*60 || offset > 24*60) { throw new IllegalArgumentException("bad offset " + offset); } // To convert _to_ UTC you must SUBTRACT the local offset offset = -offset; byte hour_offset = (byte) (offset / 60); byte min_offset = (byte) (offset - (hour_offset * 60)); if (offset < 0) { _minute += min_offset; // lower the minute value by adding a negative offset _hour += hour_offset; if (_minute < 0) { _minute += (byte) 60; _hour -= 1; } if (_hour >= 0) return; // hour is 0-23 _hour += (byte) 24; _day -= (byte) 1; if (_day >= 1) return; // day is 1-31 // we can't do this until we've figured out the month and year: _day += last_day_in_month(_year, _month); _month -= 1; if (_month >= 1) { _day += last_day_in_month(_year, _month); // now we know (when the year doesn't change assert(_day == last_day_in_month(_year, _month)); return; // 1-12 } _month += (byte) 12; _year -= (short) 1; if (_year < 1) throw new IllegalArgumentException("year is less than 1"); _day += last_day_in_month(_year, _month); // and now we know, even if the year did change assert(_day == last_day_in_month(_year, _month)); } else { _minute += min_offset; // lower the minute value by adding a negative offset _hour += hour_offset; if (_minute > 59) { _minute -= (byte) 60; _hour += (byte) 1; } if (_hour < 24) return; // hour is 0-23 _hour -= (byte) 24; _day += (byte) 1; if (_day <= last_day_in_month(_year, _month)) return; // day is 1-31 // we can't do this until we figure out the final month and year: _day -= last_day_in_month(_year, _month); _day = 1; // this is always the case _month += (byte) 1; if (_month <= 12) { return; // 1-12 } _month -= (byte) 12; _year += (short) 1; if (_year > 9999) throw new IllegalArgumentException("year exceeds 9999"); } } private static byte requireByte(int value, String location) { if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) { throw new IllegalArgumentException(String.format("%s of %d is out of range.", location, value)); } return (byte) value; } private static short requireShort(int value, String location) { if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) { throw new IllegalArgumentException(String.format("%s of %d is out of range.", location, value)); } return (short) value; } /** * This method uses deprecated methods from {@link java.util.Date} * instead of {@link Calendar} so that this code can be used (more easily) * on the mobile Java platform (which has Date but does not have Calendar). */ @SuppressWarnings("deprecation") private void set_fields_from_millis(long millis) { if(millis < MINIMUM_TIMESTAMP_IN_MILLIS){ throw new IllegalArgumentException("year is less than 1"); } Date date = new Date(millis); // The Date getters return values in the Date's time zone (i.e. the system time zone). // The components need to be converted to UTC before being validated for exact ranges, because the offset // conversion can affect which values are considered valid. Simply verify that the values can fit in the // destination type here. If they do not, they would be out of range no matter the offset. _minute = requireByte(date.getMinutes(), "Minute"); _second = requireByte(date.getSeconds(), "Second"); _hour = requireByte(date.getHours(), "Hour"); _day = requireByte(date.getDate(), "Day"); _month = requireByte(date.getMonth() + 1, "Month"); // Date does not correctly handle year values that represent year 0 or earlier in the system time zone through // getYear(). This case is detected and forced to zero. int offset = -date.getTimezoneOffset(); if(offset < 0 && MINIMUM_TIMESTAMP_IN_MILLIS - offset > millis) { _year = 0; } else { _year = requireShort(date.getYear() + 1900, "Year"); } // Now apply the offset to convert the components to UTC. apply_offset(offset); // Now that all components are in UTC, they may be validated for exact ranges. this._year = checkAndCastYear(_year); this._month = checkAndCastMonth(_month); this._day = checkAndCastDay(_day, _year, _month); this._hour = checkAndCastHour(_hour); this._minute = checkAndCastMinute(_minute); this._second = checkAndCastSecond(_second); } /** * Copies data from a {@link Calendar} into this timestamp. * Must only be called during construction due to timestamp immutabliity. * * @param cal must have at least one field set. * * @throws IllegalArgumentException if the calendar has no fields set. */ private void set_fields_from_calendar(Calendar cal, Precision precision, boolean setLocalOffset) { _precision = precision; _offset = UNKNOWN_OFFSET; boolean dayPrecision = false; boolean calendarHasMilliseconds = cal.isSet(Calendar.MILLISECOND); switch (this._precision) { case FRACTION: case SECOND: this._second = checkAndCastSecond(cal.get(Calendar.SECOND)); if (calendarHasMilliseconds) { BigDecimal millis = BigDecimal.valueOf(cal.get(Calendar.MILLISECOND)); this._fraction = millis.movePointLeft(3); // convert to fraction checkFraction(precision, this._fraction); } case MINUTE: { this._hour = checkAndCastHour(cal.get(Calendar.HOUR_OF_DAY)); this._minute = checkAndCastMinute(cal.get(Calendar.MINUTE)); // If this test is made before calling get(), it will return // false even when Calendar.setTimeZone() was called. if (setLocalOffset && cal.isSet(Calendar.ZONE_OFFSET)) { int offset = cal.get(Calendar.ZONE_OFFSET); if (cal.isSet(Calendar.DST_OFFSET)) { offset += cal.get(Calendar.DST_OFFSET); } // convert ms to minutes _offset = offset / (1000*60); } } case DAY: dayPrecision = true; case MONTH: // Calendar months are 0 based, Timestamp months are 1 based this._month = checkAndCastMonth((cal.get(Calendar.MONTH) + 1)); case YEAR: int year; if(cal.get(Calendar.ERA) == GregorianCalendar.AD) { year = cal.get(Calendar.YEAR); } else { year = -cal.get(Calendar.YEAR); } this._year = checkAndCastYear(year); } if (dayPrecision) { this._day = checkAndCastDay(cal.get(Calendar.DAY_OF_MONTH), _year, _month); } if (_offset != UNKNOWN_OFFSET) { // Transform our members from local time to Zulu this.apply_offset(_offset); } } /** * Creates a new Timestamp, precise to the year, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYYT}. */ private Timestamp(int zyear) { this(Precision.YEAR, zyear, NO_MONTH, NO_DAY, NO_HOURS, NO_MINUTES, NO_SECONDS, NO_FRACTIONAL_SECONDS, UNKNOWN_OFFSET, APPLY_OFFSET_NO, CHECK_FRACTION_NO); } /** * Creates a new Timestamp, precise to the month, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYY-MMT}. */ private Timestamp(int zyear, int zmonth) { this(Precision.MONTH, zyear, zmonth, NO_DAY, NO_HOURS, NO_MINUTES, NO_SECONDS, NO_FRACTIONAL_SECONDS, UNKNOWN_OFFSET, APPLY_OFFSET_NO, CHECK_FRACTION_NO); } /** * Creates a new Timestamp, precise to the day, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYY-MM-DD}. * * @deprecated Use {@link #forDay(int, int, int)} instead. */ @Deprecated public Timestamp(int zyear, int zmonth, int zday) { this(Precision.DAY, zyear, zmonth, zday, NO_HOURS, NO_MINUTES, NO_SECONDS, NO_FRACTIONAL_SECONDS, UNKNOWN_OFFSET, APPLY_OFFSET_NO, CHECK_FRACTION_NO); } /** * Creates a new Timestamp, precise to the minute, with a given local * offset. *

* This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm+-oo:oo}, where {@code oo:oo} represents the * hour and minutes of the local offset from UTC. * * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * * @deprecated Use {@link #forMinute(int, int, int, int, int, Integer)} instead. */ @Deprecated public Timestamp(int year, int month, int day, int hour, int minute, Integer offset) { this(Precision.MINUTE, year, month, day, hour, minute, NO_SECONDS, NO_FRACTIONAL_SECONDS, offset, APPLY_OFFSET_YES, CHECK_FRACTION_NO); } /** * Creates a new Timestamp, precise to the second, with a given local * offset. *

* This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm:ss+-oo:oo}, where {@code oo:oo} represents the * hour and minutes of the local offset from UTC. * * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset. * * @deprecated Use {@link #forSecond(int, int, int, int, int, int, Integer)} instead. */ @Deprecated public Timestamp(int year, int month, int day, int hour, int minute, int second, Integer offset) { this(Precision.SECOND, year, month, day, hour, minute, second, NO_FRACTIONAL_SECONDS, offset, APPLY_OFFSET_YES, CHECK_FRACTION_NO); } /** * Creates a new Timestamp, precise to the second or fractional second, * with a given local offset. *

* This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm:ss.fff+-oo:oo}, where {@code oo:oo} represents * the hour and minutes of the local offset from UTC, and {@code fff} * represents the fractional seconds. * * @param frac * the fractional seconds; must not be {@code null}; if negative, * its absolute value is used * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * * @throws NullPointerException if {@code frac} is {@code null} * * @deprecated Use {@link #forSecond(int, int, int, int, int, BigDecimal, Integer)} * instead. */ @Deprecated public Timestamp(int year, int month, int day, int hour, int minute, int second, BigDecimal frac, Integer offset) { this(Precision.SECOND, year, month, day, hour, minute, second, frac, offset, APPLY_OFFSET_YES, CHECK_FRACTION_YES); } /** * Creates a new Timestamp from the individual time components. The * individual time components are expected to be in UTC, * with the local offset from UTC (i.e. {@code offset}) already * applied to the time components. *

* Any time component that is more precise * than the precision parameter {@code p} will be excluded from the * calculation of the resulting Timestamp's point in time. * * @param frac must be >= 0 and < 1 * * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * * @see #createFromUtcFields(Precision, int, int, int, int, int, int, BigDecimal, Integer) */ private Timestamp(Precision p, int zyear, int zmonth, int zday, int zhour, int zminute, int zsecond, BigDecimal frac, Integer offset, boolean shouldApplyOffset, boolean shouldCheckFraction) { boolean dayPrecision = false; switch (p) { default: throw new IllegalArgumentException("invalid Precision passed to constructor"); case FRACTION: case SECOND: if (frac == null) { _fraction = null; } else if (shouldCheckFraction) { if (frac.equals(BigDecimal.ZERO)) { _fraction = null; } else { checkFraction(p, frac); _fraction = frac; } } else { _fraction = frac; } _second = checkAndCastSecond(zsecond); case MINUTE: _minute = checkAndCastMinute(zminute); _hour = checkAndCastHour(zhour); _offset = offset; // offset must be null for years/months/days case DAY: dayPrecision = true; case MONTH: _month = checkAndCastMonth(zmonth); case YEAR: _year = checkAndCastYear(zyear); } if (dayPrecision) { _day = checkAndCastDay(zday, zyear, zmonth); } _precision = p; if (shouldApplyOffset && offset != null) { apply_offset(offset); } } /** * Creates a new Timestamp from the individual time components. The * individual time components are expected to be in UTC, * with the local offset from UTC (i.e. {@code offset}) already * applied to the time components. * As such, if the given {@code offset} is non-null or zero, the resulting * Timestamp will have time values that DO NOT match the time * parameters. This method also has a behavior of precision "narrowing", * detailed in the sub-section below. * *

* For example, the following method calls will return Timestamps with * values (in its local time) respectively: *

     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, null)    will return 2012-02-03T04:05:06.007-00:00 (match)
     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, 0)       will return 2012-02-03T04:05:06.007+00:00 (match)
     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, 480)     will return 2012-02-03T12:05:06.007+08:00 (do not match)
     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, -480)    will return 2012-02-02T20:05:06.007-08:00 (do not match)
     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, 720)     will return 2012-02-03T16:05:06.007+12:00 (do not match)
     * createFromUtcFields(Precision.SECOND, 2012, 2, 3, 4, 5, 6, 0.007, -720)    will return 2012-02-02T16:05:06.007-12:00 (do not match)
     *
* Note: All of these resulting Timestamps have the similar value (in UTC) 2012-02-03T04:05:06.007Z. * *

Precision "Narrowing"

* *

* Any time component that is more precise * than the precision parameter {@code p} will be excluded from the * calculation of the resulting Timestamp's point in time. *

* For example, the following method calls will return Timestamps with * values respectively: *

     * createFromUtcFields(Precision.YEAR    , 2012, 2, 3, 4, 5, 6, 0.007, 0)    will return 2012T
     * createFromUtcFields(Precision.MONTH   , 2012, 2, 3, 4, 5, 6, 0.007, 0)    will return 2012-02T
     * createFromUtcFields(Precision.DAY     , 2012, 2, 3, 4, 5, 6, 0.007, 0)    will return 2012-02-03T
     * createFromUtcFields(Precision.MINUTE  , 2012, 2, 3, 4, 5, 6, 0.007, 0)    will return 2012-02-03T04:05Z
     * createFromUtcFields(Precision.SECOND  , 2012, 2, 3, 4, 5, 6,  null, 0)    will return 2012-02-03T04:05:06Z
     * createFromUtcFields(Precision.SECOND  , 2012, 2, 3, 4, 5, 6, 0.007, 0)    will return 2012-02-03T04:05:06.007Z
     *
* * @param p the desired timestamp precision. The result may have a * different precision if the input data isn't precise enough. * * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset. */ @Deprecated public static Timestamp createFromUtcFields(Precision p, int zyear, int zmonth, int zday, int zhour, int zminute, int zsecond, BigDecimal frac, Integer offset) { return new Timestamp(p, zyear, zmonth, zday, zhour, zminute, zsecond, frac, offset, APPLY_OFFSET_NO, CHECK_FRACTION_YES); } /** * Creates a new Timestamp from a {@link Calendar}, preserving the * {@link Calendar}'s precision and local offset from UTC. *

* The most precise calendar field of {@code cal} will be used to determine * the precision of the resulting Timestamp. * * For example, the calendar field will have a Timestamp precision accordingly: *

    *
  • {@link Calendar#YEAR} - year precision, unknown local offset
  • *
  • {@link Calendar#MONTH} - month precision, unknown local offset
  • *
  • {@link Calendar#DAY_OF_MONTH} - day precision, unknown local offset
  • *
  • {@link Calendar#HOUR_OF_DAY} or {@link Calendar#MINUTE} - minute precision
  • *
  • {@link Calendar#SECOND} - second precision
  • *
  • {@link Calendar#MILLISECOND} - fractional second precision
  • *
* * @throws IllegalArgumentException * if {@code cal} has no appropriate calendar fields set. * * @deprecated Use {@link #forCalendar(Calendar)} instead. */ @Deprecated public Timestamp(Calendar cal) { Precision precision; if (cal.isSet(Calendar.MILLISECOND) || cal.isSet(Calendar.SECOND)) { precision = Precision.SECOND; } else if (cal.isSet(Calendar.HOUR_OF_DAY) || cal.isSet(Calendar.MINUTE)) { precision = Precision.MINUTE; } else if (cal.isSet(Calendar.DAY_OF_MONTH)) { precision = Precision.DAY; } else if (cal.isSet(Calendar.MONTH)) { precision = Precision.MONTH; } else if (cal.isSet(Calendar.YEAR)) { precision = Precision.YEAR; } else { throw new IllegalArgumentException("Calendar has no fields set"); } set_fields_from_calendar(cal, precision, true); } private Timestamp(Calendar cal, Precision precision, BigDecimal fraction, Integer offset) { set_fields_from_calendar(cal, precision, false); _fraction = fraction; if (offset != null) { _offset = offset; apply_offset(offset); } } private Timestamp(BigDecimal millis, Precision precision, Integer localOffset) { // check bounds to avoid hanging when calling longValue() on decimals with large positive exponents, // e.g. 1e10000000 if(millis.compareTo(MINIMUM_TIMESTAMP_IN_MILLIS_DECIMAL) < 0 || MAXIMUM_ALLOWED_TIMESTAMP_IN_MILLIS_DECIMAL.compareTo(millis) <= 0) { throwTimestampOutOfRangeError(millis); } // quick handle integral zero long ms = isIntegralZero(millis) ? 0 : millis.longValue(); set_fields_from_millis(ms); switch (precision) { case YEAR: _month = 1; case MONTH: _day = 1; case DAY: _hour = 0; _minute = 0; case MINUTE: _second = 0; case SECOND: case FRACTION: } _offset = localOffset; // The given BigDecimal may contain greater than milliseconds precision, which is the maximum precision that // a Calendar can handle. Set the _fraction here so that extra precision (if any) is not lost. // However, don't set the fraction if the given BigDecimal does not have precision at least to the tenth of // a second. if ((precision.includes(Precision.SECOND)) && millis.scale() > -3) { BigDecimal secs = millis.movePointLeft(3); BigDecimal secsDown = fastRoundZeroFloor(secs); _fraction = secs.subtract(secsDown); } else { _fraction = null; } _precision = checkFraction(precision, _fraction); } /** * Creates a new Timestamp that represents the point in time that is * {@code millis} milliseconds (including any fractional * milliseconds) from the epoch, with a given local offset. * *

* The resulting Timestamp will be precise to the second if {@code millis} * doesn't contain information that is more granular than seconds. * For example, a {@code BigDecimal} of * value 132541995e4 (132541995 × 104) * will return a Timestamp of {@code 2012-01-01T12:12:30Z}, * precise to the second. * *

* The resulting Timestamp will be precise to the fractional second if * {@code millis} contains information that is at least granular to * milliseconds. * For example, a {@code BigDecimal} of * value 1325419950555 * will return a Timestamp of {@code 2012-01-01T12:12:30.555Z}, * precise to the fractional second. * * @param millis * number of milliseconds (including any fractional * milliseconds) from the epoch (1970-01-01T00:00:00.000Z); * must not be {@code null} * @param localOffset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * * @throws NullPointerException if {@code millis} is {@code null} * * @deprecated Use {@link #forMillis(BigDecimal, Integer)} instead. */ @Deprecated public Timestamp(BigDecimal millis, Integer localOffset) { if (millis == null) throw new NullPointerException("millis is null"); // check bounds to avoid hanging when calling longValue() on decimals with large positive exponents, // e.g. 1e10000000 if(millis.compareTo(MINIMUM_TIMESTAMP_IN_MILLIS_DECIMAL) < 0 || MAXIMUM_ALLOWED_TIMESTAMP_IN_MILLIS_DECIMAL.compareTo(millis) < 0) { throwTimestampOutOfRangeError(millis); } // quick handle integral zero long ms = isIntegralZero(millis) ? 0 : millis.longValue(); set_fields_from_millis(ms); int scale = millis.scale(); if (scale <= -3) { this._precision = Precision.SECOND; this._fraction = null; } else { BigDecimal secs = millis.movePointLeft(3); BigDecimal secsDown = fastRoundZeroFloor(secs); this._fraction = secs.subtract(secsDown); this._precision = checkFraction(Precision.SECOND, _fraction); } this._offset = localOffset; } private BigDecimal fastRoundZeroFloor(final BigDecimal decimal) { BigDecimal fastValue = decimal.signum() < 0 ? BigDecimal.ONE.negate() : BigDecimal.ZERO; return isIntegralZero(decimal) ? fastValue : decimal.setScale(0, RoundingMode.FLOOR); } private boolean isIntegralZero(final BigDecimal decimal) { // zero || no low-order bits || < 1.0 return decimal.signum() == 0 || decimal.scale() < -63 || (decimal.precision() - decimal.scale() <= 0); } private static void throwTimestampOutOfRangeError(Number millis) { throw new IllegalArgumentException("millis: " + millis + " is outside of valid the range: from " + MINIMUM_TIMESTAMP_IN_MILLIS + " (0001T)" + ", inclusive, to " + MAXIMUM_TIMESTAMP_IN_MILLIS + " (10000T)" + " , exclusive"); } /** * Creates a new Timestamp that represents the point in time that is * {@code millis} milliseconds from the epoch, with a given local offset. *

* The resulting Timestamp will be precise to the fractional second. * * @param millis * number of milliseconds from the epoch (1970-01-01T00:00:00.000Z) * @param localOffset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset. * * @deprecated Use {@link #forMillis(long, Integer)} instead. */ @Deprecated public Timestamp(long millis, Integer localOffset) { if(millis < MINIMUM_TIMESTAMP_IN_MILLIS || millis >= MAXIMUM_TIMESTAMP_IN_MILLIS) { throwTimestampOutOfRangeError(millis); } this.set_fields_from_millis(millis); // fractional seconds portion BigDecimal secs = BigDecimal.valueOf(millis).movePointLeft(3); BigDecimal secsDown = secs.setScale(0, RoundingMode.FLOOR); this._fraction = secs.subtract(secsDown); this._precision = checkFraction(Precision.SECOND, _fraction); this._offset = localOffset; } private static IllegalArgumentException fail(CharSequence input, String reason) { input = IonTextUtils.printString(input); return new IllegalArgumentException("invalid timestamp: " + reason + ": " + input); } private static IllegalArgumentException fail(CharSequence input) { input = IonTextUtils.printString(input); return new IllegalArgumentException("invalid timestamp: " + input); } static final String NULL_TIMESTAMP_IMAGE = "null.timestamp"; static final int LEN_OF_NULL_IMAGE = NULL_TIMESTAMP_IMAGE.length(); static final int END_OF_YEAR = 4; // 1234T static final int END_OF_MONTH = 7; // 1234-67T static final int END_OF_DAY = 10; // 1234-67-90T static final int END_OF_MINUTES = 16; static final int END_OF_SECONDS = 19; /** * Returns a new Timestamp that represents the point in time, precision * and local offset defined in Ion format by the {@link CharSequence}. * * @param ionFormattedTimestamp * a sequence of characters that is the Ion representation of a * Timestamp * * @throws IllegalArgumentException * if the {@code CharSequence} is an invalid Ion representation * of a Timestamp; * or if the {@code CharSequence} has excess characters which * are not one of the following valid thirteen numeric-stop * characters (escaped accordingly for readability): * {}[](),\"\'\ \t\n\r} * * @return * {@code null} if the {@code CharSequence} is "null.timestamp" * * @see Ion Timestamp Page * @see W3C Note on Date and Time Formats */ public static Timestamp valueOf(CharSequence ionFormattedTimestamp) { final CharSequence in = ionFormattedTimestamp; int pos; final int length = in.length(); if (length == 0) { throw fail(in); } // check for 'null.timestamp' if (in.charAt(0) == 'n') { if (length >= LEN_OF_NULL_IMAGE && NULL_TIMESTAMP_IMAGE.contentEquals(in.subSequence(0, LEN_OF_NULL_IMAGE))) { if (length > LEN_OF_NULL_IMAGE) { if (!isValidFollowChar(in.charAt(LEN_OF_NULL_IMAGE))) { throw fail(in); } } return null; } throw fail(in); } int year = 1; int month = 1; int day = 1; int hour = 0; int minute = 0; int seconds = 0; BigDecimal fraction = null; Precision precision; // fake label to turn goto's into a break so Java is happy :) enjoy do { // otherwise we expect yyyy-mm-ddThh:mm:ss.ssss+hh:mm if (length < END_OF_YEAR + 1) { // +1 for the "T" throw fail(in, "year is too short (must be at least yyyyT)"); } pos = END_OF_YEAR; precision = Precision.YEAR; year = read_digits(in, 0, 4, -1, "year"); char c = in.charAt(END_OF_YEAR); if (c == 'T') break; if (c != '-') { throw fail(in, "expected \"-\" between year and month, found " + printCodePointAsString(c)); } if (length < END_OF_MONTH + 1) { // +1 for the "T" throw fail(in, "month is too short (must be yyyy-mmT)"); } pos = END_OF_MONTH; precision = Precision.MONTH; month = read_digits(in, END_OF_YEAR + 1, 2, -1, "month"); c = in.charAt(END_OF_MONTH); if (c == 'T') break; if (c != '-') { throw fail(in, "expected \"-\" between month and day, found " + printCodePointAsString(c)); } if (length < END_OF_DAY) { throw fail(in, "too short for yyyy-mm-dd"); } pos = END_OF_DAY; precision = Precision.DAY; day = read_digits(in, END_OF_MONTH + 1, 2, -1, "day"); if (length == END_OF_DAY) break; c = in.charAt(END_OF_DAY); if (c != 'T') { throw fail(in, "expected \"T\" after day, found " + printCodePointAsString(c)); } if (length == END_OF_DAY + 1) break; // now lets see if we have a time value if (length < END_OF_MINUTES) { throw fail(in, "too short for yyyy-mm-ddThh:mm"); } hour = read_digits(in, 11, 2, ':', "hour"); minute = read_digits(in, 14, 2, -1, "minutes"); pos = END_OF_MINUTES; precision = Precision.MINUTE; // we may have seconds if (length <= END_OF_MINUTES || in.charAt(END_OF_MINUTES) != ':') { break; } if (length < END_OF_SECONDS) { throw fail(in, "too short for yyyy-mm-ddThh:mm:ss"); } seconds = read_digits(in, 17, 2, -1, "seconds"); pos = END_OF_SECONDS; precision = Precision.SECOND; if (length <= END_OF_SECONDS || in.charAt(END_OF_SECONDS) != '.') { break; } pos = END_OF_SECONDS + 1; while (length > pos && Character.isDigit(in.charAt(pos))) { pos++; } if (pos <= END_OF_SECONDS + 1) { throw fail(in, "must have at least one digit after decimal point"); } fraction = new BigDecimal(in.subSequence(19, pos).toString()); } while (false); Integer offset; // now see if they included a timezone offset char timezone_start = pos < length ? in.charAt(pos) : '\n'; if (timezone_start == 'Z') { offset = 0; pos++; } else if (timezone_start == '+' || timezone_start == '-') { if (length < pos + 5) { throw fail(in, "local offset too short"); } // +/- hh:mm pos++; int tzdHours = read_digits(in, pos, 2, ':', "local offset hours"); if (tzdHours < 0 || tzdHours > 23) { throw fail(in, "local offset hours must be between 0 and 23 inclusive"); } pos += 3; int tzdMinutes = read_digits(in, pos, 2, -1, "local offset minutes"); if (tzdMinutes > 59) { throw fail(in, "local offset minutes must be between 0 and 59 inclusive"); } pos += 2; int temp = tzdHours * 60 + tzdMinutes; if (timezone_start == '-') { temp = -temp; } if (temp == 0 && timezone_start == '-') { // int doesn't do negative zero very elegantly offset = null; } else { offset = temp; } } else { switch (precision) { case YEAR: case MONTH: case DAY: break; default: throw fail(in, "missing local offset"); } offset = null; } if (length > (pos + 1) && !isValidFollowChar(in.charAt(pos + 1))) { throw fail(in, "invalid excess characters"); } Timestamp ts = new Timestamp(precision, year, month, day, hour, minute, seconds, fraction, offset, APPLY_OFFSET_YES, CHECK_FRACTION_NO); return ts; } private static int read_digits(CharSequence in, int start, int length, int terminator, String field) { int ii, value = 0; int end = start + length; if (in.length() < end) { throw fail(in, field + " requires " + length + " digits"); } for (ii=start; ii= in.length() || in.charAt(ii) != terminator) { throw fail(in, field + " should end with " + printCodePointAsString(terminator)); } } // Otherwise make sure we don't have too many digits. else if (ii < in.length() && Character.isDigit(in.charAt(ii))) { throw fail(in, field + " requires " + length + " digits but has more"); } return value; } private static boolean isValidFollowChar(char c) { switch (c) { default: return false; case '{': case '}': case '[': case ']': case '(': case ')': case ',': case '\"': case '\'': case '\\': case '\t': case '\n': case '\r': return true; } } /** * Creates a copy of this Timestamp. The resulting Timestamp will * represent the same point in time and has the same precision and local * offset. *

* {@inheritDoc} */ @Override public Timestamp clone() { // The Copy-Constructor we're using here already expects the time field // values to be in UTC, and that is already what we have for this // Timestamp -- no adjustment necessary to make it local time. return new Timestamp(_precision, _year, _month, _day, _hour, _minute, _second, _fraction, _offset, APPLY_OFFSET_NO, CHECK_FRACTION_NO); } /** * Applies the local offset from UTC to each of the applicable time field * values and returns the new Timestamp. In short, this makes the Timestamp * represent local time. * * @return a new Timestamp in its local time */ private Timestamp make_localtime() { int offset = _offset != null ? _offset.intValue() : 0; // We use a Copy-Constructor that expects the time parameters to be in // UTC, as that's what we're supposed to have. // As this Copy-Constructor doesn't apply local offset to the time // field values (it assumes that the local offset is already applied to // them), we explicitly apply the local offset to the time field values // after we obtain the new Timestamp instance. Timestamp localtime = new Timestamp(_precision, _year, _month, _day, _hour, _minute, _second, _fraction, _offset, APPLY_OFFSET_NO, CHECK_FRACTION_NO); // explicitly apply the local offset to the time field values localtime.apply_offset(-offset); assert localtime._offset == _offset; return localtime; } /** * Returns a Timestamp, precise to the year, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYYT}. */ public static Timestamp forYear(int yearZ) { return new Timestamp(yearZ); } /** * Returns a Timestamp, precise to the month, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYY-MMT}. */ public static Timestamp forMonth(int yearZ, int monthZ) { return new Timestamp(yearZ, monthZ); } /** * Returns a Timestamp, precise to the day, with unknown local offset. *

* This is equivalent to the corresponding Ion value {@code YYYY-MM-DD}. * */ public static Timestamp forDay(int yearZ, int monthZ, int dayZ) { return new Timestamp(yearZ, monthZ, dayZ); } /** * Returns a Timestamp, precise to the minute, with a given local * offset. * *

This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm+-oo:oo}, where {@code oo:oo} represents the * hour and minutes of the local offset from UTC.

* *

The values of the {@code year}, {@code month}, {@code day}, * {@code hour}, and {@code minute} parameters are relative to the * local {@code offset}.

* * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * */ public static Timestamp forMinute(int year, int month, int day, int hour, int minute, Integer offset) { return new Timestamp(year, month, day, hour, minute, offset); } /** * Returns a Timestamp, precise to the second, with a given local offset. * *

This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm:ss+-oo:oo}, where {@code oo:oo} represents the * hour and minutes of the local offset from UTC.

* *

The values of the {@code year}, {@code month}, {@code day}, * {@code hour}, {@code minute} and {@code second} parameters are relative * to the local {@code offset}.

* * @param offset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * */ public static Timestamp forSecond(int year, int month, int day, int hour, int minute, int second, Integer offset) { return new Timestamp(year, month, day, hour, minute, second, offset); } /** * Returns a Timestamp, precise to the second, with a given local offset. * *

This is equivalent to the corresponding Ion value * {@code YYYY-MM-DDThh:mm:ss.sss+-oo:oo}, where {@code oo:oo} represents * the hour and minutes of the local offset from UTC.

* *

The values of the {@code year}, {@code month}, {@code day}, * {@code hour}, {@code minute} and {@code second} parameters are relative * to the local {@code offset}.

* * @param second must be at least zero and less than 60. Must not be null. * @param offset the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * */ public static Timestamp forSecond(int year, int month, int day, int hour, int minute, BigDecimal second, Integer offset) { // Tease apart the whole and fractional seconds. // Storing them separately is silly. int s = second.intValue(); BigDecimal frac = second.subtract(BigDecimal.valueOf(s)); return new Timestamp(Precision.SECOND, year, month, day, hour, minute, s, frac, offset, APPLY_OFFSET_YES, CHECK_FRACTION_YES); } /** * Returns a Timestamp that represents the point in time that is {@code millis} milliseconds from the epoch, * with a given local offset. * *

The resulting Timestamp will be precise to the millisecond.

* *

{@code millis} is relative to UTC, regardless of the value supplied for {@code localOffset}. This * varies from the {@link #forMinute} and {@link #forSecond} methods that assume the specified date and time * values are relative to the local offset. For example, the following two Timestamps represent * the same point in time:

* * * Timestamp theEpoch = Timestamp.forMillis(0, 0); * Timestamp alsoTheEpoch = Timestamp.forSecond(1969, 12, 31, 23, 0, BigDecimal.ZERO, -60); * * * @param millis the number of milliseconds from the epoch (1970-01-01T00:00:00.000Z) in UTC. * @param localOffset the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset. */ public static Timestamp forMillis(long millis, Integer localOffset) { return new Timestamp(millis, localOffset); } /** * The same as {@link #forMillis(long, Integer)} but the millisecond component is specified using a * {@link BigDecimal} and therefore may include fractional milliseconds. * * @param millis * number of milliseconds (including any fractional * milliseconds) from the epoch (1970-01-01T00:00:00.000Z); * must not be {@code null} * @param localOffset * the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset * * @throws NullPointerException if {@code millis} is {@code null} * */ public static Timestamp forMillis(BigDecimal millis, Integer localOffset) { return new Timestamp(millis, localOffset); } /** * Returns a Timestamp that represents the point in time that is {@code seconds} from the unix epoch * (1970-01-01T00:00:00.000Z), with the {@code nanoOffset} applied and a given local offset. * *

This function is intended to allow easy conversion to Timestamp from Java 8's {@code java.time.Instant} * without having to port this library to Java 8. The following snippet will yield a Timestamp {@code ts} * that equivalent to the {@code java.time.Instant} {@code i}:

* * * Instant i = Instant.now(); * Timestamp ts = Timestamp.forEpochSecond(i.getEpochSecond(), i.getNano(), 0); * * *

Like {@link #forMillis}, {@code seconds} is relative to UTC, regardless of the value * supplied for {@code localOffset}.

* * @param seconds The number of seconds from the unix epoch (1970-01-01T00:00:00.000Z) UTC. * @param nanoOffset The number of nanoseconds for the fractional component. Must be between 0 and 999,999,999. * @param localOffset the local offset from UTC, measured in minutes; * may be {@code null} to represent an unknown local offset. */ public static Timestamp forEpochSecond(long seconds, int nanoOffset, Integer localOffset) { // We do not validate seconds here because that is done within the forMillis static constructor. long millis = seconds * 1000L; Timestamp ts = forMillis(millis, localOffset); if(nanoOffset != 0) { if(nanoOffset < 0 || nanoOffset > 999999999) { throw new IllegalArgumentException("nanoOffset must be between 0 and 999,999,999"); } ts._fraction = ts._fraction.add(BigDecimal.valueOf(nanoOffset).movePointLeft(9)); } return ts; } /** * Converts a {@link Calendar} to a Timestamp, preserving the calendar's * time zone as the equivalent local offset when it has at least minutes * precision. * * @return a Timestamp instance, with precision determined by the smallest * field set in the {@code Calendar}; * or {@code null} if {@code calendar} is {@code null} * */ public static Timestamp forCalendar(Calendar calendar) { if (calendar == null) return null; return new Timestamp(calendar); } /** * Converts a {@link Date} to a Timestamp in UTC representing the same * point in time. *

* The resulting Timestamp will be precise to the millisecond. * * @return * a new Timestamp instance, in UTC, precise to the millisecond; * {@code null} if {@code date} is {@code null} * */ public static Timestamp forDateZ(Date date) { if (date == null) return null; long millis = date.getTime(); return new Timestamp(millis, UTC_OFFSET); } /** * Converts a {@link java.sql.Timestamp} to a Timestamp in UTC representing * the same point in time. *

* The resulting Timestamp will be precise to the nanosecond. * * @param sqlTimestamp assumed to have nanoseconds precision * * @return * a new Timestamp instance, in UTC, precise to the * nanosecond * {@code null} if {@code sqlTimestamp} is {@code null} * */ public static Timestamp forSqlTimestampZ(java.sql.Timestamp sqlTimestamp) { if (sqlTimestamp == null) return null; long millis = sqlTimestamp.getTime(); Timestamp ts = new Timestamp(millis, UTC_OFFSET); int nanos = sqlTimestamp.getNanos(); BigDecimal frac = BigDecimal.valueOf(nanos).movePointLeft(9); ts._fraction = frac; return ts; } /** * Returns a Timestamp representing the current time (based on the JVM * clock), with an unknown local offset. *

* The resulting Timestamp will be precise to the millisecond. * * @return * a new Timestamp instance representing the current time. */ public static Timestamp now() { long millis = System.currentTimeMillis(); return new Timestamp(millis, UNKNOWN_OFFSET); } /** * Returns a Timestamp in UTC representing the current time (based on the * the JVM clock). *

* The resulting Timestamp will be precise to the millisecond. * * @return * a new Timestamp instance, in UTC, representing the current * time. * */ public static Timestamp nowZ() { long millis = System.currentTimeMillis(); return new Timestamp(millis, UTC_OFFSET); } /** * Converts the value of this Timestamp into a {@link Date}, * representing the time in UTC. *

* This method will return the same result for all Timestamps representing * the same point in time, regardless of the local offset. *

* Because {@link Date} instances are mutable, this method returns a * new instance from each call. * * @return a new {@code Date} instance, in UTC */ public Date dateValue() { long millis = getMillis(); return new Date(millis); } /** * Converts the value of this Timestamp as a {@link Calendar}, in its * local time. *

* Because {@link Calendar} instances are mutable, this method returns a * new instance from each call. * * @return a new {@code Calendar} instance, in its local time. * */ public Calendar calendarValue() { Calendar cal = new GregorianCalendar(_Private_Utils.UTC); long millis = getMillis(); Integer offset = _offset; if (offset != null && offset != 0) { int offsetMillis = offset * 60 * 1000; millis += offsetMillis; cal.setTimeInMillis(millis); // Resets the offset! cal.set(Calendar.ZONE_OFFSET, offsetMillis); } else { cal.setTimeInMillis(millis); } switch (_precision) { case YEAR: cal.clear(Calendar.MONTH); case MONTH: cal.clear(Calendar.DAY_OF_MONTH); case DAY: cal.clear(Calendar.HOUR_OF_DAY); cal.clear(Calendar.MINUTE); case MINUTE: cal.clear(Calendar.SECOND); cal.clear(Calendar.MILLISECOND); case SECOND: if (_fraction == null) { cal.clear(Calendar.MILLISECOND); } } return cal; } /** * Returns a number representing the Timestamp's point in time that is * the number of milliseconds (ignoring any fractional milliseconds) * from the epoch. *

* This method will return the same result for all Timestamps representing * the same point in time, regardless of the local offset. * * @return * number of milliseconds (ignoring any fractional * milliseconds) from the epoch (1970-01-01T00:00:00.000Z) */ @SuppressWarnings("deprecation") public long getMillis() { // month is 0 based for Date long millis = Date.UTC(this._year - 1900, this._month - 1, this._day, this._hour, this._minute, this._second); if (this._fraction != null) { BigDecimal fracAsDecimal = this._fraction.movePointRight(3); int frac = isIntegralZero(fracAsDecimal) ? 0 : fracAsDecimal.intValue(); millis += frac; } return millis; } /** * Returns a BigDecimal representing the Timestamp's point in time that is * the number of milliseconds (including any fractional milliseconds) * from the epoch. *

* This method will return the same result for all Timestamps representing * the same point in time, regardless of the local offset. * * @return * number of milliseconds (including any fractional * milliseconds) from the epoch (1970-01-01T00:00:00.000Z) */ @SuppressWarnings("deprecation") public BigDecimal getDecimalMillis() { switch (this._precision) { case YEAR: case MONTH: case DAY: case MINUTE: case SECOND: case FRACTION: long millis = Date.UTC(this._year - 1900, this._month - 1, this._day, this._hour, this._minute, this._second); BigDecimal dec = BigDecimal.valueOf(millis); if (_fraction != null) { dec = dec.add(this._fraction.movePointRight(3)); } return dec; } throw new IllegalArgumentException(); } /** * Returns the precision of this Timestamp. */ public Precision getPrecision() { return this._precision; } /** * Returns the offset of this Timestamp, measured in minutes, for the local * timezone in UTC. *

* For example, calling this method on Timestamps of: *

    *
  • {@code 1969-02-23T07:00+07:00} will return {@code 420}
  • *
  • {@code 1969-02-22T22:45:00.00-01:15} will return {@code -75}
  • *
  • {@code 1969-02-23} (by Ion's definition, equivalent to * {@code 1969-02-23T00:00-00:00}) will return {@code null}
  • *
* * @return * {@code null} if the local offset is unknown * (i.e. {@code -00:00}) */ public Integer getLocalOffset() { return _offset; } /** * Returns the year of this Timestamp, in its local time. * * @return * a number within the range [1, 9999], in its local time */ public int getYear() { Timestamp adjusted = this; if (this._offset != null) { if (this._offset.intValue() != 0) { adjusted = make_localtime(); } } return adjusted._year; } /** * Returns the month of this Timestamp, in its local time. * * @return * a number within the range [1, 12], whereby 1 refers to January * and 12 refers to December, in its local time; * 1 is returned if the Timestamp isn't precise to * the month */ public int getMonth() { Timestamp adjusted = this; if (this._offset != null) { if (this._offset.intValue() != 0) { adjusted = make_localtime(); } } return adjusted._month; } /** * Returns the day (within the month) of this Timestamp, in its local time. * * @return * a number within the range [1, 31], in its local time; * 1 is returned if the Timestamp isn't * precise to the day */ public int getDay() { Timestamp adjusted = this; if (this._offset != null) { if (this._offset.intValue() != 0) { adjusted = make_localtime(); } } return adjusted._day; } /** * Returns the hour of this Timestamp, in its local time. * * @return * a number within the range [0, 23], in its local time; * 0 is returned if the Timestamp isn't * precise to the hour */ public int getHour() { Timestamp adjusted = this; if (this._offset != null) { if (this._offset.intValue() != 0) { adjusted = make_localtime(); } } return adjusted._hour; } /** * Returns the minute of this Timestamp, in its local time. * * @return * a number within the range [0, 59], in its local time; * 0 is returned if the Timestamp isn't * precise to the minute */ public int getMinute() { Timestamp adjusted = this; if (this._offset != null) { if (this._offset.intValue() != 0) { adjusted = make_localtime(); } } return adjusted._minute; } /** * Returns the seconds of this Timestamp, truncated to an integer. *

* Seconds are not affected by local offsets. * As such, this method produces the same output as {@link #getZSecond()}. * * @return * a number within the range [0, 59]; * 0 is returned if the Timestamp isn't precise to the second * * @see #getZSecond() */ public int getSecond() { return this._second; } /** * Returns the seconds of this Timestamp. *

* Seconds are not affected by local offsets. * As such, this method produces the same output as * {@link #getZDecimalSecond()}. * * @return * a number within the range [0, 60); * 0 is returned if the Timestamp isn't precise to the second * * @see #getZDecimalSecond() */ public BigDecimal getDecimalSecond() { BigDecimal sec = BigDecimal.valueOf(_second); if (_fraction != null) { sec = sec.add(_fraction); } return sec; } /** * Returns the fractional second of this Timestamp. *

* Fractional seconds are not affected by local offsets. * As such, this method produces the same output as * {@link #getZFractionalSecond()}. * * @return * a BigDecimal within the range [0, 1); * {@code null} is returned if the Timestamp isn't * precise to the fractional second * * @see #getZFractionalSecond() * * Use {@link #getDecimalSecond()} instead. */ @Deprecated public BigDecimal getFractionalSecond() { return this._fraction; } /** * Returns the year of this Timestamp, in UTC. * * @return * a number within the range [1, 9999], in UTC */ public int getZYear() { return this._year; } /** * Returns the month of this Timestamp, in UTC. * * @return * a number within the range [1, 12], whereby 1 refers to January * and 12 refers to December, in UTC; * 1 is returned if the Timestamp isn't precise to * the month */ public int getZMonth() { return this._month; } /** * Returns the day of this Timestamp, in UTC. * * @return * a number within the range [1, 31], in UTC; * 1 is returned if the Timestamp isn't * precise to the day */ public int getZDay() { return this._day; } /** * Returns the hour of this Timestamp, in UTC. * * @return * a number within the range [0, 23], in UTC; * 0 is returned if the Timestamp isn't * precise to the hour */ public int getZHour() { return this._hour; } /** * Returns the minute of this Timestamp, in UTC. * * @return * a number within the range [0, 59], in UTC; * 0 is returned if the Timestamp isn't * precise to the minute */ public int getZMinute() { return this._minute; } /** * Returns the second of this Timestamp. *

* Seconds are not affected by local offsets. * As such, this method produces the same output as {@link #getSecond()}. * * @return * a number within the range [0, 59]; * 0 is returned if the Timestamp isn't precise to the second * * @see #getSecond() */ public int getZSecond() { return this._second; } /** * Returns the seconds of this Timestamp. *

* Seconds are not affected by local offsets. * As such, this method produces the same output as * {@link #getDecimalSecond()}. * * @return * a number within the range [0, 60); * 0 is returned if the Timestamp isn't precise to the second * * @see #getDecimalSecond() */ public BigDecimal getZDecimalSecond() { return getDecimalSecond(); } /** * Returns the fractional second of this Timestamp. *

* Fractional seconds are not affected by local offsets. * As such, this method produces the same output as * {@link #getFractionalSecond()}. * * @return * a BigDecimal within the range [0, 1); * {@code null} is returned if the Timestamp isn't * precise to the fractional second * * @see #getFractionalSecond() * * @deprecated Use {@link #getZDecimalSecond()} instead. */ @Deprecated public BigDecimal getZFractionalSecond() { return this._fraction; } //========================================================================= // Modification methods /** * Returns a timestamp at the same point in time, but with the given local * offset. If this timestamp has precision coarser than minutes, then it * is returned unchanged since such timestamps always have an unknown * offset. */ public Timestamp withLocalOffset(Integer offset) { Precision precision = getPrecision(); if (precision.alwaysUnknownOffset() || safeEquals(offset, getLocalOffset())) { return this; } Timestamp ts = createFromUtcFields(precision, getZYear(), getZMonth(), getZDay(), getZHour(), getZMinute(), getZSecond(), getZFractionalSecond(), offset); return ts; } //========================================================================= /** * Returns the string representation (in Ion format) of this Timestamp in * its local time. * * @throws IonException if the text representation of the timestamp requires more than * {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}. * * @see #toZString() * @see #print(Appendable) */ @Override public String toString() { return toString(DEFAULT_MAXIMUM_DIGITS_TEXT); } /** * Returns the string representation (in Ion format) of this Timestamp in * its local time. * * @throws IonException if the text representation of the timestamp requires more than * the given maximum. * * @see #toZString() * @see #print(Appendable) */ String toString(int maximumDigits) { StringBuilder buffer = new StringBuilder(32); try { print(buffer, maximumDigits); } catch (IOException e) { throw new RuntimeException("Exception printing to StringBuilder", e); } return buffer.toString(); } /** * Returns the string representation (in Ion format) of this Timestamp * in UTC. * * @throws IonException if the text representation of the timestamp requires more than * {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}. * * @see #toString() * @see #printZ(Appendable) */ public String toZString() { StringBuilder buffer = new StringBuilder(32); try { printZ(buffer); } catch (IOException e) { throw new RuntimeException("Exception printing to StringBuilder", e); } return buffer.toString(); } /** * Prints to an {@code Appendable} the string representation (in Ion format) * of this Timestamp in its local time. *

* This method produces the same output as {@link #toString()}. * * @param out not {@code null} * * @throws IOException propagated when the {@link Appendable} throws it * @throws IonException if the text representation of the timestamp requires more than * {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}. * * @see #printZ(Appendable) */ public void print(Appendable out) throws IOException { print(out, DEFAULT_MAXIMUM_DIGITS_TEXT); } /** * Prints to an {@code Appendable} the string representation (in Ion format) * of this Timestamp in its local time. *

* This method produces the same output as {@link #toString()}. * * @param out not {@code null} * * @throws IOException propagated when the {@link Appendable} throws it * @throws IonException if the text representation of the timestamp requires more than * the given maximum. * * @see #printZ(Appendable) */ private void print(Appendable out, int maximumDigits) throws IOException { // we have to make a copy to preserve the "immutable" contract // on Timestamp and we don't want someone reading the calendar // member while we've shifted it around. Timestamp adjusted = this; // Adjust UTC time back to local time if (this._offset != null && this._offset.intValue() != 0) { adjusted = make_localtime(); } print(out, adjusted, maximumDigits); } /** * Prints to an {@code Appendable} the string representation (in Ion format) * of this Timestamp in UTC. *

* This method produces the same output as {@link #toZString()}. * * @param out not {@code null} * * @throws IOException propagated when the {@code Appendable} throws it. * @throws IonException if the text representation of the timestamp requires more than * {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}. * * @see #print(Appendable) */ public void printZ(Appendable out) throws IOException { switch (_precision) { case YEAR: case MONTH: case DAY: { assert _offset == UNKNOWN_OFFSET; // No need to adjust offset, we won't be using it. print(out); break; } case MINUTE: case SECOND: case FRACTION: { Timestamp ztime = this.clone(); ztime._offset = UTC_OFFSET; ztime.print(out); break; } } } /** * Throws if the text representation of the timestamp would require more than the given number of digits in its * fractional component. * @param value a Timestamp with a non-null fraction. * @param maximumDigits the maximum number of digits allowed. */ private static void requirePrecisionWithinLimit(Timestamp value, int maximumDigits) { if (value._fraction.scale() > maximumDigits) { throw new IonException(String.format( "Timestamp with %d digits of precision cannot be serialized because it exceeds the " + "configurable maximum timestamp precision of %d digits. Timestamps that require more digits " + "may be written using a text writer configured with " + "IonTextWriterBuilder.withMaximumTimestampPrecisionDigits.", value._fraction.scale(), maximumDigits )); } } /** * helper for print(out) and printZ(out) so that printZ can create * a zulu time and pass it directly and print can apply the local * offset and adjust the various fields (without breaking the * contract to be immutable). * @param out destination for the text image of the value * @param adjusted the time value with the fields adjusted to match the desired text output * @param maximumDigits the maximum number of digits allowed in the fractional seconds * @throws IOException */ private static void print(Appendable out, Timestamp adjusted, int maximumDigits) throws IOException { // null is our first "guess" to get it out of the way if (adjusted == null) { out.append("null.timestamp"); return; } if (adjusted._precision == Precision.SECOND && adjusted._fraction != null) { requirePrecisionWithinLimit(adjusted, maximumDigits); } // so we have a real value - we'll start with the date portion // which we always have print_digits(out, adjusted._year, 4); if (adjusted._precision == Precision.YEAR) { out.append("T"); return; } out.append("-"); print_digits(out, adjusted._month, 2); // convert calendar months to a base 1 value if (adjusted._precision == Precision.MONTH) { out.append("T"); return; } out.append("-"); print_digits(out, adjusted._day, 2); if (adjusted._precision == Precision.DAY) { return; } out.append("T"); print_digits(out, adjusted._hour, 2); out.append(":"); print_digits(out, adjusted._minute, 2); // ok, so how much time do we have ? if (adjusted._precision == Precision.SECOND) { out.append(":"); print_digits(out, adjusted._second, 2); if (adjusted._fraction != null) { print_fractional_digits(out, adjusted._fraction); } } if (adjusted._offset != null) { int min, hour; min = adjusted._offset; if (min == 0) { out.append('Z'); } else { if (min < 0) { min = -min; out.append('-'); } else { out.append('+'); } hour = min / 60; min = min - hour*60; print_digits(out, hour, 2); out.append(":"); print_digits(out, min, 2); } } else { out.append("-00:00"); } } private static void print_digits(Appendable out, int value, int length) throws IOException { char temp[] = new char[length]; while (length > 0) { length--; int next = value / 10; temp[length] = (char)('0' + (value - next*10)); value = next; } while (length > 0) { length--; temp[length] = '0'; } for (char c : temp) { out.append(c); } } private static void print_fractional_digits(Appendable out, BigDecimal value) throws IOException { String temp = value.toPlainString(); // crude, but it works if (temp.charAt(0) == '0') { // this should always be true temp = temp.substring(1); } out.append(temp); } //========================================================================= // Timestamp arithmetic /** * Returns a timestamp relative to this one by the given number of * milliseconds. *

* This method always returns a Timestamp with the same precision as * the original. After performing the arithmetic, the resulting Timestamp's * seconds value will be truncated to the same fractional precision as the * original. For example, adjusting {@code 2012-04-01T00:00:00Z} by one * millisecond results in {@code 2012-04-01T00:00:00Z}; adjusting * {@code 2012-04-01T00:00:00.0010Z} by -1 millisecond results in * {@code 2012-04-01T00:00:00.0000Z}. To extend the precision when the * original Timestamp has coarser than SECOND precision and to avoid * truncation of the seconds value, use {@link #addSecond(int)}. * * @param amount a number of milliseconds. */ public final Timestamp adjustMillis(long amount) { if (amount == 0) return this; Timestamp ts = addMillisForPrecision(amount, _precision, false); //ts._precision = _precision; ts.clearUnusedPrecision(); if (ts._precision.includes(Precision.SECOND)) { // Maintain the same amount of fractional precision. if (_fraction == null) { ts._fraction = null; } else { // Truncate the result only if it exceeds the fractional precision of the original. if (ts._fraction.scale() > _fraction.scale()) { ts._fraction = ts._fraction.setScale(_fraction.scale(), RoundingMode.FLOOR); } } } return ts; } /** * Returns a timestamp relative to this one by the given number of * milliseconds. *

* This method always returns a Timestamp with SECOND precision and a seconds * value precise at least to the millisecond. For example, adding one millisecond * to {@code 2011T} results in {@code 2011-01-01T00:00:00.001-00:00}. To receive * a Timestamp that always maintains the same precision as the original, use * {@link #adjustMillis(long)}. * milliseconds. * * @param amount a number of milliseconds. */ public final Timestamp addMillis(long amount) { if (amount == 0 && _precision.includes(Precision.SECOND) && _fraction != null && _fraction.scale() >= 3) { // Zero milliseconds are to be added, and the precision does not need to be increased. return this; } return addMillisForPrecision(amount, Precision.SECOND, true); } /** * Adds the given number of milliseconds, extending (if necessary) the resulting Timestamp to the given * precision. * @param amount the number of milliseconds to add. * @param precision the precision that the Timestamp will be extended to, if it does not already include that * precision. * @param millisecondsPrecision true if and only if the `amount` includes milliseconds precision. If true, the * resulting timestamp's fraction will have precision at least to the millisecond. * @return a new Timestamp. */ private Timestamp addMillisForPrecision(long amount, Precision precision, boolean millisecondsPrecision) { // When millisecondsPrecision is true, the caller must do its own short-circuiting because it must // check the fractional precision. if (!millisecondsPrecision && amount == 0 && _precision == precision) return this; // This strips off the local offset, expressing our fields as if they // were UTC. BigDecimal millis = make_localtime().getDecimalMillis(); millis = millis.add(BigDecimal.valueOf(amount)); Precision newPrecision = _precision.includes(precision) ? _precision : precision; Timestamp ts = new Timestamp(millis, newPrecision, _offset); // Anything with courser-than-millis precision will have been extended // to 3 decimal places due to use of getDecimalMillis(). Compensate for // that by setting the scale such that it is never extended unless // milliseconds precision is being added and the fraction does not yet // have milliseconds precision. int newScale = millisecondsPrecision ? 3 : 0; if (_fraction != null) { newScale = Math.max(newScale, _fraction.scale()); } if (ts._fraction != null) { ts._fraction = newScale == 0 ? null : ts._fraction.setScale(newScale, RoundingMode.FLOOR); } if (_offset != null && _offset != 0) { ts.apply_offset(_offset); } return ts; } /** * Clears any fields more precise than this Timestamp's precision supports. */ private void clearUnusedPrecision() { switch (_precision) { case YEAR: _month = 1; case MONTH: _day = 1; case DAY: _hour = 0; _minute = 0; case MINUTE: _second = 0; _fraction = null; case SECOND: } } /** * Returns a timestamp relative to this one by the given number of seconds. *

* This method always returns a Timestamp with the same precision as * the original. For example, adjusting {@code 2012-04-01T00:00Z} by one * second results in {@code 2012-04-01T00:00Z}; adjusting * {@code 2012-04-01T00:00:00Z} by -1 second results in * {@code 2012-03-31T23:59:59Z}. To extend the precision when the original * Timestamp has coarser than SECOND precision, use {@link #addSecond(int)}. * * @param amount a number of seconds. */ public final Timestamp adjustSecond(int amount) { long delta = (long) amount * 1000; return adjustMillis(delta); } /** * Returns a timestamp relative to this one by the given number of seconds. *

* This method always returns a Timestamp with SECOND precision. * For example, adding one second to {@code 2011T} results in * {@code 2011-01-01T00:00:01-00:00}. To receive a Timestamp that always * maintains the same precision as the original, use {@link #adjustSecond(int)}. * * @param amount a number of seconds. */ public final Timestamp addSecond(int amount) { long delta = (long) amount * 1000; return addMillisForPrecision(delta, Precision.SECOND, false); } /** * Returns a timestamp relative to this one by the given number of minutes. *

* This method always returns a Timestamp with the same precision as * the original. For example, adjusting {@code 2012-04-01T} by one minute * results in {@code 2012-04-01T}; adjusting {@code 2012-04-01T00:00-00:00} * by -1 minute results in {@code 2012-03-31T23:59-00:00}. To extend the * precision when the original Timestamp has coarser than MINUTE precision, * use {@link #addMinute(int)}. * * @param amount a number of minutes. */ public final Timestamp adjustMinute(int amount) { long delta = (long) amount * 60 * 1000; return adjustMillis(delta); } /** * Returns a timestamp relative to this one by the given number of minutes. *

* This method always returns a Timestamp with at least MINUTE precision. * For example, adding one minute to {@code 2011T} results in * {@code 2011-01-01T00:01-00:00}. To receive a Timestamp that always * maintains the same precision as the original, use {@link #adjustMinute(int)}. * * @param amount a number of minutes. */ public final Timestamp addMinute(int amount) { long delta = (long) amount * 60 * 1000; return addMillisForPrecision(delta, Precision.MINUTE, false); } /** * Returns a timestamp relative to this one by the given number of hours. *

* This method always returns a Timestamp with the same precision as * the original. For example, adjusting {@code 2012-04-01T} by one hour * results in {@code 2012-04-01T}; adjusting {@code 2012-04-01T00:00-00:00} * by -1 hour results in {@code 2012-03-31T23:00-00:00}. To extend the * precision when the original Timestamp has coarser than MINUTE precision, * use {@link #addHour(int)}. * * @param amount a number of hours. */ public final Timestamp adjustHour(int amount) { long delta = (long) amount * 60 * 60 * 1000; return adjustMillis(delta); } /** * Returns a timestamp relative to this one by the given number of hours. *

* This method always returns a Timestamp with at least MINUTE precision. * For example, adding one hour to {@code 2011T} results in * {@code 2011-01-01T01:00-00:00}. To receive a Timestamp that always * maintains the same precision as the original, use {@link #adjustHour(int)}. * * @param amount a number of hours. */ public final Timestamp addHour(int amount) { long delta = (long) amount * 60 * 60 * 1000; return addMillisForPrecision(delta, Precision.MINUTE, false); } /** * Returns a timestamp relative to this one by the given number of days. *

* This method always returns a Timestamp with the same precision as * the original. For example, adjusting {@code 2012-04T} by one day results * in {@code 2012-04T}; adjusting {@code 2012-04-01T} by -1 days results in * {@code 2012-03-31T}. To extend the precision when the original Timestamp * has coarser than DAY precision, use {@link #addDay(int)}. * * @param amount a number of days. */ public final Timestamp adjustDay(int amount) { long delta = (long) amount * 24 * 60 * 60 * 1000; return adjustMillis(delta); } /** * Returns a timestamp relative to this one by the given number of days. * * @param amount a number of days. */ public final Timestamp addDay(int amount) { long delta = (long) amount * 24 * 60 * 60 * 1000; return addMillisForPrecision(delta, Precision.DAY, false); } // Shifting month and year are more complicated since the length of a month // varies and we want the day-of-month to stay the same when possible. // We rely on Calendar for the logic. /** * Returns a timestamp relative to this one by the given number of months. * The day field may be adjusted to account for different month length and * leap days. *

* This method always returns a Timestamp with the same precision as the * original. For example, adjusting {@code 2011T} by one month results in * {@code 2011T}; adding 12 months to {@code 2011T} results in {@code 2012T}. * To extend the precision when the original Timestamp has coarser than MONTH * precision, use {@link #addMonth(int)}. * * @param amount a number of months. */ public final Timestamp adjustMonth(int amount) { if (amount == 0) return this; return addMonthForPrecision(amount, _precision); } /** * Adds the given number of months, extending (if necessary) the resulting Timestamp to the given * precision. * @param amount the number of months to add. * @param precision the precision that the Timestamp will be extended to, if it does not already include that * precision. * @return a new Timestamp. */ private Timestamp addMonthForPrecision(int amount, Precision precision) { Calendar cal = calendarValue(); cal.add(Calendar.MONTH, amount); return new Timestamp(cal, precision, _fraction, _offset); } /** * Returns a timestamp relative to this one by the given number of months. * The day field may be adjusted to account for different month length and * leap days. For example, adding one month to {@code 2011-01-31} * results in {@code 2011-02-28}. * * @param amount a number of months. */ public final Timestamp addMonth(int amount) { if (amount == 0 && _precision.includes(Precision.MONTH)) return this; return addMonthForPrecision(amount, _precision.includes(Precision.MONTH) ? _precision : Precision.MONTH); } /** * Returns a timestamp relative to this one by the given number of years. * The day field may be adjusted to account for leap days. For example, * adjusting {@code 2012-02-29} by one year results in {@code 2013-02-28}. *

* Because YEAR is the coarsest precision possible, this method always * returns a Timestamp with the same precision as the original and * behaves identically to {@link #addYear(int)}. * * @param amount a number of years. */ public final Timestamp adjustYear(int amount) { return addYear(amount); } /** * Returns a timestamp relative to this one by the given number of years. * The day field may be adjusted to account for leap days. For example, * adding one year to {@code 2012-02-29} results in {@code 2013-02-28}. * * @param amount a number of years. */ public final Timestamp addYear(int amount) { if (amount == 0) return this; Calendar cal = calendarValue(); cal.add(Calendar.YEAR, amount); return new Timestamp(cal, _precision, _fraction, _offset); } //========================================================================= /** * Returns a hash code consistent with {@link #equals(Object)}. *

* {@inheritDoc} */ @Override public int hashCode() { // Performs a Shift-Add-XOR-Rotate hash. Rotating at each step to // produce an "Avalanche" effect for timestamps with small deltas, which // is found to be a common input data set. final int prime = 8191; int result = HASH_SIGNATURE; result = prime * result + (_fraction != null ? _fraction.hashCode() : 0); result ^= (result << 19) ^ (result >> 13); result = prime * result + this._year; result = prime * result + this._month; result = prime * result + this._day; result = prime * result + this._hour; result = prime * result + this._minute; result = prime * result + this._second; result ^= (result << 19) ^ (result >> 13); Precision precision = this._precision == Precision.FRACTION ? Precision.SECOND : this._precision; result = prime * result + precision.toString().hashCode(); result ^= (result << 19) ^ (result >> 13); result = prime * result + (_offset == null ? 0 : _offset.hashCode()); result ^= (result << 19) ^ (result >> 13); return result; } /** * Performs a comparison of the two points in time represented by two * Timestamps. * If the point in time represented by this Timestamp precedes that of * {@code t}, then {@code -1} is returned. * If {@code t} precedes this Timestamp then {@code 1} is returned. * If the Timestamps represent the same point in time, then * {@code 0} is returned. * Note that a {@code 0} result does not imply that the two Timestamps are * {@link #equals}, as the local offset or precision of the two Timestamps * may be different. * *

* This method is provided in preference to individual methods for each of * the six boolean comparison operators (<, ==, >, >=, !=, <=). * The suggested idiom for performing these comparisons is: * {@code (x.compareTo(y)}<op>{@code 0)}, * where <op> is one of the six comparison operators. * *

* For example, the pairs below will return a {@code 0} result: *

    *
  • {@code 2009T}
  • *
  • {@code 2009-01T}
  • *
  • {@code 2009-01-01T}
  • *
  • {@code 2009-01-01T00:00Z}
  • *
  • {@code 2009-01-01T00:00:00Z}
  • *
  • {@code 2009-01-01T00:00:00.0Z}
  • *
  • {@code 2009-01-01T00:00:00.00Z}
  • * *
  • {@code 2008-12-31T16:00-08:00}
  • *
  • {@code 2008-12-31T12:00-12:00}
  • *
  • {@code 2009-01-01T12:00+12:00}
  • *
* *

* Use the {@link #equals(Timestamp)} method to compare the point * in time, including precision and local offset. * * @param t * the other {@code Timestamp} to compare this {@code Timestamp} to * * @return * -1, 0, or 1 if this {@code Timestamp} * is less than, equal to, or greater than {@code t} respectively * * @throws NullPointerException if {@code t} is null. * * @see #equals(Timestamp) */ public int compareTo(Timestamp t) { // Test at millisecond precision first. long this_millis = this.getMillis(); long arg_millis = t.getMillis(); if (this_millis != arg_millis) { return (this_millis < arg_millis) ? -1 : 1; } // Values are equivalent at millisecond precision, so compare fraction BigDecimal this_fraction = ((this._fraction == null) ? BigDecimal.ZERO : this._fraction); BigDecimal arg_fraction = (( t._fraction == null) ? BigDecimal.ZERO : t._fraction); return this_fraction.compareTo(arg_fraction); } /** * Compares this {@link Timestamp} to the specified Object. * The result is {@code true} if and only if the parameter is a * {@link Timestamp} object that represents the same point in time, * precision and local offset as this Timestamp. *

* Use the {@link #compareTo(Timestamp)} method to compare only the point * in time, ignoring precision and local offset. * * @see #equals(Timestamp) * @see #compareTo(Timestamp) */ @Override public boolean equals(Object t) { if (!(t instanceof Timestamp)) return false; return equals((Timestamp)t); } /** * Compares this {@link Timestamp} to another {@link Timestamp} object. * The result is {@code true} if and only if the parameter * represents the same point in time and has * the same precision and local offset as this object. *

* These pairs are {@link #equals} to each other, as they * represent the same points in time, precision and local offset: * *

    *
  • {@code 2001-01-01T11:22+00:00} (minute precision, in UTC)
  • *
  • {@code 2001-01-01T11:22Z} (minute precision, in UTC)
  • *
* *

* On the other hand, none of these pairs are {@link #equals} to each other, * they represent the same points in time, but with different precisions * and/or local offsets: * *

    *
  • {@code 2001T} (year precision, unknown local offset)
  • *
  • {@code 2001-01T} (month precision, unknown local offset)
  • *
  • {@code 2001-01-01T} (day precision, unknown local offset)
  • * *
  • {@code 2001-01-01T00:00-00:00} (second precision, unknown local offset)
  • *
  • {@code 2001-01-01T00:00+00:00} (second precision, in UTC)
  • * *
  • {@code 2001-01-01T00:00.000-00:00} (millisecond precision, unknown local offset)
  • *
  • {@code 2001-01-01T00:00.000+00:00} (millisecond precision, in UTC)
  • *
* *

* Use the {@link #compareTo(Timestamp)} method to compare only the point * in time, ignoring precision and local offset. * * @see #compareTo(Timestamp) */ public boolean equals(Timestamp t) { if (this == t) return true; if (t == null) return false; // FRACTION precision is equivalent to SECOND precision. Precision thisPrecision = this._precision.includes(Precision.SECOND) ? Precision.SECOND : this._precision; Precision thatPrecision = t._precision.includes(Precision.SECOND) ? Precision.SECOND : t._precision; // if the precisions are not the same the values are not // precision doesn't matter WRT equality if (thisPrecision != thatPrecision) return false; // if the local offset are not the same the values are not if (this._offset == null) { if (t._offset != null) return false; } else { if (t._offset == null) return false; } // so now we check the actual time value if (this._year != t._year) return false; if (this._month != t._month) return false; if (this._day != t._day) return false; if (this._hour != t._hour) return false; if (this._minute != t._minute) return false; if (this._second != t._second) return false; // and if we have a local offset, check the value here if (this._offset != null) { if (this._offset.intValue() != t._offset.intValue()) return false; } // we only look at the fraction if we know that it's actually there if ((this._fraction != null && t._fraction == null) || (this._fraction == null && t._fraction != null)) { // one of the fractions are null return false; } if (this._fraction == null && t._fraction == null) { // both are null return true; } return this._fraction.equals(t._fraction); } private static short checkAndCastYear(int year) { if (year < 1 || year > 9999) { throw new IllegalArgumentException(String.format("Year %s must be between 1 and 9999 inclusive", year)); } return (short) year; } private static byte checkAndCastMonth(int month) { if (month < 1 || month > 12) { throw new IllegalArgumentException(String.format("Month %s must be between 1 and 12 inclusive", month)); } return (byte) month; } private static byte checkAndCastDay(int day, int year, int month) { int lastDayInMonth = last_day_in_month(year, month); if (day < 1 || day > lastDayInMonth) { throw new IllegalArgumentException(String.format("Day %s for year %s and month %s must be between 1 and %s inclusive", day, year, month, lastDayInMonth)); } return (byte) day; } private static byte checkAndCastHour(int hour) { if (hour < 0 || hour > 23) { throw new IllegalArgumentException(String.format("Hour %s must be between 0 and 23 inclusive", hour)); } return (byte) hour; } private static byte checkAndCastMinute(int minute) { if (minute < 0 || minute > 59) { throw new IllegalArgumentException(String.format("Minute %s must be between between 0 and 59 inclusive", minute)); } return (byte) minute; } private static byte checkAndCastSecond(int second) { if (second < 0 || second > 59) { throw new IllegalArgumentException(String.format("Second %s must be between between 0 and 59 inclusive", second)); } return (byte) second; } private static Precision checkFraction(Precision precision, BigDecimal fraction) { if (precision.includes(Precision.SECOND)) { if (fraction != null && (fraction.signum() == -1 || BigDecimal.ONE.compareTo(fraction) != 1)) { throw new IllegalArgumentException(String.format("Fractional seconds %s must be greater than or equal to 0 and less than 1", fraction)); } } else { if (fraction != null) { throw new IllegalArgumentException("Fraction must be null for non-second precision: " + fraction); } } return precision; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy