Unverified 提交 794abfc5 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #879 from katzyn/datetime2

Reimplement TO_DATE without a Calendar and fix a lot of bugs an incompatibilities
......@@ -98,7 +98,7 @@ public class Function extends Expression implements FunctionCall {
XMLATTR = 83, XMLNODE = 84, XMLCOMMENT = 85, XMLCDATA = 86,
XMLSTARTDOC = 87, XMLTEXT = 88, REGEXP_REPLACE = 89, RPAD = 90,
LPAD = 91, CONCAT_WS = 92, TO_CHAR = 93, TRANSLATE = 94, ORA_HASH = 95,
TO_DATE = 96, TO_TIMESTAMP = 97, ADD_MONTHS = 98;
TO_DATE = 96, TO_TIMESTAMP = 97, ADD_MONTHS = 98, TO_TIMESTAMP_TZ = 99;
public static final int CURDATE = 100, CURTIME = 101, DATE_ADD = 102,
DATE_DIFF = 103, DAY_NAME = 104, DAY_OF_MONTH = 105,
......@@ -335,6 +335,7 @@ public class Function extends Expression implements FunctionCall {
addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP);
addFunction("ADD_MONTHS", ADD_MONTHS, 2, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP_TZ", TO_TIMESTAMP_TZ, VAR_ARGS, Value.TIMESTAMP_TZ);
// alias for MSSQLServer
addFunctionNotDeterministic("GETDATE", CURDATE,
0, Value.DATE);
......@@ -1455,16 +1456,20 @@ public class Function extends Expression implements FunctionCall {
}
break;
case TO_DATE:
result = ValueTimestamp.get(ToDateParser.toDate(v0.getString(),
v1 == null ? null : v1.getString()));
result = ToDateParser.toDate(v0.getString(),
v1 == null ? null : v1.getString());
break;
case TO_TIMESTAMP:
result = ValueTimestamp.get(ToDateParser.toTimestamp(v0.getString(),
v1 == null ? null : v1.getString()));
result = ToDateParser.toTimestamp(v0.getString(),
v1 == null ? null : v1.getString());
break;
case ADD_MONTHS:
result = dateadd("MONTH", v1.getInt(), v0);
break;
case TO_TIMESTAMP_TZ:
result = ToDateParser.toTimestampTz(v0.getString(),
v1 == null ? null : v1.getString());
break;
case TRANSLATE: {
String matching = v1.getString();
String replacement = v2.getString();
......@@ -2307,6 +2312,7 @@ public class Function extends Expression implements FunctionCall {
case XMLTEXT:
case TRUNCATE:
case TO_TIMESTAMP:
case TO_TIMESTAMP_TZ:
min = 1;
max = 2;
break;
......
......@@ -28,7 +28,7 @@ public class ToChar {
/**
* The beginning of the Julian calendar.
*/
private static final int JULIAN_EPOCH = -2_440_588;
static final int JULIAN_EPOCH = -2_440_588;
private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
5, 4, 1 };
......@@ -36,7 +36,7 @@ public class ToChar {
private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC",
"L", "XL", "X", "IX", "V", "IV", "I" };
private static final int MONTHS = 0, SHORT_MONTHS = 1, WEEKDAYS = 2, SHORT_WEEKDAYS = 3, AM_PM = 4;
static final int MONTHS = 0, SHORT_MONTHS = 1, WEEKDAYS = 2, SHORT_WEEKDAYS = 3, AM_PM = 4;
private static volatile String[][] NAMES;
......@@ -454,7 +454,7 @@ public class ToChar {
return hex;
}
private static String[] getNames(int names) {
static String[] getNames(int names) {
String[][] result = NAMES;
if (result == null) {
result = new String[5][];
......@@ -476,6 +476,44 @@ public class ToChar {
return result[names];
}
/**
* Returns time zone display name or ID for the specified date-time value.
*
* @param value
* value
* @param tzd
* if {@code true} return TZD (time zone region with Daylight Saving
* Time information included), if {@code false} return TZR (time zone
* region)
* @return time zone display name or ID
*/
private static String getTimeZone(Value value, boolean tzd) {
if (!(value instanceof ValueTimestampTimeZone)) {
TimeZone tz = TimeZone.getDefault();
if (tzd) {
boolean daylight = tz.inDaylightTime(value.getTimestamp());
return tz.getDisplayName(daylight, TimeZone.SHORT);
}
return tz.getID();
}
int offset = ((ValueTimestampTimeZone) value).getTimeZoneOffsetMins();
if (offset == 0) {
return "UTC";
}
StringBuilder b = new StringBuilder(9);
b.append("GMT");
if (offset < 0) {
b.append('-');
offset = - offset;
} else {
b.append('+');
}
StringUtils.appendZeroPadded(b, 2, offset / 60);
b.append(':');
StringUtils.appendZeroPadded(b, 2, offset % 60);
return b.toString();
}
/**
* Emulates Oracle's TO_CHAR(datetime) function.
*
......@@ -755,15 +793,10 @@ public class ToChar {
// Time zone
} else if (containsAt(format, i, "TZR") != null) {
TimeZone tz = value instanceof ValueTimestampTimeZone ?
((ValueTimestampTimeZone) value).getTimeZone() : TimeZone.getDefault();
output.append(tz.getID());
output.append(getTimeZone(value, false));
i += 3;
} else if (containsAt(format, i, "TZD") != null) {
TimeZone tz = value instanceof ValueTimestampTimeZone ?
((ValueTimestampTimeZone) value).getTimeZone() : TimeZone.getDefault();
boolean daylight = tz.inDaylightTime(new java.util.Date());
output.append(tz.getDisplayName(daylight, TimeZone.SHORT));
output.append(getTimeZone(value, true));
i += 3;
// Week
......
......@@ -7,9 +7,13 @@ package org.h2.util;
import static java.lang.String.format;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/**
* Emulates Oracle's TO_DATE function.<br>
......@@ -21,8 +25,29 @@ public class ToDateParser {
private final ConfigParam functionName;
private String inputStr;
private String formatStr;
private final Calendar resultCalendar = DateTimeUtils.createGregorianCalendar();
private Integer nanos;
private boolean doyValid = false, absoluteDayValid = false,
hour12Valid = false,
timeZoneHMValid = false;
private boolean bc;
private long absoluteDay;
private int year, month, day = 1;
private int dayOfYear;
private int hour, minute, second, nanos;
private int hour12;
boolean isAM = true;
private TimeZone timeZone;
private int timeZoneHour, timeZoneMinute;
private int currentYear, currentMonth;
/**
* @param input the input date with the date-time info
......@@ -31,20 +56,6 @@ public class ToDateParser {
* code)
*/
private ToDateParser(ConfigParam functionName, String input, String format) {
// reset calendar - default oracle behaviour
resultCalendar.set(Calendar.YEAR, 1970);
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);
resultCalendar.set(Calendar.DAY_OF_MONTH, 1);
resultCalendar.set(Calendar.HOUR, 0);
resultCalendar.set(Calendar.HOUR_OF_DAY, 0);
resultCalendar.set(Calendar.MINUTE, 0);
resultCalendar.set(Calendar.SECOND, 0);
resultCalendar.set(Calendar.MILLISECOND, 0);
resultCalendar.set(Calendar.AM_PM, Calendar.AM);
this.functionName = functionName;
inputStr = input.trim();
// Keep a copy
......@@ -59,30 +70,65 @@ public class ToDateParser {
unmodifiedFormatStr = formatStr;
}
private static ToDateParser getDateParser(String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_DATE, input, format);
private static ToDateParser getTimestampParser(ConfigParam param, String input, String format) {
ToDateParser result = new ToDateParser(param, input, format);
parse(result);
return result;
}
private static ToDateParser getTimestampParser(String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_TIMESTAMP, input, format);
parse(result);
return result;
}
private Timestamp getResultingTimestamp() {
Calendar cal = (Calendar) getResultCalendar().clone();
int nanosToSet = nanos == null ?
cal.get(Calendar.MILLISECOND) * 1000000 : nanos.intValue();
cal.set(Calendar.MILLISECOND, 0);
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ts.setNanos(nanosToSet);
return ts;
private ValueTimestamp getResultingValue() {
long dateValue;
if (absoluteDayValid) {
dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay);
} else {
int year = this.year;
if (year == 0) {
year = getCurrentYear();
}
if (bc) {
year = 1 - year;
}
if (doyValid) {
dateValue = DateTimeUtils.dateValueFromAbsoluteDay(
DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(year, 1, 1))
+ dayOfYear - 1);
} else {
int month = this.month;
if (month == 0) {
// Oracle uses current month as default
month = getCurrentMonth();
}
dateValue = DateTimeUtils.dateValue(year, month, day);
}
}
int hour;
if (hour12Valid) {
hour = hour12 % 12;
if (!isAM) {
hour += 12;
}
} else {
hour = this.hour;
}
long timeNanos = ((((hour * 60) + minute) * 60) + second) * 1_000_000_000L + nanos;
return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos);
}
Calendar getResultCalendar() {
return resultCalendar;
private ValueTimestampTimeZone getResultingValueWithTimeZone() {
ValueTimestamp ts = getResultingValue();
long dateValue = ts.getDateValue();
short offset;
if (timeZoneHMValid) {
offset = (short) (timeZoneHour * 60 + ((timeZoneHour >= 0) ? timeZoneMinute : -timeZoneMinute));
} else {
TimeZone timeZone = this.timeZone;
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
long millis = DateTimeUtils.convertDateTimeValueToMillis(timeZone, dateValue, nanos / 1000000);
offset = (short) (timeZone.getOffset(millis) / 1000 / 60);
}
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, ts.getTimeNanos(), offset);
}
String getInputStr() {
......@@ -97,10 +143,111 @@ public class ToDateParser {
return functionName.name();
}
private void queryCurrentYearAndMonth() {
GregorianCalendar gc = DateTimeUtils.getCalendar();
gc.setTimeInMillis(System.currentTimeMillis());
currentYear = gc.get(Calendar.YEAR);
currentMonth = gc.get(Calendar.MONTH) + 1;
}
int getCurrentYear() {
if (currentYear == 0) {
queryCurrentYearAndMonth();
}
return currentYear;
}
int getCurrentMonth() {
if (currentMonth == 0) {
queryCurrentYearAndMonth();
}
return currentMonth;
}
void setAbsoluteDay(int absoluteDay) {
doyValid = false;
absoluteDayValid = true;
this.absoluteDay = absoluteDay;
}
void setBC(boolean bc) {
doyValid = false;
absoluteDayValid = false;
this.bc = bc;
}
void setYear(int year) {
doyValid = false;
absoluteDayValid = false;
this.year = year;
}
void setMonth(int month) {
doyValid = false;
absoluteDayValid = false;
this.month = month;
if (year == 0) {
year = 1970;
}
}
void setDay(int day) {
doyValid = false;
absoluteDayValid = false;
this.day = day;
if (year == 0) {
year = 1970;
}
}
void setDayOfYear(int dayOfYear) {
doyValid = true;
absoluteDayValid = false;
this.dayOfYear = dayOfYear;
}
void setHour(int hour) {
hour12Valid = false;
this.hour = hour;
}
void setMinute(int minute) {
this.minute = minute;
}
void setSecord(int second) {
this.second = second;
}
void setNanos(int nanos) {
this.nanos = nanos;
}
void setAmPm(boolean isAM) {
hour12Valid = true;
this.isAM = isAM;
}
void setHour12(int hour12) {
hour12Valid = true;
this.hour12 = hour12;
}
void setTimeZone(TimeZone timeZone) {
timeZoneHMValid = false;
this.timeZone = timeZone;
}
void setTimeZoneHour(int timeZoneHour) {
timeZoneHMValid = true;
this.timeZoneHour = timeZoneHour;
}
void setTimeZoneMinute(int timeZoneMinute) {
timeZoneHMValid = true;
this.timeZoneMinute = timeZoneMinute;
}
private boolean hasToParseData() {
return formatStr.length() > 0;
}
......@@ -180,9 +327,21 @@ public class ToDateParser {
* @param format the format
* @return the timestamp
*/
public static Timestamp toTimestamp(String input, String format) {
ToDateParser parser = getTimestampParser(input, format);
return parser.getResultingTimestamp();
public static ValueTimestamp toTimestamp(String input, String format) {
ToDateParser parser = getTimestampParser(ConfigParam.TO_TIMESTAMP, input, format);
return parser.getResultingValue();
}
/**
* Parse a string as a timestamp with the given format.
*
* @param input the input
* @param format the format
* @return the timestamp
*/
public static ValueTimestampTimeZone toTimestampTz(String input, String format) {
ToDateParser parser = getTimestampParser(ConfigParam.TO_TIMESTAMP_TZ, input, format);
return parser.getResultingValueWithTimeZone();
}
/**
......@@ -192,9 +351,9 @@ public class ToDateParser {
* @param format the format
* @return the date as a timestamp
*/
public static Timestamp toDate(String input, String format) {
ToDateParser parser = getDateParser(input, format);
return parser.getResultingTimestamp();
public static ValueTimestamp toDate(String input, String format) {
ToDateParser parser = getTimestampParser(ConfigParam.TO_DATE, input, format);
return parser.getResultingValue();
}
/**
......@@ -202,7 +361,8 @@ public class ToDateParser {
*/
private enum ConfigParam {
TO_DATE("DD MON YYYY"),
TO_TIMESTAMP("DD MON YYYY HH:MI:SS");
TO_TIMESTAMP("DD MON YYYY HH:MI:SS"),
TO_TIMESTAMP_TZ("DD MON YYYY HH:MI:SS TZR");
private final String defaultFormatStr;
ConfigParam(String defaultFormatStr) {
......
......@@ -9,13 +9,10 @@ import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import org.h2.api.ErrorCode;
import org.h2.api.TimestampWithTimeZone;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.StringUtils;
/**
* Implementation of the TIMESTAMP WITH TIME ZONE data type.
......@@ -149,30 +146,6 @@ public class ValueTimestampTimeZone extends Value {
return timeZoneOffsetMins;
}
/**
* Returns compatible offset-based time zone with no DST schedule.
*
* @return compatible offset-based time zone
*/
public TimeZone getTimeZone() {
int offset = timeZoneOffsetMins;
if (offset == 0) {
return DateTimeUtils.UTC;
}
StringBuilder b = new StringBuilder(9);
b.append("GMT");
if (offset < 0) {
b.append('-');
offset = - offset;
} else {
b.append('+');
}
StringUtils.appendZeroPadded(b, 2, offset / 60);
b.append(':');
StringUtils.appendZeroPadded(b, 2, offset % 60);
return new SimpleTimeZone(offset * 60000, b.toString());
}
@Override
public Timestamp getTimestamp() {
throw new UnsupportedOperationException("unimplemented");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论