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 { ...@@ -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][];
...@@ -476,6 +476,44 @@ public class ToChar { ...@@ -476,6 +476,44 @@ public class ToChar {
return result[names]; 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. * Emulates Oracle's TO_CHAR(datetime) function.
* *
...@@ -755,15 +793,10 @@ public class ToChar { ...@@ -755,15 +793,10 @@ public class ToChar {
// Time zone // Time zone
} else if (containsAt(format, i, "TZR") != null) { } else if (containsAt(format, i, "TZR") != null) {
TimeZone tz = value instanceof ValueTimestampTimeZone ? output.append(getTimeZone(value, false));
((ValueTimestampTimeZone) value).getTimeZone() : TimeZone.getDefault();
output.append(tz.getID());
i += 3; i += 3;
} else if (containsAt(format, i, "TZD") != null) { } else if (containsAt(format, i, "TZD") != null) {
TimeZone tz = value instanceof ValueTimestampTimeZone ? output.append(getTimeZone(value, true));
((ValueTimestampTimeZone) value).getTimeZone() : TimeZone.getDefault();
boolean daylight = tz.inDaylightTime(new java.util.Date());
output.append(tz.getDisplayName(daylight, TimeZone.SHORT));
i += 3; i += 3;
// Week // Week
......
...@@ -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
...@@ -31,20 +56,6 @@ public class ToDateParser { ...@@ -31,20 +56,6 @@ public class ToDateParser {
* code) * code)
*/ */
private ToDateParser(ConfigParam functionName, String input, String format) { 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; this.functionName = functionName;
inputStr = input.trim(); inputStr = input.trim();
// Keep a copy // Keep a copy
...@@ -59,30 +70,65 @@ public class ToDateParser { ...@@ -59,30 +70,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); parse(result);
return result; return result;
} }
private static ToDateParser getTimestampParser(String input, String format) { private ValueTimestamp getResultingValue() {
ToDateParser result = new ToDateParser(ConfigParam.TO_TIMESTAMP, input, format); long dateValue;
parse(result); if (absoluteDayValid) {
return result; dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay);
} } else {
int year = this.year;
private Timestamp getResultingTimestamp() { if (year == 0) {
Calendar cal = (Calendar) getResultCalendar().clone(); year = getCurrentYear();
int nanosToSet = nanos == null ? }
cal.get(Calendar.MILLISECOND) * 1000000 : nanos.intValue(); if (bc) {
cal.set(Calendar.MILLISECOND, 0); year = 1 - year;
Timestamp ts = new Timestamp(cal.getTimeInMillis()); }
ts.setNanos(nanosToSet); if (doyValid) {
return ts; 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 +143,111 @@ public class ToDateParser { ...@@ -97,10 +143,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 +327,21 @@ public class ToDateParser { ...@@ -180,9 +327,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 +351,9 @@ public class ToDateParser { ...@@ -192,9 +351,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 +361,8 @@ public class ToDateParser { ...@@ -202,7 +361,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) {
......
...@@ -6,17 +6,12 @@ ...@@ -6,17 +6,12 @@
package org.h2.util; package org.h2.util;
import static java.lang.String.format; import static java.lang.String.format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
...@@ -144,38 +139,36 @@ class ToDateTokenizer { ...@@ -144,38 +139,36 @@ class ToDateTokenizer {
* Parslet responsible for parsing year parameter * Parslet responsible for parsing year parameter
*/ */
static class YearParslet implements ToDateParslet { static class YearParslet implements ToDateParslet {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
case SYYYY: case SYYYY:
case YYYY: case YYYY:
case IYYY:
inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
// only necessary for Java1.6
if (inputFragmentStr.startsWith("+")) {
inputFragmentStr = inputFragmentStr.substring(1);
}
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
if (dateNr == 0) { if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
} }
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case YYY: case YYY:
case IYY:
inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); 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. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 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; break;
case RRRR: case RRRR:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
...@@ -191,15 +184,14 @@ class ToDateTokenizer { ...@@ -191,15 +184,14 @@ class ToDateTokenizer {
if (dateNr == 0) { if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
} }
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case RR: case RR:
Calendar calendar = DateTimeUtils.createGregorianCalendar(); int cc = params.getCurrentYear() / 100;
int cc = calendar.get(Calendar.YEAR) / 100;
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) + cc * 100; dateNr = Integer.parseInt(inputFragmentStr) + cc * 100;
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case EE /* NOT supported yet */: case EE /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.", throwException(params, format("token '%s' not supported yet.",
...@@ -210,38 +202,40 @@ class ToDateTokenizer { ...@@ -210,38 +202,40 @@ class ToDateTokenizer {
formatTokenEnum.name())); formatTokenEnum.name()));
break; break;
case YY: case YY:
case IY:
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); 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. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 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; break;
case SCC: case SCC:
case CC: case CC:
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) * 100; dateNr = Integer.parseInt(inputFragmentStr) * 100;
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case Y: case Y:
case I:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); 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. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 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; break;
case BC_AD: case BC_AD:
inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params, inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params,
formatTokenEnum); formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("B")) { params.setBC(inputFragmentStr.toUpperCase().startsWith("B"));
result.set(Calendar.ERA, GregorianCalendar.BC);
} else {
result.set(Calendar.ERA, GregorianCalendar.AD);
}
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -262,29 +256,26 @@ class ToDateTokenizer { ...@@ -262,29 +256,26 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String s = params.getInputStr(); String s = params.getInputStr();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
case MONTH: case MONTH:
inputFragmentStr = setByName(result, params, Calendar.MONTH, inputFragmentStr = setByName(params, ToChar.MONTHS);
Calendar.LONG);
break; break;
case Q /* NOT supported yet */: case Q /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.", throwException(params, format("token '%s' not supported yet.",
formatTokenEnum.name())); formatTokenEnum.name()));
break; break;
case MON: case MON:
inputFragmentStr = setByName(result, params, Calendar.MONTH, inputFragmentStr = setByName(params, ToChar.SHORT_MONTHS);
Calendar.SHORT);
break; break;
case MM: case MM:
// Note: In Calendar Month go from 0 - 11 // Note: In Calendar Month go from 0 - 11
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MONTH, dateNr - 1); params.setMonth(dateNr);
break; break;
case RM: case RM:
dateNr = 0; dateNr = 0;
...@@ -293,7 +284,7 @@ class ToDateTokenizer { ...@@ -293,7 +284,7 @@ class ToDateTokenizer {
int len = monthName.length(); int len = monthName.length();
if (s.length() >= len && monthName if (s.length() >= len && monthName
.equalsIgnoreCase(s.substring(0, len))) { .equalsIgnoreCase(s.substring(0, len))) {
result.set(Calendar.MONTH, dateNr); params.setMonth(dateNr + 1);
inputFragmentStr = monthName; inputFragmentStr = monthName;
break; break;
} }
...@@ -322,7 +313,6 @@ class ToDateTokenizer { ...@@ -322,7 +313,6 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
...@@ -330,40 +320,31 @@ class ToDateTokenizer { ...@@ -330,40 +320,31 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_YEAR, dateNr); params.setDayOfYear(dateNr);
break; break;
case DD: case DD:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr); params.setDay(dateNr);
break; break;
case D: case D:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr); params.setDay(dateNr);
break; break;
case DAY: case DAY:
inputFragmentStr = setByName(result, params, inputFragmentStr = setByName(params, ToChar.WEEKDAYS);
Calendar.DAY_OF_WEEK, Calendar.LONG);
break; break;
case DY: case DY:
inputFragmentStr = setByName(result, params, inputFragmentStr = setByName(params, ToChar.SHORT_WEEKDAYS);
Calendar.DAY_OF_WEEK, Calendar.SHORT);
break; break;
case J: case J:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
try { dateNr = Integer.parseInt(inputFragmentStr);
Date date = new SimpleDateFormat("Myydd") params.setAbsoluteDay(dateNr + ToChar.JULIAN_EPOCH);
.parse(inputFragmentStr);
result.setTime(date);
} catch (ParseException e) {
throwException(params,
format("Failed to parse Julian date: %s",
inputFragmentStr));
}
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -382,7 +363,6 @@ class ToDateTokenizer { ...@@ -382,7 +363,6 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
...@@ -390,97 +370,76 @@ class ToDateTokenizer { ...@@ -390,97 +370,76 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, dateNr); params.setHour(dateNr);
break; break;
case HH12: case HH12:
case HH: case HH:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR, dateNr); params.setHour12(dateNr);
break; break;
case MI: case MI:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MINUTE, dateNr); params.setMinute(dateNr);
break; break;
case SS: case SS:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.SECOND, dateNr); params.setSecord(dateNr);
break; break;
case SSSSS: case SSSSS: {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, 0); int second = dateNr % 60;
result.set(Calendar.MINUTE, 0); dateNr /= 60;
result.set(Calendar.SECOND, dateNr); int minute = dateNr % 60;
dateNr /= 60;
int hour = dateNr % 24;
params.setHour(hour);
params.setMinute(minute);
params.setSecord(second);
break; break;
}
case FF: case FF:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
String paddedRightNrStr = format("%-9s", inputFragmentStr) String paddedRightNrStr = format("%-9s", inputFragmentStr)
.replace(' ', '0'); .replace(' ', '0');
paddedRightNrStr = paddedRightNrStr.substring(0, 9); paddedRightNrStr = paddedRightNrStr.substring(0, 9);
Double nineDigits = Double.parseDouble(paddedRightNrStr); double nineDigits = Double.parseDouble(paddedRightNrStr);
params.setNanos(nineDigits.intValue()); params.setNanos((int) nineDigits);
dateNr = (int) Math.round(nineDigits / 1000000.0);
result.set(Calendar.MILLISECOND, dateNr);
break; break;
case AM_PM: case AM_PM:
inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params, inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params,
formatTokenEnum); formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("A")) { if (inputFragmentStr.toUpperCase().startsWith("A")) {
result.set(Calendar.AM_PM, Calendar.AM); params.setAmPm(true);
} else { } else {
result.set(Calendar.AM_PM, Calendar.PM); params.setAmPm(false);
} }
break; break;
case TZH: case TZH:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
TimeZone tz = result.getTimeZone(); params.setTimeZoneHour(dateNr);
int offsetMillis = tz.getRawOffset();
// purge min and sec
offsetMillis = (offsetMillis / MILLIS_IN_HOUR) * MILLIS_IN_HOUR;
tz.setRawOffset(offsetMillis + dateNr);
result.setTimeZone(tz);
break; break;
case TZM: case TZM:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
tz = result.getTimeZone(); params.setTimeZoneMinute(dateNr);
offsetMillis = tz.getRawOffset();
// purge hour
offsetMillis = offsetMillis % MILLIS_IN_HOUR;
tz.setRawOffset(dateNr * MILLIS_IN_HOUR + offsetMillis);
result.setTimeZone(tz);
break; break;
case TZR: 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: case TZD:
// Must correspond with TZR region. Example: PST (for US/Pacific String tzName = params.getInputStr();
// standard time) params.setTimeZone(TimeZone.getTimeZone(tzName));
throwException(params, format("token '%s' not supported yet.", inputFragmentStr = tzName;
formatTokenEnum.name()));
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -535,22 +494,33 @@ class ToDateTokenizer { ...@@ -535,22 +494,33 @@ class ToDateTokenizer {
/** /**
* Set the given field in the calendar. * Set the given field in the calendar.
* *
* @param c the calendar
* @param params the parameters with the input string * @param params the parameters with the input string
* @param field the field to set * @param field the field to set
* @param style the data type
* @return the matched value * @return the matched value
*/ */
static String setByName(Calendar c, ToDateParser params, int field, static String setByName(ToDateParser params, int field) {
int style) {
String inputFragmentStr = null; String inputFragmentStr = null;
String s = params.getInputStr(); String s = params.getInputStr();
Map<String, Integer> timeStringMap = c.getDisplayNames(field, style, String[] values = ToChar.getNames(field);
Locale.getDefault()); for (int i = 0; i < values.length; i++) {
for (String dayName : timeStringMap.keySet()) { String dayName = values[i];
if (dayName == null) {
continue;
}
int len = dayName.length(); int len = dayName.length();
if (dayName.equalsIgnoreCase(s.substring(0, len))) { 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; inputFragmentStr = dayName;
break; break;
} }
...@@ -558,7 +528,7 @@ class ToDateTokenizer { ...@@ -558,7 +528,7 @@ class ToDateTokenizer {
if (inputFragmentStr == null || inputFragmentStr.isEmpty()) { if (inputFragmentStr == null || inputFragmentStr.isEmpty()) {
throwException(params, format( throwException(params, format(
"Tried to parse one of '%s' but failed (may be an internal error?)", "Tried to parse one of '%s' but failed (may be an internal error?)",
timeStringMap.keySet())); Arrays.toString(values)));
} }
return inputFragmentStr; return inputFragmentStr;
} }
...@@ -583,9 +553,10 @@ class ToDateTokenizer { ...@@ -583,9 +553,10 @@ class ToDateTokenizer {
YYYY(PARSLET_YEAR), YYYY(PARSLET_YEAR),
// 4-digit year with sign (- = B.C.) // 4-digit year with sign (- = B.C.)
SYYYY(PARSLET_YEAR), SYYYY(PARSLET_YEAR),
// 4-digit year based on the ISO standard (?) // 3-digit year
IYYY(PARSLET_YEAR), YYY(PARSLET_YEAR), IYY(PARSLET_YEAR), YY( YYY(PARSLET_YEAR),
PARSLET_YEAR), IY(PARSLET_YEAR), // 2-digit year
YY(PARSLET_YEAR),
// Two-digit century with with sign (- = B.C.) // Two-digit century with with sign (- = B.C.)
SCC(PARSLET_YEAR), SCC(PARSLET_YEAR),
// Two-digit century. // Two-digit century.
...@@ -638,7 +609,7 @@ class ToDateTokenizer { ...@@ -638,7 +609,7 @@ class ToDateTokenizer {
// NOT supported yet - // NOT supported yet -
// Abbreviated era name (Japanese Imperial, // Abbreviated era name (Japanese Imperial,
// ROC Official, and Thai Buddha calendars). // ROC Official, and Thai Buddha calendars).
E(PARSLET_YEAR), Y(PARSLET_YEAR), I(PARSLET_YEAR), E(PARSLET_YEAR), Y(PARSLET_YEAR),
// Quarter of year (1, 2, 3, 4; JAN-MAR = 1). // Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
Q(PARSLET_MONTH), Q(PARSLET_MONTH),
// Day of week (1-7). // Day of week (1-7).
......
...@@ -9,13 +9,10 @@ import java.math.BigDecimal; ...@@ -9,13 +9,10 @@ import java.math.BigDecimal;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.TimestampWithTimeZone; import org.h2.api.TimestampWithTimeZone;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.StringUtils;
/** /**
* Implementation of the TIMESTAMP WITH TIME ZONE data type. * Implementation of the TIMESTAMP WITH TIME ZONE data type.
...@@ -149,30 +146,6 @@ public class ValueTimestampTimeZone extends Value { ...@@ -149,30 +146,6 @@ public class ValueTimestampTimeZone extends Value {
return timeZoneOffsetMins; 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 @Override
public Timestamp getTimestamp() { public Timestamp getTimestamp() {
throw new UnsupportedOperationException("unimplemented"); throw new UnsupportedOperationException("unimplemented");
......
...@@ -34,6 +34,7 @@ import java.util.ArrayList; ...@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Currency; import java.util.Currency;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
...@@ -56,6 +57,8 @@ import org.h2.util.StringUtils; ...@@ -56,6 +57,8 @@ import org.h2.util.StringUtils;
import org.h2.util.ToChar.Capitalization; import org.h2.util.ToChar.Capitalization;
import org.h2.util.ToDateParser; import org.h2.util.ToDateParser;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/** /**
* Tests for user defined functions and aggregates. * Tests for user defined functions and aggregates.
...@@ -1296,10 +1299,13 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1296,10 +1299,13 @@ public class TestFunctions extends TestBase implements AggregateFunction {
} }
private void testToDate() throws ParseException { private void testToDate() throws ParseException {
final int month = DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH); GregorianCalendar calendar = DateTimeUtils.createGregorianCalendar();
int year = calendar.get(Calendar.YEAR);
Date date = null; int month = calendar.get(Calendar.MONTH) + 1;
date = new SimpleDateFormat("yyyy-MM-dd").parse("1979-11-12"); // 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-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*foo*1112", "YYYY\"*foo*\"MM\"\"DD"));
assertEquals(date, ToDateParser.toDate("1979-11-12", "YYYY-MM-DD")); assertEquals(date, ToDateParser.toDate("1979-11-12", "YYYY-MM-DD"));
...@@ -1309,126 +1315,124 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1309,126 +1315,124 @@ 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"));
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"); date = ValueTimestamp.parse("1979-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("1979", "YYYY")); assertEquals(date, ToDateParser.toDate("1979", "YYYY"));
assertEquals(date, ToDateParser.toDate("1979 AD", "YYYY AD")); assertEquals(date, ToDateParser.toDate("1979 AD", "YYYY AD"));
assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY A.D.")); assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY A.D."));
assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY BC")); assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY BC"));
assertEquals(date, ToDateParser.toDate("1979", "IYYY"));
assertEquals(date, ToDateParser.toDate("+1979", "SYYYY")); assertEquals(date, ToDateParser.toDate("+1979", "SYYYY"));
assertEquals(date, ToDateParser.toDate("79", "RRRR")); assertEquals(date, ToDateParser.toDate("79", "RRRR"));
date = new SimpleDateFormat("yyyy-mm").parse("1970-12"); date = ValueTimestamp.parse(defDate + "00:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("12", "MI")); 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("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")); assertEquals(date, ToDateParser.toDate("XI", "RM"));
date = new SimpleDateFormat("yyyy").parse("9"); int y = (year / 10) * 10 + 9;
setMonth(date, month); date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("9", "Y")); assertEquals(date, ToDateParser.toDate("9", "Y"));
assertEquals(date, ToDateParser.toDate("9", "I")); y = (year / 100) * 100 + 79;
date = new SimpleDateFormat("yyyy").parse("79"); date = ValueTimestamp.parse(y + "-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("79", "YY")); assertEquals(date, ToDateParser.toDate("79", "YY"));
assertEquals(date, ToDateParser.toDate("79", "IY")); y = (year / 1_000) * 1_000 + 979;
date = ValueTimestamp.parse(y + "-" + month + "-01");
date = new SimpleDateFormat("yyyy").parse("979");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("979", "YYY")); assertEquals(date, ToDateParser.toDate("979", "YYY"));
assertEquals(date, ToDateParser.toDate("979", "IYY"));
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("yyy").parse("-99"); date = ValueTimestamp.parse("-99-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("0100 BC", "YYYY BC")); assertEquals(date, ToDateParser.toDate("0100 BC", "YYYY BC"));
assertEquals(date, ToDateParser.toDate("0100 B.C.", "YYYY B.C.")); 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", "SYYYY"));
assertEquals(date, ToDateParser.toDate("-0100", "YYYY")); assertEquals(date, ToDateParser.toDate("-0100", "YYYY"));
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("y").parse("0"); y = -((year / 1_000) * 1_000 + 99);
setMonth(date, month); 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")); 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")); assertEquals(date, ToDateParser.toDate("1 BC", "Y BC"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00"); date = ValueTimestamp.parse(defDate + "08:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12 AM", "HH:MI AM")); 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 A.M.", "HH:MI A.M."));
assertEquals(date, ToDateParser.toDate("08:12", "HH24:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH24:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00"); date = ValueTimestamp.parse(defDate + "08:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12", "HH:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH:MI"));
assertEquals(date, ToDateParser.toDate("08:12", "HH12:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH12:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:34"); date = ValueTimestamp.parse(defDate + "08:12:34");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12:34", "HH:MI:SS")); assertEquals(date, ToDateParser.toDate("08:12:34", "HH:MI:SS"));
date = new SimpleDateFormat("ss").parse("34"); date = ValueTimestamp.parse(defDate + "12:00:00");
setMonth(date, month); 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")); assertEquals(date, ToDateParser.toDate("34", "SS"));
date = new SimpleDateFormat("yyyy hh:mm:ss").parse("1970 08:12:34"); date = ValueTimestamp.parse(defDate + "08:12:34");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("29554", "SSSSS")); assertEquals(date, ToDateParser.toDate("29554", "SSSSS"));
date = new SimpleDateFormat("yyyy hh:mm:ss SSS").parse("1970 08:12:34 550"); date = ValueTimestamp.parse(defDate + "08:12:34.550");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12:34 550", "HH:MI:SS FF")); assertEquals(date, ToDateParser.toDate("08:12:34 550", "HH:MI:SS FF"));
assertEquals(date, ToDateParser.toDate("08:12:34 55", "HH:MI:SS FF2")); assertEquals(date, ToDateParser.toDate("08:12:34 55", "HH:MI:SS FF2"));
date = new SimpleDateFormat("hh:mm:ss").parse("14:04:00"); date = ValueTimestamp.parse(defDate + "14:04:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("02:04 P.M.", "HH:MI p.M.")); assertEquals(date, ToDateParser.toDate("02:04 P.M.", "HH:MI p.M."));
assertEquals(date, ToDateParser.toDate("02:04 PM", "HH:MI PM")); assertEquals(date, ToDateParser.toDate("02:04 PM", "HH:MI PM"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-12-12"); date = ValueTimestamp.parse("1970-" + month + "-12");
// does not work in all timezones assertEquals(date, ToDateParser.toDate("12", "DD"));
// 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"));
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"); date = ValueTimestamp.parse("2013-01-29");
assertEquals(date, ToDateParser.toDate("113029", "J")); assertEquals(date, ToDateParser.toDate("2456322", "J"));
if (Locale.getDefault() == Locale.ENGLISH) { if (Locale.getDefault().getLanguage().equals("en")) {
date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse("9999-12-31T23:59:59"); 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-YYYY HH24:MI:SS"));
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-RRRR 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(ValueTimestamp.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR"));
assertEquals(ymd.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(ymd.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(ymd.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(ymd.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(ymd.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(ymd.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(ymd.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(ymd.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"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR"));
} }
}
private static void setMonth(Date date, int month) { assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
Calendar c = DateTimeUtils.createGregorianCalendar(); ToDateParser.toTimestampTz("2000-05-10 10:11:12 -8:15", "YYYY-MM-DD HH24:MI:SS TZH:TZM"));
c.setTime(date); assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
c.set(Calendar.MONTH, month); ToDateParser.toTimestampTz("2000-05-10 10:11:12 GMT-08:15", "YYYY-MM-DD HH24:MI:SS TZR"));
date.setTime(c.getTimeInMillis()); 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 { private void testToCharFromDateTime() throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论