提交 778748d1 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Reimplement TO_DATE without a Calendar and fix a lot of bugs an incompatibilities

上级 aac17564
...@@ -98,7 +98,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -98,7 +98,7 @@ public class Function extends Expression implements FunctionCall {
XMLATTR = 83, XMLNODE = 84, XMLCOMMENT = 85, XMLCDATA = 86, XMLATTR = 83, XMLNODE = 84, XMLCOMMENT = 85, XMLCDATA = 86,
XMLSTARTDOC = 87, XMLTEXT = 88, REGEXP_REPLACE = 89, RPAD = 90, XMLSTARTDOC = 87, XMLTEXT = 88, REGEXP_REPLACE = 89, RPAD = 90,
LPAD = 91, CONCAT_WS = 92, TO_CHAR = 93, TRANSLATE = 94, ORA_HASH = 95, 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, public static final int CURDATE = 100, CURTIME = 101, DATE_ADD = 102,
DATE_DIFF = 103, DAY_NAME = 104, DAY_OF_MONTH = 105, DATE_DIFF = 103, DAY_NAME = 104, DAY_OF_MONTH = 105,
...@@ -335,6 +335,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -335,6 +335,7 @@ public class Function extends Expression implements FunctionCall {
addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP); addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP); addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP);
addFunction("ADD_MONTHS", ADD_MONTHS, 2, 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 // alias for MSSQLServer
addFunctionNotDeterministic("GETDATE", CURDATE, addFunctionNotDeterministic("GETDATE", CURDATE,
0, Value.DATE); 0, Value.DATE);
...@@ -1455,16 +1456,20 @@ public class Function extends Expression implements FunctionCall { ...@@ -1455,16 +1456,20 @@ public class Function extends Expression implements FunctionCall {
} }
break; break;
case TO_DATE: case TO_DATE:
result = ValueTimestamp.get(ToDateParser.toDate(v0.getString(), result = ToDateParser.toDate(v0.getString(),
v1 == null ? null : v1.getString())); v1 == null ? null : v1.getString());
break; break;
case TO_TIMESTAMP: case TO_TIMESTAMP:
result = ValueTimestamp.get(ToDateParser.toTimestamp(v0.getString(), result = ToDateParser.toTimestamp(v0.getString(),
v1 == null ? null : v1.getString())); v1 == null ? null : v1.getString());
break; break;
case ADD_MONTHS: case ADD_MONTHS:
result = dateadd("MONTH", v1.getInt(), v0); result = dateadd("MONTH", v1.getInt(), v0);
break; break;
case TO_TIMESTAMP_TZ:
result = ToDateParser.toTimestampTz(v0.getString(),
v1 == null ? null : v1.getString());
break;
case TRANSLATE: { case TRANSLATE: {
String matching = v1.getString(); String matching = v1.getString();
String replacement = v2.getString(); String replacement = v2.getString();
...@@ -2307,6 +2312,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -2307,6 +2312,7 @@ public class Function extends Expression implements FunctionCall {
case XMLTEXT: case XMLTEXT:
case TRUNCATE: case TRUNCATE:
case TO_TIMESTAMP: case TO_TIMESTAMP:
case TO_TIMESTAMP_TZ:
min = 1; min = 1;
max = 2; max = 2;
break; break;
......
...@@ -28,7 +28,7 @@ public class ToChar { ...@@ -28,7 +28,7 @@ public class ToChar {
/** /**
* The beginning of the Julian calendar. * 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, private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
5, 4, 1 }; 5, 4, 1 };
...@@ -36,7 +36,7 @@ public class ToChar { ...@@ -36,7 +36,7 @@ public class ToChar {
private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC", private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC",
"L", "XL", "X", "IX", "V", "IV", "I" }; "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; private static volatile String[][] NAMES;
...@@ -454,7 +454,7 @@ public class ToChar { ...@@ -454,7 +454,7 @@ public class ToChar {
return hex; return hex;
} }
private static String[] getNames(int names) { static String[] getNames(int names) {
String[][] result = NAMES; String[][] result = NAMES;
if (result == null) { if (result == null) {
result = new String[5][]; result = new String[5][];
......
...@@ -7,9 +7,13 @@ package org.h2.util; ...@@ -7,9 +7,13 @@ package org.h2.util;
import static java.lang.String.format; import static java.lang.String.format;
import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List; 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> * Emulates Oracle's TO_DATE function.<br>
...@@ -21,8 +25,29 @@ public class ToDateParser { ...@@ -21,8 +25,29 @@ public class ToDateParser {
private final ConfigParam functionName; private final ConfigParam functionName;
private String inputStr; private String inputStr;
private String formatStr; 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 * @param input the input date with the date-time info
...@@ -32,18 +57,6 @@ public class ToDateParser { ...@@ -32,18 +57,6 @@ public class ToDateParser {
*/ */
private ToDateParser(ConfigParam functionName, String input, String format) { private ToDateParser(ConfigParam functionName, String input, String format) {
// reset calendar - default oracle behaviour // 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; this.functionName = functionName;
inputStr = input.trim(); inputStr = input.trim();
...@@ -59,30 +72,65 @@ public class ToDateParser { ...@@ -59,30 +72,65 @@ public class ToDateParser {
unmodifiedFormatStr = formatStr; unmodifiedFormatStr = formatStr;
} }
private static ToDateParser getDateParser(String input, String format) { private static ToDateParser getTimestampParser(ConfigParam param, String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_DATE, input, 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); parse(result);
return result; return result;
} }
private Timestamp getResultingTimestamp() { private ValueTimestamp getResultingValue() {
Calendar cal = (Calendar) getResultCalendar().clone(); long dateValue;
int nanosToSet = nanos == null ? if (absoluteDayValid) {
cal.get(Calendar.MILLISECOND) * 1000000 : nanos.intValue(); dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay);
cal.set(Calendar.MILLISECOND, 0); } else {
Timestamp ts = new Timestamp(cal.getTimeInMillis()); int year = this.year;
ts.setNanos(nanosToSet); if (year == 0) {
return ts; 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() { private ValueTimestampTimeZone getResultingValueWithTimeZone() {
return resultCalendar; 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() { String getInputStr() {
...@@ -97,10 +145,111 @@ public class ToDateParser { ...@@ -97,10 +145,111 @@ public class ToDateParser {
return functionName.name(); 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) { void setNanos(int nanos) {
this.nanos = 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() { private boolean hasToParseData() {
return formatStr.length() > 0; return formatStr.length() > 0;
} }
...@@ -180,9 +329,21 @@ public class ToDateParser { ...@@ -180,9 +329,21 @@ public class ToDateParser {
* @param format the format * @param format the format
* @return the timestamp * @return the timestamp
*/ */
public static Timestamp toTimestamp(String input, String format) { public static ValueTimestamp toTimestamp(String input, String format) {
ToDateParser parser = getTimestampParser(input, format); ToDateParser parser = getTimestampParser(ConfigParam.TO_TIMESTAMP, input, format);
return parser.getResultingTimestamp(); 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 +353,9 @@ public class ToDateParser { ...@@ -192,9 +353,9 @@ public class ToDateParser {
* @param format the format * @param format the format
* @return the date as a timestamp * @return the date as a timestamp
*/ */
public static Timestamp toDate(String input, String format) { public static ValueTimestamp toDate(String input, String format) {
ToDateParser parser = getDateParser(input, format); ToDateParser parser = getTimestampParser(ConfigParam.TO_DATE, input, format);
return parser.getResultingTimestamp(); return parser.getResultingValue();
} }
/** /**
...@@ -202,7 +363,8 @@ public class ToDateParser { ...@@ -202,7 +363,8 @@ public class ToDateParser {
*/ */
private enum ConfigParam { private enum ConfigParam {
TO_DATE("DD MON YYYY"), 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; private final String defaultFormatStr;
ConfigParam(String defaultFormatStr) { ConfigParam(String defaultFormatStr) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论