001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import java.sql.Date;
034: import java.sql.Time;
035: import java.sql.Timestamp;
036: import java.text.SimpleDateFormat;
037: import java.util.Calendar;
038: import java.util.GregorianCalendar;
039: import java.util.TimeZone;
040:
041: // fredt@users 20020130 - patch 1.7.0 by fredt - new class
042: // replaces patch by deforest@users
043: // fredt@users 20020414 - patch 517028 by peterhudson@users - use of calendar
044: // fredt@users 20020414 - patch 828957 by tjcrowder@users - JDK 1.3 compatibility
045: // fredt@users 20040105 - patch 870957 by Gerhard Hiller - JDK bug workaround
046:
047: /**
048: * collection of static methods to convert Date, Time and Timestamp strings
049: * into corresponding Java objects. Also accepts SQL literals such as NOW,
050: * TODAY as valid strings and returns the current date / time / datetime.
051: * Compatible with jdk 1.1.x.<p>
052: *
053: * Was reviewed for 1.7.2 resulting in centralising all DATETIME related
054: * operstions.<p>
055: *
056: * HSQLDB uses the client and server's default timezone for all DATETIME
057: * operations. It stores the DATETIME values in .log and .script files using
058: * the default locale of the server. The same values are stored as binary
059: * UTC timestamps in .data files. If the database is trasported from one
060: * timezone to another, then the DATETIME values in cached tables will be
061: * handled as UTC but those in other tables will be treated as local. So
062: * a timestamp representing 12 noon stored in Tokyo timezone will be treated
063: * as 9 pm in London when stored in a cached table but the same value stored
064: * in a memory table will be treated as 12 noon.
065: *
066: * @author fredt@users
067: * @version 1.7.2
068: * @since 1.7.0
069: */
070: public class HsqlDateTime {
071:
072: /**
073: * A reusable static value for today's date. Should only be accessed
074: * by getToday()
075: */
076: private static Calendar today = new GregorianCalendar();
077: private static Calendar tempCal = new GregorianCalendar();
078: private static Calendar tempCalDefault = new GregorianCalendar();
079: private static Calendar tempCalGMT = new GregorianCalendar(TimeZone
080: .getTimeZone("GMT"));
081: private static Date tempDate = new Date(0);
082: private static Date currentDate;
083:
084: static {
085: resetToday(System.currentTimeMillis());
086: }
087:
088: static final String zerodatetime = "1970-01-01 00:00:00.000000000";
089: static final String zeronanos = "000000000";
090:
091: /**
092: * Converts a string in JDBC timestamp escape format to a
093: * <code>Timestamp</code> value.
094: *
095: * @param s timestamp in format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
096: * where end part can be omitted, or "NOW" (case insensitive)
097: * @return corresponding <code>Timestamp</code> value
098: * @exception java.lang.IllegalArgumentException if the given argument
099: * does not have the format <code>yyyy-mm-dd hh:mm:ss.fffffffff</code>
100: */
101: public static Timestamp timestampValue(String s)
102: throws HsqlException {
103:
104: if (s == null) {
105: throw Trace.error(Trace.HsqlDateTime_null_string);
106: }
107:
108: if (s.length() > zerodatetime.length()) {
109: throw Trace.error(Trace.STRING_DATA_TRUNCATION);
110: }
111:
112: s = s + zerodatetime.substring(s.length());
113:
114: return Timestamp.valueOf(s);
115: }
116:
117: /**
118: * For use with .script file, simpler than above
119: */
120: public static Timestamp simpleTimestampValue(String s) {
121: return Timestamp.valueOf(s);
122: }
123:
124: /**
125: * @param time milliseconds
126: * @param nano nanoseconds
127: * @return Timestamp object
128: */
129: public static Timestamp timestampValue(long time, int nano) {
130:
131: Timestamp ts = new Timestamp(time);
132:
133: ts.setNanos(nano);
134:
135: return ts;
136: }
137:
138: /**
139: * Converts a string in JDBC date escape format to a <code>Date</code>
140: * value. Also accepts Timestamp values.
141: *
142: * @param s date in format <code>yyyy-mm-dd</code>,
143: * @return corresponding <code>Date</code> value
144: * @exception java.lang.IllegalArgumentException if the given argument
145: * does not have the format <code>yyyy-mm-dd</code>
146: */
147: public static Date dateValue(String s) throws HsqlException {
148:
149: if (s == null) {
150: throw Trace.error(Trace.HsqlDateTime_null_string);
151: }
152:
153: if (s.length() > sdfdPattern.length()) {
154: s = s.substring(0, sdfdPattern.length());
155: }
156:
157: return Date.valueOf(s);
158: }
159:
160: /**
161: * Converts a string in JDBC date escape format to a
162: * <code>Time</code> value.
163: *
164: * @param s date in format <code>hh:mm:ss</code>
165: * @return corresponding <code>Time</code> value
166: * @exception java.lang.IllegalArgumentException if the given argument
167: * does not have the format <code>hh:mm:ss</code>
168: */
169: public static Time timeValue(String s) {
170:
171: if (s == null) {
172: throw new java.lang.IllegalArgumentException(Trace
173: .getMessage(Trace.HsqlDateTime_null_string));
174: }
175:
176: return Time.valueOf(s);
177: }
178:
179: static int compare(Date a, Date b) {
180:
181: long atime = a.getTime();
182: long btime = b.getTime();
183:
184: if (atime == btime) {
185: return 0;
186: }
187:
188: return atime > btime ? 1 : -1;
189: }
190:
191: static int compare(Time a, Time b) {
192:
193: long atime = a.getTime();
194: long btime = b.getTime();
195:
196: if (atime == btime) {
197: return 0;
198: }
199:
200: return atime > btime ? 1 : -1;
201: }
202:
203: static int compare(Timestamp a, Timestamp b) {
204:
205: long atime = a.getTime();
206: long btime = b.getTime();
207:
208: if (atime == btime) {
209: if (a.getNanos() == b.getNanos()) {
210: return 0;
211: }
212:
213: return a.getNanos() > b.getNanos() ? 1 : -1;
214: }
215:
216: return atime > btime ? 1 : -1;
217: }
218:
219: public static synchronized Date getCurrentDate(long millis) {
220:
221: getToday(millis);
222:
223: return currentDate;
224: }
225:
226: public static Timestamp getTimestamp(long millis) {
227: return new Timestamp(millis);
228: }
229:
230: private static final String sdftPattern = "HH:mm:ss";
231: private static final String sdfdPattern = "yyyy-MM-dd";
232: private static final String sdftsPattern = "yyyy-MM-dd HH:mm:ss.";
233: private static final String sdftsSysPattern = "yyyy-MM-dd HH:mm:ss.SSS";
234: static SimpleDateFormat sdfd = new SimpleDateFormat(sdfdPattern);
235: static SimpleDateFormat sdft = new SimpleDateFormat(sdftPattern);
236: static SimpleDateFormat sdfts = new SimpleDateFormat(sdftsPattern);
237: static SimpleDateFormat sdftsSys = new SimpleDateFormat(
238: sdftsSysPattern);
239:
240: /**
241: * Creates a valid timestamp string - jre 1.3 returns incorrect date part
242: * for Timestamp.toString();
243: */
244: public static String getTimestampString(Timestamp x) {
245:
246: synchronized (sdfts) {
247: sdfts.setCalendar(tempCalDefault);
248:
249: String n = String.valueOf(x.getNanos());
250:
251: return sdfts.format(x) + zeronanos.substring(n.length())
252: + n;
253: }
254: }
255:
256: /**
257: * Creates a full length timestamp string, with 9 digist for nanos
258: */
259: public static String getTimestampString(Timestamp x, Calendar cal) {
260:
261: synchronized (sdfts) {
262: sdfts.setCalendar(cal == null ? tempCalDefault : cal);
263:
264: String n = String.valueOf(x.getNanos());
265:
266: return sdfts.format(x) + zeronanos.substring(n.length())
267: + n;
268: }
269: }
270:
271: private static java.util.Date sysDate = new java.util.Date();
272:
273: public static String getSytemTimeString() {
274:
275: synchronized (sdftsSys) {
276: sysDate.setTime(System.currentTimeMillis());
277:
278: return sdftsSys.format(sysDate);
279: }
280: }
281:
282: public static String getTimestampString(long timestamp) {
283:
284: synchronized (sdftsSys) {
285: sysDate.setTime(timestamp);
286:
287: return sdftsSys.format(sysDate);
288: }
289: }
290:
291: public static String getTimeString(java.util.Date x, Calendar cal) {
292:
293: synchronized (sdft) {
294: sdft.setCalendar(cal == null ? tempCalDefault : cal);
295:
296: return sdft.format(x);
297: }
298: }
299:
300: public static String getDateString(java.util.Date x, Calendar cal) {
301:
302: synchronized (sdfd) {
303: sdfd.setCalendar(cal == null ? tempCalDefault : cal);
304:
305: return sdfd.format(x);
306: }
307: }
308:
309: /**
310: * Returns the same Date Object. This object should be treated as
311: * read-only.
312: */
313: static synchronized Calendar getToday(long millis) {
314:
315: if (millis - getTimeInMillis(today) >= 24 * 3600 * 1000) {
316: resetToday(millis);
317: }
318:
319: return today;
320: }
321:
322: public static void resetToDate(Calendar cal) {
323:
324: cal.set(Calendar.HOUR_OF_DAY, 0);
325: cal.set(Calendar.MINUTE, 0);
326: cal.set(Calendar.SECOND, 0);
327: cal.set(Calendar.MILLISECOND, 0);
328: }
329:
330: public static void resetToTime(Calendar cal) {
331:
332: cal.set(Calendar.YEAR, 1970);
333: cal.set(Calendar.MONTH, 0);
334: cal.set(Calendar.DATE, 1);
335: cal.set(Calendar.MILLISECOND, 0);
336: }
337:
338: /**
339: * resets the static reusable value today
340: */
341: private static synchronized void resetToday(long millis) {
342:
343: //#ifdef JDBC3
344: // Use method directly
345: today.setTimeInMillis(millis);
346:
347: //#else
348: /*
349: // Have to go indirect
350: tempDate.setTime(millis);
351: today.setTime(tempDate);
352: */
353:
354: //#endif JDBC3
355: resetToDate(today);
356:
357: currentDate = new Date(getTimeInMillis(today));
358: }
359:
360: /**
361: * Sets the time in the given Calendar using the given milliseconds value; wrapper method to
362: * allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
363: *
364: * @param cal the Calendar
365: * @param millis the time value in milliseconds
366: */
367: private static void setTimeInMillis(Calendar cal, long millis) {
368:
369: //#ifdef JDBC3
370: // Use method directly
371: cal.setTimeInMillis(millis);
372:
373: //#else
374: /*
375: // Have to go indirect
376: synchronized (tempDate) {
377: tempDate.setTime(millis);
378: cal.setTime(tempDate);
379: }
380: */
381:
382: //#endif JDBC3
383: }
384:
385: public static long getTimeInMillis(java.util.Date dt,
386: Calendar source, Calendar target) {
387:
388: if (source == null) {
389: source = tempCalDefault;
390: }
391:
392: if (target == null) {
393: target = tempCalDefault;
394: }
395:
396: synchronized (tempCal) {
397: tempCal.setTimeZone(source.getTimeZone());
398: tempCal.setTime(dt);
399: tempCal.setTimeZone(target.getTimeZone());
400:
401: return getTimeInMillis(tempCal);
402: }
403: }
404:
405: /**
406: * Gets the time from the given Calendar as a milliseconds value; wrapper method to
407: * allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
408: *
409: * @param cal the Calendar
410: * @return the time value in milliseconds
411: */
412: public static long getTimeInMillis(Calendar cal) {
413:
414: //#ifdef JDBC3
415: // Use method directly
416: return (cal.getTimeInMillis());
417:
418: //#else
419: /*
420: // Have to go indirect
421: return (cal.getTime().getTime());
422: */
423:
424: //#endif JDBC3
425: }
426:
427: public static long getNormalisedTime(long t) {
428:
429: synchronized (tempCalDefault) {
430: setTimeInMillis(tempCalDefault, t);
431: resetToTime(tempCalDefault);
432:
433: return getTimeInMillis(tempCalDefault);
434: }
435: }
436:
437: public static Time getNormalisedTime(Time t) {
438: return new Time(getNormalisedTime(t.getTime()));
439: }
440:
441: public static Time getNormalisedTime(Timestamp ts) {
442: return new Time(getNormalisedTime(ts.getTime()));
443: }
444:
445: public static long getNormalisedDate(long d) {
446:
447: synchronized (tempCalDefault) {
448: setTimeInMillis(tempCalDefault, d);
449: resetToDate(tempCalDefault);
450:
451: return getTimeInMillis(tempCalDefault);
452: }
453: }
454:
455: public static Date getNormalisedDate(Timestamp ts) {
456:
457: synchronized (tempCalDefault) {
458: setTimeInMillis(tempCalDefault, ts.getTime());
459: resetToDate(tempCalDefault);
460:
461: long value = getTimeInMillis(tempCalDefault);
462:
463: return new Date(value);
464: }
465: }
466:
467: public static Date getNormalisedDate(Date d) {
468:
469: synchronized (tempCalDefault) {
470: setTimeInMillis(tempCalDefault, d.getTime());
471: resetToDate(tempCalDefault);
472:
473: long value = getTimeInMillis(tempCalDefault);
474:
475: return new Date(value);
476: }
477: }
478:
479: public static Timestamp getNormalisedTimestamp(Time t) {
480:
481: synchronized (tempCalDefault) {
482: setTimeInMillis(tempCalDefault, System.currentTimeMillis());
483: resetToDate(tempCalDefault);
484:
485: long value = getTimeInMillis(tempCalDefault) + t.getTime();
486:
487: return new Timestamp(value);
488: }
489: }
490:
491: public static Timestamp getNormalisedTimestamp(Date d) {
492:
493: synchronized (tempCalDefault) {
494: setTimeInMillis(tempCalDefault, d.getTime());
495: resetToDate(tempCalDefault);
496:
497: long value = getTimeInMillis(tempCalDefault);
498:
499: return new Timestamp(value);
500: }
501: }
502:
503: /**
504: * Returns the indicated part of the given <code>java.util.Date</code> object.
505: * @param d the <code>Date</code> object from which to extract the indicated part
506: * @param part an integer code corresponding to the desired date part
507: * @return the indicated part of the given <code>java.util.Date</code> object
508: */
509: static int getDateTimePart(java.util.Date d, int part) {
510:
511: synchronized (tempCalDefault) {
512: tempCalDefault.setTime(d);
513:
514: return tempCalDefault.get(part);
515: }
516: }
517:
518: private static final char[][] dateTokens = {
519: { 'R', 'R', 'R', 'R' }, { 'I', 'Y', 'Y', 'Y' },
520: { 'Y', 'Y', 'Y', 'Y' }, { 'I', 'Y' }, { 'Y', 'Y' },
521: { 'B', 'C' }, { 'B', '.', 'C', '.' }, { 'A', 'D' },
522: { 'A', '.', 'D', '.' }, { 'M', 'O', 'N' },
523: { 'M', 'O', 'N', 'T', 'H' }, { 'D' }, { 'I', 'W' },
524: { 'D', 'D' }, { 'D', 'D', 'D' }, { 'H', 'H', '2', '4' },
525: { 'H', 'H', '1', '2' }, { 'H', 'H' }, { 'M', 'I', },
526: { 'S', 'S' }, { 'A', 'M' }, { 'P', 'M', },
527: { 'A', '.', 'M', '.' }, { 'P', '.', 'M', '.' } };
528: private static final String[] javaDateTokens = { "yyyy", "yyyy",
529: "yyyy", "yy", "yy", "G", "G", "G", "G", "MMM", "MMMMM",
530: "E", "w", "dd", "D", "k", "K", "K", "mm", "ss", "aaa",
531: "aaa", "aaa", "aaa" };
532:
533: /** Indicates end-of-input */
534: public static final char e = 0xffff;
535:
536: /**
537: * Converts the given format into a pattern accepted by <code>java.text.SimpleDataFormat</code>
538: * @param format
539: * @return
540: */
541: public static String toJavaDatePattern(String format) {
542:
543: int len = format.length();
544: char ch;
545: StringBuffer pattern = new StringBuffer(len);
546: Tokenizer tokenizer = new Tokenizer();
547:
548: for (int i = 0; i <= len; i++) {
549: ch = (i == len) ? e : format.charAt(i);
550:
551: if (!tokenizer.next(ch, dateTokens)) {
552: int index = tokenizer.getLastMatch();
553:
554: if (index >= 0) {
555: pattern.setLength(pattern.length()
556: - tokenizer.length());
557: pattern.append(javaDateTokens[index]);
558: }
559:
560: tokenizer.reset();
561:
562: if (tokenizer.isConsumed()) {
563: continue;
564: }
565: }
566:
567: pattern.append(ch);
568: }
569:
570: pattern.setLength(pattern.length() - 1);
571:
572: return pattern.toString();
573: }
574:
575: /**
576: * This class can match 64 tokens at maximum.
577: */
578: static class Tokenizer {
579:
580: private int last;
581: private int offset;
582: private long state;
583: private boolean consumed;
584:
585: public Tokenizer() {
586: reset();
587: }
588:
589: /**
590: * Resets for next reuse.
591: *
592: */
593: public void reset() {
594:
595: last = -1;
596: offset = -1;
597: state = 0;
598: }
599:
600: /**
601: * Returns a length of a token to match.
602: * @return
603: */
604: public int length() {
605: return offset;
606: }
607:
608: /**
609: * Returns an index of the last matched token.
610: * @return
611: */
612: public int getLastMatch() {
613: return last;
614: }
615:
616: /**
617: * Indicates whethe the last character has been consumed by the matcher.
618: * @return
619: */
620: public boolean isConsumed() {
621: return consumed;
622: }
623:
624: /**
625: * Checks whether the specified bit is not set.
626: * @param bit
627: * @return
628: */
629: private boolean isZeroBit(int bit) {
630: return (state & (1L << bit)) == 0;
631: }
632:
633: /**
634: * Sets the specified bit.
635: * @param bit
636: */
637: private void setBit(int bit) {
638: state |= (1L << bit);
639: }
640:
641: /**
642: * Matches the specified character against tokens.
643: * @param ch
644: * @param tokens
645: * @return
646: */
647: public boolean next(char ch, char[][] tokens) {
648:
649: // Use local variable for performance
650: int index = ++offset;
651: int len = offset + 1;
652: int left = 0;
653:
654: consumed = false;
655:
656: for (int i = tokens.length; --i >= 0;) {
657: if (isZeroBit(i)) {
658: if (tokens[i][index] == ch) {
659: consumed = true;
660:
661: if (tokens[i].length == len) {
662: setBit(i);
663:
664: last = i;
665: } else {
666: ++left;
667: }
668: } else {
669: setBit(i);
670: }
671: }
672: }
673:
674: return left > 0;
675: }
676: }
677: }
|