Unverified 提交 8556fd23 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #881 from katzyn/datetime

Reimplement dateValueFromDate() and nanosFromDate() without a Calendar
...@@ -85,6 +85,11 @@ public class DateTimeUtils { ...@@ -85,6 +85,11 @@ public class DateTimeUtils {
private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE = private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE =
new ThreadLocal<>(); new ThreadLocal<>();
/**
* Cached local time zone.
*/
private static volatile TimeZone timeZone;
/** /**
* Observed JVM behaviour is that if the timezone of the host computer is * Observed JVM behaviour is that if the timezone of the host computer is
* changed while the JVM is running, the zone offset does not change but * changed while the JVM is running, the zone offset does not change but
...@@ -100,12 +105,26 @@ public class DateTimeUtils { ...@@ -100,12 +105,26 @@ public class DateTimeUtils {
// utility class // utility class
} }
/**
* Returns local time zone.
*
* @return local time zone
*/
private static TimeZone getTimeZone() {
TimeZone tz = timeZone;
if (tz == null) {
timeZone = tz = TimeZone.getDefault();
}
return tz;
}
/** /**
* Reset the cached calendar for default timezone, for example after * Reset the cached calendar for default timezone, for example after
* changing the default timezone. * changing the default timezone.
*/ */
public static void resetCalendar() { public static void resetCalendar() {
CACHED_CALENDAR.remove(); CACHED_CALENDAR.remove();
timeZone = null;
zoneOffsetMillis = DateTimeUtils.createGregorianCalendar().get(Calendar.ZONE_OFFSET); zoneOffsetMillis = DateTimeUtils.createGregorianCalendar().get(Calendar.ZONE_OFFSET);
} }
...@@ -1043,9 +1062,13 @@ public class DateTimeUtils { ...@@ -1043,9 +1062,13 @@ public class DateTimeUtils {
* @return the date value * @return the date value
*/ */
public static long dateValueFromDate(long ms) { public static long dateValueFromDate(long ms) {
Calendar cal = getCalendar(); ms += getTimeZone().getOffset(ms);
cal.setTimeInMillis(ms); long absoluteDay = ms / MILLIS_PER_DAY;
return dateValueFromCalendar(cal); // Round toward negative infinity
if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) {
absoluteDay--;
}
return dateValueFromAbsoluteDay(absoluteDay);
} }
/** /**
...@@ -1072,9 +1095,13 @@ public class DateTimeUtils { ...@@ -1072,9 +1095,13 @@ public class DateTimeUtils {
* @return the nanoseconds * @return the nanoseconds
*/ */
public static long nanosFromDate(long ms) { public static long nanosFromDate(long ms) {
Calendar cal = getCalendar(); ms += getTimeZone().getOffset(ms);
cal.setTimeInMillis(ms); long absoluteDay = ms / MILLIS_PER_DAY;
return nanosFromCalendar(cal); // Round toward negative infinity
if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) {
absoluteDay--;
}
return (ms - absoluteDay * MILLIS_PER_DAY) * 1_000_000;
} }
/** /**
...@@ -1129,7 +1156,7 @@ public class DateTimeUtils { ...@@ -1129,7 +1156,7 @@ public class DateTimeUtils {
m += 12; 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))) { if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1015))) {
// Julian calendar (cutover at 1582-10-04 / 1582-10-15) // Julian calendar (cutover at 1582-10-04 / 1582-10-15)
a += 13; a += 13;
} else if (y < 1901 || y > 2099) { } else if (y < 1901 || y > 2099) {
......
...@@ -37,6 +37,7 @@ public class TestClearReferences extends TestBase { ...@@ -37,6 +37,7 @@ public class TestClearReferences extends TestBase {
"org.h2.tools.CompressTool.cachedBuffer", "org.h2.tools.CompressTool.cachedBuffer",
"org.h2.util.CloseWatcher.queue", "org.h2.util.CloseWatcher.queue",
"org.h2.util.CloseWatcher.refs", "org.h2.util.CloseWatcher.refs",
"org.h2.util.DateTimeUtils.timeZone",
"org.h2.util.MathUtils.cachedSecureRandom", "org.h2.util.MathUtils.cachedSecureRandom",
"org.h2.util.NetUtils.cachedLocalAddress", "org.h2.util.NetUtils.cachedLocalAddress",
"org.h2.util.StringUtils.softCache", "org.h2.util.StringUtils.softCache",
......
...@@ -6,10 +6,15 @@ ...@@ -6,10 +6,15 @@
package org.h2.test.unit; package org.h2.test.unit;
import static org.h2.util.DateTimeUtils.dateValue; import static org.h2.util.DateTimeUtils.dateValue;
import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.value.ValueTimestamp;
/** /**
* Unit tests for the DateTimeUtils class * Unit tests for the DateTimeUtils class
...@@ -19,9 +24,18 @@ public class TestDateTimeUtils extends TestBase { ...@@ -19,9 +24,18 @@ public class TestDateTimeUtils extends TestBase {
/** /**
* Run just this test. * Run just this test.
* *
* @param a ignored * @param a
* if {@code "testUtc2Value"} only {@link #testUTC2Value(boolean)}
* will be executed with all time zones (slow). Otherwise all tests
* in this test unit will be executed with local time zone.
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
if (a.length == 1) {
if ("testUtc2Value".equals(a[0])) {
new TestDateTimeUtils().testUTC2Value(true);
return;
}
}
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
...@@ -31,6 +45,7 @@ public class TestDateTimeUtils extends TestBase { ...@@ -31,6 +45,7 @@ public class TestDateTimeUtils extends TestBase {
testDayOfWeek(); testDayOfWeek();
testWeekOfYear(); testWeekOfYear();
testDateValueFromDenormalizedDate(); testDateValueFromDenormalizedDate();
testUTC2Value(false);
} }
private void testParseTimeNanosDB2Format() { private void testParseTimeNanosDB2Format() {
...@@ -104,4 +119,45 @@ public class TestDateTimeUtils extends TestBase { ...@@ -104,4 +119,45 @@ public class TestDateTimeUtils extends TestBase {
assertEquals(dateValue(-100, 2, 29), DateTimeUtils.dateValueFromDenormalizedDate(-100, 2, 30)); assertEquals(dateValue(-100, 2, 29), DateTimeUtils.dateValueFromDenormalizedDate(-100, 2, 30));
} }
private void testUTC2Value(boolean allTimeZones) {
TimeZone def = TimeZone.getDefault();
GregorianCalendar gc = new GregorianCalendar();
if (allTimeZones) {
try {
for (String id : TimeZone.getAvailableIDs()) {
System.out.println(id);
TimeZone tz = TimeZone.getTimeZone(id);
TimeZone.setDefault(tz);
DateTimeUtils.resetCalendar();
testUTC2ValueImpl(tz, gc);
}
} finally {
TimeZone.setDefault(def);
DateTimeUtils.resetCalendar();
}
} else {
testUTC2ValueImpl(def, gc);
}
}
private void testUTC2ValueImpl(TimeZone tz, GregorianCalendar gc) {
gc.setTimeZone(tz);
gc.set(Calendar.MILLISECOND, 0);
long absoluteStart = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(1950, 01, 01));
long absoluteEnd = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(2050, 01, 01));
for (long i = absoluteStart; i < absoluteEnd; i++) {
long dateValue = DateTimeUtils.dateValueFromAbsoluteDay(i);
int year = DateTimeUtils.yearFromDateValue(dateValue);
int month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
for (int j = 0; j < 48; j++) {
gc.set(year, month - 1, day, j / 2, (j & 1) * 30, 0);
long timeMillis = gc.getTimeInMillis();
ValueTimestamp ts = DateTimeUtils.convertTimestamp(new Timestamp(timeMillis), gc);
assertEquals(ts.getDateValue(), DateTimeUtils.dateValueFromDate(timeMillis));
assertEquals(ts.getTimeNanos(), DateTimeUtils.nanosFromDate(timeMillis));
}
}
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论