microsoft.sql.DateTimeOffset Maven / Gradle / Ivy
Show all versions of mssql-jdbc Show documentation
/*
* Microsoft JDBC Driver for SQL Server
*
* Copyright(c) Microsoft Corporation All rights reserved.
*
* This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package microsoft.sql;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* A Java class for accessing values of the SQL Server DATETIMEOFFSET data type.
*
* The DateTimeOffset class represents a java.sql.Timestamp, including fractional seconds, plus an integer representing the number of minutes offset
* from GMT.
*/
public final class DateTimeOffset implements java.io.Serializable, java.lang.Comparable {
private static final long serialVersionUID = 541973748553014280L;
private final long utcMillis;
private final int nanos;
private final int minutesOffset;
private static final int NANOS_MIN = 0;
private static final int NANOS_MAX = 999999999;
private static final int SS_NANOS_MAX = 9999999;
private static final int MINUTES_OFFSET_MIN = -14 * 60;
private static final int MINUTES_OFFSET_MAX = 14 * 60;
private static final int HUNDRED_NANOS_PER_SECOND = 10000000;
/**
* Constructs a DateTimeOffset instance.
*
* This method does not check that its arguments represent a timestamp value that falls within the range of values acceptable to SQL Server for
* the DATETIMEOFFSET data type. That is, it is possible to create a DateTimeOffset instance representing a value outside the range from 1 January
* 1AD 00:00:00 UTC to 31 December 9999 00:00:00 UTC.
*/
private DateTimeOffset(java.sql.Timestamp timestamp,
int minutesOffset) {
// Combined time zone and DST offset must be between -14:00 and 14:00
if (minutesOffset < MINUTES_OFFSET_MIN || minutesOffset > MINUTES_OFFSET_MAX)
throw new IllegalArgumentException();
this.minutesOffset = minutesOffset;
// Nanos must be between 0 and 999999999 inclusive
int timestampNanos = timestamp.getNanos();
if (timestampNanos < NANOS_MIN || timestampNanos > NANOS_MAX)
throw new IllegalArgumentException();
// This class represents values to 100ns precision. If the java.sql.Timestamp argument
// represents a value that is more precise, then nanos in excess of the 100ns precision
// allowed by this class are rounded to the nearest multiple of 100ns.
//
// Values within 50 nanoseconds of the next second are rounded up to the next second.
// Note: Values within 50 nanoseconds of the end of time wrap back to the beginning.
int hundredNanos = (timestampNanos + 50) / 100;
this.nanos = 100 * (hundredNanos % HUNDRED_NANOS_PER_SECOND);
this.utcMillis = timestamp.getTime() - timestamp.getNanos() / 1000000 + 1000 * (hundredNanos / HUNDRED_NANOS_PER_SECOND);
// Postconditions
assert this.minutesOffset >= MINUTES_OFFSET_MIN && this.minutesOffset <= MINUTES_OFFSET_MAX : "minutesOffset: " + this.minutesOffset;
assert this.nanos >= NANOS_MIN && this.nanos <= NANOS_MAX : "nanos: " + this.nanos;
assert 0 == this.nanos % 100 : "nanos: " + this.nanos;
assert 0 == this.utcMillis % 1000L : "utcMillis: " + this.utcMillis;
}
/**
* Converts a java.sql.Timestamp value with an integer offset to the equivalent DateTimeOffset value
*
* @param timestamp
* A java.sql.Timestamp value
* @param minutesOffset
* An integer offset in minutes
* @return The DateTimeOffset value of the input timestamp and minutesOffset
*/
public static DateTimeOffset valueOf(java.sql.Timestamp timestamp,
int minutesOffset) {
return new DateTimeOffset(timestamp, minutesOffset);
}
/**
* Converts a java.sql.Timestamp value with a Calendar value to the equivalent DateTimeOffset value
*
* @param timestamp
* A java.sql.Timestamp value
* @param calendar
* A java.util.Calendar value
* @return The DateTimeOffset value of the input timestamp and calendar
*/
public static DateTimeOffset valueOf(java.sql.Timestamp timestamp,
Calendar calendar) {
// (Re)Set the calendar's time to the value in the timestamp so that get(ZONE_OFFSET) and get(DST_OFFSET) report
// the correct values for the time indicated, taking into account DST transition times and any historical changes
// to the DST transition schedule.
calendar.setTimeInMillis(timestamp.getTime());
return new DateTimeOffset(timestamp, (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000));
}
private String formattedValue = null;
/**
* Formats a datetimeoffset as yyyy-mm-dd hh:mm:ss[.fffffffff] [+|-]hh:mm, where yyyy-mm-dd hh:mm:ss[.fffffffff] indicates a timestamp that is
* offset from UTC by the number of minutes indicated by [+|-]hh:mm.
*
* @return a String object in yyyy-mm-dd hh:mm:ss[.fffffffff] [+|-]hh:mm format
*/
@Override
public String toString() {
// Because formatting the value as a string is computationally expensive (involving creation of a Calendar and
// a TimeZone, String formatters, etc.), cache the formatted value the first time it is needed. This can be done
// simply with the single-check idiom because the DateTimeOffset class is effectively immutable.
String result = formattedValue;
if (null == result) {
// Format the offset as +hh:mm or -hh:mm. Zero offset is formatted as +00:00.
String formattedOffset = (minutesOffset < 0) ?
String.format(Locale.US, "-%1$02d:%2$02d", -minutesOffset / 60, -minutesOffset % 60) :
String.format(Locale.US, "+%1$02d:%2$02d", minutesOffset / 60, minutesOffset % 60);
// Like java.sql.Date.toString() and java.sql.Timestamp.toString(), DateTimeOffset.toString() produces
// a value that is not locale-sensitive. The date part of the returned string is a Gregorian date, even
// if the VM default locale would otherwise indicate that a Buddhist calendar should be used.
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT" + formattedOffset), Locale.US);
// Initialize the calendar with the UTC milliseconds value represented by this DateTimeOffset object
calendar.setTimeInMillis(utcMillis);
// Assumption: nanos is in a valid range for printing as a 0-prefixed, 7-digit decimal number
// The DateTimeOffset constructor ensures that this is the case.
assert nanos >= NANOS_MIN && nanos <= NANOS_MAX;
// Format the returned string value from the calendar's component fields and the UTC offset
formattedValue = result = (0 == nanos) ?
String.format(Locale.US, "%1$tF %1$tT %2$s", calendar, formattedOffset) :
String.format(Locale.US, "%1$tF %1$tT.%2$s %3$s", calendar, // Example (nanos = 123456000):
java.math.BigDecimal.valueOf(nanos, 9) // -> 0.123456000
.stripTrailingZeros() // -> 0.123456
.toPlainString() // -> "0.123456"
.substring(2), // -> "123456"
formattedOffset);
}
return result;
}
@Override
public boolean equals(Object o) {
// Fast check for reference equality
if (this == o)
return true;
// Check other object's type (and implicitly test for null)
if (!(o instanceof DateTimeOffset))
return false;
DateTimeOffset other = (DateTimeOffset) o;
return utcMillis == other.utcMillis && nanos == other.nanos && minutesOffset == other.minutesOffset;
}
@Override
public int hashCode() {
// Start by approximately folding the date and time components together.
// Ignore any sub-second component of the utcMillis, which is always 0.
// Milliseconds are kept in the nanos field.
assert 0 == utcMillis % 1000L;
long seconds = utcMillis / 1000L;
int result = 571;
result = 2011 * result + (int) seconds;
result = 3217 * result + (int) (seconds / 60 * 60 * 24 * 365);
// Fold in nanoseconds/microseconds/milliseconds
result = 3919 * result + nanos / 100000;
result = 4463 * result + nanos / 1000;
result = 5227 * result + nanos;
// Fold in the hour and minute portions of the time zone offset
// Typically the minutes are 0, so the hours have more impact on the hash
result = 6689 * result + minutesOffset;
result = 7577 * result + minutesOffset / 60;
// The low order bits of the result should at this point be very
// sensitive to differences in any of the DateTimeOffset fields,
// even for small bucket sizes.
return result;
}
/**
* Gets this DateTimeOffset object's timestamp value.
*
* The returned value represents an instant in time as the number of milliseconds since January 1, 1970, 00:00:00 GMT.
*
* @return this DateTimeOffset object's timestamp component
*/
public java.sql.Timestamp getTimestamp() {
java.sql.Timestamp timestamp = new java.sql.Timestamp(utcMillis);
timestamp.setNanos(nanos);
return timestamp;
}
/**
* Gets this DateTimeOffset object's offset value.
*
* @return this DateTimeOffset object's minutes offset from GMT
*/
public int getMinutesOffset() {
return minutesOffset;
}
/**
* Compares this DateTimeOffset object with another DateTimeOffset object to determine their relative order.
*
* The ordering is based on the timestamp component only. The offset component is not compared. Two DateTimeOffset objects are considered
* equivalent with respect to ordering as long as they represent the same moment in time, regardless of the location of the event. This is how SQL
* Server orders DATETIMEOFFSET values.
*
* @return a negative integer, zero, or a positive integer as this DateTimeOffset is less than, equal to, or greater than the specified
* DateTimeOffset.
*/
public int compareTo(DateTimeOffset other) {
// Note that no explicit check for null==other is necessary. The contract for compareTo()
// says that a NullPointerException is to be thrown if null is passed as an argument.
// The fact that nanos are non-negative guarantees the subtraction at the end
// cannot produce a signed value outside the range representable in an int.
assert nanos >= 0;
assert other.nanos >= 0;
return (utcMillis > other.utcMillis) ? 1 : (utcMillis < other.utcMillis) ? -1 : nanos - other.nanos;
}
private static class SerializationProxy implements java.io.Serializable {
private final long utcMillis;
private final int nanos;
private final int minutesOffset;
SerializationProxy(DateTimeOffset dateTimeOffset) {
this.utcMillis = dateTimeOffset.utcMillis;
this.nanos = dateTimeOffset.nanos;
this.minutesOffset = dateTimeOffset.minutesOffset;
}
private static final long serialVersionUID = 664661379547314226L;
private Object readResolve() {
java.sql.Timestamp timestamp = new java.sql.Timestamp(utcMillis);
timestamp.setNanos(nanos);
return new DateTimeOffset(timestamp, minutesOffset);
}
}
private Object writeReplace() {
return new SerializationProxy(this);
}
private void readObject(java.io.ObjectInputStream stream) throws java.io.InvalidObjectException {
// For added security/robustness, the only way to rehydrate a serialized DateTimeOffset
// is to use a SerializationProxy. Direct use of readObject() is not supported.
throw new java.io.InvalidObjectException("");
}
}