//jTDS JDBC Driver for Microsoft SQL Server and Sybase //Copyright (C) 2004 The jTDS Project // //This library is free software; you can redistribute it and/or //modify it under the terms of the GNU Lesser General Public //License as published by the Free Software Foundation; either //version 2.1 of the License, or (at your option) any later version. // //This library 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 //Lesser General Public License for more details. // //You should have received a copy of the GNU Lesser General Public //License along with this library; if not, write to the Free Software //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc; import java.sql.Date; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; import java.util.GregorianCalendar; /** * Encapsulates Sybase date/time values and provides conversions to and from * Java classes. * * @author Mike Hutchinson * @version $Id: DateTime.java,v 1.4.2.2 2009-08-20 19:44:04 ickzon Exp $ */ public class DateTime { /** Indicates date value not used. */ static final int DATE_NOT_USED = Integer.MIN_VALUE; /** Indicates time value not used. */ static final int TIME_NOT_USED = Integer.MIN_VALUE; /** The date component of the server datetime value. */ private int date; /** The time component of the server datetime value. */ private int time; /** Unpacked year value. */ private short year; /** Unpacked month value. */ private short month; /** Unpacked day value. */ private short day; /** Unpacked hour value. */ private short hour; /** Unpacked minute value. */ private short minute; /** Unpacked second value. */ private short second; /** Unpacked millisecond value. */ private short millis; /** Indicates server datetime values have been unpacked. */ private boolean unpacked; /** Cached value of the datetime as a <code>String</code>. */ private String stringValue; /** Cached value of the datetime as a <code>java.sql.Timestamp</code>. */ private Timestamp tsValue; /** Cached value of the datetime as a <code>java.sql.Date</code>. */ private Date dateValue; /** Cached value of the datetime as a <code>java.sql.Time</code>. */ private Time timeValue; /** * Constructs a DateTime object from the two integer components of a * datetime. * * @param date server date field * @param time server time field */ DateTime(int date, int time) { this.date = date; this.time = time; } /** * Constructs a DateTime object from the two short components of a * smalldatetime. * * @param date server date field * @param time server time field */ DateTime(short date, short time) { this.date = date & 0xFFFF; this.time = time * 60 * 300; } /** * Constructs a DateTime object from a <code>java.sql.Timestamp</code>. * * @param ts <code>Timestamp</code> object representing the datetime * @throws SQLException if the date is out of range */ DateTime(Timestamp ts) throws SQLException { tsValue = ts; GregorianCalendar cal = new GregorianCalendar(); cal.setTime(ts); if (cal.get(Calendar.ERA) != GregorianCalendar.AD) throw new SQLException(Messages.get("error.datetime.range.era"), "22007"); if (!Driver.JDBC3) { // Not Running under 1.4 so need to add milliseconds cal.set(Calendar.MILLISECOND, ts.getNanos() / 1000000); } year = (short)cal.get(Calendar.YEAR); month = (short)(cal.get(Calendar.MONTH) + 1); day = (short)cal.get(Calendar.DAY_OF_MONTH); hour = (short)cal.get(Calendar.HOUR_OF_DAY); minute = (short)cal.get(Calendar.MINUTE); second = (short)cal.get(Calendar.SECOND); millis = (short)cal.get(Calendar.MILLISECOND); packDate(); packTime(); unpacked = true; } /** * Constructs a DateTime object from a <code>java.sql.Time</code>. * * @param t <code>Time</code> object representing the datetime * @throws SQLException if the time (date) is out of range */ DateTime(Time t) throws SQLException { timeValue = t; GregorianCalendar cal = new GregorianCalendar(); cal.setTime(t); if (cal.get(Calendar.ERA) != GregorianCalendar.AD) throw new SQLException(Messages.get("error.datetime.range.era"), "22007"); date = DATE_NOT_USED; year = 1900; month = 1; day = 1; hour = (short)cal.get(Calendar.HOUR_OF_DAY); minute = (short)cal.get(Calendar.MINUTE); second = (short)cal.get(Calendar.SECOND); millis = (short)cal.get(Calendar.MILLISECOND); packTime(); year = 1970; month = 1; day = 1; unpacked = true; } /** * Constructs a DateTime object from a <code>java.sql.Date</code>. * * @param d <code>Date</code> object representing the datetime * @throws SQLException if the Date is out of range */ DateTime(Date d) throws SQLException { dateValue = d; GregorianCalendar cal = new GregorianCalendar(); cal.setTime(d); if (cal.get(Calendar.ERA) != GregorianCalendar.AD) throw new SQLException(Messages.get("error.datetime.range.era"), "22007"); year = (short)cal.get(Calendar.YEAR); month = (short)(cal.get(Calendar.MONTH) + 1); day = (short)cal.get(Calendar.DAY_OF_MONTH); hour = 0; minute = 0; second = 0; millis = 0; packDate(); time = TIME_NOT_USED; unpacked = true; } /** * Retrieves the date component of a datetime value. * * @return the datetime date component as an <code>int</code> */ int getDate() { return (date == DATE_NOT_USED) ? 0 : date; } /** * Retrieves the time component of a datetime value. * * @return the datetime time component as an <code>int</code> */ int getTime() { return (time == TIME_NOT_USED) ? 0 : time; } /** * Converts a Julian datetime from the Sybase epoch of 1900-01-01 to the * equivalent unpacked year/month/day etc. * * Algorithm from Fliegel, H F and van Flandern, T C (1968). * Communications of the ACM, Vol 11, No 10 (October, 1968). * <pre> * SUBROUTINE GDATE (JD, YEAR,MONTH,DAY) * C * C---COMPUTES THE GREGORIAN CALENDAR DATE (YEAR,MONTH,DAY) * C GIVEN THE JULIAN DATE (JD). * C * INTEGER JD,YEAR,MONTH,DAY,I,J,K * C * L= JD+68569 * N= 4*L/146097 * L= L-(146097*N+3)/4 * I= 4000*(L+1)/1461001 * L= L-1461*I/4+31 * J= 80*L/2447 * K= L-2447*J/80 * L= J/11 * J= J+2-12*L * I= 100*(N-49)+I+L * C * YEAR= I * MONTH= J * DAY= K * C * RETURN * END * </pre> */ private void unpackDateTime() { if (date == DATE_NOT_USED) { year = 1970; month = 1; day = 1; } else { if (date == 0) { // Optimize common case of 1900-01-01 which is used as // the default date for datetimes where only the time is set. year = 1900; month = 1; day = 1; } else { int l = date + 68569 + 2415021; int n = 4 * l / 146097; l = l - (146097 * n + 3) / 4; int i = 4000 * (l + 1) / 1461001; l = l - 1461 * i / 4 + 31; int j = 80 * l / 2447; int k = l - 2447 * j / 80; l = j / 11; j = j + 2 - 12 * l; i = 100 * (n - 49) + i + l; year = (short)i; month = (short)j; day = (short)k; } } if (time == TIME_NOT_USED) { hour = 0; minute = 0; second = 0; } else { int hours = time / 1080000; time = time - hours * 1080000; int minutes = time / 18000; time = time - (minutes * 18000); int seconds = time / 300; time = time - seconds * 300; time = Math.round(time * 1000 / 300f); hour = (short)hours; minute = (short)minutes; second = (short)seconds; millis = (short)time; } unpacked = true; } /** * Converts a calendar date into days since 1900 (Sybase epoch). * <p> * Algorithm from Fliegel, H F and van Flandern, T C (1968). * Communications of the ACM, Vol 11, No 10 (October, 1968). * * <pre> * INTEGER FUNCTION JD (YEAR,MONTH,DAY) * C * C---COMPUTES THE JULIAN DATE (JD) GIVEN A GREGORIAN CALENDAR * C DATE (YEAR,MONTH,DAY). * C * INTEGER YEAR,MONTH,DAY,I,J,K * C * I= YEAR * J= MONTH * K= DAY * C * JD= K-32075+1461*(I+4800+(J-14)/12)/4+367*(J-2-(J-14)/12*12) * 2 /12-3*((I+4900+(J-14)/12)/100)/4 * C * RETURN * END * </pre> * * @throws java.sql.SQLException if the date is outside the accepted range, 1753-9999 */ public void packDate() throws SQLException { if (year < 1753 || year > 9999) { throw new SQLException(Messages.get("error.datetime.range"), "22003"); } date = day - 32075 + 1461 * (year + 4800 + (month - 14) / 12) / 4 + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 - 3 * ((year + 4900 + (month -14) / 12) / 100) / 4 - 2415021; } /** * Converts separate time components into a datetime time value. */ public void packTime() { time = hour * 1080000; time += minute * 18000; time += second * 300; time += Math.round(millis * 300f / 1000); if (time > 25919999) { // Time field has overflowed need to increment days // Sybase does not allow invalid time component time = 0; hour = 0; minute = 0; second = 0; millis = 0; if (date != DATE_NOT_USED) { GregorianCalendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, day); cal.add(Calendar.DATE, 1); year = (short)cal.get(Calendar.YEAR); month = (short)(cal.get(Calendar.MONTH) + 1); day = (short)cal.get(Calendar.DAY_OF_MONTH); date++; } } } /** * Retrieves the current datetime value as a Timestamp. * * @return the current datetime value as a <code>java.sql.Timestamp</code> */ public Timestamp toTimestamp() { if (tsValue == null) { if (!unpacked) { unpackDateTime(); } GregorianCalendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, day); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.set(Calendar.MILLISECOND, millis); tsValue = new Timestamp(cal.getTime().getTime()); } return tsValue; } /** * Retrieves the current datetime value as a Date. * * @return the current datetime value as a <code>java.sql.Date</code> */ public Date toDate() { if (dateValue == null) { if (!unpacked) { unpackDateTime(); } GregorianCalendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, day); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); dateValue = new Date(cal.getTime().getTime()); } return dateValue; } /** * Retrieves the current datetime value as a Time. * * @return the current datetime value as a <code>java.sql.Time</code> */ public Time toTime() { if (timeValue == null) { if (!unpacked) { unpackDateTime(); } GregorianCalendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, 1970); cal.set(Calendar.MONTH, 0); cal.set(Calendar.DAY_OF_MONTH, 1); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.set(Calendar.MILLISECOND, millis); timeValue = new Time(cal.getTime().getTime()); } return timeValue; } /** * Retrieves the current datetime value as a Time, Date or Timestamp. * * @return the current datetime value as an <code>java.lang.Object</code> */ public Object toObject() { if (date == DATE_NOT_USED) { return toTime(); } if (time == TIME_NOT_USED) { return toDate(); } return toTimestamp(); } /** * Retrieves the current datetime value as a String. * * @return the current datetime value as a <code>String</code> */ public String toString() { if (stringValue == null) { if (!unpacked) { unpackDateTime(); } // // Make local copies to avoid corrupting unpacked // components. // int day = this.day; int month = this.month; int year = this.year; int millis = this.millis; int second = this.second; int minute = this.minute; int hour = this.hour; char buf[] = new char[23]; int p = 0; if (date != DATE_NOT_USED) { p = 10; buf[--p] = (char)('0' + day % 10); day /= 10; buf[--p] = (char)('0' + day % 10); buf[--p] = '-'; buf[--p] = (char)('0' + month % 10); month /= 10; buf[--p] = (char)('0' + month % 10); buf[--p] = '-'; buf[--p] = (char)('0' + year % 10); year /= 10; buf[--p] = (char)('0' + year % 10); year /= 10; buf[--p] = (char)('0' + year % 10); year /= 10; buf[--p] = (char)('0' + year % 10); p += 10; if (time != TIME_NOT_USED) { buf[p++] = ' '; } } if (time != TIME_NOT_USED) { p += 12; buf[--p] = (char)('0' + millis % 10); millis /= 10; buf[--p] = (char)('0' + millis % 10); millis /= 10; buf[--p] = (char)('0' + millis % 10); buf[--p] = '.'; buf[--p] = (char)('0' + second % 10); second /= 10; buf[--p] = (char)('0' + second % 10); buf[--p] = ':'; buf[--p] = (char)('0' + minute % 10); minute /= 10; buf[--p] = (char)('0' + minute % 10); buf[--p] = ':'; buf[--p] = (char)('0' + hour % 10); hour /= 10; buf[--p] = (char)('0' + hour % 10); p += 12; if (buf[p-1] == '0') { p--; } if (buf[p-1] == '0') { p--; } } stringValue = String.valueOf(buf, 0, p); } return stringValue; } }