/* Copyright (c) 2001-2009, 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.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; import org.hsqldb.lib.StringUtil; // fredt@users 20020414 - patch 828957 by tjcrowder@users - JDK 1.3 compatibility /** * collection of static methods to convert Date and Timestamp strings * into corresponding Java objects and perform other Calendar related * operation.<p> * * Was reviewed for 1.7.2 resulting in centralising all DATETIME related * operstions.<p> * * From version 1.9.0, HSQLDB supports TIME ZONE with datetime types. The * values are stored internally as UTC seconds from 1970, regardless of the * time zone of the JVM, and converted as and when required, to the local * timezone. * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 1.9.0 * @since 1.7.0 */ public class HsqlDateTime { /** * A reusable static value for today's date. Should only be accessed * by getToday() */ private static long currentDateMillis; private static final Calendar tempCalDefault = new GregorianCalendar(); public static final Calendar tempCalGMT = new GregorianCalendar(TimeZone.getTimeZone("GMT")); private static final Date tempDate = new Date(0); private static final String sdfdPattern = "yyyy-MM-dd"; static SimpleDateFormat sdfd = new SimpleDateFormat(sdfdPattern); private static final String sdftPattern = "HH:mm:ss"; static SimpleDateFormat sdft = new SimpleDateFormat(sdftPattern); private static final String sdftsPattern = "yyyy-MM-dd HH:mm:ss"; static SimpleDateFormat sdfts = new SimpleDateFormat(sdftsPattern); private static final String sdftsSysPattern = "yyyy-MM-dd HH:mm:ss.SSS"; static SimpleDateFormat sdftsSys = new SimpleDateFormat(sdftsSysPattern); static { tempCalGMT.setLenient(false); sdfd.setCalendar(new GregorianCalendar(TimeZone.getTimeZone("GMT"))); sdfd.setLenient(false); sdft.setCalendar(new GregorianCalendar(TimeZone.getTimeZone("GMT"))); sdft.setLenient(false); sdfts.setCalendar(new GregorianCalendar(TimeZone.getTimeZone("GMT"))); sdfts.setLenient(false); } static { currentDateMillis = getNormalisedDate(System.currentTimeMillis()); } public static long getDateSeconds(String s) { try { synchronized (sdfd) { java.util.Date d = sdfd.parse(s); return d.getTime() / 1000; } } catch (Exception e) { throw Error.error(ErrorCode.X_22007); } } public static String getDateString(long seconds) { synchronized (sdfd) { sysDate.setTime(seconds * 1000); return sdfd.format(sysDate); } } public static long getTimestampSeconds(String s) { try { synchronized (sdfts) { java.util.Date d = sdfts.parse(s); return d.getTime() / 1000; } } catch (Exception e) { throw Error.error(ErrorCode.X_22007); } } public static void getTimestampString(StringBuffer sb, long seconds, int nanos, int scale) { synchronized (sdfts) { tempDate.setTime(seconds * 1000); sb.append(sdfts.format(tempDate)); if (scale > 0) { sb.append('.'); sb.append(StringUtil.toZeroPaddedString(nanos, 9, scale)); } } } public static String getTimestampString(long millis) { synchronized (sdfts) { sysDate.setTime(millis); return sdfts.format(sysDate); } } public static synchronized long getCurrentDateMillis(long millis) { if (millis - currentDateMillis >= 24 * 3600 * 1000) { currentDateMillis = getNormalisedDate(millis); } return currentDateMillis; } 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 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); } /** * 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 */ public 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 } /** * 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 convertToNormalisedTime(long t) { return convertToNormalisedTime(t, tempCalGMT); } public static long convertToNormalisedTime(long t, Calendar cal) { synchronized (cal) { setTimeInMillis(cal, t); resetToDate(cal); long t1 = getTimeInMillis(cal); return t - t1; } } public static long convertToNormalisedDate(long t, Calendar cal) { synchronized (cal) { setTimeInMillis(cal, t); resetToDate(cal); return getTimeInMillis(cal); } } public static long getNormalisedTime(long t) { Calendar cal = tempCalGMT; synchronized (cal) { setTimeInMillis(cal, t); resetToTime(cal); return getTimeInMillis(cal); } } public static long getNormalisedDate(long d) { synchronized (tempCalGMT) { setTimeInMillis(tempCalGMT, d); resetToDate(tempCalGMT); return getTimeInMillis(tempCalGMT); } } public static int getZoneSeconds(Calendar cal) { return (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000; } public static int getZoneMillis(Calendar cal, long millis) { //#ifdef JAVA4 // get zone for the specific date return cal.getTimeZone().getOffset(millis); //#else /* // get zone for the specific date setTimeInMillis(cal, millis); return (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET) ); */ //#endif JAVA4 } /** * Returns the indicated part of the given <code>java.util.Date</code> object. * @param m the millisecond time value 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 */ public static int getDateTimePart(long m, int part) { synchronized (tempCalGMT) { tempCalGMT.setTimeInMillis(m); return tempCalGMT.get(part); } } //J- 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', 'A', 'Y' }, { 'D', 'Y' }, { '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', '.' }, { 'F', 'F' } }; private static final String[] javaDateTokens = { "yyyy", "yyyy", "yyyy", "yy", "yy", "G", "G", "G", "G", "MMM", "MMMMM", "EEEE", "EE", "w", "dd", "D", "k", "K", "K", "mm", "ss", "aaa", "aaa", "aaa", "aaa", "S" }; //J+ /** Indicates end-of-input */ private static final char e = 0xffff; /** * Converts the given format into a pattern accepted by <code>java.text.SimpleDataFormat</code> * * @param format */ public static String toJavaDatePattern(String format) { int len = format.length(); char ch; StringBuffer sb = 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) { sb.setLength(sb.length() - tokenizer.length()); sb.append(javaDateTokens[index]); } tokenizer.reset(); if (tokenizer.isConsumed()) { continue; } } sb.append(ch); } sb.setLength(sb.length() - 1); String javaPattern = sb.toString(); return javaPattern; } /** * 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. */ public int length() { return offset; } /** * Returns an index of the last matched token. */ public int getLastMatch() { return last; } /** * Indicates whethe the last character has been consumed by the matcher. */ public boolean isConsumed() { return consumed; } /** * Checks whether the specified bit is not set. * * @param bit */ 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 */ 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; } } }