提交 8c95b033 authored 作者: Thomas Mueller's avatar Thomas Mueller

Date, time, and timestamp data type processing has been re-implemented.

上级 2421bad7
...@@ -18,7 +18,10 @@ Change Log ...@@ -18,7 +18,10 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>- <ul><li>Date, time, and timestamp data type processing has been re-implemented.
Time now supports nanoseconds resolution and now now supports a wider range
(negative and large values), similar to PostgreSQL.
</li><li>SQL statements with a non-breaking space were considered invalid.
</li></ul> </li></ul>
<h2>Version 1.3.157 (2011-06-25)</h2> <h2>Version 1.3.157 (2011-06-25)</h2>
......
...@@ -2465,15 +2465,15 @@ public class Parser { ...@@ -2465,15 +2465,15 @@ public class Parser {
if (equalsToken("DATE", name)) { if (equalsToken("DATE", name)) {
String date = currentValue.getString(); String date = currentValue.getString();
read(); read();
r = ValueExpression.get(ValueDate.get(ValueDate.parseDate(date))); r = ValueExpression.get(ValueDate.parse(date));
} else if (equalsToken("TIME", name)) { } else if (equalsToken("TIME", name)) {
String time = currentValue.getString(); String time = currentValue.getString();
read(); read();
r = ValueExpression.get(ValueTime.get(ValueTime.parseTime(time))); r = ValueExpression.get(ValueTime.parse(time));
} else if (equalsToken("TIMESTAMP", name)) { } else if (equalsToken("TIMESTAMP", name)) {
String timestamp = currentValue.getString(); String timestamp = currentValue.getString();
read(); read();
r = ValueExpression.get(ValueTimestamp.getNoCopy(ValueTimestamp.parseTimestamp(timestamp))); r = ValueExpression.get(ValueTimestamp.parse(timestamp));
} else if (equalsToken("X", name)) { } else if (equalsToken("X", name)) {
read(); read();
byte[] buffer = StringUtils.convertHexToBytes(currentValue.getString()); byte[] buffer = StringUtils.convertHexToBytes(currentValue.getString());
...@@ -3231,7 +3231,7 @@ public class Parser { ...@@ -3231,7 +3231,7 @@ public class Parser {
} else if (c >= '0' && c <= '9') { } else if (c >= '0' && c <= '9') {
type = CHAR_VALUE; type = CHAR_VALUE;
} else { } else {
if (c <= ' ' || Character.isWhitespace(c)) { if (c <= ' ' || Character.isSpaceChar(c)) {
// whitespace // whitespace
} else if (Character.isJavaIdentifierPart(c)) { } else if (Character.isJavaIdentifierPart(c)) {
type = CHAR_NAME; type = CHAR_NAME;
......
...@@ -56,6 +56,11 @@ public class Constants { ...@@ -56,6 +56,11 @@ public class Constants {
*/ */
public static final int TCP_PROTOCOL_VERSION_8 = 8; public static final int TCP_PROTOCOL_VERSION_8 = 8;
/**
* The TCP protocol version number 9.
*/
public static final int TCP_PROTOCOL_VERSION_9 = 9;
/** /**
* The major version of this database. * The major version of this database.
*/ */
......
...@@ -94,7 +94,7 @@ public class SessionRemote extends SessionWithState implements DataHandler { ...@@ -94,7 +94,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
trans.setSSL(ci.isSSL()); trans.setSSL(ci.isSSL());
trans.init(); trans.init();
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6); trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_8); trans.writeInt(Constants.TCP_PROTOCOL_VERSION_9);
trans.writeString(db); trans.writeString(db);
trans.writeString(ci.getOriginalURL()); trans.writeString(ci.getOriginalURL());
trans.writeString(ci.getUserName()); trans.writeString(ci.getUserName());
......
...@@ -629,52 +629,52 @@ public class Function extends Expression implements FunctionCall { ...@@ -629,52 +629,52 @@ public class Function extends Expression implements FunctionCall {
break; break;
case DAY_NAME: { case DAY_NAME: {
SimpleDateFormat dayName = new SimpleDateFormat("EEEE", Locale.ENGLISH); SimpleDateFormat dayName = new SimpleDateFormat("EEEE", Locale.ENGLISH);
result = ValueString.get(dayName.format(v0.getDateNoCopy())); result = ValueString.get(dayName.format(v0.getDate()));
break; break;
} }
case DAY_OF_MONTH: case DAY_OF_MONTH:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.DAY_OF_MONTH)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.DAY_OF_MONTH));
break; break;
case DAY_OF_WEEK: case DAY_OF_WEEK:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.DAY_OF_WEEK)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.DAY_OF_WEEK));
break; break;
case DAY_OF_YEAR: case DAY_OF_YEAR:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.DAY_OF_YEAR)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.DAY_OF_YEAR));
break; break;
case HOUR: case HOUR:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestampNoCopy(), Calendar.HOUR_OF_DAY)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestamp(), Calendar.HOUR_OF_DAY));
break; break;
case MINUTE: case MINUTE:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestampNoCopy(), Calendar.MINUTE)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestamp(), Calendar.MINUTE));
break; break;
case MONTH: case MONTH:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.MONTH)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.MONTH));
break; break;
case MONTH_NAME: { case MONTH_NAME: {
SimpleDateFormat monthName = new SimpleDateFormat("MMMM", Locale.ENGLISH); SimpleDateFormat monthName = new SimpleDateFormat("MMMM", Locale.ENGLISH);
result = ValueString.get(monthName.format(v0.getDateNoCopy())); result = ValueString.get(monthName.format(v0.getDate()));
break; break;
} }
case QUARTER: case QUARTER:
result = ValueInt.get((DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.MONTH) - 1) / 3 + 1); result = ValueInt.get((DateTimeUtils.getDatePart(v0.getDate(), Calendar.MONTH) - 1) / 3 + 1);
break; break;
case SECOND: case SECOND:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestampNoCopy(), Calendar.SECOND)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestamp(), Calendar.SECOND));
break; break;
case WEEK: case WEEK:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.WEEK_OF_YEAR)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.WEEK_OF_YEAR));
break; break;
case YEAR: case YEAR:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.YEAR)); result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.YEAR));
break; break;
case ISO_YEAR: case ISO_YEAR:
result = ValueInt.get(DateTimeUtils.getIsoYear(v0.getDateNoCopy())); result = ValueInt.get(DateTimeUtils.getIsoYear(v0.getDate()));
break; break;
case ISO_WEEK: case ISO_WEEK:
result = ValueInt.get(DateTimeUtils.getIsoWeek(v0.getDateNoCopy())); result = ValueInt.get(DateTimeUtils.getIsoWeek(v0.getDate()));
break; break;
case ISO_DAY_OF_WEEK: case ISO_DAY_OF_WEEK:
result = ValueInt.get(DateTimeUtils.getIsoDayOfWeek(v0.getDateNoCopy())); result = ValueInt.get(DateTimeUtils.getIsoDayOfWeek(v0.getDate()));
break; break;
case CURDATE: case CURDATE:
case CURRENT_DATE: { case CURRENT_DATE: {
...@@ -693,7 +693,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -693,7 +693,7 @@ public class Function extends Expression implements FunctionCall {
case NOW: case NOW:
case CURRENT_TIMESTAMP: { case CURRENT_TIMESTAMP: {
long now = session.getTransactionStart(); long now = session.getTransactionStart();
ValueTimestamp vt = ValueTimestamp.getNoCopy(new Timestamp(now)); ValueTimestamp vt = ValueTimestamp.get(new Timestamp(now));
if (v0 != null) { if (v0 != null) {
Mode mode = database.getMode(); Mode mode = database.getMode();
vt = (ValueTimestamp) vt.convertScale(mode.convertOnlyToSmallerScale, v0.getInt()); vt = (ValueTimestamp) vt.convertScale(mode.convertOnlyToSmallerScale, v0.getInt());
...@@ -1038,10 +1038,10 @@ public class Function extends Expression implements FunctionCall { ...@@ -1038,10 +1038,10 @@ public class Function extends Expression implements FunctionCall {
break; break;
// date // date
case DATE_ADD: case DATE_ADD:
result = ValueTimestamp.getNoCopy(dateadd(v0.getString(), v1.getInt(), v2.getTimestampNoCopy())); result = ValueTimestamp.get(dateadd(v0.getString(), v1.getInt(), v2.getTimestamp()));
break; break;
case DATE_DIFF: case DATE_DIFF:
result = ValueLong.get(datediff(v0.getString(), v1.getTimestampNoCopy(), v2.getTimestampNoCopy())); result = ValueLong.get(datediff(v0.getString(), v1.getTimestamp(), v2.getTimestamp()));
break; break;
case EXTRACT: { case EXTRACT: {
int field = getDatePart(v0.getString()); int field = getDatePart(v0.getString());
...@@ -1065,7 +1065,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1065,7 +1065,7 @@ public class Function extends Expression implements FunctionCall {
String locale = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString(); String locale = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString();
String tz = v3 == null ? null : v3 == ValueNull.INSTANCE ? null : v3.getString(); String tz = v3 == null ? null : v3 == ValueNull.INSTANCE ? null : v3.getString();
java.util.Date d = DateTimeUtils.parseDateTime(v0.getString(), v1.getString(), locale, tz); java.util.Date d = DateTimeUtils.parseDateTime(v0.getString(), v1.getString(), locale, tz);
result = ValueTimestamp.getNoCopy(new Timestamp(d.getTime())); result = ValueTimestamp.get(new Timestamp(d.getTime()));
} }
break; break;
} }
......
...@@ -611,7 +611,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat ...@@ -611,7 +611,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) { if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE); setParameter(parameterIndex, ValueNull.INSTANCE);
} else { } else {
setParameter(parameterIndex, DateTimeUtils.convertDateToUniversal(x, calendar)); setParameter(parameterIndex, DateTimeUtils.convertDateToUTC(x, calendar));
} }
} catch (Exception e) { } catch (Exception e) {
throw logAndConvert(e); throw logAndConvert(e);
...@@ -635,7 +635,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat ...@@ -635,7 +635,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) { if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE); setParameter(parameterIndex, ValueNull.INSTANCE);
} else { } else {
setParameter(parameterIndex, DateTimeUtils.convertTimeToUniversal(x, calendar)); setParameter(parameterIndex, DateTimeUtils.convertTimeToUTC(x, calendar));
} }
} catch (Exception e) { } catch (Exception e) {
throw logAndConvert(e); throw logAndConvert(e);
......
...@@ -71,12 +71,12 @@ public class TcpServerThread implements Runnable { ...@@ -71,12 +71,12 @@ public class TcpServerThread implements Runnable {
int minClientVersion = transfer.readInt(); int minClientVersion = transfer.readInt();
if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) { if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6); throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6);
} else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_8) { } else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_9) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_8); throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_9);
} }
int maxClientVersion = transfer.readInt(); int maxClientVersion = transfer.readInt();
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_8) { if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_9) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_8; clientVersion = Constants.TCP_PROTOCOL_VERSION_9;
} else { } else {
clientVersion = minClientVersion; clientVersion = minClientVersion;
} }
......
...@@ -471,34 +471,38 @@ public class Data { ...@@ -471,34 +471,38 @@ public class Data {
case Value.TIME: case Value.TIME:
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_TIME); writeByte((byte) LOCAL_TIME);
writeVarLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy())); writeVarLong(((ValueTime) v).getNanos());
} else { } else {
writeByte((byte) type); writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy())); writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
} }
break; break;
case Value.DATE: { case Value.DATE: {
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_DATE); writeByte((byte) LOCAL_DATE);
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy()); long x = ((ValueDate) v).getDateValue();
writeVarLong(x / MILLIS_PER_MINUTE); writeVarLong(x);
} else { } else {
writeByte((byte) type); writeByte((byte) type);
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy()); long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDate());
writeVarLong(x / MILLIS_PER_MINUTE); writeVarLong(x / MILLIS_PER_MINUTE);
} }
break; break;
} }
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
Timestamp ts = v.getTimestampNoCopy();
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_TIMESTAMP); writeByte((byte) LOCAL_TIMESTAMP);
writeVarLong(DateTimeUtils.getTimeLocal(ts)); ValueTimestamp ts = (ValueTimestamp) v;
long dateValue = ts.getDateValue();
long nanos = ts.getNanos();
writeVarLong(dateValue);
writeVarLong(nanos);
} else { } else {
Timestamp ts = v.getTimestamp();
writeByte((byte) type); writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(ts)); writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(ts));
}
writeVarInt(ts.getNanos()); writeVarInt(ts.getNanos());
}
break; break;
} }
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
...@@ -679,28 +683,26 @@ public class Data { ...@@ -679,28 +683,26 @@ public class Data {
return ValueDecimal.get(new BigDecimal(b, scale)); return ValueDecimal.get(new BigDecimal(b, scale));
} }
case LOCAL_DATE: { case LOCAL_DATE: {
long x = readVarLong() * MILLIS_PER_MINUTE; return ValueDate.get(readVarLong());
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(x)));
} }
case Value.DATE: { case Value.DATE: {
long x = readVarLong() * MILLIS_PER_MINUTE; long x = readVarLong() * MILLIS_PER_MINUTE;
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMTWithoutDst(x))); return ValueDate.get(new Date(DateTimeUtils.getTimeUTCWithoutDst(x)));
} }
case LOCAL_TIME: case LOCAL_TIME:
// need to normalize the year, month and day return ValueTime.get(readVarLong());
return ValueTime.get(new Time(DateTimeUtils.getTimeGMT(readVarLong())));
case Value.TIME: case Value.TIME:
// need to normalize the year, month and day // need to normalize the year, month and day
return ValueTime.get(new Time(DateTimeUtils.getTimeGMTWithoutDst(readVarLong()))); return ValueTime.get(new Time(DateTimeUtils.getTimeUTCWithoutDst(readVarLong())));
case LOCAL_TIMESTAMP: { case LOCAL_TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readVarLong())); long dateValue = readVarLong();
ts.setNanos(readVarInt()); long nanos = readVarLong();
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.get(dateValue, nanos);
} }
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readVarLong())); Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readVarLong()));
ts.setNanos(readVarInt()); ts.setNanos(readVarInt());
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.get(ts);
} }
case Value.BYTES: { case Value.BYTES: {
int len = readVarInt(); int len = readVarInt();
...@@ -893,23 +895,25 @@ public class Data { ...@@ -893,23 +895,25 @@ public class Data {
} }
case Value.TIME: case Value.TIME:
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(v.getTimeNoCopy())); return 1 + getVarLongLen(((ValueTime) v).getNanos());
} }
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy())); return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
case Value.DATE: { case Value.DATE: {
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy()); long dateValue = ((ValueDate) v).getDateValue();
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE); return 1 + getVarLongLen(dateValue);
} }
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy()); long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDate());
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE); return 1 + getVarLongLen(x / MILLIS_PER_MINUTE);
} }
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
if (SysProperties.STORE_LOCAL_TIME) { if (SysProperties.STORE_LOCAL_TIME) {
Timestamp ts = v.getTimestampNoCopy(); ValueTimestamp ts = (ValueTimestamp) v;
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(ts)) + getVarIntLen(ts.getNanos()); long dateValue = ts.getDateValue();
long nanos = ts.getNanos();
return 1 + getVarLongLen(dateValue) + getVarLongLen(nanos);
} }
Timestamp ts = v.getTimestampNoCopy(); Timestamp ts = v.getTimestamp();
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + getVarIntLen(ts.getNanos()); return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + getVarIntLen(ts.getNanos());
} }
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
......
...@@ -230,7 +230,7 @@ public class FileLock implements Runnable { ...@@ -230,7 +230,7 @@ public class FileLock implements Runnable {
transfer.setSocket(socket); transfer.setSocket(socket);
transfer.init(); transfer.init();
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6); transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_8); transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_9);
transfer.writeString(null); transfer.writeString(null);
transfer.writeString(null); transfer.writeString(null);
transfer.writeString(id); transfer.writeString(id);
......
...@@ -284,12 +284,15 @@ public class Column { ...@@ -284,12 +284,15 @@ public class Column {
if (dt.decimal) { if (dt.decimal) {
value = ValueInt.get(0).convertTo(type); value = ValueInt.get(0).convertTo(type);
} else if (dt.type == Value.TIMESTAMP) { } else if (dt.type == Value.TIMESTAMP) {
value = ValueTimestamp.getNoCopy(new Timestamp(System.currentTimeMillis())); // TODO
value = ValueTimestamp.get(new Timestamp(System.currentTimeMillis()));
} else if (dt.type == Value.TIME) { } else if (dt.type == Value.TIME) {
// TODO
// need to normalize // need to normalize
value = ValueTime.get(Time.valueOf("0:0:0")); value = ValueTime.get(Time.valueOf("0:0:0"));
} else if (dt.type == Value.DATE) { } else if (dt.type == Value.DATE) {
value = ValueTimestamp.getNoCopy(new Timestamp(System.currentTimeMillis())).convertTo(dt.type); // TODO
value = ValueTimestamp.get(new Timestamp(System.currentTimeMillis())).convertTo(dt.type);
} else { } else {
value = ValueString.get("").convertTo(type); value = ValueString.get("").convertTo(type);
} }
......
...@@ -34,6 +34,22 @@ public class DateTimeUtils { ...@@ -34,6 +34,22 @@ public class DateTimeUtils {
private static final int DEFAULT_DAY = 1; private static final int DEFAULT_DAY = 1;
private static final int DEFAULT_HOUR = 0; private static final int DEFAULT_HOUR = 0;
private static final int SHIFT_YEAR = 9;
private static final int SHIFT_MONTH = 5;
private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L;
private static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1000000;
private static final int[] NORMAL_DAYS_PER_MONTH = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
/**
* Offsets of month within a year, starting with March, April,...
*/
private static final int[] DAYS_OFFSET =
{ 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 366 };
private static int zoneOffset; private static int zoneOffset;
private static Calendar cachedCalendar; private static Calendar cachedCalendar;
...@@ -78,52 +94,6 @@ public class DateTimeUtils { ...@@ -78,52 +94,6 @@ public class DateTimeUtils {
return x; return x;
} }
/**
* Clone a time object and reset the day to 1970-01-01.
*
* @param value the time value
* @return the time value without the date component
*/
public static Time cloneAndNormalizeTime(Time value) {
Calendar cal = getCalendar();
long time;
synchronized (cal) {
cal.setTime(value);
cal.set(Calendar.ERA, GregorianCalendar.AD);
// month is 0 based
cal.set(DEFAULT_YEAR, DEFAULT_MONTH - 1, DEFAULT_DAY);
time = cal.getTimeInMillis();
}
return new Time(time);
}
/**
* Clone a date object and reset the hour, minutes, seconds, and
* milliseconds to zero.
*
* @param value the date value
* @return the date value at midnight
*/
public static Date cloneAndNormalizeDate(Date value) {
Calendar cal = getCalendar();
long time;
synchronized (cal) {
cal.setTime(value);
// if we don't enable lenient processing, dates between
// 1916-06-03 and 1920-03-21,
// 1940-06-15, 1947-03-16, and
// 1966-05-22 to 1979-05-27 don't work
// (central european timezone CET)
cal.setLenient(true);
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.HOUR_OF_DAY, DEFAULT_HOUR);
time = cal.getTime().getTime();
}
return new Date(time);
}
/** /**
* Convert the date from the specified time zone to UTC. * Convert the date from the specified time zone to UTC.
* *
...@@ -131,7 +101,7 @@ public class DateTimeUtils { ...@@ -131,7 +101,7 @@ public class DateTimeUtils {
* @param source the calendar * @param source the calendar
* @return the date in UTC * @return the date in UTC
*/ */
public static Value convertDateToUniversal(Date x, Calendar source) { public static Value convertDateToUTC(Date x, Calendar source) {
return ValueDate.get(new Date(getUniversalTime(source, x))); return ValueDate.get(new Date(getUniversalTime(source, x)));
} }
...@@ -142,7 +112,7 @@ public class DateTimeUtils { ...@@ -142,7 +112,7 @@ public class DateTimeUtils {
* @param source the calendar * @param source the calendar
* @return the time in UTC * @return the time in UTC
*/ */
public static Value convertTimeToUniversal(Time x, Calendar source) { public static Value convertTimeToUTC(Time x, Calendar source) {
return ValueTime.get(new Time(getUniversalTime(source, x))); return ValueTime.get(new Time(getUniversalTime(source, x)));
} }
...@@ -157,7 +127,7 @@ public class DateTimeUtils { ...@@ -157,7 +127,7 @@ public class DateTimeUtils {
Timestamp y = new Timestamp(getUniversalTime(source, x)); Timestamp y = new Timestamp(getUniversalTime(source, x));
// fix the nano seconds // fix the nano seconds
y.setNanos(x.getNanos()); y.setNanos(x.getNanos());
return ValueTimestamp.getNoCopy(y); return ValueTimestamp.get(y);
} }
/** /**
...@@ -235,13 +205,13 @@ public class DateTimeUtils { ...@@ -235,13 +205,13 @@ public class DateTimeUtils {
* @param type the value type (Value.TIME, TIMESTAMP, or DATE) * @param type the value type (Value.TIME, TIMESTAMP, or DATE)
* @return the date object * @return the date object
*/ */
public static java.util.Date parseDateTime(String original, int type) { public static Value parse(String original, int type) {
String s = original; String s = original;
if (s == null) { if (s == null) {
return null; return null;
} }
try { try {
int timeStart = 0; int timeStart;
TimeZone tz = null; TimeZone tz = null;
if (type == Value.TIME) { if (type == Value.TIME) {
timeStart = 0; timeStart = 0;
...@@ -252,7 +222,6 @@ public class DateTimeUtils { ...@@ -252,7 +222,6 @@ public class DateTimeUtils {
timeStart = s.indexOf('T') + 1; timeStart = s.indexOf('T') + 1;
} }
} }
int year = DEFAULT_YEAR, month = DEFAULT_MONTH, day = DEFAULT_DAY; int year = DEFAULT_YEAR, month = DEFAULT_MONTH, day = DEFAULT_DAY;
if (type != Value.TIME) { if (type != Value.TIME) {
if (s.startsWith("+")) { if (s.startsWith("+")) {
...@@ -271,7 +240,8 @@ public class DateTimeUtils { ...@@ -271,7 +240,8 @@ public class DateTimeUtils {
int end = timeStart == 0 ? s.length() : timeStart - 1; int end = timeStart == 0 ? s.length() : timeStart - 1;
day = Integer.parseInt(s.substring(s2 + 1, end)); day = Integer.parseInt(s.substring(s2 + 1, end));
} }
int hour = DEFAULT_HOUR, minute = 0, second = 0, nano = 0; int hour = DEFAULT_HOUR, minute = 0, second = 0;
long millis = 0, nanos = 0;
int s1 = s.indexOf(':', timeStart); int s1 = s.indexOf(':', timeStart);
if (type == Value.TIME || (type == Value.TIMESTAMP && s1 >= 0)) { if (type == Value.TIME || (type == Value.TIMESTAMP && s1 >= 0)) {
int s2 = s.indexOf(':', s1 + 1); int s2 = s.indexOf(':', s1 + 1);
...@@ -280,7 +250,6 @@ public class DateTimeUtils { ...@@ -280,7 +250,6 @@ public class DateTimeUtils {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
DataType.getDataType(type).name, original); DataType.getDataType(type).name, original);
} }
if (s.endsWith("Z")) { if (s.endsWith("Z")) {
s = s.substring(0, s.length() - 1); s = s.substring(0, s.length() - 1);
tz = TimeZone.getTimeZone("UTC"); tz = TimeZone.getTimeZone("UTC");
...@@ -300,7 +269,6 @@ public class DateTimeUtils { ...@@ -300,7 +269,6 @@ public class DateTimeUtils {
s = s.substring(0, timeZoneStart).trim(); s = s.substring(0, timeZoneStart).trim();
} }
} }
hour = Integer.parseInt(s.substring(timeStart, s1)); hour = Integer.parseInt(s.substring(timeStart, s1));
minute = Integer.parseInt(s.substring(s1 + 1, s2)); minute = Integer.parseInt(s.substring(s1 + 1, s2));
if (s3 < 0) { if (s3 < 0) {
...@@ -308,12 +276,90 @@ public class DateTimeUtils { ...@@ -308,12 +276,90 @@ public class DateTimeUtils {
} else { } else {
second = Integer.parseInt(s.substring(s2 + 1, s3)); second = Integer.parseInt(s.substring(s2 + 1, s3));
String n = (s + "000000000").substring(s3 + 1, s3 + 10); String n = (s + "000000000").substring(s3 + 1, s3 + 10);
nano = Integer.parseInt(n); nanos = Integer.parseInt(n);
millis = nanos / 1000000;
nanos -= millis * 1000000;
}
}
if (!isValidDate(year, month, day)) {
throw new IllegalArgumentException(year + "-" + month + "-" + day);
}
if (!isValidTime(hour, minute, second)) {
throw new IllegalArgumentException(hour + ":" + minute + ":" + second);
}
long dateValue;
if (tz == null) {
dateValue = dateValue(year, month, day);
} else {
long ms = getMillis(tz, year, month, day, hour, minute, second, (int) millis);
ms = DateTimeUtils.getLocalTime(new Date(ms),
Calendar.getInstance(TimeZone.getTimeZone("UTC")));
dateValue = dateValueFromDate(ms);
// TODO verify this always works
hour = minute = second = 0;
millis = ms - absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY;
}
if (type == Value.DATE) {
return ValueDate.get(dateValue);
} else if (type == Value.TIMESTAMP) {
nanos += (((((hour * 60L) + minute) * 60) + second) * 1000 + millis) * 1000000;
return ValueTimestamp.get(dateValue, nanos);
} else {
throw DbException.throwInternalError("type:" + type);
}
} catch (IllegalArgumentException e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, DataType.getDataType(type).name, original);
}
}
public static long parseTime(String s) {
int hour = 0, minute = 0, second = 0, nanos = 0;
int s1 = s.indexOf(':');
int s2 = s.indexOf(':', s1 + 1);
int s3 = s.indexOf('.', s2 + 1);
if (s1 <= 0 || s2 <= s1) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
"TIME", s);
}
try {
hour = Integer.parseInt(s.substring(0, s1));
minute = Integer.parseInt(s.substring(s1 + 1, s2));
if (s3 < 0) {
second = Integer.parseInt(s.substring(s2 + 1));
} else {
second = Integer.parseInt(s.substring(s2 + 1, s3));
String n = (s + "000000000").substring(s3 + 1, s3 + 10);
nanos = Integer.parseInt(n);
} }
} catch (NumberFormatException e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
"TIME", s);
}
if (minute < 0 || minute >= 60 || second < 0 || second >= 60) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
"TIME", s);
} }
long time; return ((((hour * 60L) + minute) * 60) + second) * 1000000000 + nanos;
}
/**
* Calculate the milliseconds for the given date and time in the specified timezone.
*
* @param tz the timezone
* @param year the absolute year (positive or negative)
* @param month the month (1-12)
* @param day the day (1-31)
* @param hour the hour (0-23)
* @param minute the minutes (0-59)
* @param second the number of seconds (0-59)
* @param millis the number of milliseconds
* @return the number of milliseconds
*/
public static long getMillis(TimeZone tz, int year, int month, int day, int hour, int minute, int second, int millis) {
int todoInternal;
try { try {
time = getTime(false, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano); return getTimeTry(false, tz, year, month, day, hour, minute, second, millis);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// special case: if the time simply doesn't exist because of // special case: if the time simply doesn't exist because of
// daylight saving time changes, use the lenient version // daylight saving time changes, use the lenient version
...@@ -322,7 +368,7 @@ public class DateTimeUtils { ...@@ -322,7 +368,7 @@ public class DateTimeUtils {
if (hour < 0 || hour > 23) { if (hour < 0 || hour > 23) {
throw e; throw e;
} }
time = getTime(true, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano); return getTimeTry(true, tz, year, month, day, hour, minute, second, millis);
} else if (message.indexOf("DAY_OF_MONTH") > 0) { } else if (message.indexOf("DAY_OF_MONTH") > 0) {
int maxDay; int maxDay;
if (month == 2) { if (month == 2) {
...@@ -337,33 +383,16 @@ public class DateTimeUtils { ...@@ -337,33 +383,16 @@ public class DateTimeUtils {
// using the timezone Brasilia and others, // using the timezone Brasilia and others,
// for example for 2042-10-12 00:00:00. // for example for 2042-10-12 00:00:00.
hour += 6; hour += 6;
time = getTime(true, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano); return getTimeTry(true, tz, year, month, day, hour, minute, second, millis);
} else { } else {
throw e; return getTimeTry(true, tz, year, month, day, hour, minute, second, millis);
} }
} }
switch (type) {
case Value.DATE:
return new java.sql.Date(time);
case Value.TIME:
return new java.sql.Time(time);
case Value.TIMESTAMP: {
Timestamp ts = new Timestamp(time);
ts.setNanos(nano);
return ts;
}
default:
throw DbException.throwInternalError("type:" + type);
}
} catch (IllegalArgumentException e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, DataType.getDataType(type).name, original);
}
} }
private static long getTime(boolean lenient, TimeZone tz, private static long getTimeTry(boolean lenient, TimeZone tz,
int year, int month, int day, int hour, int minute, int second, int year, int month, int day, int hour, int minute, int second,
boolean setMillis, int nano) { int millis) {
Calendar c; Calendar c;
if (tz == null) { if (tz == null) {
c = getCalendar(); c = getCalendar();
...@@ -371,6 +400,7 @@ public class DateTimeUtils { ...@@ -371,6 +400,7 @@ public class DateTimeUtils {
c = Calendar.getInstance(tz); c = Calendar.getInstance(tz);
} }
synchronized (c) { synchronized (c) {
c.clear();
c.setLenient(lenient); c.setLenient(lenient);
if (year <= 0) { if (year <= 0) {
c.set(Calendar.ERA, GregorianCalendar.BC); c.set(Calendar.ERA, GregorianCalendar.BC);
...@@ -385,9 +415,7 @@ public class DateTimeUtils { ...@@ -385,9 +415,7 @@ public class DateTimeUtils {
c.set(Calendar.HOUR_OF_DAY, hour); c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.MINUTE, minute); c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, second); c.set(Calendar.SECOND, second);
if (setMillis) { c.set(Calendar.MILLISECOND, millis);
c.set(Calendar.MILLISECOND, nano / 1000000);
}
return c.getTime().getTime(); return c.getTime().getTime();
} }
} }
...@@ -402,20 +430,18 @@ public class DateTimeUtils { ...@@ -402,20 +430,18 @@ public class DateTimeUtils {
*/ */
public static int getDatePart(java.util.Date d, int field) { public static int getDatePart(java.util.Date d, int field) {
Calendar c = getCalendar(); Calendar c = getCalendar();
int value;
synchronized (c) { synchronized (c) {
c.setTime(d); c.setTime(d);
value = c.get(field); if (field == Calendar.YEAR) {
return getYear(c);
} }
int value = c.get(field);
if (field == Calendar.MONTH) { if (field == Calendar.MONTH) {
value++; return value + 1;
} else if (field == Calendar.YEAR) {
if (c.get(Calendar.ERA) == GregorianCalendar.BC) {
value = 1 - value;
}
} }
return value; return value;
} }
}
/** /**
* Get the year (positive or negative) from a calendar. * Get the year (positive or negative) from a calendar.
...@@ -423,7 +449,7 @@ public class DateTimeUtils { ...@@ -423,7 +449,7 @@ public class DateTimeUtils {
* @param calendar the calendar * @param calendar the calendar
* @return the year * @return the year
*/ */
public static int getYear(Calendar calendar) { private static int getYear(Calendar calendar) {
int year = calendar.get(Calendar.YEAR); int year = calendar.get(Calendar.YEAR);
if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) { if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
year = 1 - year; year = 1 - year;
...@@ -431,20 +457,6 @@ public class DateTimeUtils { ...@@ -431,20 +457,6 @@ public class DateTimeUtils {
return year; return year;
} }
/**
* Get the number of milliseconds since 1970-01-01 in the local timezone.
*
* @param d the date
* @return the milliseconds
*/
public static long getTimeLocal(java.util.Date d) {
Calendar c = getCalendar();
synchronized (c) {
c.setTime(d);
return c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET);
}
}
/** /**
* Get the number of milliseconds since 1970-01-01 in the local timezone, but * Get the number of milliseconds since 1970-01-01 in the local timezone, but
* without daylight saving time into account. * without daylight saving time into account.
...@@ -458,28 +470,12 @@ public class DateTimeUtils { ...@@ -458,28 +470,12 @@ public class DateTimeUtils {
/** /**
* Convert the number of milliseconds since 1970-01-01 in the local timezone * Convert the number of milliseconds since 1970-01-01 in the local timezone
* to GMT. * to UTC, but without daylight saving time into account.
*
* @param millis the number of milliseconds in the local timezone
* @return the number of milliseconds in GMT
*/
public static long getTimeGMT(long millis) {
Calendar c = getCalendar();
synchronized (c) {
c.setTimeInMillis(millis);
return c.getTime().getTime() - c.get(Calendar.ZONE_OFFSET) - c.get(Calendar.DST_OFFSET);
}
}
/**
* Convert the number of milliseconds since 1970-01-01 in the local timezone
* to GMT, but
* without daylight saving time into account.
* *
* @param millis the number of milliseconds in the local timezone * @param millis the number of milliseconds in the local timezone
* @return the number of milliseconds in GMT * @return the number of milliseconds in UTC
*/ */
public static long getTimeGMTWithoutDst(long millis) { public static long getTimeUTCWithoutDst(long millis) {
return millis - zoneOffset; return millis - zoneOffset;
} }
...@@ -535,7 +531,7 @@ public class DateTimeUtils { ...@@ -535,7 +531,7 @@ public class DateTimeUtils {
cal.setTimeInMillis(date.getTime()); cal.setTimeInMillis(date.getTime());
cal.setFirstDayOfWeek(Calendar.MONDAY); cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.setMinimalDaysInFirstWeek(4); cal.setMinimalDaysInFirstWeek(4);
int year = cal.get(Calendar.YEAR); int year = getYear(cal);
int month = cal.get(Calendar.MONTH); int month = cal.get(Calendar.MONTH);
int week = cal.get(Calendar.WEEK_OF_YEAR); int week = cal.get(Calendar.WEEK_OF_YEAR);
if (month == 0 && week > 51) { if (month == 0 && week > 51) {
...@@ -603,4 +599,250 @@ public class DateTimeUtils { ...@@ -603,4 +599,250 @@ public class DateTimeUtils {
} }
} }
/**
* Verify if the specified date is valid.
*
* @param year the year
* @param month the month (January is 1)
* @param day the day (1 is the first of the month)
* @return true if it is valid
*/
public static boolean isValidDate(int year, int month, int day) {
if (month < 1 || month > 12 || day < 1) {
return false;
}
if (year > 1582) {
// Gregorian calendar
if (month != 2) {
return day <= NORMAL_DAYS_PER_MONTH[month];
}
// February
if ((year & 3) != 0) {
return day <= 28;
}
return day <= ((year % 100 != 0) || (year % 400 == 0) ? 29 : 28);
} else if (year == 1582 && month == 10) {
// special case: days 1582-10-05 .. 1582-10-14 don't exist
return day <= 31 && (day < 5 || day > 14);
}
if (month != 2 && day <= NORMAL_DAYS_PER_MONTH[month]) {
return true;
}
return day <= ((year & 3) != 0 ? 28 : 29);
}
/**
* Verify if the specified time is valid.
*
* @param hour the hour
* @param minute the minute
* @param second the second
* @return true if it is valid
*/
public static boolean isValidTime(int hour, int minute, int second) {
return hour >= 0 && hour < 24 &&
minute >= 0 && minute < 60 &&
second >= 0 && second < 60;
}
public static Date convertDateValueToDate(long dateValue) {
long millis = getMillis(TimeZone.getDefault(),
yearFromDateValue(dateValue),
monthFromDateValue(dateValue),
dayFromDateValue(dateValue), 0, 0, 0, 0);
return new Date(millis);
}
public static Timestamp convertDateValueToTimestamp(long dateValue, long nanos) {
long millis = nanos / 1000000;
nanos -= millis * 1000000;
long s = millis / 1000;
millis -= s * 1000;
long m = s / 60;
s -= m * 60;
long h = m / 60;
m -= h * 60;
long ms = getMillis(TimeZone.getDefault(),
yearFromDateValue(dateValue),
monthFromDateValue(dateValue),
dayFromDateValue(dateValue),
(int) h, (int) m, (int) s, 0);
Timestamp ts = new Timestamp(ms);
ts.setNanos((int) (nanos + millis * 1000000));
return ts;
}
public static Time convertNanoToTime(long nanos) {
long millis = nanos / 1000000;
long s = millis / 1000;
millis -= s * 1000;
long m = s / 60;
s -= m * 60;
long h = m / 60;
m -= h * 60;
long ms = getMillis(TimeZone.getDefault(),
1970, 1, 1, (int) (h % 24), (int) m, (int) s, (int) millis);
return new Time(ms);
}
private static int yearFromDateValue(long x) {
return (int) (x >>> SHIFT_YEAR);
}
private static int monthFromDateValue(long x) {
return (int) (x >>> SHIFT_MONTH) & 15;
}
public static int dayFromDateValue(long x) {
return (int) (x & 31);
}
public static long dateValue(long year, int month, int day) {
return (year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day;
}
public static long dateValueFromDate(long ms) {
Calendar cal = getCalendar();
synchronized (cal) {
cal.clear();
cal.setTimeInMillis(ms);
int year, month, day;
year = getYear(cal);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day;
}
}
public static long nanosFromDate(long ms) {
Calendar cal = getCalendar();
synchronized (cal) {
cal.clear();
cal.setTimeInMillis(ms);
int h = cal.get(Calendar.HOUR_OF_DAY);
int m = cal.get(Calendar.MINUTE);
int s = cal.get(Calendar.SECOND);
int millis = cal.get(Calendar.MILLISECOND);
return ((((((h * 60L) + m) * 60) + s) * 1000) + millis) * 1000000;
}
}
public static ValueTimestamp normalize(long absoluteDay, long nanos) {
if (nanos > NANOS_PER_DAY || nanos < 0) {
long d;
if (nanos > NANOS_PER_DAY) {
d = nanos / NANOS_PER_DAY;
} else {
d = (nanos - NANOS_PER_DAY + 1) / NANOS_PER_DAY;
}
nanos -= d * NANOS_PER_DAY;
absoluteDay += d;
}
return ValueTimestamp.get(dateValueFromAbsoluteDay(absoluteDay), nanos);
}
public static long absoluteDayFromDateValue(long dateValue) {
long y = yearFromDateValue(dateValue);
int m = monthFromDateValue(dateValue);
int d = dayFromDateValue(dateValue);
if (m <= 2) {
y--;
m += 12;
}
long a = ((y * 2922L) >>> 3) + DAYS_OFFSET[m - 3] + d - 719484;
if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1005))) {
// Julian calendar (cutover at 1582-10-04 / 1582-10-15)
a += 13;
} else if (y < 1901 || y > 2099) {
// Gregorian calendar (slow mode)
a += (y / 400) - (y / 100) + 15;
}
return a;
}
public static long dateValueFromAbsoluteDay(long absoluteDay) {
long d = absoluteDay + 719468;
long y100 = 0, offset;
if (d > 578040) {
// Gregorian calendar
long y400 = d / 146097;
d -= y400 * 146097;
y100 = d / 36524;
d -= y100 * 36524;
offset = y400 * 400 + y100 * 100;
} else {
// Julian calendar
d += 292200000002L;
offset = -800000000;
}
long y4 = d / 1461;
d -= y4 * 1461;
long y = d / 365;
d -= y * 365;
if (d == 0 && (y == 4 || y100 == 4)) {
y--;
d += 365;
}
y += offset + y4 * 4;
// month of a day
int m = ((int) d * 2 + 1) * 5 / 306;
d -= DAYS_OFFSET[m] - 1;
if (m >= 10) {
y++;
m -= 12;
}
return dateValue(y, m + 3, (int) d);
}
public static void appendDate(StringBuilder buff, long dateValue) {
int y = DateTimeUtils.yearFromDateValue(dateValue);
int m = DateTimeUtils.monthFromDateValue(dateValue);
int d = DateTimeUtils.dayFromDateValue(dateValue);
if (y > 0 && y < 10000) {
StringUtils.appendZeroPadded(buff, 4, y);
} else {
buff.append(y);
}
buff.append('-');
StringUtils.appendZeroPadded(buff, 2, m);
buff.append('-');
StringUtils.appendZeroPadded(buff, 2, d);
}
public static void appendTime(StringBuilder buff, long n, boolean alwaysAddMillis) {
long ms = n / 1000000;
n -= ms * 1000000;
long s = ms / 1000;
ms -= s * 1000;
long m = s / 60;
s -= m * 60;
long h = m / 60;
m -= h * 60;
if (h < 0) {
buff.append(h);
} else {
StringUtils.appendZeroPadded(buff, 2, h);
}
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, m);
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, s);
if (ms > 0 || n > 0) {
buff.append('.');
int start = buff.length();
StringUtils.appendZeroPadded(buff, 3, ms);
if (n > 0) {
StringUtils.appendZeroPadded(buff, 6, n);
}
for (int i = buff.length() - 1; i > start; i--) {
if (buff.charAt(i) != '0') {
break;
}
buff.deleteCharAt(i);
}
} else if (alwaysAddMillis) {
buff.append(".0");
}
}
} }
...@@ -320,27 +320,37 @@ public class Transfer { ...@@ -320,27 +320,37 @@ public class Transfer {
writeByte(v.getByte()); writeByte(v.getByte());
break; break;
case Value.TIME: case Value.TIME:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy())); writeLong(((ValueTime) v).getNanos());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
} else { } else {
writeLong(v.getTimeNoCopy().getTime()); writeLong(v.getTime().getTime());
} }
break; break;
case Value.DATE: case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy())); writeLong(((ValueDate) v).getDateValue());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getDate()));
} else { } else {
writeLong(v.getDateNoCopy().getTime()); writeLong(v.getDate().getTime());
} }
break; break;
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
Timestamp ts = v.getTimestampNoCopy(); if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { ValueTimestamp ts = (ValueTimestamp) v;
writeLong(ts.getDateValue());
writeLong(ts.getNanos());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
Timestamp ts = v.getTimestamp();
writeLong(DateTimeUtils.getTimeLocalWithoutDst(ts)); writeLong(DateTimeUtils.getTimeLocalWithoutDst(ts));
writeInt(ts.getNanos());
} else { } else {
Timestamp ts = v.getTimestamp();
writeLong(ts.getTime()); writeLong(ts.getTime());
}
writeInt(ts.getNanos()); writeInt(ts.getNanos());
}
break; break;
} }
case Value.DECIMAL: case Value.DECIMAL:
...@@ -460,24 +470,30 @@ public class Transfer { ...@@ -460,24 +470,30 @@ public class Transfer {
case Value.BYTE: case Value.BYTE:
return ValueByte.get(readByte()); return ValueByte.get(readByte());
case Value.DATE: case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMTWithoutDst(readLong()))); return ValueDate.get(readLong());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueDate.get(new Date(DateTimeUtils.getTimeUTCWithoutDst(readLong())));
} }
return ValueDate.getNoCopy(new Date(readLong())); return ValueDate.get(new Date(readLong()));
case Value.TIME: case Value.TIME:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
return ValueTime.getNoCopy(new Time(DateTimeUtils.getTimeGMTWithoutDst(readLong()))); return ValueTime.get(readLong());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueTime.get(new Time(DateTimeUtils.getTimeUTCWithoutDst(readLong())));
} }
return ValueTime.getNoCopy(new Time(readLong())); return ValueTime.get(new Time(readLong()));
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readLong())); return ValueTimestamp.get(readLong(), readLong());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readLong()));
ts.setNanos(readInt()); ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.get(ts);
} }
Timestamp ts = new Timestamp(readLong()); Timestamp ts = new Timestamp(readLong());
ts.setNanos(readInt()); ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.get(ts);
} }
case Value.DECIMAL: case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(readString())); return ValueDecimal.get(new BigDecimal(readString()));
......
...@@ -24,6 +24,7 @@ import org.h2.message.DbException; ...@@ -24,6 +24,7 @@ import org.h2.message.DbException;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
import org.h2.store.LobStorage; import org.h2.store.LobStorage;
import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
...@@ -370,26 +371,14 @@ public abstract class Value { ...@@ -370,26 +371,14 @@ public abstract class Value {
return ((ValueDate) convertTo(Value.DATE)).getDate(); return ((ValueDate) convertTo(Value.DATE)).getDate();
} }
public Date getDateNoCopy() {
return ((ValueDate) convertTo(Value.DATE)).getDateNoCopy();
}
public Time getTime() { public Time getTime() {
return ((ValueTime) convertTo(Value.TIME)).getTime(); return ((ValueTime) convertTo(Value.TIME)).getTime();
} }
public Time getTimeNoCopy() {
return ((ValueTime) convertTo(Value.TIME)).getTimeNoCopy();
}
public Timestamp getTimestamp() { public Timestamp getTimestamp() {
return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestamp(); return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestamp();
} }
public Timestamp getTimestampNoCopy() {
return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestampNoCopy();
}
public byte[] getBytes() { public byte[] getBytes() {
return ((ValueBytes) convertTo(Value.BYTES)).getBytes(); return ((ValueBytes) convertTo(Value.BYTES)).getBytes();
} }
...@@ -693,9 +682,11 @@ public abstract class Value { ...@@ -693,9 +682,11 @@ public abstract class Value {
case DATE: { case DATE: {
switch (getType()) { switch (getType()) {
case TIME: case TIME:
return ValueDate.get(new Date(getTimeNoCopy().getTime())); // because the time has set the date to 1970-01-01,
// this will be the result
return ValueDate.get(DateTimeUtils.dateValue(1970, 1, 1));
case TIMESTAMP: case TIMESTAMP:
return ValueDate.get(new Date(getTimestampNoCopy().getTime())); return ValueDate.get(((ValueTimestamp) this).getDateValue());
} }
break; break;
} }
...@@ -703,19 +694,22 @@ public abstract class Value { ...@@ -703,19 +694,22 @@ public abstract class Value {
switch (getType()) { switch (getType()) {
case DATE: case DATE:
// need to normalize the year, month and day // need to normalize the year, month and day
return ValueTime.get(new Time(getDateNoCopy().getTime())); // because a date has the time set to 0, the result will be 0
return ValueTime.get(0);
case TIMESTAMP: case TIMESTAMP:
// need to normalize the year, month and day // need to normalize the year, month and day
return ValueTime.get(new Time(getTimestampNoCopy().getTime())); return ValueTime.get(new Time(getTimestamp().getTime()));
} }
break; break;
} }
case TIMESTAMP: { case TIMESTAMP: {
switch (getType()) { switch (getType()) {
case TIME: case TIME:
return ValueTimestamp.getNoCopy(new Timestamp(getTimeNoCopy().getTime())); // TODO
return ValueTimestamp.get(new Timestamp(getTime().getTime()));
case DATE: case DATE:
return ValueTimestamp.getNoCopy(new Timestamp(getDateNoCopy().getTime())); // TODO
return ValueTimestamp.get(new Timestamp(getDate().getTime()));
} }
break; break;
} }
...@@ -815,11 +809,11 @@ public abstract class Value { ...@@ -815,11 +809,11 @@ public abstract class Value {
case DECIMAL: case DECIMAL:
return ValueDecimal.get(new BigDecimal(s.trim())); return ValueDecimal.get(new BigDecimal(s.trim()));
case TIME: case TIME:
return ValueTime.getNoCopy(ValueTime.parseTime(s.trim())); return ValueTime.parse(s.trim());
case DATE: case DATE:
return ValueDate.getNoCopy(ValueDate.parseDate(s.trim())); return ValueDate.parse(s.trim());
case TIMESTAMP: case TIMESTAMP:
return ValueTimestamp.getNoCopy(ValueTimestamp.parseTimestamp(s.trim())); return ValueTimestamp.parse(s.trim());
case BYTES: case BYTES:
return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim())); return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim()));
case JAVA_OBJECT: case JAVA_OBJECT:
......
...@@ -9,8 +9,8 @@ package org.h2.value; ...@@ -9,8 +9,8 @@ package org.h2.value;
import java.sql.Date; import java.sql.Date;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Calendar;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
/** /**
* Implementation of the DATE data type. * Implementation of the DATE data type.
...@@ -28,29 +28,29 @@ public class ValueDate extends Value { ...@@ -28,29 +28,29 @@ public class ValueDate extends Value {
*/ */
static final int DISPLAY_SIZE = 10; static final int DISPLAY_SIZE = 10;
private final Date value; private final long dateValue;
private ValueDate(Date value) { private ValueDate(long dateValue) {
this.value = value; this.dateValue = dateValue;
} }
/** /**
* Parse a string to a java.sql.Date object. * Parse a string to a ValueDate.
* *
* @param s the string to parse * @param s the string to parse
* @return the date * @return the date
*/ */
public static Date parseDate(String s) { public static ValueDate parse(String s) {
return (Date) DateTimeUtils.parseDateTime(s, Value.DATE); Value x = DateTimeUtils.parse(s, Value.DATE);
return (ValueDate) Value.cache(x);
} }
public Date getDate() { public Date getDate() {
// this class is mutable - must copy the object return DateTimeUtils.convertDateValueToDate(dateValue);
return (Date) value.clone();
} }
public Date getDateNoCopy() { public long getDateValue() {
return value; return dateValue;
} }
public String getSQL() { public String getSQL() {
...@@ -62,22 +62,13 @@ public class ValueDate extends Value { ...@@ -62,22 +62,13 @@ public class ValueDate extends Value {
} }
protected int compareSecure(Value o, CompareMode mode) { protected int compareSecure(Value o, CompareMode mode) {
ValueDate v = (ValueDate) o; return MathUtils.compareLong(dateValue, ((ValueDate) o).dateValue);
return Integer.signum(value.compareTo(v.value));
} }
public String getString() { public String getString() {
String s = value.toString(); StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
long time = value.getTime(); DateTimeUtils.appendDate(buff, dateValue);
// special case: java.sql.Date doesn't format return buff.toString();
// years below year 1 (BC) and years above 9999 correctly
if (time < ValueTimestamp.YEAR_ONE || time > ValueTimestamp.YEAR_9999) {
int year = DateTimeUtils.getDatePart(value, Calendar.YEAR);
if (year < 1 || year > 9999) {
s = year + s.substring(s.indexOf('-'));
}
}
return s;
} }
public long getPrecision() { public long getPrecision() {
...@@ -85,39 +76,36 @@ public class ValueDate extends Value { ...@@ -85,39 +76,36 @@ public class ValueDate extends Value {
} }
public int hashCode() { public int hashCode() {
return value.hashCode(); return (int) (dateValue ^ (dateValue >>> 32));
} }
public Object getObject() { public Object getObject() {
// this class is mutable - must copy the object
return getDate(); return getDate();
} }
public void set(PreparedStatement prep, int parameterIndex) throws SQLException { public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setDate(parameterIndex, value); prep.setDate(parameterIndex, getDate());
} }
/** /**
* Get or create a date value for the given date. * Get or create a date value for the given date.
* Clone the date.
* *
* @param date the date * @param date the date
* @return the value * @return the value
*/ */
public static ValueDate get(Date date) { public static ValueDate get(Date date) {
date = DateTimeUtils.cloneAndNormalizeDate(date); long x = DateTimeUtils.dateValueFromDate(date.getTime());
return getNoCopy(date); return get(x);
} }
/** /**
* Get or create a date value for the given date. * Get or create a date value for the given date.
* Do not clone the date.
* *
* @param date the date * @param dateValue the date value
* @return the value * @return the value
*/ */
public static ValueDate getNoCopy(Date date) { public static ValueDate get(long dateValue) {
return (ValueDate) Value.cache(new ValueDate(date)); return (ValueDate) Value.cache(new ValueDate(dateValue));
} }
public int getDisplaySize() { public int getDisplaySize() {
...@@ -125,7 +113,10 @@ public class ValueDate extends Value { ...@@ -125,7 +113,10 @@ public class ValueDate extends Value {
} }
public boolean equals(Object other) { public boolean equals(Object other) {
return other instanceof ValueDate && value.equals(((ValueDate) other).value); if (this == other) {
return true;
}
return other instanceof ValueDate && dateValue == (((ValueDate) other).dateValue);
} }
} }
...@@ -10,6 +10,7 @@ import java.sql.PreparedStatement; ...@@ -10,6 +10,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Time; import java.sql.Time;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
/** /**
* Implementation of the TIME data type. * Implementation of the TIME data type.
...@@ -27,29 +28,28 @@ public class ValueTime extends Value { ...@@ -27,29 +28,28 @@ public class ValueTime extends Value {
*/ */
static final int DISPLAY_SIZE = 8; static final int DISPLAY_SIZE = 8;
private final Time value; private final long nanos;
private ValueTime(Time value) { private ValueTime(long nanos) {
this.value = value; this.nanos = nanos;
} }
/** /**
* Parse a string to a java.sql.Time object. * Parse a string to a ValueTime.
* *
* @param s the string to parse * @param s the string to parse
* @return the time * @return the time
*/ */
public static Time parseTime(String s) { public static ValueTime parse(String s) {
return (Time) DateTimeUtils.parseDateTime(s, Value.TIME); return new ValueTime(DateTimeUtils.parseTime(s));
} }
public Time getTime() { public Time getTime() {
// this class is mutable - must copy the object return DateTimeUtils.convertNanoToTime(nanos);
return (Time) value.clone();
} }
public Time getTimeNoCopy() { public long getNanos() {
return value; return nanos;
} }
public String getSQL() { public String getSQL() {
...@@ -61,12 +61,13 @@ public class ValueTime extends Value { ...@@ -61,12 +61,13 @@ public class ValueTime extends Value {
} }
protected int compareSecure(Value o, CompareMode mode) { protected int compareSecure(Value o, CompareMode mode) {
ValueTime v = (ValueTime) o; return MathUtils.compareLong(nanos, ((ValueTime) o).nanos);
return Integer.signum(value.compareTo(v.value));
} }
public String getString() { public String getString() {
return value.toString(); StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendTime(buff, nanos, false);
return buff.toString();
} }
public long getPrecision() { public long getPrecision() {
...@@ -74,7 +75,7 @@ public class ValueTime extends Value { ...@@ -74,7 +75,7 @@ public class ValueTime extends Value {
} }
public int hashCode() { public int hashCode() {
return value.hashCode(); return (int) (nanos ^ (nanos >>> 32));
} }
public Object getObject() { public Object getObject() {
...@@ -82,30 +83,28 @@ public class ValueTime extends Value { ...@@ -82,30 +83,28 @@ public class ValueTime extends Value {
} }
public void set(PreparedStatement prep, int parameterIndex) throws SQLException { public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setTime(parameterIndex, value); prep.setTime(parameterIndex, getTime());
} }
/** /**
* Get or create a time value for the given time. * Get or create a time value for the given time.
* Clone the time.
* *
* @param time the time * @param time the time
* @return the value * @return the value
*/ */
public static ValueTime get(Time time) { public static ValueTime get(Time time) {
time = DateTimeUtils.cloneAndNormalizeTime(time); long x = DateTimeUtils.nanosFromDate(time.getTime());
return getNoCopy(time); return get(x);
} }
/** /**
* Get or create a time value for the given time. * Get or create a time value.
* Do not clone the time.
* *
* @param time the time * @param nanos the nanoseconds
* @return the value * @return the value
*/ */
public static ValueTime getNoCopy(Time time) { public static ValueTime get(long nanos) {
return (ValueTime) Value.cache(new ValueTime(time)); return (ValueTime) Value.cache(new ValueTime(nanos));
} }
public int getDisplaySize() { public int getDisplaySize() {
...@@ -113,31 +112,28 @@ public class ValueTime extends Value { ...@@ -113,31 +112,28 @@ public class ValueTime extends Value {
} }
public boolean equals(Object other) { public boolean equals(Object other) {
return other instanceof ValueTime && value.equals(((ValueTime) other).value); if (this == other) {
return true;
}
return other instanceof ValueTime && nanos == (((ValueTime) other).nanos);
} }
public Value add(Value v) { public Value add(Value v) {
Time t = new Time(value.getTime() + v.getTime().getTime()); ValueTime t = (ValueTime) v.convertTo(Value.TIME);
return ValueTime.get(t); return ValueTime.get(nanos + t.getNanos());
} }
public Value subtract(Value v) { public Value subtract(Value v) {
Time t = new Time(value.getTime() - v.getTime().getTime()); ValueTime t = (ValueTime) v.convertTo(Value.TIME);
return ValueTime.get(t); return ValueTime.get(nanos - t.getNanos());
} }
public Value multiply(Value v) { public Value multiply(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime(); return ValueTime.get((long) (nanos * v.getDouble()));
long t = value.getTime() - zeroTime;
t = (long) (t * v.getDouble()) + zeroTime;
return ValueTime.get(new Time(t));
} }
public Value divide(Value v) { public Value divide(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime(); return ValueTime.get((long) (nanos / v.getDouble()));
long t = value.getTime() - zeroTime;
t = (long) (t / v.getDouble()) + zeroTime;
return ValueTime.get(new Time(t));
} }
} }
...@@ -9,9 +9,7 @@ package org.h2.value; ...@@ -9,9 +9,7 @@ package org.h2.value;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Calendar;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
...@@ -37,33 +35,24 @@ public class ValueTimestamp extends Value { ...@@ -37,33 +35,24 @@ public class ValueTimestamp extends Value {
*/ */
static final int DEFAULT_SCALE = 10; static final int DEFAULT_SCALE = 10;
/** private final long dateValue;
* This is used to find out if a date is possibly BC. Because of time zone private final long nanos;
* issues (the date is time zone specific), the second day is used. That
* means the value is not exact, but it does not need to be.
*/
static final long YEAR_ONE = java.sql.Date.valueOf("0001-01-02").getTime();
/**
* This is used to find out if the year is possibly larger than 9999.
* Because of time zone issues (the date is time zone specific), it's a few
* days before the last day. That means the value is not exact, but it does
* not need to be.
*/
static final long YEAR_9999 = java.sql.Date.valueOf("9999-12-20").getTime();
private final Timestamp value;
private ValueTimestamp(Timestamp value) { private ValueTimestamp(long dateValue, long nanos) {
this.value = value; this.dateValue = dateValue;
this.nanos = nanos;
} }
public Timestamp getTimestamp() { public Timestamp getTimestamp() {
return (Timestamp) value.clone(); return DateTimeUtils.convertDateValueToTimestamp(dateValue, nanos);
}
public long getDateValue() {
return dateValue;
} }
public Timestamp getTimestampNoCopy() { public long getNanos() {
return value; return nanos;
} }
public String getSQL() { public String getSQL() {
...@@ -71,13 +60,25 @@ public class ValueTimestamp extends Value { ...@@ -71,13 +60,25 @@ public class ValueTimestamp extends Value {
} }
/** /**
* Parse a string to a java.sql.Timestamp object. * Get or create a date value for the given date.
*
* @param dateValue the date value
* @param nanos the nanoseconds
* @return the value
*/
public static ValueTimestamp get(long dateValue, long nanos) {
return (ValueTimestamp) Value.cache(new ValueTimestamp(dateValue, nanos));
}
/**
* Parse a string to a ValueTimestamp.
* *
* @param s the string to parse * @param s the string to parse
* @return the timestamp * @return the date
*/ */
public static Timestamp parseTimestamp(String s) { public static ValueTimestamp parse(String s) {
return (Timestamp) DateTimeUtils.parseDateTime(s, Value.TIMESTAMP); Value x = DateTimeUtils.parse(s, Value.TIMESTAMP);
return (ValueTimestamp) Value.cache(x);
} }
public int getType() { public int getType() {
...@@ -85,22 +86,21 @@ public class ValueTimestamp extends Value { ...@@ -85,22 +86,21 @@ public class ValueTimestamp extends Value {
} }
protected int compareSecure(Value o, CompareMode mode) { protected int compareSecure(Value o, CompareMode mode) {
ValueTimestamp v = (ValueTimestamp) o; ValueTimestamp t = (ValueTimestamp) o;
return Integer.signum(value.compareTo(v.value)); int c = MathUtils.compareLong(dateValue, t.dateValue);
if (c != 0) {
return c;
}
return MathUtils.compareLong(nanos, t.nanos);
} }
public String getString() { public String getString() {
String s = value.toString(); // TODO verify display size
long time = value.getTime(); StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
// special case: java.sql.Timestamp doesn't format DateTimeUtils.appendDate(buff, dateValue);
// years below year 1 (BC) correctly buff.append(' ');
if (time < YEAR_ONE) { DateTimeUtils.appendTime(buff, nanos, true);
int year = DateTimeUtils.getDatePart(value, Calendar.YEAR); return buff.toString();
if (year < 1) {
s = year + s.substring(s.indexOf('-'));
}
}
return s;
} }
public long getPrecision() { public long getPrecision() {
...@@ -112,63 +112,48 @@ public class ValueTimestamp extends Value { ...@@ -112,63 +112,48 @@ public class ValueTimestamp extends Value {
} }
public int hashCode() { public int hashCode() {
return value.hashCode(); return (int) (dateValue ^ (dateValue >>> 32) ^ nanos ^ (nanos >>> 32));
} }
public Object getObject() { public Object getObject() {
// this class is mutable - must copy the object
return getTimestamp(); return getTimestamp();
} }
public void set(PreparedStatement prep, int parameterIndex) throws SQLException { public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setTimestamp(parameterIndex, value); prep.setTimestamp(parameterIndex, getTimestamp());
} }
/** /**
* Get or create a timestamp value for the given timestamp. * Get or create a timestamp value for the given timestamp.
* Clone the timestamp.
* *
* @param timestamp the timestamp * @param timestamp the timestamp
* @return the value * @return the value
*/ */
public static ValueTimestamp get(Timestamp timestamp) { public static ValueTimestamp get(Timestamp timestamp) {
timestamp = (Timestamp) timestamp.clone(); long ms = timestamp.getTime();
return getNoCopy(timestamp); long dateValue = DateTimeUtils.dateValueFromDate(ms);
} long nanos = DateTimeUtils.nanosFromDate(ms);
nanos += timestamp.getNanos() % 1000000;
/** return get(dateValue, nanos);
* Get or create a timestamp value for the given timestamp.
* Do not clone the timestamp.
*
* @param timestamp the timestamp
* @return the value
*/
public static ValueTimestamp getNoCopy(Timestamp timestamp) {
return (ValueTimestamp) Value.cache(new ValueTimestamp(timestamp));
} }
public Value convertScale(boolean onlyToSmallerScale, int targetScale) { public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale == DEFAULT_SCALE) {
return this;
}
if (targetScale < 0 || targetScale > DEFAULT_SCALE) { if (targetScale < 0 || targetScale > DEFAULT_SCALE) {
// TODO convertScale for Timestamps: may throw an exception?
throw DbException.getInvalidValueException("scale", targetScale); throw DbException.getInvalidValueException("scale", targetScale);
} }
int nanos = value.getNanos(); long n = nanos;
BigDecimal bd = BigDecimal.valueOf(nanos); BigDecimal bd = BigDecimal.valueOf(n);
bd = bd.movePointLeft(9); bd = bd.movePointLeft(9);
bd = MathUtils.setScale(bd, targetScale); bd = MathUtils.setScale(bd, targetScale);
bd = bd.movePointRight(9); bd = bd.movePointRight(9);
int n2 = bd.intValue(); long n2 = bd.longValue();
if (n2 == nanos) { if (n2 == n) {
return this; return this;
} }
long t = value.getTime(); return get(dateValue, n2);
while (n2 >= 1000000000) {
t += 1000;
n2 -= 1000000000;
}
Timestamp t2 = new Timestamp(t);
t2.setNanos(n2);
return ValueTimestamp.getNoCopy(t2);
} }
public int getDisplaySize() { public int getDisplaySize() {
...@@ -176,21 +161,29 @@ public class ValueTimestamp extends Value { ...@@ -176,21 +161,29 @@ public class ValueTimestamp extends Value {
} }
public boolean equals(Object other) { public boolean equals(Object other) {
return other instanceof ValueTimestamp && value.equals(((ValueTimestamp) other).value); if (this == other) {
return true;
} else if (!(other instanceof ValueTimestamp)) {
return false;
}
ValueTimestamp x = (ValueTimestamp) other;
return dateValue == x.dateValue && nanos == x.nanos;
} }
public Value add(Value v) { public Value add(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime(); // TODO test sum of timestamps, dates, times
long t = value.getTime() - zeroTime + v.getTimestamp().getTime(); ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP);
Timestamp ts = new Timestamp(t); long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue);
return ValueTimestamp.get(ts); long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue);
return DateTimeUtils.normalize(d1 + d2, nanos + t.nanos);
} }
public Value subtract(Value v) { public Value subtract(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime(); // TODO test sum of timestamps, dates, times
long t = value.getTime() + zeroTime - v.getTimestamp().getTime(); ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP);
Timestamp ts = new Timestamp(t); long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue);
return ValueTimestamp.get(ts); long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue);
return DateTimeUtils.normalize(d1 - d2, nanos - t.nanos);
} }
} }
...@@ -339,7 +339,8 @@ java org.h2.test.TestAll timer ...@@ -339,7 +339,8 @@ java org.h2.test.TestAll timer
System.setProperty("h2.delayWrongPasswordMin", "0"); System.setProperty("h2.delayWrongPasswordMin", "0");
System.setProperty("h2.delayWrongPasswordMax", "0"); System.setProperty("h2.delayWrongPasswordMax", "0");
// System.setProperty("h2.storeLocalTime", "true"); int todoTestBoth;
// System.setProperty("h2.storeLocalTime", "true");
// speedup // speedup
// System.setProperty("h2.syncMethod", ""); // System.setProperty("h2.syncMethod", "");
......
...@@ -156,6 +156,7 @@ public class TestCompatibility extends TestBase { ...@@ -156,6 +156,7 @@ public class TestCompatibility extends TestBase {
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')"); stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')");
org.h2.mode.FunctionsMySQL.register(conn); org.h2.mode.FunctionsMySQL.register(conn);
assertResult("0", stat, "SELECT UNIX_TIMESTAMP('1970-01-01 00:00:00Z')");
assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP('2007-11-30 10:30:19Z')"); assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP('2007-11-30 10:30:19Z')");
assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1196418619))"); assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1196418619))");
assertResult("2007 November", stat, "SELECT FROM_UNIXTIME(1196300000, '%Y %M')"); assertResult("2007 November", stat, "SELECT FROM_UNIXTIME(1196300000, '%Y %M')");
......
...@@ -123,7 +123,7 @@ public class TestDateStorage extends TestBase { ...@@ -123,7 +123,7 @@ public class TestDateStorage extends TestBase {
} }
private static void test(int year, int month, int day, int hour) { private static void test(int year, int month, int day, int hour) {
DateTimeUtils.parseDateTime(year + "-" + month + "-" + day + " " + hour + ":00:00", DateTimeUtils.parse(year + "-" + month + "-" + day + " " + hour + ":00:00",
Value.TIMESTAMP); Value.TIMESTAMP);
} }
......
...@@ -30,6 +30,7 @@ public class TestLinkedTable extends TestBase { ...@@ -30,6 +30,7 @@ public class TestLinkedTable extends TestBase {
* @param a ignored * @param a ignored
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
// System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
......
...@@ -59,7 +59,7 @@ public class TestScriptSimple extends TestBase { ...@@ -59,7 +59,7 @@ public class TestScriptSimple extends TestBase {
while (rs.next()) { while (rs.next()) {
String expected = reader.readStatement().trim(); String expected = reader.readStatement().trim();
String got = "> " + rs.getString(1); String got = "> " + rs.getString(1);
assertEquals(expected, got); assertEquals(sql, expected, got);
} }
} else { } else {
conn.createStatement().execute(sql); conn.createStatement().execute(sql);
......
...@@ -7233,7 +7233,7 @@ INSERT INTO TEST VALUES(0, '0:0:0','1-2-3','2-3-4 0:0:0'); ...@@ -7233,7 +7233,7 @@ INSERT INTO TEST VALUES(0, '0:0:0','1-2-3','2-3-4 0:0:0');
INSERT INTO TEST VALUES(1, '01:02:03','2001-02-03','2001-02-29 0:0:0'); INSERT INTO TEST VALUES(1, '01:02:03','2001-02-03','2001-02-29 0:0:0');
> exception > exception
INSERT INTO TEST VALUES(1, '24:02:03','2001-02-03','2001-02-01 0:0:0'); INSERT INTO TEST VALUES(1, '24:62:03','2001-02-03','2001-02-01 0:0:0');
> exception > exception
INSERT INTO TEST VALUES(1, '23:02:03','2001-04-31','2001-02-01 0:0:0'); INSERT INTO TEST VALUES(1, '23:02:03','2001-04-31','2001-02-01 0:0:0');
...@@ -7270,10 +7270,10 @@ SELECT ID, CAST(XT AS DATE) T2D, CAST(XTS AS DATE) TS2D, ...@@ -7270,10 +7270,10 @@ SELECT ID, CAST(XT AS DATE) T2D, CAST(XTS AS DATE) TS2D,
CAST(XD AS TIME) D2T, CAST(XTS AS TIME) TS2T, CAST(XD AS TIME) D2T, CAST(XTS AS TIME) TS2T,
CAST(XT AS TIMESTAMP) D2TS, CAST(XD AS TIMESTAMP) D2TS FROM TEST; CAST(XT AS TIMESTAMP) D2TS, CAST(XD AS TIMESTAMP) D2TS FROM TEST;
> ID T2D TS2D D2T TS2T D2TS D2TS > ID T2D TS2D D2T TS2T D2TS D2TS
> ---- ---------- ---------- -------- -------- --------------------- --------------------- > ---- ---------- ---------- -------- ------------ --------------------- ---------------------
> 0 1970-01-01 0002-03-04 00:00:00 00:00:00 1970-01-01 00:00:00.0 0001-02-03 00:00:00.0 > 0 1970-01-01 0002-03-04 00:00:00 00:00:00 1970-01-01 00:00:00.0 0001-02-03 00:00:00.0
> 1 1970-01-01 0007-08-09 00:00:00 00:01:02 1970-01-01 01:02:03.0 0004-05-06 00:00:00.0 > 1 1970-01-01 0007-08-09 00:00:00 00:01:02 1970-01-01 01:02:03.0 0004-05-06 00:00:00.0
> 2 1970-01-01 1999-12-31 00:00:00 23:59:59 1970-01-01 23:59:59.0 1999-12-31 00:00:00.0 > 2 1970-01-01 1999-12-31 00:00:00 23:59:59.123 1970-01-01 23:59:59.0 1999-12-31 00:00:00.0
> null null null null null null null > null null null null null null null
> rows: 4 > rows: 4
...@@ -9331,10 +9331,10 @@ select length(curdate()) c1, length(current_date()) c2, substring(curdate(), 5, ...@@ -9331,10 +9331,10 @@ select length(curdate()) c1, length(current_date()) c2, substring(curdate(), 5,
> 10 10 - > 10 10 -
> rows: 1 > rows: 1
select length(curtime()) c1, length(current_time()) c2, substring(curtime(), 3, 1) c3 from test; select length(curtime())>=8 c1, length(current_time())>=8 c2, substring(curtime(), 3, 1) c3 from test;
> C1 C2 C3 > C1 C2 C3
> -- -- -- > ---- ---- --
> 8 8 : > TRUE TRUE :
> rows: 1 > rows: 1
select length(now())>20 c1, length(current_timestamp())>20 c2, length(now(0))>20 c3, length(now(2))>20 c4, substring(now(5), 20, 1) c5 from test; select length(now())>20 c1, length(current_timestamp())>20 c2, length(now(0))>20 c3, length(now(2))>20 c4, substring(now(5), 20, 1) c5 from test;
......
...@@ -38,15 +38,105 @@ public class TestDate extends TestBase { ...@@ -38,15 +38,105 @@ public class TestDate extends TestBase {
* @param a ignored * @param a ignored
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
System.setProperty("h2.storeLocalTime", "true"); // System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() throws SQLException { public void test() throws SQLException {
testValidDate();
testValidTime();
testCalculateLocalMillis();
testTimeOperationsAcrossTimeZones(); testTimeOperationsAcrossTimeZones();
testDateTimeUtils(); testDateTimeUtils();
} }
private void testValidTime() {
for (int h = -1; h < 28; h++) {
for (int m = -1; m < 65; m++) {
for (int s = -1; s < 65; s++) {
boolean valid = DateTimeUtils.isValidTime(h, m, s);
boolean expected = h >= 0 && h < 24 && m >= 0 && m < 60 &&
s >= 0 && s < 60;
assertEquals(expected, valid);
}
}
}
}
private void testValidDate() {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
c.setLenient(false);
for (int y = -2000; y < 3000; y++) {
for (int m = -10; m <= 20; m++) {
for (int d = -10; d <= 40; d++) {
boolean valid = DateTimeUtils.isValidDate(y, m, d);
if (m < 1 || m > 12) {
assertFalse(valid);
} else if (d < 1 || d > 31) {
assertFalse(valid);
} else {
if (y <= 0) {
c.set(Calendar.ERA, GregorianCalendar.BC);
c.set(Calendar.YEAR, 1 - y);
} else {
c.set(Calendar.ERA, GregorianCalendar.AD);
c.set(Calendar.YEAR, y);
}
c.set(Calendar.MONTH, m - 1);
c.set(Calendar.DAY_OF_MONTH, d);
boolean expected = true;
try {
c.getTimeInMillis();
} catch (Exception e) {
expected = false;
}
if (expected != valid) {
fail(y + "-" + m + "-" + d + " expected: " + expected + " got: " + valid);
}
}
}
}
}
}
private void testCalculateLocalMillis() {
TimeZone defaultTimeZone = TimeZone.getDefault();
try {
for (TimeZone tz : TestDate.getDistinctTimeZones()) {
TimeZone.setDefault(tz);
for (int y = 1900; y < 2039; y++) {
if (y == 1993) {
// timezone change in Kwajalein
} else if (y == 1995) {
// timezone change in Enderbury and Kiritimati
}
for (int m = 1; m <= 12; m++) {
for (int day = 1; day < 29; day++) {
if (y == 1582 && m == 10 && day >= 5 && day <= 14) {
continue;
}
testDate(y, m, day);
}
}
}
}
} finally {
TimeZone.setDefault(defaultTimeZone);
}
}
static void testDate(int y, int m, int day) {
long millis = DateTimeUtils.getMillis(TimeZone.getDefault(), y, m, day, 0, 0, 0, 0);
String st = new java.sql.Date(millis).toString();
int y2 = Integer.parseInt(st.substring(0, 4));
int m2 = Integer.parseInt(st.substring(5, 7));
int d2 = Integer.parseInt(st.substring(8, 10));
if (y != y2 || m != m2 || day != d2) {
String s = y + "-" + (m < 10 ? "0" + m : m) + "-" + (day < 10 ? "0" + day : day);
System.out.println(s + "<>" + st + " " + TimeZone.getDefault().getID());
}
}
private void testTimeOperationsAcrossTimeZones() { private void testTimeOperationsAcrossTimeZones() {
if (!SysProperties.STORE_LOCAL_TIME) { if (!SysProperties.STORE_LOCAL_TIME) {
return; return;
...@@ -124,25 +214,20 @@ public class TestDate extends TestBase { ...@@ -124,25 +214,20 @@ public class TestDate extends TestBase {
} }
private void testDateTimeUtils() { private void testDateTimeUtils() {
java.sql.Timestamp ts1 = (Timestamp) DateTimeUtils.parseDateTime("-999-08-07 13:14:15.16", Value.TIMESTAMP); ValueTimestamp ts1 = (ValueTimestamp) DateTimeUtils.parse("-999-08-07 13:14:15.16", Value.TIMESTAMP);
java.sql.Timestamp ts2 = (Timestamp) DateTimeUtils.parseDateTime("19999-08-07 13:14:15.16", Value.TIMESTAMP); ValueTimestamp ts2 = (ValueTimestamp) DateTimeUtils.parse("19999-08-07 13:14:15.16", Value.TIMESTAMP);
java.sql.Time t1 = DateTimeUtils.cloneAndNormalizeTime(new java.sql.Time(ts1.getTime())); ValueTime t1 = (ValueTime) ts1.convertTo(Value.TIME);
java.sql.Time t2 = DateTimeUtils.cloneAndNormalizeTime(new java.sql.Time(ts2.getTime())); ValueTime t2 = (ValueTime) ts2.convertTo(Value.TIME);
java.sql.Date d1 = DateTimeUtils.cloneAndNormalizeDate(new java.sql.Date(ts1.getTime())); ValueDate d1 = (ValueDate) ts1.convertTo(Value.DATE);
java.sql.Date d2 = DateTimeUtils.cloneAndNormalizeDate(new java.sql.Date(ts2.getTime())); ValueDate d2 = (ValueDate) ts2.convertTo(Value.DATE);
assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1).getString()); assertEquals("-999-08-07 13:14:15.16", ts1.getString());
assertEquals("-999-08-07", ValueDate.get(d1).getString()); assertEquals("-999-08-07", d1.getString());
assertEquals("13:14:15", ValueTime.get(t1).getString()); assertEquals("13:14:15.16", t1.getString());
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2).getString()); assertEquals("19999-08-07 13:14:15.16", ts2.getString());
assertEquals("19999-08-07", ValueDate.get(d2).getString()); assertEquals("19999-08-07", d2.getString());
assertEquals("13:14:15", ValueTime.get(t2).getString()); assertEquals("13:14:15.16", t2.getString());
Calendar cal = Calendar.getInstance(); java.sql.Timestamp ts1a = DateTimeUtils.convertTimestampToCalendar(ts1.getTimestamp(), Calendar.getInstance());
cal.setTime(t1); java.sql.Timestamp ts2a = DateTimeUtils.convertTimestampToCalendar(ts2.getTimestamp(), Calendar.getInstance());
assertEquals(GregorianCalendar.AD, cal.get(Calendar.ERA));
cal.setTime(t2);
assertEquals(GregorianCalendar.AD, cal.get(Calendar.ERA));
java.sql.Timestamp ts1a = DateTimeUtils.convertTimestampToCalendar(ts1, Calendar.getInstance());
java.sql.Timestamp ts2a = DateTimeUtils.convertTimestampToCalendar(ts2, Calendar.getInstance());
assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1a).getString()); assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1a).getString());
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString()); assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString());
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论