提交 8224ba44 authored 作者: Thomas Mueller's avatar Thomas Mueller

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

上级 69e5bb70
......@@ -659,7 +659,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTimestampToUniversal(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertTimestampToUTC(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......
......@@ -18,7 +18,6 @@ import java.util.Locale;
import java.util.TimeZone;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueTime;
......@@ -29,26 +28,19 @@ import org.h2.value.ValueTimestamp;
*/
public class DateTimeUtils {
private static final int DEFAULT_YEAR = 1970;
private static final int DEFAULT_MONTH = 1;
private static final int DEFAULT_DAY = 1;
private static final int DEFAULT_HOUR = 0;
public 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 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
};
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 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;
......@@ -77,6 +69,28 @@ public class DateTimeUtils {
return cachedCalendar;
}
/**
* Convert the date to the specified time zone.
*
* @param x the date
* @param calendar the calendar
* @return the date using the correct time zone
*/
public static Date convertDateToCalendar(Date x, Calendar calendar) {
return x == null ? null : new Date(convertToLocal(x, calendar));
}
/**
* Convert the time to the specified time zone.
*
* @param x the time
* @param calendar the calendar
* @return the time using the correct time zone
*/
public static Time convertTimeToCalendar(Time x, Calendar calendar) {
return x == null ? null : new Time(convertToLocal(x, calendar));
}
/**
* Convert the timestamp to the specified time zone.
*
......@@ -86,7 +100,7 @@ public class DateTimeUtils {
*/
public static Timestamp convertTimestampToCalendar(Timestamp x, Calendar calendar) {
if (x != null) {
Timestamp y = new Timestamp(getLocalTime(x, calendar));
Timestamp y = new Timestamp(convertToLocal(x, calendar));
// fix the nano seconds
y.setNanos(x.getNanos());
x = y;
......@@ -102,7 +116,7 @@ public class DateTimeUtils {
* @return the date in UTC
*/
public static Value convertDateToUTC(Date x, Calendar source) {
return ValueDate.get(new Date(getUniversalTime(source, x)));
return ValueDate.get(new Date(convertToUTC(x, source)));
}
/**
......@@ -113,7 +127,7 @@ public class DateTimeUtils {
* @return the time in UTC
*/
public static Value convertTimeToUTC(Time x, Calendar source) {
return ValueTime.get(new Time(getUniversalTime(source, x)));
return ValueTime.get(new Time(convertToUTC(x, source)));
}
/**
......@@ -123,8 +137,8 @@ public class DateTimeUtils {
* @param source the calendar
* @return the timestamp in UTC
*/
public static Value convertTimestampToUniversal(Timestamp x, Calendar source) {
Timestamp y = new Timestamp(getUniversalTime(source, x));
public static Value convertTimestampToUTC(Timestamp x, Calendar source) {
Timestamp y = new Timestamp(convertToUTC(x, source));
// fix the nano seconds
y.setNanos(x.getNanos());
return ValueTimestamp.get(y);
......@@ -133,11 +147,11 @@ public class DateTimeUtils {
/**
* Convert the date value to UTC using the given calendar.
*
* @param source the source calendar
* @param x the date
* @param source the source calendar
* @return the UTC number of milliseconds.
*/
private static long getUniversalTime(Calendar source, java.util.Date x) {
private static long convertToUTC(java.util.Date x, Calendar source) {
if (source == null) {
throw DbException.getInvalidValueException("calendar", null);
}
......@@ -150,7 +164,7 @@ public class DateTimeUtils {
}
}
private static long getLocalTime(java.util.Date x, Calendar target) {
public static long convertToLocal(java.util.Date x, Calendar target) {
if (target == null) {
throw DbException.getInvalidValueException("calendar", null);
}
......@@ -174,177 +188,67 @@ public class DateTimeUtils {
to.set(Calendar.MILLISECOND, from.get(Calendar.MILLISECOND));
}
/**
* Convert the date to the specified time zone.
*
* @param x the date
* @param calendar the calendar
* @return the date using the correct time zone
*/
public static Date convertDateToCalendar(Date x, Calendar calendar) {
return x == null ? null : new Date(getLocalTime(x, calendar));
}
/**
* Convert the time to the specified time zone.
*
* @param x the time
* @param calendar the calendar
* @return the time using the correct time zone
*/
public static Time convertTimeToCalendar(Time x, Calendar calendar) {
return x == null ? null : new Time(getLocalTime(x, calendar));
}
/**
* Parse a date, time or timestamp value. This method supports the format
* +/-year-month-day hour:minute:seconds.fractional and an optional timezone
* part.
*
* @param original the original string
* @param type the value type (Value.TIME, TIMESTAMP, or DATE)
* @return the date object
*/
public static Value parse(String original, int type) {
String s = original;
if (s == null) {
return null;
public static long parseDateValue(String s, int start, int end) {
if (s.charAt(start) == '+') {
// +year
start++;
}
try {
int timeStart;
TimeZone tz = null;
if (type == Value.TIME) {
timeStart = 0;
} else {
timeStart = s.indexOf(' ') + 1;
if (timeStart <= 0) {
// ISO 8601 compatibility
timeStart = s.indexOf('T') + 1;
}
}
int year = DEFAULT_YEAR, month = DEFAULT_MONTH, day = DEFAULT_DAY;
if (type != Value.TIME) {
if (s.startsWith("+")) {
// +year
s = s.substring(1);
}
// start at position 1 to support -year
int s1 = s.indexOf('-', 1);
int s2 = s.indexOf('-', s1 + 1);
if (s1 <= 0 || s2 <= s1) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
DataType.getDataType(type).name, s);
}
year = Integer.parseInt(s.substring(0, s1));
month = Integer.parseInt(s.substring(s1 + 1, s2));
int end = timeStart == 0 ? s.length() : timeStart - 1;
day = Integer.parseInt(s.substring(s2 + 1, end));
}
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);
int s3 = s.indexOf('.', s2 + 1);
if (s1 <= 0 || s2 <= s1) {
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");
} else {
int timeZoneStart = s.indexOf('+', s2 + 1);
if (timeZoneStart < 0) {
timeZoneStart = s.indexOf('-', s2 + 1);
}
if (timeZoneStart >= 0) {
String tzName = "GMT" + s.substring(timeZoneStart);
tz = TimeZone.getTimeZone(tzName);
if (!tz.getID().startsWith(tzName)) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
DataType.getDataType(type).name,
original + " (" + tz.getID() + " <>" + tzName + ")");
}
s = s.substring(0, timeZoneStart).trim();
}
}
hour = Integer.parseInt(s.substring(timeStart, 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);
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);
// start at position 1 to support "-year"
int s1 = s.indexOf('-', start + 1);
int s2 = s.indexOf('-', s1 + 1);
if (s1 <= 0 || s2 <= s1) {
throw new IllegalArgumentException(s);
}
int year = Integer.parseInt(s.substring(start, s1));
int month = Integer.parseInt(s.substring(s1 + 1, s2));
int day = Integer.parseInt(s.substring(s2 + 1, end));
if (!isValidDate(year, month, day)) {
throw new IllegalArgumentException(year + "-" + month + "-" + day);
}
return dateValue(year, month, day);
}
public static long parseTime(String s) {
int hour = 0, minute = 0, second = 0, nanos = 0;
int s1 = s.indexOf(':');
public static long parseTimeNanos(String s, int start, int end, boolean timeOfDay) {
int hour = 0, minute = 0, second = 0;
long nanos = 0;
int s1 = s.indexOf(':', start);
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);
throw new IllegalArgumentException(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);
boolean negative;
hour = Integer.parseInt(s.substring(start, s1));
if (hour < 0) {
if (timeOfDay) {
throw new IllegalArgumentException(s);
}
} catch (NumberFormatException e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
"TIME", s);
negative = true;
hour = -hour;
} else {
negative = false;
}
if (minute < 0 || minute >= 60 || second < 0 || second >= 60) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
"TIME", s);
minute = Integer.parseInt(s.substring(s1 + 1, s2));
if (s3 < 0) {
second = Integer.parseInt(s.substring(s2 + 1, end));
} else {
second = Integer.parseInt(s.substring(s2 + 1, s3));
String n = (s.substring(s3 + 1, end) + "000000000").substring(0, 9);
nanos = Integer.parseInt(n);
}
return ((((hour * 60L) + minute) * 60) + second) * 1000000000 + nanos;
if (hour >= 2000000 || minute < 0 || minute >= 60 || second < 0 || second >= 60) {
throw new IllegalArgumentException(s);
}
if (timeOfDay && hour >= 24) {
throw new IllegalArgumentException(s);
}
nanos += ((((hour * 60L) + minute) * 60) + second) * 1000000000;
return negative ? -nanos : nanos;
}
/**
* Calculate the milliseconds for the given date and time in the specified timezone.
* 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)
......@@ -357,7 +261,6 @@ public class DateTimeUtils {
* @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 {
return getTimeTry(false, tz, year, month, day, hour, minute, second, millis);
} catch (IllegalArgumentException e) {
......@@ -506,7 +409,6 @@ public class DateTimeUtils {
* the December 28th always belongs to the last week.
*
* @author Robert Rathsack
*
* @param date the date object which week of year should be calculated
* @return the week of the year
*/
......@@ -631,20 +533,6 @@ public class DateTimeUtils {
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),
......@@ -685,11 +573,11 @@ public class DateTimeUtils {
return new Time(ms);
}
private static int yearFromDateValue(long x) {
public static int yearFromDateValue(long x) {
return (int) (x >>> SHIFT_YEAR);
}
private static int monthFromDateValue(long x) {
public static int monthFromDateValue(long x) {
return (int) (x >>> SHIFT_MONTH) & 15;
}
......@@ -727,7 +615,7 @@ public class DateTimeUtils {
}
}
public static ValueTimestamp normalize(long absoluteDay, long nanos) {
public static ValueTimestamp normalizeTimestamp(long absoluteDay, long nanos) {
if (nanos > NANOS_PER_DAY || nanos < 0) {
long d;
if (nanos > NANOS_PER_DAY) {
......@@ -749,7 +637,7 @@ public class DateTimeUtils {
y--;
m += 12;
}
long a = ((y * 2922L) >>> 3) + DAYS_OFFSET[m - 3] + d - 719484;
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;
......@@ -794,55 +682,4 @@ public class DateTimeUtils {
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");
}
}
}
......@@ -697,19 +697,16 @@ public abstract class Value {
// 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(getTimestamp().getTime()));
return ValueTime.get(((ValueTimestamp) this).getNanos());
}
break;
}
case TIMESTAMP: {
switch (getType()) {
case TIME:
// TODO
return ValueTimestamp.get(new Timestamp(getTime().getTime()));
return DateTimeUtils.normalizeTimestamp(0, ((ValueTime) this).getNanos());
case DATE:
// TODO
return ValueTimestamp.get(new Timestamp(getDate().getTime()));
return ValueTimestamp.get(((ValueDate) this).getDateValue(), 0);
}
break;
}
......
......@@ -9,8 +9,11 @@ package org.h2.value;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/**
* Implementation of the DATE data type.
......@@ -41,8 +44,12 @@ public class ValueDate extends Value {
* @return the date
*/
public static ValueDate parse(String s) {
Value x = DateTimeUtils.parse(s, Value.DATE);
return (ValueDate) Value.cache(x);
try {
return get(DateTimeUtils.parseDateValue(s, 0, s.length()));
} catch (Exception e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, "DATE", s);
}
}
public Date getDate() {
......@@ -67,7 +74,7 @@ public class ValueDate extends Value {
public String getString() {
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendDate(buff, dateValue);
appendDate(buff, dateValue);
return buff.toString();
}
......@@ -94,8 +101,7 @@ public class ValueDate extends Value {
* @return the value
*/
public static ValueDate get(Date date) {
long x = DateTimeUtils.dateValueFromDate(date.getTime());
return get(x);
return get(DateTimeUtils.dateValueFromDate(date.getTime()));
}
/**
......@@ -119,4 +125,19 @@ public class ValueDate extends Value {
return other instanceof ValueDate && dateValue == (((ValueDate) other).dateValue);
}
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);
}
}
......@@ -9,8 +9,11 @@ package org.h2.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/**
* Implementation of the TIME data type.
......@@ -40,8 +43,14 @@ public class ValueTime extends Value {
* @param s the string to parse
* @return the time
*/
public static ValueTime parse(String s) {
return new ValueTime(DateTimeUtils.parseTime(s));
try {
return get(DateTimeUtils.parseTimeNanos(s, 0, s.length(), false));
} catch (Exception e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, "TIME", s);
}
}
public Time getTime() {
......@@ -66,7 +75,7 @@ public class ValueTime extends Value {
public String getString() {
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendTime(buff, nanos, false);
appendTime(buff, nanos, false);
return buff.toString();
}
......@@ -93,8 +102,7 @@ public class ValueTime extends Value {
* @return the value
*/
public static ValueTime get(Time time) {
long x = DateTimeUtils.nanosFromDate(time.getTime());
return get(x);
return get(DateTimeUtils.nanosFromDate(time.getTime()));
}
/**
......@@ -136,4 +144,46 @@ public class ValueTime extends Value {
return ValueTime.get((long) (nanos / v.getDouble()));
}
public int getSignum() {
return Long.signum(nanos);
}
public Value negate() {
return ValueTime.get(-nanos);
}
static void appendTime(StringBuilder buff, long n, boolean alwaysAddMillis) {
if (n < 0) {
buff.append('-');
n = -n;
}
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;
StringUtils.appendZeroPadded(buff, 2, h);
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, m);
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, s);
if (alwaysAddMillis || 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);
}
}
}
}
......@@ -7,9 +7,13 @@
package org.h2.value;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.TimeZone;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
......@@ -71,14 +75,85 @@ public class ValueTimestamp extends Value {
}
/**
* Parse a string to a ValueTimestamp.
* Parse a string to a ValueTimestamp. This method supports the format
* +/-year-month-day hour:minute:seconds.fractional and an optional timezone
* part.
*
* @param s the string to parse
* @return the date
*/
public static ValueTimestamp parse(String s) {
Value x = DateTimeUtils.parse(s, Value.TIMESTAMP);
return (ValueTimestamp) Value.cache(x);
try {
return parseTry(s);
} catch (Exception e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, "TIMESTAMP", s);
}
}
private static ValueTimestamp parseTry(String s) {
int dateEnd = s.indexOf(' ');
if (dateEnd <= 0) {
// ISO 8601 compatibility
dateEnd = s.indexOf('T');
}
int timeStart;
if (dateEnd < 0) {
dateEnd = s.length();
timeStart = -1;
} else {
timeStart = dateEnd + 1;
}
long dateValue = DateTimeUtils.parseDateValue(s, 0, dateEnd);
long nanos;
if (timeStart < 0) {
nanos = 0;
} else {
int timeEnd = s.length();
TimeZone tz = null;
if (s.endsWith("Z")) {
tz = TimeZone.getTimeZone("UTC");
timeEnd--;
while (s.charAt(timeEnd - 1) == ' ') {
timeEnd--;
}
} else {
int timeZoneStart = s.indexOf('+', dateEnd);
if (timeZoneStart < 0) {
timeZoneStart = s.indexOf('-', dateEnd);
}
if (timeZoneStart >= 0) {
String tzName = "GMT" + s.substring(timeZoneStart);
tz = TimeZone.getTimeZone(tzName);
if (!tz.getID().startsWith(tzName)) {
throw new IllegalArgumentException(tzName);
}
timeEnd = timeZoneStart;
while (s.charAt(timeEnd - 1) == ' ') {
timeEnd--;
}
}
}
nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true);
if (tz != null) {
int year = DateTimeUtils.yearFromDateValue(dateValue);
int month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
long ms = nanos / 1000000;
nanos -= ms * 1000000;
long second = ms / 1000;
ms -= second * 1000;
int minute = (int) (second / 60);
second -= minute * 60;
int hour = minute / 60;
minute -= hour * 60;
long millis = DateTimeUtils.getMillis(tz, year, month, day, hour, minute, (int) second, (int) ms);
ms = DateTimeUtils.convertToLocal(new Date(millis), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
dateValue = DateTimeUtils.dateValueFromDate(ms);
nanos += (ms - DateTimeUtils.absoluteDayFromDateValue(dateValue) * DateTimeUtils.MILLIS_PER_DAY) * 1000000;
}
}
return ValueTimestamp.get(dateValue, nanos);
}
public int getType() {
......@@ -97,9 +172,9 @@ public class ValueTimestamp extends Value {
public String getString() {
// TODO verify display size
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
DateTimeUtils.appendDate(buff, dateValue);
ValueDate.appendDate(buff, dateValue);
buff.append(' ');
DateTimeUtils.appendTime(buff, nanos, true);
ValueTime.appendTime(buff, nanos, true);
return buff.toString();
}
......@@ -138,10 +213,10 @@ public class ValueTimestamp extends Value {
}
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale == DEFAULT_SCALE) {
if (targetScale >= DEFAULT_SCALE) {
return this;
}
if (targetScale < 0 || targetScale > DEFAULT_SCALE) {
if (targetScale < 0) {
throw DbException.getInvalidValueException("scale", targetScale);
}
long n = nanos;
......@@ -175,7 +250,7 @@ public class ValueTimestamp extends Value {
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);
return DateTimeUtils.normalizeTimestamp(d1 + d2, nanos + t.nanos);
}
public Value subtract(Value v) {
......@@ -183,7 +258,7 @@ public class ValueTimestamp extends Value {
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);
return DateTimeUtils.normalizeTimestamp(d1 - d2, nanos - t.nanos);
}
}
......@@ -18,7 +18,7 @@ import org.h2.constant.SysProperties;
import org.h2.test.TestBase;
import org.h2.test.unit.TestDate;
import org.h2.util.DateTimeUtils;
import org.h2.value.Value;
import org.h2.value.ValueTimestamp;
/**
* Tests the date transfer and storage.
......@@ -111,7 +111,7 @@ public class TestDateStorage extends TestBase {
}
private static void testCurrentTimeZone() {
for (int year = 1970; year < 2050; year += 3) {
for (int year = 1890; year < 2050; year += 3) {
for (int month = 1; month <= 12; month++) {
for (int day = 1; day < 29; day++) {
for (int hour = 0; hour < 24; hour++) {
......@@ -123,8 +123,7 @@ public class TestDateStorage extends TestBase {
}
private static void test(int year, int month, int day, int hour) {
DateTimeUtils.parse(year + "-" + month + "-" + day + " " + hour + ":00:00",
Value.TIMESTAMP);
ValueTimestamp.parse(year + "-" + month + "-" + day + " " + hour + ":00:00");
}
private void testAllTimeZones() throws SQLException {
......
......@@ -7269,12 +7269,12 @@ SELECT XD+1, XD-1, XD-XD FROM TEST;
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.123 1970-01-01 23:59:59.0 1999-12-31 00:00:00.0
> null null null null null null null
> 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.123456789 1970-01-01 23:59:59.0 1999-12-31 00:00:00.0
> null null null null null null null
> rows: 4
SCRIPT SIMPLE NOPASSWORDS NOSETTINGS;
......
......@@ -6,7 +6,9 @@
*/
package org.h2.test.unit;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
......@@ -17,8 +19,11 @@ import org.h2.store.Data;
import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils;
import org.h2.util.New;
import org.h2.util.Profiler;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueDouble;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
import org.h2.value.ValueString;
import org.h2.value.ValueTime;
......@@ -43,21 +48,241 @@ public class TestDate extends TestBase {
}
public void test() throws SQLException {
int test;
//Profiler prof = new Profiler();
//prof.startCollecting();
testValueDate();
testValueTime();
testValueTimestamp();
testValidDate();
testValidTime();
testAbsoluteDay();
testCalculateLocalMillis();
testTimeOperationsAcrossTimeZones();
testDateTimeUtils();
//System.out.println(prof.getTop(5));
}
private void testValueDate() {
assertEquals("2000-01-01", ValueDate.get(Date.valueOf("2000-01-01")).getString());
assertEquals("0-00-00", ValueDate.get(0).getString());
assertEquals("9999-12-31", ValueDate.parse("9999-12-31").getString());
assertEquals("-9999-12-31", ValueDate.parse("-9999-12-31").getString());
assertEquals(
Integer.MAX_VALUE + "-12-31",
ValueDate.parse(Integer.MAX_VALUE + "-12-31").getString());
assertEquals(
Integer.MIN_VALUE + "-12-31",
ValueDate.parse(Integer.MIN_VALUE + "-12-31").getString());
ValueDate d1 = ValueDate.parse("2001-01-01");
assertEquals("2001-01-01", d1.getDate().toString());
assertEquals("DATE '2001-01-01'", d1.getSQL());
assertEquals("DATE '2001-01-01'", d1.toString());
assertEquals(Value.DATE, d1.getType());
long dv = d1.getDateValue();
assertEquals((int) ((dv >>> 32) ^ dv), d1.hashCode());
assertEquals(d1.getString().length(), d1.getDisplaySize());
assertEquals(ValueDate.PRECISION, d1.getPrecision());
assertEquals("java.sql.Date", d1.getObject().getClass().getName());
ValueDate d1b = ValueDate.parse("2001-01-01");
assertTrue(d1 == d1b);
Value.clearCache();
d1b = ValueDate.parse("2001-01-01");
assertFalse(d1 == d1b);
assertTrue(d1.equals(d1));
assertTrue(d1.equals(d1b));
assertTrue(d1b.equals(d1));
assertEquals(0, d1.compareTo(d1b, null));
assertEquals(0, d1b.compareTo(d1, null));
ValueDate d2 = ValueDate.parse("2002-02-02");
assertFalse(d1.equals(d2));
assertFalse(d2.equals(d1));
assertEquals(-1, d1.compareTo(d2, null));
assertEquals(1, d2.compareTo(d1, null));
// can't convert using java.util.Date
assertEquals(
Integer.MAX_VALUE + "-12-31 00:00:00.0",
ValueDate.parse(Integer.MAX_VALUE + "-12-31").
convertTo(Value.TIMESTAMP).getString());
assertEquals(
Integer.MIN_VALUE + "-12-31 00:00:00.0",
ValueDate.parse(Integer.MIN_VALUE + "-12-31").
convertTo(Value.TIMESTAMP).getString());
assertEquals(
"00:00:00",
ValueDate.parse(Integer.MAX_VALUE + "-12-31").
convertTo(Value.TIME).getString());
assertEquals(
"00:00:00",
ValueDate.parse(Integer.MIN_VALUE + "-12-31").
convertTo(Value.TIME).getString());
}
private void testValueTime() {
assertEquals("10:20:30", ValueTime.get(Time.valueOf("10:20:30")).getString());
assertEquals("00:00:00", ValueTime.get(0).getString());
assertEquals("23:59:59", ValueTime.parse("23:59:59").getString());
assertEquals("99:59:59", ValueTime.parse("99:59:59").getString());
assertEquals("-99:02:03.001002003", ValueTime.parse("-99:02:03.001002003").getString());
assertEquals("-99:02:03.001002", ValueTime.parse("-99:02:03.001002000").getString());
assertEquals("-99:02:03", ValueTime.parse("-99:02:03.0000000000001").getString());
assertEquals("1999999:59:59.999999999",
ValueTime.parse("1999999:59:59.999999999").getString());
assertEquals("-1999999:59:59.999999999",
ValueTime.parse("-1999999:59:59.999999999").getString());
ValueTime t1 = ValueTime.parse("11:11:11");
assertEquals("11:11:11", t1.getTime().toString());
assertEquals("1970-01-01", t1.getDate().toString());
assertEquals("TIME '11:11:11'", t1.getSQL());
assertEquals("TIME '11:11:11'", t1.toString());
assertEquals(1, t1.getSignum());
assertEquals(-1, t1.negate().getSignum());
assertEquals(0, t1.multiply(ValueInt.get(0)).getSignum());
assertEquals(0, t1.subtract(t1).getSignum());
assertEquals("05:35:35.5", t1.multiply(ValueDouble.get(0.5)).getString());
assertEquals("22:22:22", t1.divide(ValueDouble.get(0.5)).getString());
assertEquals("-11:11:11", t1.negate().getString());
assertEquals("11:11:11", t1.negate().negate().getString());
assertEquals(Value.TIME, t1.getType());
long nanos = t1.getNanos();
assertEquals((int) ((nanos >>> 32) ^ nanos), t1.hashCode());
assertEquals(t1.getString().length(), t1.getDisplaySize());
assertEquals(ValueTime.PRECISION, t1.getPrecision());
assertEquals("java.sql.Time", t1.getObject().getClass().getName());
ValueTime t1b = ValueTime.parse("11:11:11");
assertTrue(t1 == t1b);
Value.clearCache();
t1b = ValueTime.parse("11:11:11");
assertFalse(t1 == t1b);
assertTrue(t1.equals(t1));
assertTrue(t1.equals(t1b));
assertTrue(t1b.equals(t1));
assertEquals(0, t1.compareTo(t1b, null));
assertEquals(0, t1b.compareTo(t1, null));
ValueTime t2 = ValueTime.parse("22:22:22");
assertFalse(t1.equals(t2));
assertFalse(t2.equals(t1));
assertEquals("33:33:33", t1.add(t2).getString());
assertEquals("33:33:33", t1.multiply(ValueInt.get(4)).subtract(t1).getString());
assertEquals(-1, t1.compareTo(t2, null));
assertEquals(1, t2.compareTo(t1, null));
// can't convert using java.util.Date
assertEquals(
"1969-12-31 23:00:00.0",
ValueTime.parse("-1:00:00").
convertTo(Value.TIMESTAMP).getString());
assertEquals(
"1970-01-01",
ValueTime.parse("-1:00:00").
convertTo(Value.DATE).getString());
}
private void testValueTimestamp() {
assertEquals("2001-02-03 04:05:06.0", ValueTimestamp.get(
Timestamp.valueOf("2001-02-03 04:05:06")).getString());
assertEquals("2001-02-03 04:05:06.001002003", ValueTimestamp.get(
Timestamp.valueOf("2001-02-03 04:05:06.001002003")).getString());
assertEquals("0-00-00 00:00:00.0", ValueTimestamp.get(0, 0).getString());
assertEquals("9999-12-31 23:59:59.0",
ValueTimestamp.parse("9999-12-31 23:59:59").getString());
assertEquals(
Integer.MAX_VALUE + "-12-31 01:02:03.04050607",
ValueTimestamp.parse(Integer.MAX_VALUE + "-12-31 01:02:03.0405060708").getString());
assertEquals(
Integer.MIN_VALUE + "-12-31 01:02:03.04050607",
ValueTimestamp.parse(Integer.MIN_VALUE + "-12-31 01:02:03.0405060708").getString());
ValueTimestamp t1 = ValueTimestamp.parse("2001-01-01 01:01:01.111");
assertEquals("2001-01-01 01:01:01.111", t1.getTimestamp().toString());
assertEquals("2001-01-01", t1.getDate().toString());
assertEquals("01:01:01", t1.getTime().toString());
assertEquals("TIMESTAMP '2001-01-01 01:01:01.111'", t1.getSQL());
assertEquals("TIMESTAMP '2001-01-01 01:01:01.111'", t1.toString());
assertEquals(Value.TIMESTAMP, t1.getType());
long dateValue = t1.getDateValue();
long nanos = t1.getNanos();
assertEquals((int) ((dateValue >>> 32) ^ dateValue ^
(nanos >>> 32) ^ nanos),
t1.hashCode());
assertEquals(t1.getString().length(), t1.getDisplaySize());
assertEquals(ValueTimestamp.PRECISION, t1.getPrecision());
assertEquals(10, t1.getScale());
assertEquals("java.sql.Timestamp", t1.getObject().getClass().getName());
ValueTimestamp t1b = ValueTimestamp.parse("2001-01-01 01:01:01.111");
assertTrue(t1 == t1b);
Value.clearCache();
t1b = ValueTimestamp.parse("2001-01-01 01:01:01.111");
assertFalse(t1 == t1b);
assertTrue(t1.equals(t1));
assertTrue(t1.equals(t1b));
assertTrue(t1b.equals(t1));
assertEquals(0, t1.compareTo(t1b, null));
assertEquals(0, t1b.compareTo(t1, null));
ValueTimestamp t2 = ValueTimestamp.parse("2002-02-02 02:02:02.222");
assertFalse(t1.equals(t2));
assertFalse(t2.equals(t1));
assertEquals(-1, t1.compareTo(t2, null));
assertEquals(1, t2.compareTo(t1, null));
t1 = ValueTimestamp.parse("2001-01-01 01:01:01.123456789");
assertEquals("2001-01-01 01:01:01.123456789", t1.getString());
assertEquals("2001-01-01 01:01:01.123456789", t1.convertScale(true, 10).getString());
assertEquals("2001-01-01 01:01:01.123456789", t1.convertScale(true, 9).getString());
assertEquals("2001-01-01 01:01:01.12345679", t1.convertScale(true, 8).getString());
assertEquals("2001-01-01 01:01:01.1234568", t1.convertScale(true, 7).getString());
assertEquals("2001-01-01 01:01:01.123457", t1.convertScale(true, 6).getString());
assertEquals("2001-01-01 01:01:01.12346", t1.convertScale(true, 5).getString());
assertEquals("2001-01-01 01:01:01.1235", t1.convertScale(true, 4).getString());
assertEquals("2001-01-01 01:01:01.123", t1.convertScale(true, 3).getString());
assertEquals("2001-01-01 01:01:01.12", t1.convertScale(true, 2).getString());
assertEquals("2001-01-01 01:01:01.1", t1.convertScale(true, 1).getString());
assertEquals("2001-01-01 01:01:01.0", t1.convertScale(true, 0).getString());
t1 = ValueTimestamp.parse("-2001-01-01 01:01:01.123456789");
assertEquals("-2001-01-01 01:01:01.123457", t1.convertScale(true, 6).getString());
// classes do not match
assertFalse(ValueTimestamp.parse("2001-01-01").equals(ValueDate.parse("2001-01-01")));
assertEquals("2001-01-01 01:01:01.0",
ValueTimestamp.parse("2001-01-01").add(
ValueTime.parse("01:01:01")).getString());
assertEquals("2001-01-02 01:01:01.0",
ValueTimestamp.parse("2001-01-01").add(
ValueTime.parse("25:01:01")).getString());
assertEquals("1010-10-10 00:00:00.0",
ValueTimestamp.parse("1010-10-10 10:10:10").subtract(
ValueTime.parse("10:10:10")).getString());
assertEquals("1010-10-10 10:00:00.0",
ValueTimestamp.parse("1010-10-11 10:10:10").subtract(
ValueTime.parse("24:10:10")).getString());
assertEquals("-2001-01-01 01:01:01.0",
ValueTimestamp.parse("-2001-01-01").add(
ValueTime.parse("01:01:01")).getString());
assertEquals("-1010-10-10 00:00:00.0",
ValueTimestamp.parse("-1010-10-10 10:10:10").subtract(
ValueTime.parse("10:10:10")).getString());
}
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 testAbsoluteDay() {
long next = Long.MIN_VALUE;
for (int y = -2000; y < 3000; y++) {
for (int m = -3; m <= 14; m++) {
for (int d = -2; d <= 35; d++) {
if (!DateTimeUtils.isValidDate(y, m, d)) {
continue;
}
long date = DateTimeUtils.dateValue(y, m, d);
long abs = DateTimeUtils.absoluteDayFromDateValue(date);
if (abs != next && next != Long.MIN_VALUE) {
assertEquals(abs, next);
}
next = abs + 1;
long d2 = DateTimeUtils.dateValueFromAbsoluteDay(abs);
assertEquals(date, d2);
assertEquals(y, DateTimeUtils.yearFromDateValue(date));
assertEquals(m, DateTimeUtils.monthFromDateValue(date));
assertEquals(d, DateTimeUtils.dayFromDateValue(date));
}
}
}
......@@ -67,13 +292,15 @@ public class TestDate extends TestBase {
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++) {
for (int m = -3; m <= 14; m++) {
for (int d = -2; d <= 35; 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 != 1582 && d >= 1 && d <= 27) {
assertTrue(valid);
} else {
if (y <= 0) {
c.set(Calendar.ERA, GregorianCalendar.BC);
......@@ -104,17 +331,18 @@ public class TestDate extends TestBase {
try {
for (TimeZone tz : TestDate.getDistinctTimeZones()) {
TimeZone.setDefault(tz);
for (int y = 1900; y < 2039; y++) {
for (int y = 1900; y < 2039; y += 10) {
if (y == 1993) {
// timezone change in Kwajalein
} else if (y == 1995) {
// timezone change in Enderbury and Kiritimati
}
for (int m = 1; m <= 12; m++) {
if (m != 3 && m != 4 && m != 10 && m != 11) {
// only test daylight saving time transitions
continue;
}
for (int day = 1; day < 29; day++) {
if (y == 1582 && m == 10 && day >= 5 && day <= 14) {
continue;
}
testDate(y, m, day);
}
}
......@@ -214,8 +442,8 @@ public class TestDate extends TestBase {
}
private void testDateTimeUtils() {
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);
ValueTimestamp ts1 = ValueTimestamp.parse("-999-08-07 13:14:15.16");
ValueTimestamp ts2 = ValueTimestamp.parse("19999-08-07 13:14:15.16");
ValueTime t1 = (ValueTime) ts1.convertTo(Value.TIME);
ValueTime t2 = (ValueTime) ts2.convertTo(Value.TIME);
ValueDate d1 = (ValueDate) ts1.convertTo(Value.DATE);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论