/* Copyright (c) 2001-2008, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
// fredt@users 20020130 - patch 1.7.0 by fredt - new class
// replaces patch by deforest@users
// fredt@users 20020414 - patch 517028 by peterhudson@users - use of calendar
// fredt@users 20020414 - patch 828957 by tjcrowder@users - JDK 1.3 compatibility
// fredt@users 20040105 - patch 870957 by Gerhard Hiller - JDK bug workaround
/**
* collection of static methods to convert Date, Time and Timestamp strings
* into corresponding Java objects. Also accepts SQL literals such as NOW,
* TODAY as valid strings and returns the current date / time / datetime.
* Compatible with jdk 1.1.x.<p>
*
* Was reviewed for 1.7.2 resulting in centralising all DATETIME related
* operstions.<p>
*
* HSQLDB uses the client and server's default timezone for all DATETIME
* operations. It stores the DATETIME values in .log and .script files using
* the default locale of the server. The same values are stored as binary
* UTC timestamps in .data files. If the database is trasported from one
* timezone to another, then the DATETIME values in cached tables will be
* handled as UTC but those in other tables will be treated as local. So
* a timestamp representing 12 noon stored in Tokyo timezone will be treated
* as 9 pm in London when stored in a cached table but the same value stored
* in a memory table will be treated as 12 noon.
*
* @author fredt@users
* @version 1.7.2
* @since 1.7.0
*/
public class HsqlDateTime {
/**
* A reusable static value for today's date. Should only be accessed
* by getToday()
*/
private static Calendar today = new GregorianCalendar();
private static Calendar tempCal = new GregorianCalendar();
private static Calendar tempCalDefault = new GregorianCalendar();
private static Calendar tempCalGMT =
new GregorianCalendar(TimeZone.getTimeZone("GMT"));
private static Date tempDate = new Date(0);
private static Date currentDate;
static {
resetToday(System.currentTimeMillis());
}
static final String zerodatetime = "1970-01-01 00:00:00.000000000";
static final String zeronanos = "000000000";
/**
* Converts a string in JDBC timestamp escape format to a
* <code>Timestamp</code> value.
*
* @param s timestamp in format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
* where end part can be omitted, or "NOW" (case insensitive)
* @return corresponding <code>Timestamp</code> value
* @exception java.lang.IllegalArgumentException if the given argument
* does not have the format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
*/
public static Timestamp timestampValue(String s) throws HsqlException {
if (s == null) {
throw Trace.error(Trace.HsqlDateTime_null_string);
}
if (s.length() > zerodatetime.length()) {
throw Trace.error(Trace.STRING_DATA_TRUNCATION);
}
s = s + zerodatetime.substring(s.length());
return Timestamp.valueOf(s);
}
/**
* For use with .script file, simpler than above
*/
public static Timestamp simpleTimestampValue(String s) {
return Timestamp.valueOf(s);
}
/**
* @param time milliseconds
* @param nano nanoseconds
* @return Timestamp object
*/
public static Timestamp timestampValue(long time, int nano) {
Timestamp ts = new Timestamp(time);
ts.setNanos(nano);
return ts;
}
/**
* Converts a string in JDBC date escape format to a <code>Date</code>
* value. Also accepts Timestamp values.
*
* @param s date in format <code>yyyy-mm-dd</code>,
* @return corresponding <code>Date</code> value
* @exception java.lang.IllegalArgumentException if the given argument
* does not have the format <code>yyyy-mm-dd</code>
*/
public static Date dateValue(String s) throws HsqlException {
if (s == null) {
throw Trace.error(Trace.HsqlDateTime_null_string);
}
if (s.length() > sdfdPattern.length()) {
s = s.substring(0, sdfdPattern.length());
}
return Date.valueOf(s);
}
/**
* Converts a string in JDBC date escape format to a
* <code>Time</code> value.
*
* @param s date in format <code>hh:mm:ss</code>
* @return corresponding <code>Time</code> value
* @exception java.lang.IllegalArgumentException if the given argument
* does not have the format <code>hh:mm:ss</code>
*/
public static Time timeValue(String s) {
if (s == null) {
throw new java.lang.IllegalArgumentException(
Trace.getMessage(Trace.HsqlDateTime_null_string));
}
return Time.valueOf(s);
}
static int compare(Date a, Date b) {
long atime = a.getTime();
long btime = b.getTime();
if (atime == btime) {
return 0;
}
return atime > btime ? 1
: -1;
}
static int compare(Time a, Time b) {
long atime = a.getTime();
long btime = b.getTime();
if (atime == btime) {
return 0;
}
return atime > btime ? 1
: -1;
}
static int compare(Timestamp a, Timestamp b) {
long atime = a.getTime();
long btime = b.getTime();
if (atime == btime) {
if (a.getNanos() == b.getNanos()) {
return 0;
}
return a.getNanos() > b.getNanos() ? 1
: -1;
}
return atime > btime ? 1
: -1;
}
public static synchronized Date getCurrentDate(long millis) {
getToday(millis);
return currentDate;
}
public static Timestamp getTimestamp(long millis) {
return new Timestamp(millis);
}
private static final String sdftPattern = "HH:mm:ss";
private static final String sdfdPattern = "yyyy-MM-dd";
private static final String sdftsPattern = "yyyy-MM-dd HH:mm:ss.";
private static final String sdftsSysPattern = "yyyy-MM-dd HH:mm:ss.SSS";
static SimpleDateFormat sdfd = new SimpleDateFormat(sdfdPattern);
static SimpleDateFormat sdft = new SimpleDateFormat(sdftPattern);
static SimpleDateFormat sdfts = new SimpleDateFormat(sdftsPattern);
static SimpleDateFormat sdftsSys = new SimpleDateFormat(sdftsSysPattern);
/**
* Creates a valid timestamp string - jre 1.3 returns incorrect date part
* for Timestamp.toString();
*/
public static String getTimestampString(Timestamp x) {
synchronized (sdfts) {
sdfts.setCalendar(tempCalDefault);
String n = String.valueOf(x.getNanos());
return sdfts.format(x) + zeronanos.substring(n.length()) + n;
}
}
/**
* Creates a full length timestamp string, with 9 digist for nanos
*/
public static String getTimestampString(Timestamp x, Calendar cal) {
synchronized (sdfts) {
sdfts.setCalendar(cal == null ? tempCalDefault
: cal);
String n = String.valueOf(x.getNanos());
return sdfts.format(x) + zeronanos.substring(n.length()) + n;
}
}
private static java.util.Date sysDate = new java.util.Date();
public static String getSytemTimeString() {
synchronized (sdftsSys) {
sysDate.setTime(System.currentTimeMillis());
return sdftsSys.format(sysDate);
}
}
public static String getTimestampString(long timestamp) {
synchronized (sdftsSys) {
sysDate.setTime(timestamp);
return sdftsSys.format(sysDate);
}
}
public static String getTimeString(java.util.Date x, Calendar cal) {
synchronized (sdft) {
sdft.setCalendar(cal == null ? tempCalDefault
: cal);
return sdft.format(x);
}
}
public static String getDateString(java.util.Date x, Calendar cal) {
synchronized (sdfd) {
sdfd.setCalendar(cal == null ? tempCalDefault
: cal);
return sdfd.format(x);
}
}
/**
* Returns the same Date Object. This object should be treated as
* read-only.
*/
static synchronized Calendar getToday(long millis) {
if (millis - getTimeInMillis(today) >= 24 * 3600 * 1000) {
resetToday(millis);
}
return today;
}
public static void resetToDate(Calendar cal) {
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
}
public static void resetToTime(Calendar cal) {
cal.set(Calendar.YEAR, 1970);
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DATE, 1);
cal.set(Calendar.MILLISECOND, 0);
}
/**
* resets the static reusable value today
*/
private static synchronized void resetToday(long millis) {
//#ifdef JAVA4
// Use method directly
today.setTimeInMillis(millis);
//#else
/*
// Have to go indirect
tempDate.setTime(millis);
today.setTime(tempDate);
*/
//#endif JAVA4
resetToDate(today);
currentDate = new Date(getTimeInMillis(today));
}
/**
* Sets the time in the given Calendar using the given milliseconds value; wrapper method to
* allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
*
* @param cal the Calendar
* @param millis the time value in milliseconds
*/
private static void setTimeInMillis(Calendar cal, long millis) {
//#ifdef JAVA4
// Use method directly
cal.setTimeInMillis(millis);
//#else
/*
// Have to go indirect
synchronized (tempDate) {
tempDate.setTime(millis);
cal.setTime(tempDate);
}
*/
//#endif JAVA4
}
public static long getTimeInMillis(java.util.Date dt, Calendar source,
Calendar target) {
if (source == null) {
source = tempCalDefault;
}
if (target == null) {
target = tempCalDefault;
}
synchronized (tempCal) {
tempCal.setTimeZone(source.getTimeZone());
tempCal.setTime(dt);
tempCal.setTimeZone(target.getTimeZone());
return getTimeInMillis(tempCal);
}
}
/**
* Gets the time from the given Calendar as a milliseconds value; wrapper method to
* allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
*
* @param cal the Calendar
* @return the time value in milliseconds
*/
public static long getTimeInMillis(Calendar cal) {
//#ifdef JAVA4
// Use method directly
return (cal.getTimeInMillis());
//#else
/*
// Have to go indirect
return (cal.getTime().getTime());
*/
//#endif JAVA4
}
public static long getNormalisedTime(long t) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, t);
resetToTime(tempCalDefault);
return getTimeInMillis(tempCalDefault);
}
}
public static Time getNormalisedTime(Time t) {
return new Time(getNormalisedTime(t.getTime()));
}
public static Time getNormalisedTime(Timestamp ts) {
return new Time(getNormalisedTime(ts.getTime()));
}
public static long getNormalisedDate(long d) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, d);
resetToDate(tempCalDefault);
return getTimeInMillis(tempCalDefault);
}
}
public static Date getNormalisedDate(Timestamp ts) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, ts.getTime());
resetToDate(tempCalDefault);
long value = getTimeInMillis(tempCalDefault);
return new Date(value);
}
}
public static Date getNormalisedDate(Date d) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, d.getTime());
resetToDate(tempCalDefault);
long value = getTimeInMillis(tempCalDefault);
return new Date(value);
}
}
public static Timestamp getNormalisedTimestamp(Time t) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, System.currentTimeMillis());
resetToDate(tempCalDefault);
long value = getTimeInMillis(tempCalDefault) + t.getTime();
return new Timestamp(value);
}
}
public static Timestamp getNormalisedTimestamp(Date d) {
synchronized (tempCalDefault) {
setTimeInMillis(tempCalDefault, d.getTime());
resetToDate(tempCalDefault);
long value = getTimeInMillis(tempCalDefault);
return new Timestamp(value);
}
}
/**
* Returns the indicated part of the given <code>java.util.Date</code> object.
* @param d the <code>Date</code> object from which to extract the indicated part
* @param part an integer code corresponding to the desired date part
* @return the indicated part of the given <code>java.util.Date</code> object
*/
static int getDateTimePart(java.util.Date d, int part) {
synchronized (tempCalDefault) {
tempCalDefault.setTime(d);
return tempCalDefault.get(part);
}
}
private static final char[][] dateTokens = {
{
'R', 'R', 'R', 'R'
}, {
'I', 'Y', 'Y', 'Y'
}, {
'Y', 'Y', 'Y', 'Y'
}, {
'I', 'Y'
}, {
'Y', 'Y'
}, {
'B', 'C'
}, {
'B', '.', 'C', '.'
}, {
'A', 'D'
}, {
'A', '.', 'D', '.'
}, {
'M', 'O', 'N'
}, {
'M', 'O', 'N', 'T', 'H'
}, { 'D' }, {
'I', 'W'
}, {
'D', 'D'
}, {
'D', 'D', 'D'
}, {
'H', 'H', '2', '4'
}, {
'H', 'H', '1', '2'
}, {
'H', 'H'
}, {
'M', 'I',
}, {
'S', 'S'
}, {
'A', 'M'
}, {
'P', 'M',
}, {
'A', '.', 'M', '.'
}, {
'P', '.', 'M', '.'
}
};
private static final String[] javaDateTokens = {
"yyyy", "yyyy", "yyyy", "yy", "yy", "G", "G", "G", "G", "MMM",
"MMMMM", "E", "w", "dd", "D", "k", "K", "K", "mm", "ss", "aaa", "aaa",
"aaa", "aaa"
};
/** Indicates end-of-input */
public static final char e = 0xffff;
/**
* Converts the given format into a pattern accepted by <code>java.text.SimpleDataFormat</code>
* @param format
* @return
*/
public static String toJavaDatePattern(String format) {
int len = format.length();
char ch;
StringBuffer pattern = new StringBuffer(len);
Tokenizer tokenizer = new Tokenizer();
for (int i = 0; i <= len; i++) {
ch = (i == len) ? e
: format.charAt(i);
if (!tokenizer.next(ch, dateTokens)) {
int index = tokenizer.getLastMatch();
if (index >= 0) {
pattern.setLength(pattern.length() - tokenizer.length());
pattern.append(javaDateTokens[index]);
}
tokenizer.reset();
if (tokenizer.isConsumed()) {
continue;
}
}
pattern.append(ch);
}
pattern.setLength(pattern.length() - 1);
return pattern.toString();
}
/**
* This class can match 64 tokens at maximum.
*/
static class Tokenizer {
private int last;
private int offset;
private long state;
private boolean consumed;
public Tokenizer() {
reset();
}
/**
* Resets for next reuse.
*
*/
public void reset() {
last = -1;
offset = -1;
state = 0;
}
/**
* Returns a length of a token to match.
* @return
*/
public int length() {
return offset;
}
/**
* Returns an index of the last matched token.
* @return
*/
public int getLastMatch() {
return last;
}
/**
* Indicates whethe the last character has been consumed by the matcher.
* @return
*/
public boolean isConsumed() {
return consumed;
}
/**
* Checks whether the specified bit is not set.
* @param bit
* @return
*/
private boolean isZeroBit(int bit) {
return (state & (1L << bit)) == 0;
}
/**
* Sets the specified bit.
* @param bit
*/
private void setBit(int bit) {
state |= (1L << bit);
}
/**
* Matches the specified character against tokens.
* @param ch
* @param tokens
* @return
*/
public boolean next(char ch, char[][] tokens) {
// Use local variable for performance
int index = ++offset;
int len = offset + 1;
int left = 0;
consumed = false;
for (int i = tokens.length; --i >= 0; ) {
if (isZeroBit(i)) {
if (tokens[i][index] == ch) {
consumed = true;
if (tokens[i].length == len) {
setBit(i);
last = i;
} else {
++left;
}
} else {
setBit(i);
}
}
}
return left > 0;
}
}
}