提交 baefd062 authored 作者: Thomas Mueller's avatar Thomas Mueller

Date values that match the daylight saving time end were not allowed sometimes.

上级 f717f797
...@@ -25,11 +25,23 @@ import org.h2.value.ValueTimestamp; ...@@ -25,11 +25,23 @@ import org.h2.value.ValueTimestamp;
*/ */
public class DateTimeUtils { public class DateTimeUtils {
private static final Calendar CALENDAR = Calendar.getInstance(); 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;
private static Calendar calendar = Calendar.getInstance();
private DateTimeUtils() { private DateTimeUtils() {
// utility class // utility class
} }
/**
* Reset the calendar, for example after changing the default timezone.
*/
public static void resetCalendar() {
calendar = Calendar.getInstance();
}
/** /**
* Convert the timestamp to the specified time zone. * Convert the timestamp to the specified time zone.
...@@ -55,11 +67,12 @@ public class DateTimeUtils { ...@@ -55,11 +67,12 @@ public class DateTimeUtils {
* @return the time value without the date component * @return the time value without the date component
*/ */
public static Time cloneAndNormalizeTime(Time value) { public static Time cloneAndNormalizeTime(Time value) {
Calendar cal = CALENDAR; Calendar cal = calendar;
long time; long time;
synchronized (cal) { synchronized (cal) {
cal.setTime(value); cal.setTime(value);
cal.set(1970, 0, 1); // month is 0 based
cal.set(DEFAULT_YEAR, DEFAULT_MONTH - 1, DEFAULT_DAY);
time = cal.getTime().getTime(); time = cal.getTime().getTime();
} }
return new Time(time); return new Time(time);
...@@ -73,14 +86,14 @@ public class DateTimeUtils { ...@@ -73,14 +86,14 @@ public class DateTimeUtils {
* @return the date value at midnight * @return the date value at midnight
*/ */
public static Date cloneAndNormalizeDate(Date value) { public static Date cloneAndNormalizeDate(Date value) {
Calendar cal = CALENDAR; Calendar cal = calendar;
long time; long time;
synchronized (cal) { synchronized (cal) {
cal.setTime(value); cal.setTime(value);
cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0); cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.HOUR_OF_DAY, DEFAULT_HOUR);
time = cal.getTime().getTime(); time = cal.getTime().getTime();
} }
return new Date(time); return new Date(time);
...@@ -134,10 +147,12 @@ public class DateTimeUtils { ...@@ -134,10 +147,12 @@ public class DateTimeUtils {
throw Message.getInvalidValueException("calendar", null); throw Message.getInvalidValueException("calendar", null);
} }
source = (Calendar) source.clone(); source = (Calendar) source.clone();
Calendar universal = Calendar.getInstance(); Calendar universal = calendar;
source.setTime(x); synchronized (universal) {
convertTime(source, universal); source.setTime(x);
return universal.getTime().getTime(); convertTime(source, universal);
return universal.getTime().getTime();
}
} }
private static long getLocalTime(java.util.Date x, Calendar target) throws SQLException { private static long getLocalTime(java.util.Date x, Calendar target) throws SQLException {
...@@ -146,8 +161,10 @@ public class DateTimeUtils { ...@@ -146,8 +161,10 @@ public class DateTimeUtils {
} }
target = (Calendar) target.clone(); target = (Calendar) target.clone();
Calendar local = Calendar.getInstance(); Calendar local = Calendar.getInstance();
local.setTime(x); synchronized (local) {
convertTime(local, target); local.setTime(x);
convertTime(local, target);
}
return target.getTime().getTime(); return target.getTime().getTime();
} }
...@@ -211,7 +228,7 @@ public class DateTimeUtils { ...@@ -211,7 +228,7 @@ public class DateTimeUtils {
} }
} }
int year = 1970, month = 1, day = 1; int year = DEFAULT_YEAR, month = DEFAULT_MONTH, day = DEFAULT_DAY;
if (type != Value.TIME) { if (type != Value.TIME) {
if (s.startsWith("+")) { if (s.startsWith("+")) {
// +year // +year
...@@ -228,7 +245,7 @@ public class DateTimeUtils { ...@@ -228,7 +245,7 @@ public class DateTimeUtils {
int end = timeStart == 0 ? s.length() : timeStart - 1; int end = timeStart == 0 ? s.length() : timeStart - 1;
day = Integer.parseInt(s.substring(s2 + 1, end)); day = Integer.parseInt(s.substring(s2 + 1, end));
} }
int hour = 0, minute = 0, second = 0, nano = 0; int hour = DEFAULT_HOUR, minute = 0, second = 0, nano = 0;
if (type != Value.DATE) { if (type != Value.DATE) {
int s1 = s.indexOf(':', timeStart); int s1 = s.indexOf(':', timeStart);
int s2 = s.indexOf(':', s1 + 1); int s2 = s.indexOf(':', s1 + 1);
...@@ -265,15 +282,32 @@ public class DateTimeUtils { ...@@ -265,15 +282,32 @@ public class DateTimeUtils {
nano = Integer.parseInt(n); nano = Integer.parseInt(n);
} }
} }
if (hour < 0 || hour > 23) {
throw new IllegalArgumentException("hour: " + hour);
}
long time; long time;
try { try {
time = getTime(false, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano); time = getTime(false, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// special case: if the time simply doesn't exist, use the lenient version // special case: if the time simply doesn't exist because of
if (e.toString().indexOf("HOUR_OF_DAY") > 0) { // daylight saving time changes, use the lenient version
String message = e.toString();
if (message.indexOf("HOUR_OF_DAY") > 0) {
if (hour < 0 || hour > 23) {
throw e;
}
time = getTime(true, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano);
} else if (message.indexOf("DAY_OF_MONTH") > 0) {
int maxDay;
if (month == 2) {
maxDay = new GregorianCalendar().isLeapYear(year) ? 29 : 28;
} else {
maxDay = 30 + ((month + (month > 7 ? 1 : 0)) & 1);
}
if (day < 1 || day > maxDay) {
throw e;
}
// DAY_OF_MONTH is thrown for years > 2037
// 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); time = getTime(true, tz, year, month, day, hour, minute, second, type != Value.TIMESTAMP, nano);
} else { } else {
throw e; throw e;
...@@ -300,27 +334,30 @@ public class DateTimeUtils { ...@@ -300,27 +334,30 @@ public class DateTimeUtils {
private static long getTime(boolean lenient, TimeZone tz, int year, int month, int day, int hour, int minute, int second, boolean setMillis, int nano) { private static long getTime(boolean lenient, TimeZone tz, int year, int month, int day, int hour, int minute, int second, boolean setMillis, int nano) {
Calendar c; Calendar c;
if (tz == null) { if (tz == null) {
c = Calendar.getInstance(); c = calendar;
} else { } else {
c = Calendar.getInstance(tz); c = Calendar.getInstance(tz);
} }
c.setLenient(lenient); synchronized (c) {
if (year <= 0) { c.setLenient(lenient);
c.set(Calendar.ERA, GregorianCalendar.BC); if (year <= 0) {
c.set(Calendar.YEAR, 1 - year); c.set(Calendar.ERA, GregorianCalendar.BC);
} else { c.set(Calendar.YEAR, 1 - year);
c.set(Calendar.YEAR, year); } else {
} c.set(Calendar.ERA, GregorianCalendar.AD);
// january is 0 c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month - 1); }
c.set(Calendar.DAY_OF_MONTH, day); // january is 0
c.set(Calendar.HOUR_OF_DAY, hour); c.set(Calendar.MONTH, month - 1);
c.set(Calendar.MINUTE, minute); c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.SECOND, second); c.set(Calendar.HOUR_OF_DAY, hour);
if (setMillis) { c.set(Calendar.MINUTE, minute);
c.set(Calendar.MILLISECOND, nano / 1000000); c.set(Calendar.SECOND, second);
if (setMillis) {
c.set(Calendar.MILLISECOND, nano / 1000000);
}
return c.getTime().getTime();
} }
return c.getTime().getTime();
} }
/** /**
...@@ -332,9 +369,12 @@ public class DateTimeUtils { ...@@ -332,9 +369,12 @@ public class DateTimeUtils {
* @return the value * @return the value
*/ */
public static int getDatePart(java.util.Date d, int field) { public static int getDatePart(java.util.Date d, int field) {
Calendar c = Calendar.getInstance(); Calendar c = calendar;
c.setTime(d); int value;
int value = c.get(field); synchronized (c) {
c.setTime(d);
value = c.get(field);
}
if (field == Calendar.MONTH) { if (field == Calendar.MONTH) {
value++; value++;
} else if (field == Calendar.YEAR) { } else if (field == Calendar.YEAR) {
......
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
*/ */
package org.h2.test.unit; package org.h2.test.unit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.TimeZone;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -20,9 +24,23 @@ import org.h2.value.Value; ...@@ -20,9 +24,23 @@ import org.h2.value.Value;
* Non-lenient parsing would not work in this case. * Non-lenient parsing would not work in this case.
*/ */
public class TestDate extends TestBase { public class TestDate extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String[] a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws SQLException { public void test() throws SQLException {
for (int year = 1970; year < 2070; year++) { testAllTimeZones();
testCurrentTimeZone();
}
private void testCurrentTimeZone() throws SQLException {
for (int year = 1970; year < 2050; year += 3) {
for (int month = 1; month <= 12; month++) { for (int month = 1; month <= 12; month++) {
for (int day = 1; day < 29; day++) { for (int day = 1; day < 29; day++) {
for (int hour = 0; hour < 24; hour++) { for (int hour = 0; hour < 24; hour++) {
...@@ -37,5 +55,45 @@ public class TestDate extends TestBase { ...@@ -37,5 +55,45 @@ public class TestDate extends TestBase {
DateTimeUtils.parseDateTime(year + "-" + month + "-" + day + " " + hour + ":00:00", Value.TIMESTAMP, ErrorCode.TIMESTAMP_CONSTANT_2); DateTimeUtils.parseDateTime(year + "-" + month + "-" + day + " " + hour + ":00:00", Value.TIMESTAMP, ErrorCode.TIMESTAMP_CONSTANT_2);
} }
private void testAllTimeZones() throws SQLException {
Connection conn = getConnection("date");
TimeZone defaultTimeZone = TimeZone.getDefault();
PreparedStatement prep = conn.prepareStatement("CALL CAST(? AS DATE)");
try {
String[] ids = TimeZone.getAvailableIDs();
for (int i = 0; i < ids.length; i++) {
TimeZone.setDefault(TimeZone.getTimeZone(ids[i]));
DateTimeUtils.resetCalendar();
for (int d = 101; d < 129; d++) {
test(prep, d);
}
}
} finally {
TimeZone.setDefault(defaultTimeZone);
DateTimeUtils.resetCalendar();
}
}
void test(PreparedStatement prep, int d) throws SQLException {
String s = "2040-10-" + ("" + d).substring(1);
// some dates don't work in some versions of Java
// java.sql.Date date = java.sql.Date.valueOf(s);
// long time = date.getTime();
// int plus = 0;
// while (true) {
// date = new java.sql.Date(time);
// String x = date.toString();
// if (x.equals(s)) {
// break;
// }
// time += 1000;
// plus += 1000;
// }
prep.setString(1, s);
ResultSet rs = prep.executeQuery();
rs.next();
String t = rs.getString(1);
assertEquals(s, t);
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论