/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.types;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.json_voltpatches.JSONString;
import org.voltdb.common.Constants;
/**
* Represent a microsecond-accurate VoltDB timestamp type.
*/
public class TimestampType implements JSONString, Comparable<TimestampType> {
/**
* Create a TimestampType from microseconds from epoch.
* @param timestamp microseconds since epoch.
*/
public TimestampType(long timestamp) {
m_usecs = (short) (timestamp % 1000);
long millis = (timestamp - m_usecs) / 1000;
m_date = new Date(millis);
}
/**
* Create a TimestampType from a Java Date class.
* Microseconds will be rounded to zero.
* @param date Java Date instance.
*/
public TimestampType(Date date) {
m_usecs = 0;
m_date = (Date) date.clone();
}
private static long microsFromJDBCformat(String param){
java.sql.Timestamp sqlTS;
if (param.length() == 10) {
sqlTS = java.sql.Timestamp.valueOf(param + " 00:00:00.000");
}
else {
sqlTS = java.sql.Timestamp.valueOf(param);
}
final long timeInMillis = sqlTS.getTime();
final long fractionalSecondsInNanos = sqlTS.getNanos();
// Fractional microseconds would get truncated so flag them as an error.
if ((fractionalSecondsInNanos % 1000) != 0) {
throw new IllegalArgumentException("Can't convert from String to TimestampType with fractional microseconds");
}
// Milliseconds would be doubly counted as millions of nanos if they weren't truncated out via %.
return (timeInMillis * 1000) + ((fractionalSecondsInNanos % 1000000)/1000);
}
/**
* Given a string parseable by the JDBC Timestamp parser, return the fractional component
* in milliseconds.
*
* @param param A timstamp in string format that is parseable by JDBC.
* @return The fraction of a second portion of this timestamp expressed in milliseconds.
* @throws IllegalArgumentException if the timestamp uses higher than millisecond precision.
*/
public static long millisFromJDBCformat(String param) {
java.sql.Timestamp sqlTS = java.sql.Timestamp.valueOf(param);
final long fractionalSecondsInNanos = sqlTS.getNanos();
// Fractional milliseconds would get truncated so flag them as an error.
if ((fractionalSecondsInNanos % 1000000) != 0) {
throw new IllegalArgumentException("Can't convert from String to Date with fractional milliseconds");
}
return sqlTS.getTime();
}
/**
* Construct from a timestamp string in a complete date or time format.
* This is typically used for reading CSV data or data output
* from {@link java.sql.Timestamp}'s string format.
*
* @param param A string in one of these formats:
* "YYYY-MM-DD", "YYYY-MM-DD HH:MM:SS",
* OR "YYYY-MM-DD HH:MM:SS.sss" with sss
* allowed to be from 0 up to 6 significant digits.
*/
public TimestampType(String param) {
this(microsFromJDBCformat(param));
}
/**
* Create a TimestampType instance for the current time.
*/
public TimestampType() {
m_usecs = 0;
m_date = new Date();
}
/**
* Read the microsecond in time stored by this timestamp.
* @return microseconds
*/
public long getTime() {
long millis = m_date.getTime();
return millis * 1000 + m_usecs;
}
/**
* Get the microsecond portion of this timestamp
* @return Microsecond portion of timestamp as a short
*/
public short getUSec() {
return m_usecs;
}
/**
* Equality.
* @return true if equal.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof TimestampType))
return false;
TimestampType ts = (TimestampType)o;
if (!ts.m_date.equals(this.m_date))
return false;
if (!(ts.m_usecs == this.m_usecs))
return false;
return true;
}
/**
* An implementation of toString for debugging and printing VoltTables.
* This formats the timestamp value using a standard ODBC date format,
* similar to ISO-8601. The format is yyyy-MM-dd HH:mm:ss.SSSSSS.
* Here, unlike the Java SimpleDateFormat formatter, the SSSSSS part
* is the number of microseconds. The timezone is the default
* timezone of the JVM. This will be the timezone of the application,
* which may not match the timezone of the database server. See the overload
* of this function which accepts a time zone for more control
* over the specification of the timezone.
*/
@Override
public String toString() {
return toString(TimeZone.getDefault());
}
/**
* An implementation of toString for debugging and printing VoltTables
* which allows the specification of a timezone.
*
* @param zone Desired timezone.
* @return A string with the time in ODBC format with the desired timezone.
*/
public String toString(TimeZone zone) {
// This should all be replaced with Java 8's java.time
// facilities.
SimpleDateFormat sdf = new SimpleDateFormat(Constants.ODBC_DATE_FORMAT_STRING);
if (zone != null) {
sdf.setTimeZone(zone);
}
Date dateToMillis = (Date) m_date.clone(); // deep copy as we change it later
short usecs = m_usecs;
if (usecs < 0) {
// Negative usecs can occur for dates before 1970.
// To be expressed as positive decimals, they must "borrow" a milli from the date
// and convert it to 1000 micros.
dateToMillis.setTime(dateToMillis.getTime()-1);
usecs += 1000;
}
assert(usecs >= 0);
String format = sdf.format(dateToMillis);
// zero-pad so 1 or 2 digit usecs get appended correctly
return format + String.format("%03d", usecs);
}
/**
* Hashcode with the same uniqueness as a Java Date.
*/
@Override
public int hashCode() {
long usec = this.getTime();
return (int) usec ^ (int) (usec >> 32);
}
/**
* CompareTo - to mimic Java Date
*/
@Override
public int compareTo(TimestampType dateval) {
int comp = m_date.compareTo(dateval.m_date);
if (comp == 0) {
return m_usecs - dateval.m_usecs;
}
else {
return comp;
}
}
/**
* Retrieve a copy of the approximate Java date.
* The returned date is a copy; this object will not be affected by
* modifications of the returned instance.
* @return Clone of underlying Date object.
*/
public Date asApproximateJavaDate() {
return (Date) m_date.clone();
}
/**
* Retrieve a copy of the Java date for a TimeStamp with millisecond granularity.
* The returned date is a copy; this object will not be affected by
* modifications of the returned instance.
* @return Clone of underlying Date object.
*/
public Date asExactJavaDate() {
if (m_usecs != 0) {
throw new RuntimeException("Can't convert to java Date from TimestampType with fractional milliseconds");
}
return (Date) m_date.clone();
}
/**
* Retrieve a properly typed copy of the Java date for a TimeStamp with millisecond granularity.
* The returned date is a copy; this object will not be affected by
* modifications of the returned instance.
* @return specifically typed copy of underlying Date object.
*/
public java.sql.Date asExactJavaSqlDate() {
if (m_usecs != 0) {
throw new RuntimeException("Can't convert to sql Date from TimestampType with fractional milliseconds");
}
return new java.sql.Date(m_date.getTime());
}
/**
* Retrieve a properly typed copy of the Java Timestamp for the VoltDB TimeStamp.
* The returned Timestamp is a copy; this object will not be affected by
* modifications of the returned instance.
* @return reformatted Timestamp expressed internally as 1000s of nanoseconds.
*/
public java.sql.Timestamp asJavaTimestamp() {
java.sql.Timestamp result = new java.sql.Timestamp(m_date.getTime());
result.setNanos(result.getNanos() + m_usecs * 1000);
return result;
}
@Override
public String toJSONString() {
return String.valueOf(getTime());
}
private final Date m_date; // stores milliseconds from epoch.
private final short m_usecs; // stores microsecond within date's millisecond.
}