Unverified 提交 67aa05ba authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #676 from imotov/explicit-gregorian-calendar

Use Gregorian calendar regardless of the default locale
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #675: Fix date operations on Locales with non-Gregorian calendars
</li>
<li>Fix removal of LOB when rolling back transaction on a table containing more than one LOB column.
</li>
<li>Issue #654: List ENUM type values in INFORMATION_SCHEMA.COLUMNS
......
......@@ -1252,7 +1252,7 @@ public class Function extends Expression implements FunctionCall {
case TRUNCATE: {
if (v0.getType() == Value.TIMESTAMP) {
java.sql.Timestamp d = v0.getTimestamp();
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(d);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
......@@ -1261,7 +1261,7 @@ public class Function extends Expression implements FunctionCall {
result = ValueTimestamp.fromMillis(c.getTimeInMillis());
} else if (v0.getType() == Value.DATE) {
ValueDate vd = (ValueDate) v0;
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(vd.getDate());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
......@@ -1270,7 +1270,7 @@ public class Function extends Expression implements FunctionCall {
result = ValueTimestamp.fromMillis(c.getTimeInMillis());
} else if (v0.getType() == Value.STRING) {
ValueString vd = (ValueString) v0;
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(ValueTimestamp.parse(vd.getString(), session.getDatabase().getMode()).getDate());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
......@@ -1822,7 +1822,7 @@ public class Function extends Expression implements FunctionCall {
if (count > Integer.MAX_VALUE) {
throw DbException.getInvalidValueException("DATEADD count", count);
}
Calendar calendar = Calendar.getInstance();
Calendar calendar = DateTimeUtils.createGregorianCalendar();
int nanos = d.getNanos() % 1000000;
calendar.setTime(d);
calendar.add(field, (int) count);
......@@ -1846,7 +1846,7 @@ public class Function extends Expression implements FunctionCall {
*/
private static long datediff(String part, Timestamp d1, Timestamp d2) {
int field = getDatePart(part);
Calendar calendar = Calendar.getInstance();
Calendar calendar = DateTimeUtils.createGregorianCalendar();
long t1 = d1.getTime(), t2 = d2.getTime();
// need to convert to UTC, otherwise we get inconsistent results with
// certain time zones (those that are 30 minutes off)
......@@ -1896,7 +1896,7 @@ public class Function extends Expression implements FunctionCall {
default:
break;
}
calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar = DateTimeUtils.createGregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTimeInMillis(t1);
int year1 = calendar.get(Calendar.YEAR);
int month1 = calendar.get(Calendar.MONTH);
......
......@@ -3799,7 +3799,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
} else if (type == java.util.Date.class) {
return type.cast(new java.util.Date(value.getTimestamp().getTime()));
} else if (type == Calendar.class) {
Calendar calendar = Calendar.getInstance();
Calendar calendar = DateTimeUtils.createGregorianCalendar();
calendar.setTime(value.getTimestamp());
return type.cast(calendar);
} else if (type == UUID.class) {
......
......@@ -73,7 +73,7 @@ public class DateTimeUtils {
* use a fixed value throughout the duration of the JVM's life, rather than
* have this offset change, possibly midway through a long-running query.
*/
private static int zoneOffsetMillis = Calendar.getInstance()
private static int zoneOffsetMillis = DateTimeUtils.createGregorianCalendar()
.get(Calendar.ZONE_OFFSET);
private DateTimeUtils() {
......@@ -86,7 +86,7 @@ public class DateTimeUtils {
*/
public static void resetCalendar() {
CACHED_CALENDAR.remove();
zoneOffsetMillis = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
zoneOffsetMillis = DateTimeUtils.createGregorianCalendar().get(Calendar.ZONE_OFFSET);
}
/**
......@@ -97,7 +97,7 @@ public class DateTimeUtils {
private static Calendar getCalendar() {
Calendar c = CACHED_CALENDAR.get();
if (c == null) {
c = Calendar.getInstance();
c = DateTimeUtils.createGregorianCalendar();
CACHED_CALENDAR.set(c);
}
c.clear();
......@@ -113,13 +113,40 @@ public class DateTimeUtils {
private static Calendar getCalendar(TimeZone tz) {
Calendar c = CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.get();
if (c == null || !c.getTimeZone().equals(tz)) {
c = Calendar.getInstance(tz);
c = DateTimeUtils.createGregorianCalendar(tz);
CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.set(c);
}
c.clear();
return c;
}
/**
* Creates a Gregorian calendar for the default timezone using the default
* locale. Dates in H2 are represented in a Gregorian calendar. So this
* method should be used instead of Calendar.getInstance() to ensure that
* the Gregorian calendar is used for all date processing instead of a
* default locale calendar that can be non-Gregorian in some locales.
*
* @return a new calendar instance.
*/
public static Calendar createGregorianCalendar() {
return new GregorianCalendar();
}
/**
* Creates a Gregorian calendar for the given timezone using the default
* locale. Dates in H2 are represented in a Gregorian calendar. So this
* method should be used instead of Calendar.getInstance() to ensure that
* the Gregorian calendar is used for all date processing instead of a
* default locale calendar that can be non-Gregorian in some locales.
*
* @param tz timezone for the calendar, is never null
* @return a new calendar instance.
*/
public static Calendar createGregorianCalendar(TimeZone tz) {
return new GregorianCalendar(tz);
}
/**
* Convert the date to the specified time zone.
*
......@@ -253,11 +280,9 @@ public class DateTimeUtils {
throw DbException.getInvalidValueException("calendar", null);
}
target = (Calendar) target.clone();
Calendar local = Calendar.getInstance();
synchronized (local) {
local.setTime(x);
convertTime(local, target);
}
Calendar local = DateTimeUtils.createGregorianCalendar();
local.setTime(x);
convertTime(local, target);
return target.getTime().getTime();
}
......@@ -531,7 +556,7 @@ public class DateTimeUtils {
* @return the day of the week, Monday as 1 to Sunday as 7
*/
public static int getIsoDayOfWeek(java.util.Date date) {
Calendar cal = Calendar.getInstance();
Calendar cal = DateTimeUtils.createGregorianCalendar();
cal.setTimeInMillis(date.getTime());
int val = cal.get(Calendar.DAY_OF_WEEK) - 1;
return val == 0 ? 7 : val;
......@@ -552,7 +577,7 @@ public class DateTimeUtils {
* @return the week of the year
*/
public static int getIsoWeek(java.util.Date date) {
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTimeInMillis(date.getTime());
c.setFirstDayOfWeek(Calendar.MONDAY);
c.setMinimalDaysInFirstWeek(4);
......@@ -567,7 +592,7 @@ public class DateTimeUtils {
* @return the year
*/
public static int getIsoYear(java.util.Date date) {
Calendar cal = Calendar.getInstance();
Calendar cal = DateTimeUtils.createGregorianCalendar();
cal.setTimeInMillis(date.getTime());
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.setMinimalDaysInFirstWeek(4);
......@@ -942,7 +967,7 @@ public class DateTimeUtils {
* @return the new timestamp
*/
public static Timestamp addMonths(Timestamp refDate, int nrOfMonthsToAdd) {
Calendar calendar = Calendar.getInstance();
Calendar calendar = DateTimeUtils.createGregorianCalendar();
calendar.setTime(refDate);
calendar.add(Calendar.MONTH, nrOfMonthsToAdd);
......
......@@ -21,7 +21,7 @@ public class ToDateParser {
private final ConfigParam functionName;
private String inputStr;
private String formatStr;
private final Calendar resultCalendar = (Calendar) Calendar.getInstance().clone();
private final Calendar resultCalendar = DateTimeUtils.createGregorianCalendar();
private Integer nanos;
/**
......@@ -33,7 +33,7 @@ public class ToDateParser {
private ToDateParser(ConfigParam functionName, String input, String format) {
// reset calendar - default oracle behaviour
resultCalendar.set(Calendar.YEAR, 1970);
resultCalendar.set(Calendar.MONTH, Calendar.getInstance().get(Calendar.MONTH));
resultCalendar.set(Calendar.MONTH, DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH));
resultCalendar.clear(Calendar.DAY_OF_YEAR);
resultCalendar.clear(Calendar.DAY_OF_WEEK);
resultCalendar.clear(Calendar.DAY_OF_WEEK_IN_MONTH);
......
......@@ -194,7 +194,7 @@ class ToDateTokenizer {
result.set(Calendar.YEAR, dateNr);
break;
case RR:
Calendar calendar = Calendar.getInstance();
Calendar calendar = DateTimeUtils.createGregorianCalendar();
int cc = calendar.get(Calendar.YEAR) / 100;
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum);
......
......@@ -10,7 +10,6 @@ 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.api.ErrorCode;
import org.h2.engine.Mode;
......@@ -217,7 +216,7 @@ public class ValueTimestamp extends Value {
tz, year, month, day, hour, minute, (int) second, (int) ms);
ms = DateTimeUtils.convertToLocal(
new Date(millis),
Calendar.getInstance(TimeZone.getTimeZone("UTC")));
DateTimeUtils.createGregorianCalendar(TimeZone.getTimeZone("UTC")));
long md = DateTimeUtils.MILLIS_PER_DAY;
long absoluteDay = (ms >= 0 ? ms : ms - md + 1) / md;
dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay);
......
......@@ -1199,7 +1199,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
"SELECT CURRENT_TIMESTAMP(), " +
"TRUNCATE(CURRENT_TIMESTAMP()) FROM dual");
rs.next();
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(rs.getTimestamp(1));
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
......@@ -1305,7 +1305,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
}
private void testToDate() throws ParseException {
final int month = Calendar.getInstance().get(Calendar.MONTH);
final int month = DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH);
Date date = null;
date = new SimpleDateFormat("yyyy-MM-dd").parse("1979-11-12");
......@@ -1434,7 +1434,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
}
private static void setMonth(Date date, int month) {
Calendar c = Calendar.getInstance();
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(date);
c.set(Calendar.MONTH, month);
date.setTime(c.getTimeInMillis());
......
......@@ -37,6 +37,7 @@ import java.util.TimeZone;
import org.h2.api.ErrorCode;
import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils;
import org.h2.util.LocalDateTimeUtils;
......@@ -1408,7 +1409,7 @@ public class TestResultSet extends TestBase {
"D DATE, T TIME, TS TIMESTAMP)");
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO TEST VALUES(?, ?, ?, ?)");
Calendar regular = Calendar.getInstance();
Calendar regular = DateTimeUtils.createGregorianCalendar();
Calendar other = null;
// search a locale that has a _different_ raw offset
long testTime = java.sql.Date.valueOf("2001-02-03").getTime();
......@@ -1421,7 +1422,7 @@ public class TestResultSet extends TestBase {
if (rawOffsetDiff != 0 && rawOffsetDiff != 1000 * 60 * 60 * 24) {
if (regular.getTimeZone().getOffset(testTime) !=
zone.getOffset(testTime)) {
other = Calendar.getInstance(zone);
other = DateTimeUtils.createGregorianCalendar(zone);
break;
}
}
......
......@@ -42,6 +42,7 @@ import org.h2.test.synth.sql.RandomGen;
import org.h2.tools.Backup;
import org.h2.tools.DeleteDbFiles;
import org.h2.tools.Restore;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
import org.h2.util.New;
......@@ -485,7 +486,7 @@ public class TestCrashAPI extends TestBase implements Runnable {
// TODO should use generated savepoints
return null;
} else if (type == Calendar.class) {
return Calendar.getInstance();
return DateTimeUtils.createGregorianCalendar();
} else if (type == java.net.URL.class) {
return null;
} else if (type == java.math.BigDecimal.class) {
......
......@@ -352,7 +352,7 @@ public class TestDate extends TestBase {
}
private void testValidDate() {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Calendar c = DateTimeUtils.createGregorianCalendar(TimeZone.getTimeZone("UTC"));
c.setLenient(false);
for (int y = -2000; y < 3000; y++) {
for (int m = -3; m <= 14; m++) {
......@@ -467,9 +467,9 @@ public class TestDate extends TestBase {
assertEquals("19999-08-07", d2.getString());
assertEquals("13:14:15.16", t2.getString());
ValueTimestamp ts1a = DateTimeUtils.convertTimestamp(
ts1.getTimestamp(), Calendar.getInstance());
ts1.getTimestamp(), DateTimeUtils.createGregorianCalendar());
ValueTimestamp ts2a = DateTimeUtils.convertTimestamp(
ts2.getTimestamp(), Calendar.getInstance());
ts2.getTimestamp(), DateTimeUtils.createGregorianCalendar());
assertEquals("-999-08-07 13:14:15.16", ts1a.getString());
assertEquals("19999-08-07 13:14:15.16", ts2a.getString());
......
......@@ -29,6 +29,7 @@ public class TestLocale extends TestBase {
@Override
public void test() throws SQLException {
testSpecialLocale();
testDatesInJapanLocale();
}
private void testSpecialLocale() throws SQLException {
......@@ -55,4 +56,32 @@ public class TestLocale extends TestBase {
conn.close();
}
private void testDatesInJapanLocale() throws SQLException {
deleteDb(getTestName());
Connection conn = getConnection(getTestName());
Statement stat = conn.createStatement();
Locale old = Locale.getDefault();
try {
// when using Japanese as the default locale, the default calendar is
// the imperial japanese calendar
Locale.setDefault(new Locale("ja", "JP", "JP"));
stat.execute("CREATE TABLE test(d TIMESTAMP, dz TIMESTAMP WITH TIME ZONE) " +
"as select '2017-12-03T00:00:00Z', '2017-12-03T00:00:00Z'");
ResultSet rs = stat.executeQuery("select YEAR(d) y, YEAR(dz) yz from test");
rs.next();
assertEquals(2017, rs.getInt("y"));
assertEquals(2017, rs.getInt("yz"));
stat.execute("drop table test");
rs = stat.executeQuery(
"CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', 'yyyy-MM-dd HH:mm:ss', 'en')");
rs.next();
assertEquals("2001-02-03 04:05:06", rs.getString(1));
} finally {
Locale.setDefault(old);
}
conn.close();
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论