提交 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
<h1>Change Log</h1>
<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>
<h2>Version 1.3.157 (2011-06-25)</h2>
......
......@@ -2465,15 +2465,15 @@ public class Parser {
if (equalsToken("DATE", name)) {
String date = currentValue.getString();
read();
r = ValueExpression.get(ValueDate.get(ValueDate.parseDate(date)));
r = ValueExpression.get(ValueDate.parse(date));
} else if (equalsToken("TIME", name)) {
String time = currentValue.getString();
read();
r = ValueExpression.get(ValueTime.get(ValueTime.parseTime(time)));
r = ValueExpression.get(ValueTime.parse(time));
} else if (equalsToken("TIMESTAMP", name)) {
String timestamp = currentValue.getString();
read();
r = ValueExpression.get(ValueTimestamp.getNoCopy(ValueTimestamp.parseTimestamp(timestamp)));
r = ValueExpression.get(ValueTimestamp.parse(timestamp));
} else if (equalsToken("X", name)) {
read();
byte[] buffer = StringUtils.convertHexToBytes(currentValue.getString());
......@@ -3231,7 +3231,7 @@ public class Parser {
} else if (c >= '0' && c <= '9') {
type = CHAR_VALUE;
} else {
if (c <= ' ' || Character.isWhitespace(c)) {
if (c <= ' ' || Character.isSpaceChar(c)) {
// whitespace
} else if (Character.isJavaIdentifierPart(c)) {
type = CHAR_NAME;
......
......@@ -56,6 +56,11 @@ public class Constants {
*/
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.
*/
......
......@@ -94,7 +94,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
trans.setSSL(ci.isSSL());
trans.init();
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(ci.getOriginalURL());
trans.writeString(ci.getUserName());
......
......@@ -629,52 +629,52 @@ public class Function extends Expression implements FunctionCall {
break;
case DAY_NAME: {
SimpleDateFormat dayName = new SimpleDateFormat("EEEE", Locale.ENGLISH);
result = ValueString.get(dayName.format(v0.getDateNoCopy()));
result = ValueString.get(dayName.format(v0.getDate()));
break;
}
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;
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;
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;
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;
case MINUTE:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestampNoCopy(), Calendar.MINUTE));
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestamp(), Calendar.MINUTE));
break;
case MONTH:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.MONTH));
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.MONTH));
break;
case MONTH_NAME: {
SimpleDateFormat monthName = new SimpleDateFormat("MMMM", Locale.ENGLISH);
result = ValueString.get(monthName.format(v0.getDateNoCopy()));
result = ValueString.get(monthName.format(v0.getDate()));
break;
}
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;
case SECOND:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestampNoCopy(), Calendar.SECOND));
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getTimestamp(), Calendar.SECOND));
break;
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;
case YEAR:
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDateNoCopy(), Calendar.YEAR));
result = ValueInt.get(DateTimeUtils.getDatePart(v0.getDate(), Calendar.YEAR));
break;
case ISO_YEAR:
result = ValueInt.get(DateTimeUtils.getIsoYear(v0.getDateNoCopy()));
result = ValueInt.get(DateTimeUtils.getIsoYear(v0.getDate()));
break;
case ISO_WEEK:
result = ValueInt.get(DateTimeUtils.getIsoWeek(v0.getDateNoCopy()));
result = ValueInt.get(DateTimeUtils.getIsoWeek(v0.getDate()));
break;
case ISO_DAY_OF_WEEK:
result = ValueInt.get(DateTimeUtils.getIsoDayOfWeek(v0.getDateNoCopy()));
result = ValueInt.get(DateTimeUtils.getIsoDayOfWeek(v0.getDate()));
break;
case CURDATE:
case CURRENT_DATE: {
......@@ -693,7 +693,7 @@ public class Function extends Expression implements FunctionCall {
case NOW:
case CURRENT_TIMESTAMP: {
long now = session.getTransactionStart();
ValueTimestamp vt = ValueTimestamp.getNoCopy(new Timestamp(now));
ValueTimestamp vt = ValueTimestamp.get(new Timestamp(now));
if (v0 != null) {
Mode mode = database.getMode();
vt = (ValueTimestamp) vt.convertScale(mode.convertOnlyToSmallerScale, v0.getInt());
......@@ -1038,10 +1038,10 @@ public class Function extends Expression implements FunctionCall {
break;
// date
case DATE_ADD:
result = ValueTimestamp.getNoCopy(dateadd(v0.getString(), v1.getInt(), v2.getTimestampNoCopy()));
result = ValueTimestamp.get(dateadd(v0.getString(), v1.getInt(), v2.getTimestamp()));
break;
case DATE_DIFF:
result = ValueLong.get(datediff(v0.getString(), v1.getTimestampNoCopy(), v2.getTimestampNoCopy()));
result = ValueLong.get(datediff(v0.getString(), v1.getTimestamp(), v2.getTimestamp()));
break;
case EXTRACT: {
int field = getDatePart(v0.getString());
......@@ -1065,7 +1065,7 @@ public class Function extends Expression implements FunctionCall {
String locale = v2 == null ? null : v2 == ValueNull.INSTANCE ? null : v2.getString();
String tz = v3 == null ? null : v3 == ValueNull.INSTANCE ? null : v3.getString();
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;
}
......
......@@ -611,7 +611,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertDateToUniversal(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertDateToUTC(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......@@ -635,7 +635,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTimeToUniversal(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertTimeToUTC(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......
......@@ -71,12 +71,12 @@ public class TcpServerThread implements Runnable {
int minClientVersion = transfer.readInt();
if (minClientVersion < 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) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + 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_9);
}
int maxClientVersion = transfer.readInt();
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_8) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_8;
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_9) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_9;
} else {
clientVersion = minClientVersion;
}
......
......@@ -471,34 +471,38 @@ public class Data {
case Value.TIME:
if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_TIME);
writeVarLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy()));
writeVarLong(((ValueTime) v).getNanos());
} else {
writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy()));
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
}
break;
case Value.DATE: {
if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_DATE);
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy());
writeVarLong(x / MILLIS_PER_MINUTE);
long x = ((ValueDate) v).getDateValue();
writeVarLong(x);
} else {
writeByte((byte) type);
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy());
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDate());
writeVarLong(x / MILLIS_PER_MINUTE);
}
break;
}
case Value.TIMESTAMP: {
Timestamp ts = v.getTimestampNoCopy();
if (SysProperties.STORE_LOCAL_TIME) {
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 {
Timestamp ts = v.getTimestamp();
writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(ts));
}
writeVarInt(ts.getNanos());
}
break;
}
case Value.JAVA_OBJECT: {
......@@ -679,28 +683,26 @@ public class Data {
return ValueDecimal.get(new BigDecimal(b, scale));
}
case LOCAL_DATE: {
long x = readVarLong() * MILLIS_PER_MINUTE;
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(x)));
return ValueDate.get(readVarLong());
}
case Value.DATE: {
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:
// need to normalize the year, month and day
return ValueTime.get(new Time(DateTimeUtils.getTimeGMT(readVarLong())));
return ValueTime.get(readVarLong());
case Value.TIME:
// 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: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readVarLong()));
ts.setNanos(readVarInt());
return ValueTimestamp.getNoCopy(ts);
long dateValue = readVarLong();
long nanos = readVarLong();
return ValueTimestamp.get(dateValue, nanos);
}
case Value.TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readVarLong()));
Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readVarLong()));
ts.setNanos(readVarInt());
return ValueTimestamp.getNoCopy(ts);
return ValueTimestamp.get(ts);
}
case Value.BYTES: {
int len = readVarInt();
......@@ -893,23 +895,25 @@ public class Data {
}
case Value.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: {
if (SysProperties.STORE_LOCAL_TIME) {
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy());
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE);
long dateValue = ((ValueDate) v).getDateValue();
return 1 + getVarLongLen(dateValue);
}
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy());
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDate());
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE);
}
case Value.TIMESTAMP: {
if (SysProperties.STORE_LOCAL_TIME) {
Timestamp ts = v.getTimestampNoCopy();
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(ts)) + getVarIntLen(ts.getNanos());
ValueTimestamp ts = (ValueTimestamp) v;
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());
}
case Value.JAVA_OBJECT: {
......
......@@ -230,7 +230,7 @@ public class FileLock implements Runnable {
transfer.setSocket(socket);
transfer.init();
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(id);
......
......@@ -284,12 +284,15 @@ public class Column {
if (dt.decimal) {
value = ValueInt.get(0).convertTo(type);
} 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) {
// TODO
// need to normalize
value = ValueTime.get(Time.valueOf("0:0:0"));
} 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 {
value = ValueString.get("").convertTo(type);
}
......
......@@ -34,6 +34,22 @@ public class DateTimeUtils {
private static final int DEFAULT_DAY = 1;
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 Calendar cachedCalendar;
......@@ -78,52 +94,6 @@ public class DateTimeUtils {
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.
*
......@@ -131,7 +101,7 @@ public class DateTimeUtils {
* @param source the calendar
* @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)));
}
......@@ -142,7 +112,7 @@ public class DateTimeUtils {
* @param source the calendar
* @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)));
}
......@@ -157,7 +127,7 @@ public class DateTimeUtils {
Timestamp y = new Timestamp(getUniversalTime(source, x));
// fix the nano seconds
y.setNanos(x.getNanos());
return ValueTimestamp.getNoCopy(y);
return ValueTimestamp.get(y);
}
/**
......@@ -235,13 +205,13 @@ public class DateTimeUtils {
* @param type the value type (Value.TIME, TIMESTAMP, or DATE)
* @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;
if (s == null) {
return null;
}
try {
int timeStart = 0;
int timeStart;
TimeZone tz = null;
if (type == Value.TIME) {
timeStart = 0;
......@@ -252,7 +222,6 @@ public class DateTimeUtils {
timeStart = s.indexOf('T') + 1;
}
}
int year = DEFAULT_YEAR, month = DEFAULT_MONTH, day = DEFAULT_DAY;
if (type != Value.TIME) {
if (s.startsWith("+")) {
......@@ -271,7 +240,8 @@ public class DateTimeUtils {
int end = timeStart == 0 ? s.length() : timeStart - 1;
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);
if (type == Value.TIME || (type == Value.TIMESTAMP && s1 >= 0)) {
int s2 = s.indexOf(':', s1 + 1);
......@@ -280,7 +250,6 @@ public class DateTimeUtils {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
DataType.getDataType(type).name, original);
}
if (s.endsWith("Z")) {
s = s.substring(0, s.length() - 1);
tz = TimeZone.getTimeZone("UTC");
......@@ -300,7 +269,6 @@ public class DateTimeUtils {
s = s.substring(0, timeZoneStart).trim();
}
}
hour = Integer.parseInt(s.substring(timeStart, s1));
minute = Integer.parseInt(s.substring(s1 + 1, s2));
if (s3 < 0) {
......@@ -308,12 +276,90 @@ public class DateTimeUtils {
} else {
second = Integer.parseInt(s.substring(s2 + 1, s3));
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 {
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) {
// special case: if the time simply doesn't exist because of
// daylight saving time changes, use the lenient version
......@@ -322,7 +368,7 @@ public class DateTimeUtils {
if (hour < 0 || hour > 23) {
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) {
int maxDay;
if (month == 2) {
......@@ -337,33 +383,16 @@ public class DateTimeUtils {
// using the timezone Brasilia and others,
// for example for 2042-10-12 00:00:00.
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 {
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,
boolean setMillis, int nano) {
int millis) {
Calendar c;
if (tz == null) {
c = getCalendar();
......@@ -371,6 +400,7 @@ public class DateTimeUtils {
c = Calendar.getInstance(tz);
}
synchronized (c) {
c.clear();
c.setLenient(lenient);
if (year <= 0) {
c.set(Calendar.ERA, GregorianCalendar.BC);
......@@ -385,9 +415,7 @@ public class DateTimeUtils {
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, second);
if (setMillis) {
c.set(Calendar.MILLISECOND, nano / 1000000);
}
c.set(Calendar.MILLISECOND, millis);
return c.getTime().getTime();
}
}
......@@ -402,20 +430,18 @@ public class DateTimeUtils {
*/
public static int getDatePart(java.util.Date d, int field) {
Calendar c = getCalendar();
int value;
synchronized (c) {
c.setTime(d);
value = c.get(field);
if (field == Calendar.YEAR) {
return getYear(c);
}
int value = c.get(field);
if (field == Calendar.MONTH) {
value++;
} else if (field == Calendar.YEAR) {
if (c.get(Calendar.ERA) == GregorianCalendar.BC) {
value = 1 - value;
}
return value + 1;
}
return value;
}
}
/**
* Get the year (positive or negative) from a calendar.
......@@ -423,7 +449,7 @@ public class DateTimeUtils {
* @param calendar the calendar
* @return the year
*/
public static int getYear(Calendar calendar) {
private static int getYear(Calendar calendar) {
int year = calendar.get(Calendar.YEAR);
if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
year = 1 - year;
......@@ -431,20 +457,6 @@ public class DateTimeUtils {
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
* without daylight saving time into account.
......@@ -458,28 +470,12 @@ public class DateTimeUtils {
/**
* Convert the number of milliseconds since 1970-01-01 in the local timezone
* to GMT.
*
* @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.
* 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
* @return the number of milliseconds in UTC
*/
public static long getTimeGMTWithoutDst(long millis) {
public static long getTimeUTCWithoutDst(long millis) {
return millis - zoneOffset;
}
......@@ -535,7 +531,7 @@ public class DateTimeUtils {
cal.setTimeInMillis(date.getTime());
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.setMinimalDaysInFirstWeek(4);
int year = cal.get(Calendar.YEAR);
int year = getYear(cal);
int month = cal.get(Calendar.MONTH);
int week = cal.get(Calendar.WEEK_OF_YEAR);
if (month == 0 && week > 51) {
......@@ -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 {
writeByte(v.getByte());
break;
case Value.TIME:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy()));
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(((ValueTime) v).getNanos());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
} else {
writeLong(v.getTimeNoCopy().getTime());
writeLong(v.getTime().getTime());
}
break;
case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy()));
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(((ValueDate) v).getDateValue());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getDate()));
} else {
writeLong(v.getDateNoCopy().getTime());
writeLong(v.getDate().getTime());
}
break;
case Value.TIMESTAMP: {
Timestamp ts = v.getTimestampNoCopy();
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
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));
writeInt(ts.getNanos());
} else {
Timestamp ts = v.getTimestamp();
writeLong(ts.getTime());
}
writeInt(ts.getNanos());
}
break;
}
case Value.DECIMAL:
......@@ -460,24 +470,30 @@ public class Transfer {
case Value.BYTE:
return ValueByte.get(readByte());
case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMTWithoutDst(readLong())));
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
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:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueTime.getNoCopy(new Time(DateTimeUtils.getTimeGMTWithoutDst(readLong())));
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
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: {
if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readLong()));
if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
return ValueTimestamp.get(readLong(), readLong());
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readLong()));
ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts);
return ValueTimestamp.get(ts);
}
Timestamp ts = new Timestamp(readLong());
ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts);
return ValueTimestamp.get(ts);
}
case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(readString()));
......
......@@ -24,6 +24,7 @@ import org.h2.message.DbException;
import org.h2.store.DataHandler;
import org.h2.store.LobStorage;
import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
......@@ -370,26 +371,14 @@ public abstract class Value {
return ((ValueDate) convertTo(Value.DATE)).getDate();
}
public Date getDateNoCopy() {
return ((ValueDate) convertTo(Value.DATE)).getDateNoCopy();
}
public Time getTime() {
return ((ValueTime) convertTo(Value.TIME)).getTime();
}
public Time getTimeNoCopy() {
return ((ValueTime) convertTo(Value.TIME)).getTimeNoCopy();
}
public Timestamp getTimestamp() {
return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestamp();
}
public Timestamp getTimestampNoCopy() {
return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestampNoCopy();
}
public byte[] getBytes() {
return ((ValueBytes) convertTo(Value.BYTES)).getBytes();
}
......@@ -693,9 +682,11 @@ public abstract class Value {
case DATE: {
switch (getType()) {
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:
return ValueDate.get(new Date(getTimestampNoCopy().getTime()));
return ValueDate.get(((ValueTimestamp) this).getDateValue());
}
break;
}
......@@ -703,19 +694,22 @@ public abstract class Value {
switch (getType()) {
case DATE:
// 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:
// need to normalize the year, month and day
return ValueTime.get(new Time(getTimestampNoCopy().getTime()));
return ValueTime.get(new Time(getTimestamp().getTime()));
}
break;
}
case TIMESTAMP: {
switch (getType()) {
case TIME:
return ValueTimestamp.getNoCopy(new Timestamp(getTimeNoCopy().getTime()));
// TODO
return ValueTimestamp.get(new Timestamp(getTime().getTime()));
case DATE:
return ValueTimestamp.getNoCopy(new Timestamp(getDateNoCopy().getTime()));
// TODO
return ValueTimestamp.get(new Timestamp(getDate().getTime()));
}
break;
}
......@@ -815,11 +809,11 @@ public abstract class Value {
case DECIMAL:
return ValueDecimal.get(new BigDecimal(s.trim()));
case TIME:
return ValueTime.getNoCopy(ValueTime.parseTime(s.trim()));
return ValueTime.parse(s.trim());
case DATE:
return ValueDate.getNoCopy(ValueDate.parseDate(s.trim()));
return ValueDate.parse(s.trim());
case TIMESTAMP:
return ValueTimestamp.getNoCopy(ValueTimestamp.parseTimestamp(s.trim()));
return ValueTimestamp.parse(s.trim());
case BYTES:
return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim()));
case JAVA_OBJECT:
......
......@@ -9,8 +9,8 @@ package org.h2.value;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Calendar;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
/**
* Implementation of the DATE data type.
......@@ -28,29 +28,29 @@ public class ValueDate extends Value {
*/
static final int DISPLAY_SIZE = 10;
private final Date value;
private final long dateValue;
private ValueDate(Date value) {
this.value = value;
private ValueDate(long dateValue) {
this.dateValue = dateValue;
}
/**
* Parse a string to a java.sql.Date object.
* Parse a string to a ValueDate.
*
* @param s the string to parse
* @return the date
*/
public static Date parseDate(String s) {
return (Date) DateTimeUtils.parseDateTime(s, Value.DATE);
public static ValueDate parse(String s) {
Value x = DateTimeUtils.parse(s, Value.DATE);
return (ValueDate) Value.cache(x);
}
public Date getDate() {
// this class is mutable - must copy the object
return (Date) value.clone();
return DateTimeUtils.convertDateValueToDate(dateValue);
}
public Date getDateNoCopy() {
return value;
public long getDateValue() {
return dateValue;
}
public String getSQL() {
......@@ -62,22 +62,13 @@ public class ValueDate extends Value {
}
protected int compareSecure(Value o, CompareMode mode) {
ValueDate v = (ValueDate) o;
return Integer.signum(value.compareTo(v.value));
return MathUtils.compareLong(dateValue, ((ValueDate) o).dateValue);
}
public String getString() {
String s = value.toString();
long time = value.getTime();
// special case: java.sql.Date doesn't format
// 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;
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendDate(buff, dateValue);
return buff.toString();
}
public long getPrecision() {
......@@ -85,39 +76,36 @@ public class ValueDate extends Value {
}
public int hashCode() {
return value.hashCode();
return (int) (dateValue ^ (dateValue >>> 32));
}
public Object getObject() {
// this class is mutable - must copy the object
return getDate();
}
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.
* Clone the date.
*
* @param date the date
* @return the value
*/
public static ValueDate get(Date date) {
date = DateTimeUtils.cloneAndNormalizeDate(date);
return getNoCopy(date);
long x = DateTimeUtils.dateValueFromDate(date.getTime());
return get(x);
}
/**
* 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
*/
public static ValueDate getNoCopy(Date date) {
return (ValueDate) Value.cache(new ValueDate(date));
public static ValueDate get(long dateValue) {
return (ValueDate) Value.cache(new ValueDate(dateValue));
}
public int getDisplaySize() {
......@@ -125,7 +113,10 @@ public class ValueDate extends Value {
}
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;
import java.sql.SQLException;
import java.sql.Time;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
/**
* Implementation of the TIME data type.
......@@ -27,29 +28,28 @@ public class ValueTime extends Value {
*/
static final int DISPLAY_SIZE = 8;
private final Time value;
private final long nanos;
private ValueTime(Time value) {
this.value = value;
private ValueTime(long nanos) {
this.nanos = nanos;
}
/**
* Parse a string to a java.sql.Time object.
* Parse a string to a ValueTime.
*
* @param s the string to parse
* @return the time
*/
public static Time parseTime(String s) {
return (Time) DateTimeUtils.parseDateTime(s, Value.TIME);
public static ValueTime parse(String s) {
return new ValueTime(DateTimeUtils.parseTime(s));
}
public Time getTime() {
// this class is mutable - must copy the object
return (Time) value.clone();
return DateTimeUtils.convertNanoToTime(nanos);
}
public Time getTimeNoCopy() {
return value;
public long getNanos() {
return nanos;
}
public String getSQL() {
......@@ -61,12 +61,13 @@ public class ValueTime extends Value {
}
protected int compareSecure(Value o, CompareMode mode) {
ValueTime v = (ValueTime) o;
return Integer.signum(value.compareTo(v.value));
return MathUtils.compareLong(nanos, ((ValueTime) o).nanos);
}
public String getString() {
return value.toString();
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendTime(buff, nanos, false);
return buff.toString();
}
public long getPrecision() {
......@@ -74,7 +75,7 @@ public class ValueTime extends Value {
}
public int hashCode() {
return value.hashCode();
return (int) (nanos ^ (nanos >>> 32));
}
public Object getObject() {
......@@ -82,30 +83,28 @@ public class ValueTime extends Value {
}
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.
* Clone the time.
*
* @param time the time
* @return the value
*/
public static ValueTime get(Time time) {
time = DateTimeUtils.cloneAndNormalizeTime(time);
return getNoCopy(time);
long x = DateTimeUtils.nanosFromDate(time.getTime());
return get(x);
}
/**
* Get or create a time value for the given time.
* Do not clone the time.
* Get or create a time value.
*
* @param time the time
* @param nanos the nanoseconds
* @return the value
*/
public static ValueTime getNoCopy(Time time) {
return (ValueTime) Value.cache(new ValueTime(time));
public static ValueTime get(long nanos) {
return (ValueTime) Value.cache(new ValueTime(nanos));
}
public int getDisplaySize() {
......@@ -113,31 +112,28 @@ public class ValueTime extends Value {
}
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) {
Time t = new Time(value.getTime() + v.getTime().getTime());
return ValueTime.get(t);
ValueTime t = (ValueTime) v.convertTo(Value.TIME);
return ValueTime.get(nanos + t.getNanos());
}
public Value subtract(Value v) {
Time t = new Time(value.getTime() - v.getTime().getTime());
return ValueTime.get(t);
ValueTime t = (ValueTime) v.convertTo(Value.TIME);
return ValueTime.get(nanos - t.getNanos());
}
public Value multiply(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime();
long t = value.getTime() - zeroTime;
t = (long) (t * v.getDouble()) + zeroTime;
return ValueTime.get(new Time(t));
return ValueTime.get((long) (nanos * v.getDouble()));
}
public Value divide(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime();
long t = value.getTime() - zeroTime;
t = (long) (t / v.getDouble()) + zeroTime;
return ValueTime.get(new Time(t));
return ValueTime.get((long) (nanos / v.getDouble()));
}
}
......@@ -9,9 +9,7 @@ package org.h2.value;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
......@@ -37,33 +35,24 @@ public class ValueTimestamp extends Value {
*/
static final int DEFAULT_SCALE = 10;
/**
* This is used to find out if a date is possibly BC. Because of time zone
* 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 final long dateValue;
private final long nanos;
private ValueTimestamp(Timestamp value) {
this.value = value;
private ValueTimestamp(long dateValue, long nanos) {
this.dateValue = dateValue;
this.nanos = nanos;
}
public Timestamp getTimestamp() {
return (Timestamp) value.clone();
return DateTimeUtils.convertDateValueToTimestamp(dateValue, nanos);
}
public long getDateValue() {
return dateValue;
}
public Timestamp getTimestampNoCopy() {
return value;
public long getNanos() {
return nanos;
}
public String getSQL() {
......@@ -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
* @return the timestamp
* @return the date
*/
public static Timestamp parseTimestamp(String s) {
return (Timestamp) DateTimeUtils.parseDateTime(s, Value.TIMESTAMP);
public static ValueTimestamp parse(String s) {
Value x = DateTimeUtils.parse(s, Value.TIMESTAMP);
return (ValueTimestamp) Value.cache(x);
}
public int getType() {
......@@ -85,22 +86,21 @@ public class ValueTimestamp extends Value {
}
protected int compareSecure(Value o, CompareMode mode) {
ValueTimestamp v = (ValueTimestamp) o;
return Integer.signum(value.compareTo(v.value));
ValueTimestamp t = (ValueTimestamp) o;
int c = MathUtils.compareLong(dateValue, t.dateValue);
if (c != 0) {
return c;
}
return MathUtils.compareLong(nanos, t.nanos);
}
public String getString() {
String s = value.toString();
long time = value.getTime();
// special case: java.sql.Timestamp doesn't format
// years below year 1 (BC) correctly
if (time < YEAR_ONE) {
int year = DateTimeUtils.getDatePart(value, Calendar.YEAR);
if (year < 1) {
s = year + s.substring(s.indexOf('-'));
}
}
return s;
// TODO verify display size
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendDate(buff, dateValue);
buff.append(' ');
DateTimeUtils.appendTime(buff, nanos, true);
return buff.toString();
}
public long getPrecision() {
......@@ -112,63 +112,48 @@ public class ValueTimestamp extends Value {
}
public int hashCode() {
return value.hashCode();
return (int) (dateValue ^ (dateValue >>> 32) ^ nanos ^ (nanos >>> 32));
}
public Object getObject() {
// this class is mutable - must copy the object
return getTimestamp();
}
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.
* Clone the timestamp.
*
* @param timestamp the timestamp
* @return the value
*/
public static ValueTimestamp get(Timestamp timestamp) {
timestamp = (Timestamp) timestamp.clone();
return getNoCopy(timestamp);
}
/**
* 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));
long ms = timestamp.getTime();
long dateValue = DateTimeUtils.dateValueFromDate(ms);
long nanos = DateTimeUtils.nanosFromDate(ms);
nanos += timestamp.getNanos() % 1000000;
return get(dateValue, nanos);
}
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale == DEFAULT_SCALE) {
return this;
}
if (targetScale < 0 || targetScale > DEFAULT_SCALE) {
// TODO convertScale for Timestamps: may throw an exception?
throw DbException.getInvalidValueException("scale", targetScale);
}
int nanos = value.getNanos();
BigDecimal bd = BigDecimal.valueOf(nanos);
long n = nanos;
BigDecimal bd = BigDecimal.valueOf(n);
bd = bd.movePointLeft(9);
bd = MathUtils.setScale(bd, targetScale);
bd = bd.movePointRight(9);
int n2 = bd.intValue();
if (n2 == nanos) {
long n2 = bd.longValue();
if (n2 == n) {
return this;
}
long t = value.getTime();
while (n2 >= 1000000000) {
t += 1000;
n2 -= 1000000000;
}
Timestamp t2 = new Timestamp(t);
t2.setNanos(n2);
return ValueTimestamp.getNoCopy(t2);
return get(dateValue, n2);
}
public int getDisplaySize() {
......@@ -176,21 +161,29 @@ public class ValueTimestamp extends Value {
}
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) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime();
long t = value.getTime() - zeroTime + v.getTimestamp().getTime();
Timestamp ts = new Timestamp(t);
return ValueTimestamp.get(ts);
// TODO test sum of timestamps, dates, times
ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP);
long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue);
long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue);
return DateTimeUtils.normalize(d1 + d2, nanos + t.nanos);
}
public Value subtract(Value v) {
long zeroTime = ValueTime.get(new Time(0)).getDate().getTime();
long t = value.getTime() + zeroTime - v.getTimestamp().getTime();
Timestamp ts = new Timestamp(t);
return ValueTimestamp.get(ts);
// TODO test sum of timestamps, dates, times
ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP);
long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue);
long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue);
return DateTimeUtils.normalize(d1 - d2, nanos - t.nanos);
}
}
......@@ -339,7 +339,8 @@ java org.h2.test.TestAll timer
System.setProperty("h2.delayWrongPasswordMin", "0");
System.setProperty("h2.delayWrongPasswordMax", "0");
// System.setProperty("h2.storeLocalTime", "true");
int todoTestBoth;
// System.setProperty("h2.storeLocalTime", "true");
// speedup
// System.setProperty("h2.syncMethod", "");
......
......@@ -156,6 +156,7 @@ public class TestCompatibility extends TestBase {
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')");
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(FROM_UNIXTIME(1196418619))");
assertResult("2007 November", stat, "SELECT FROM_UNIXTIME(1196300000, '%Y %M')");
......
......@@ -123,7 +123,7 @@ public class TestDateStorage extends TestBase {
}
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);
}
......
......@@ -30,6 +30,7 @@ public class TestLinkedTable extends TestBase {
* @param a ignored
*/
public static void main(String... a) throws Exception {
// System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test();
}
......
......@@ -59,7 +59,7 @@ public class TestScriptSimple extends TestBase {
while (rs.next()) {
String expected = reader.readStatement().trim();
String got = "> " + rs.getString(1);
assertEquals(expected, got);
assertEquals(sql, expected, got);
}
} else {
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');
INSERT INTO TEST VALUES(1, '01:02:03','2001-02-03','2001-02-29 0:0:0');
> 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
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,
CAST(XD AS TIME) D2T, CAST(XTS AS TIME) TS2T,
CAST(XT AS TIMESTAMP) D2TS, CAST(XD AS TIMESTAMP) D2TS FROM TEST;
> 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
> 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
> rows: 4
......@@ -9331,10 +9331,10 @@ select length(curdate()) c1, length(current_date()) c2, substring(curdate(), 5,
> 10 10 -
> 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
> -- -- --
> 8 8 :
> ---- ---- --
> TRUE TRUE :
> 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;
......
......@@ -38,15 +38,105 @@ public class TestDate extends TestBase {
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.storeLocalTime", "true");
// System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test();
}
public void test() throws SQLException {
testValidDate();
testValidTime();
testCalculateLocalMillis();
testTimeOperationsAcrossTimeZones();
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() {
if (!SysProperties.STORE_LOCAL_TIME) {
return;
......@@ -124,25 +214,20 @@ public class TestDate extends TestBase {
}
private void testDateTimeUtils() {
java.sql.Timestamp ts1 = (Timestamp) DateTimeUtils.parseDateTime("-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);
java.sql.Time t1 = DateTimeUtils.cloneAndNormalizeTime(new java.sql.Time(ts1.getTime()));
java.sql.Time t2 = DateTimeUtils.cloneAndNormalizeTime(new java.sql.Time(ts2.getTime()));
java.sql.Date d1 = DateTimeUtils.cloneAndNormalizeDate(new java.sql.Date(ts1.getTime()));
java.sql.Date d2 = DateTimeUtils.cloneAndNormalizeDate(new java.sql.Date(ts2.getTime()));
assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1).getString());
assertEquals("-999-08-07", ValueDate.get(d1).getString());
assertEquals("13:14:15", ValueTime.get(t1).getString());
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2).getString());
assertEquals("19999-08-07", ValueDate.get(d2).getString());
assertEquals("13:14:15", ValueTime.get(t2).getString());
Calendar cal = Calendar.getInstance();
cal.setTime(t1);
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());
ValueTimestamp ts1 = (ValueTimestamp) DateTimeUtils.parse("-999-08-07 13:14:15.16", Value.TIMESTAMP);
ValueTimestamp ts2 = (ValueTimestamp) DateTimeUtils.parse("19999-08-07 13:14:15.16", Value.TIMESTAMP);
ValueTime t1 = (ValueTime) ts1.convertTo(Value.TIME);
ValueTime t2 = (ValueTime) ts2.convertTo(Value.TIME);
ValueDate d1 = (ValueDate) ts1.convertTo(Value.DATE);
ValueDate d2 = (ValueDate) ts2.convertTo(Value.DATE);
assertEquals("-999-08-07 13:14:15.16", ts1.getString());
assertEquals("-999-08-07", d1.getString());
assertEquals("13:14:15.16", t1.getString());
assertEquals("19999-08-07 13:14:15.16", ts2.getString());
assertEquals("19999-08-07", d2.getString());
assertEquals("13:14:15.16", t2.getString());
java.sql.Timestamp ts1a = DateTimeUtils.convertTimestampToCalendar(ts1.getTimestamp(), Calendar.getInstance());
java.sql.Timestamp ts2a = DateTimeUtils.convertTimestampToCalendar(ts2.getTimestamp(), Calendar.getInstance());
assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1a).getString());
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString());
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论