提交 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 {
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][];
......
......@@ -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
......@@ -32,18 +57,6 @@ 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, 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();
......@@ -59,30 +72,65 @@ public class ToDateParser {
unmodifiedFormatStr = formatStr;
}
private static ToDateParser getDateParser(String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_DATE, input, format);
parse(result);
return result;
}
private static ToDateParser getTimestampParser(String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_TIMESTAMP, input, format);
private static ToDateParser getTimestampParser(ConfigParam param, String input, String format) {
ToDateParser result = new ToDateParser(param, 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 +145,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 +329,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 +353,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 +363,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) {
......
......@@ -6,17 +6,12 @@
package org.h2.util;
import static java.lang.String.format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
......@@ -144,10 +139,10 @@ class ToDateTokenizer {
* Parslet responsible for parsing year parameter
*/
static class YearParslet implements ToDateParslet {
@Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
......@@ -156,26 +151,26 @@ class ToDateTokenizer {
case IYYY:
inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS,
params, formatTokenEnum);
// only necessary for Java1.6
if (inputFragmentStr.startsWith("+")) {
inputFragmentStr = inputFragmentStr.substring(1);
}
dateNr = Integer.parseInt(inputFragmentStr);
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
if (dateNr == 0) {
throwException(params, "Year may not be zero");
}
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1);
params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break;
case YYY:
case IYY:
inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS,
params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 999) {
throwException(params, "Year may have only three digits with specified format");
}
dateNr += (params.getCurrentYear() / 1_000) * 1_000;
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1);
params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break;
case RRRR:
inputFragmentStr = matchStringOrThrow(
......@@ -191,15 +186,14 @@ class ToDateTokenizer {
if (dateNr == 0) {
throwException(params, "Year may not be zero");
}
result.set(Calendar.YEAR, dateNr);
params.setYear(dateNr);
break;
case RR:
Calendar calendar = DateTimeUtils.createGregorianCalendar();
int cc = calendar.get(Calendar.YEAR) / 100;
int cc = params.getCurrentYear() / 100;
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) + cc * 100;
result.set(Calendar.YEAR, dateNr);
params.setYear(dateNr);
break;
case EE /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.",
......@@ -214,34 +208,38 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 99) {
throwException(params, "Year may have only two digits with specified format");
}
dateNr += (params.getCurrentYear() / 100) * 100;
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1);
params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break;
case SCC:
case CC:
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) * 100;
result.set(Calendar.YEAR, dateNr);
params.setYear(dateNr);
break;
case Y:
case I:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 9) {
throwException(params, "Year may have only two digits with specified format");
}
dateNr += (params.getCurrentYear() / 10) * 10;
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1);
params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break;
case BC_AD:
inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params,
formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("B")) {
result.set(Calendar.ERA, GregorianCalendar.BC);
} else {
result.set(Calendar.ERA, GregorianCalendar.AD);
}
params.setBC(inputFragmentStr.toUpperCase().startsWith("B"));
break;
default:
throw new IllegalArgumentException(format(
......@@ -262,29 +260,26 @@ class ToDateTokenizer {
@Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) {
Calendar result = params.getResultCalendar();
String s = params.getInputStr();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case MONTH:
inputFragmentStr = setByName(result, params, Calendar.MONTH,
Calendar.LONG);
inputFragmentStr = setByName(params, ToChar.MONTHS);
break;
case Q /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.",
formatTokenEnum.name()));
break;
case MON:
inputFragmentStr = setByName(result, params, Calendar.MONTH,
Calendar.SHORT);
inputFragmentStr = setByName(params, ToChar.SHORT_MONTHS);
break;
case MM:
// Note: In Calendar Month go from 0 - 11
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MONTH, dateNr - 1);
params.setMonth(dateNr);
break;
case RM:
dateNr = 0;
......@@ -293,7 +288,7 @@ class ToDateTokenizer {
int len = monthName.length();
if (s.length() >= len && monthName
.equalsIgnoreCase(s.substring(0, len))) {
result.set(Calendar.MONTH, dateNr);
params.setMonth(dateNr + 1);
inputFragmentStr = monthName;
break;
}
......@@ -322,7 +317,6 @@ class ToDateTokenizer {
@Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
......@@ -330,40 +324,31 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_YEAR, dateNr);
params.setDayOfYear(dateNr);
break;
case DD:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr);
params.setDay(dateNr);
break;
case D:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr);
params.setDay(dateNr);
break;
case DAY:
inputFragmentStr = setByName(result, params,
Calendar.DAY_OF_WEEK, Calendar.LONG);
inputFragmentStr = setByName(params, ToChar.WEEKDAYS);
break;
case DY:
inputFragmentStr = setByName(result, params,
Calendar.DAY_OF_WEEK, Calendar.SHORT);
inputFragmentStr = setByName(params, ToChar.SHORT_WEEKDAYS);
break;
case J:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum);
try {
Date date = new SimpleDateFormat("Myydd")
.parse(inputFragmentStr);
result.setTime(date);
} catch (ParseException e) {
throwException(params,
format("Failed to parse Julian date: %s",
inputFragmentStr));
}
dateNr = Integer.parseInt(inputFragmentStr);
params.setAbsoluteDay(dateNr + ToChar.JULIAN_EPOCH);
break;
default:
throw new IllegalArgumentException(format(
......@@ -382,7 +367,6 @@ class ToDateTokenizer {
@Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
......@@ -390,97 +374,76 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, dateNr);
params.setHour(dateNr);
break;
case HH12:
case HH:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR, dateNr);
params.setHour12(dateNr);
break;
case MI:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MINUTE, dateNr);
params.setMinute(dateNr);
break;
case SS:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.SECOND, dateNr);
params.setSecord(dateNr);
break;
case SSSSS:
case SSSSS: {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, 0);
result.set(Calendar.MINUTE, 0);
result.set(Calendar.SECOND, dateNr);
int second = dateNr % 60;
dateNr /= 60;
int minute = dateNr % 60;
dateNr /= 60;
int hour = dateNr % 24;
params.setHour(hour);
params.setMinute(minute);
params.setSecord(second);
break;
}
case FF:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum);
String paddedRightNrStr = format("%-9s", inputFragmentStr)
.replace(' ', '0');
paddedRightNrStr = paddedRightNrStr.substring(0, 9);
Double nineDigits = Double.parseDouble(paddedRightNrStr);
params.setNanos(nineDigits.intValue());
dateNr = (int) Math.round(nineDigits / 1000000.0);
result.set(Calendar.MILLISECOND, dateNr);
double nineDigits = Double.parseDouble(paddedRightNrStr);
params.setNanos((int) nineDigits);
break;
case AM_PM:
inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params,
formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("A")) {
result.set(Calendar.AM_PM, Calendar.AM);
params.setAmPm(true);
} else {
result.set(Calendar.AM_PM, Calendar.PM);
params.setAmPm(false);
}
break;
case TZH:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
TimeZone tz = result.getTimeZone();
int offsetMillis = tz.getRawOffset();
// purge min and sec
offsetMillis = (offsetMillis / MILLIS_IN_HOUR) * MILLIS_IN_HOUR;
tz.setRawOffset(offsetMillis + dateNr);
result.setTimeZone(tz);
params.setTimeZoneHour(dateNr);
break;
case TZM:
inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr);
tz = result.getTimeZone();
offsetMillis = tz.getRawOffset();
// purge hour
offsetMillis = offsetMillis % MILLIS_IN_HOUR;
tz.setRawOffset(dateNr * MILLIS_IN_HOUR + offsetMillis);
result.setTimeZone(tz);
params.setTimeZoneMinute(dateNr);
break;
case TZR:
// Example: US/Pacific
String s = params.getInputStr();
tz = result.getTimeZone();
for (String tzName : TimeZone.getAvailableIDs()) {
int length = tzName.length();
if (s.length() >= length && tzName
.equalsIgnoreCase(s.substring(0, length))) {
tz.setID(tzName);
result.setTimeZone(tz);
inputFragmentStr = tzName;
break;
}
}
break;
case TZD:
// Must correspond with TZR region. Example: PST (for US/Pacific
// standard time)
throwException(params, format("token '%s' not supported yet.",
formatTokenEnum.name()));
String tzName = params.getInputStr();
params.setTimeZone(TimeZone.getTimeZone(tzName));
inputFragmentStr = tzName;
break;
default:
throw new IllegalArgumentException(format(
......@@ -535,22 +498,33 @@ class ToDateTokenizer {
/**
* Set the given field in the calendar.
*
* @param c the calendar
* @param params the parameters with the input string
* @param field the field to set
* @param style the data type
* @return the matched value
*/
static String setByName(Calendar c, ToDateParser params, int field,
int style) {
static String setByName(ToDateParser params, int field) {
String inputFragmentStr = null;
String s = params.getInputStr();
Map<String, Integer> timeStringMap = c.getDisplayNames(field, style,
Locale.getDefault());
for (String dayName : timeStringMap.keySet()) {
String[] values = ToChar.getNames(field);
for (int i = 0; i < values.length; i++) {
String dayName = values[i];
if (dayName == null) {
continue;
}
int len = dayName.length();
if (dayName.equalsIgnoreCase(s.substring(0, len))) {
c.set(field, timeStringMap.get(dayName));
switch (field) {
case ToChar.MONTHS:
case ToChar.SHORT_MONTHS:
params.setMonth(i + 1);
break;
case ToChar.WEEKDAYS:
case ToChar.SHORT_WEEKDAYS:
// TODO
break;
default:
throw new IllegalArgumentException();
}
inputFragmentStr = dayName;
break;
}
......@@ -558,7 +532,7 @@ class ToDateTokenizer {
if (inputFragmentStr == null || inputFragmentStr.isEmpty()) {
throwException(params, format(
"Tried to parse one of '%s' but failed (may be an internal error?)",
timeStringMap.keySet()));
Arrays.toString(values)));
}
return inputFragmentStr;
}
......
......@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Properties;
......@@ -56,6 +57,8 @@ import org.h2.util.StringUtils;
import org.h2.util.ToChar.Capitalization;
import org.h2.util.ToDateParser;
import org.h2.value.Value;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/**
* Tests for user defined functions and aggregates.
......@@ -1296,10 +1299,13 @@ public class TestFunctions extends TestBase implements AggregateFunction {
}
private void testToDate() throws ParseException {
final int month = DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH);
Date date = null;
date = new SimpleDateFormat("yyyy-MM-dd").parse("1979-11-12");
GregorianCalendar calendar = DateTimeUtils.createGregorianCalendar();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1;
// Default date in Oracle is the first day of the current month
String defDate = year + "-" + month + "-1 ";
ValueTimestamp date = null;
date = ValueTimestamp.parse("1979-11-12");
assertEquals(date, ToDateParser.toDate("1979-11-12T00:00:00Z", "YYYY-MM-DD\"T\"HH24:MI:SS\"Z\""));
assertEquals(date, ToDateParser.toDate("1979*foo*1112", "YYYY\"*foo*\"MM\"\"DD"));
assertEquals(date, ToDateParser.toDate("1979-11-12", "YYYY-MM-DD"));
......@@ -1309,8 +1315,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertEquals(date, ToDateParser.toDate("1979;11;12", "YYYY;MM;DD"));
assertEquals(date, ToDateParser.toDate("1979:11:12", "YYYY:MM:DD"));
date = new SimpleDateFormat("yyyy").parse("1979");
setMonth(date, month);
date = ValueTimestamp.parse("1979-" + month + "-01");
assertEquals(date, ToDateParser.toDate("1979", "YYYY"));
assertEquals(date, ToDateParser.toDate("1979 AD", "YYYY AD"));
assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY A.D."));
......@@ -1319,116 +1324,119 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertEquals(date, ToDateParser.toDate("+1979", "SYYYY"));
assertEquals(date, ToDateParser.toDate("79", "RRRR"));
date = new SimpleDateFormat("yyyy-mm").parse("1970-12");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "00:12:00");
assertEquals(date, ToDateParser.toDate("12", "MI"));
date = new SimpleDateFormat("yyyy-MM").parse("1970-11");
date = ValueTimestamp.parse("1970-11-01");
assertEquals(date, ToDateParser.toDate("11", "MM"));
assertEquals(date, ToDateParser.toDate("11", "Mm"));
assertEquals(date, ToDateParser.toDate("11", "mM"));
assertEquals(date, ToDateParser.toDate("11", "mm"));
assertEquals(date, ToDateParser.toDate("XI", "RM"));
date = new SimpleDateFormat("yyyy").parse("9");
setMonth(date, month);
int y = (year / 10) * 10 + 9;
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("9", "Y"));
assertEquals(date, ToDateParser.toDate("9", "I"));
date = new SimpleDateFormat("yyyy").parse("79");
setMonth(date, month);
y = (year / 100) * 100 + 79;
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("79", "YY"));
assertEquals(date, ToDateParser.toDate("79", "IY"));
date = new SimpleDateFormat("yyyy").parse("979");
setMonth(date, month);
y = (year / 1_000) * 1_000 + 979;
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("979", "YYY"));
assertEquals(date, ToDateParser.toDate("979", "IYY"));
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("yyy").parse("-99");
setMonth(date, month);
date = ValueTimestamp.parse("-99-" + month + "-01");
assertEquals(date, ToDateParser.toDate("0100 BC", "YYYY BC"));
assertEquals(date, ToDateParser.toDate("0100 B.C.", "YYYY B.C."));
assertEquals(date, ToDateParser.toDate("100 BC", "YYY BC"));
assertEquals(date, ToDateParser.toDate("-0100", "SYYYY"));
assertEquals(date, ToDateParser.toDate("-0100", "YYYY"));
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("y").parse("0");
setMonth(date, month);
y = -((year / 1_000) * 1_000 + 99);
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("100 BC", "YYY BC"));
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
y = -((year / 100) * 100);
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("01 BC", "YY BC"));
y = -((year / 10) * 10);
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("1 BC", "Y BC"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "08:12:00");
assertEquals(date, ToDateParser.toDate("08:12 AM", "HH:MI AM"));
assertEquals(date, ToDateParser.toDate("08:12 A.M.", "HH:MI A.M."));
assertEquals(date, ToDateParser.toDate("08:12", "HH24:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "08:12:00");
assertEquals(date, ToDateParser.toDate("08:12", "HH:MI"));
assertEquals(date, ToDateParser.toDate("08:12", "HH12:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:34");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "08:12:34");
assertEquals(date, ToDateParser.toDate("08:12:34", "HH:MI:SS"));
date = new SimpleDateFormat("ss").parse("34");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "12:00:00");
assertEquals(date, ToDateParser.toDate("12:00:00 PM", "HH12:MI:SS AM"));
date = ValueTimestamp.parse(defDate + "00:00:00");
assertEquals(date, ToDateParser.toDate("12:00:00 AM", "HH12:MI:SS AM"));
date = ValueTimestamp.parse(defDate + "00:00:34");
assertEquals(date, ToDateParser.toDate("34", "SS"));
date = new SimpleDateFormat("yyyy hh:mm:ss").parse("1970 08:12:34");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "08:12:34");
assertEquals(date, ToDateParser.toDate("29554", "SSSSS"));
date = new SimpleDateFormat("yyyy hh:mm:ss SSS").parse("1970 08:12:34 550");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "08:12:34.550");
assertEquals(date, ToDateParser.toDate("08:12:34 550", "HH:MI:SS FF"));
assertEquals(date, ToDateParser.toDate("08:12:34 55", "HH:MI:SS FF2"));
date = new SimpleDateFormat("hh:mm:ss").parse("14:04:00");
setMonth(date, month);
date = ValueTimestamp.parse(defDate + "14:04:00");
assertEquals(date, ToDateParser.toDate("02:04 P.M.", "HH:MI p.M."));
assertEquals(date, ToDateParser.toDate("02:04 PM", "HH:MI PM"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-12-12");
// does not work in all timezones
// assertEquals(date, ToDateParser.toDate("12", "DD"));
date = ValueTimestamp.parse("1970-" + month + "-12");
assertEquals(date, ToDateParser.toDate("12", "DD"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-11-12");
date = ValueTimestamp.parse(year + (calendar.isLeapYear(year) ? "11-11" : "-11-12"));
assertEquals(date, ToDateParser.toDate("316", "DDD"));
assertEquals(date, ToDateParser.toDate("316", "DdD"));
assertEquals(date, ToDateParser.toDate("316", "dDD"));
assertEquals(date, ToDateParser.toDate("316", "ddd"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("2013-01-29");
assertEquals(date, ToDateParser.toDate("113029", "J"));
date = ValueTimestamp.parse("2013-01-29");
assertEquals(date, ToDateParser.toDate("2456322", "J"));
if (Locale.getDefault() == Locale.ENGLISH) {
date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse("9999-12-31T23:59:59");
if (Locale.getDefault().getLanguage().equals("en")) {
date = ValueTimestamp.parse("9999-12-31 23:59:59");
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-YYYY HH24:MI:SS"));
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-RRRR HH24:MI:SS"));
SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
assertEquals(ymd.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR"));
assertEquals(ymd.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099", "DD-MON-RRRR"));
assertEquals(ymd.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00", "DD-MON-RRRR"));
assertEquals(ymd.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49", "DD-MON-RRRR"));
assertEquals(ymd.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50", "DD-MON-RRRR"));
assertEquals(ValueTimestamp.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR"));
}
}
private static void setMonth(Date date, int month) {
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(date);
c.set(Calendar.MONTH, month);
date.setTime(c.getTimeInMillis());
assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
ToDateParser.toTimestampTz("2000-05-10 10:11:12 -8:15", "YYYY-MM-DD HH24:MI:SS TZH:TZM"));
assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
ToDateParser.toTimestampTz("2000-05-10 10:11:12 GMT-08:15", "YYYY-MM-DD HH24:MI:SS TZR"));
assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08"),
ToDateParser.toTimestampTz("2000-02-10 10:11:12 US/Pacific", "YYYY-MM-DD HH24:MI:SS TZR"));
assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08"),
ToDateParser.toTimestampTz("2000-02-10 10:11:12 PST", "YYYY-MM-DD HH24:MI:SS TZD"));
}
private void testToCharFromDateTime() throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论