提交 0c7d6634 authored 作者: Patrick Brielmayer's avatar Patrick Brielmayer

Added TO_DATE and TO_TIMESTAMP functionality

上级 990e81ce
...@@ -1001,6 +1001,12 @@ public class ErrorCode { ...@@ -1001,6 +1001,12 @@ public class ErrorCode {
*/ */
public static final int UNSUPPORTED_CIPHER = 90055; public static final int UNSUPPORTED_CIPHER = 90055;
/**
* The error with code <code>90056</code> is thrown when trying to format a
* timestamp using TO_DATE and TO_TIMESTAMP with an invalid format.
*/
public static final int INVALID_TO_DATE_FORMAT = 90056;
/** /**
* The error with code <code>90057</code> is thrown when * The error with code <code>90057</code> is thrown when
* trying to drop a constraint that does not exist. * trying to drop a constraint that does not exist.
...@@ -1923,7 +1929,7 @@ public class ErrorCode { ...@@ -1923,7 +1929,7 @@ public class ErrorCode {
public static final int STEP_SIZE_MUST_NOT_BE_ZERO = 90142; public static final int STEP_SIZE_MUST_NOT_BE_ZERO = 90142;
// next are 90056, 90110, 90122, 90143 // next are 90110, 90122, 90143
private ErrorCode() { private ErrorCode() {
// utility class // utility class
......
...@@ -44,16 +44,7 @@ import org.h2.table.Table; ...@@ -44,16 +44,7 @@ import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.tools.CompressTool; import org.h2.tools.CompressTool;
import org.h2.tools.Csv; import org.h2.tools.Csv;
import org.h2.util.AutoCloseInputStream; import org.h2.util.*;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.ToChar;
import org.h2.util.Utils;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
...@@ -93,7 +84,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -93,7 +84,8 @@ public class Function extends Expression implements FunctionCall {
STRINGDECODE = 80, STRINGTOUTF8 = 81, UTF8TOSTRING = 82, STRINGDECODE = 80, STRINGTOUTF8 = 81, UTF8TOSTRING = 82,
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;
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,
...@@ -306,6 +298,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -306,6 +298,8 @@ public class Function extends Expression implements FunctionCall {
0, Value.DATE); 0, Value.DATE);
addFunctionNotDeterministic("CURDATE", CURDATE, addFunctionNotDeterministic("CURDATE", CURDATE,
0, Value.DATE); 0, Value.DATE);
addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.STRING);
addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.STRING);
// alias for MSSQLServer // alias for MSSQLServer
addFunctionNotDeterministic("GETDATE", CURDATE, addFunctionNotDeterministic("GETDATE", CURDATE,
0, Value.DATE); 0, Value.DATE);
...@@ -1429,6 +1423,14 @@ public class Function extends Expression implements FunctionCall { ...@@ -1429,6 +1423,14 @@ public class Function extends Expression implements FunctionCall {
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
} }
break; break;
case TO_DATE:
result = ValueTimestamp.get(ToDate.TO_DATE(v0.getString(),
v1 == null ? null : v1.getString()));
break;
case TO_TIMESTAMP:
result = ValueTimestamp.get(ToDate.TO_TIMESTAMP(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();
...@@ -2123,10 +2125,12 @@ public class Function extends Expression implements FunctionCall { ...@@ -2123,10 +2125,12 @@ public class Function extends Expression implements FunctionCall {
case ROUND: case ROUND:
case XMLTEXT: case XMLTEXT:
case TRUNCATE: case TRUNCATE:
case TO_TIMESTAMP:
min = 1; min = 1;
max = 2; max = 2;
break; break;
case TO_CHAR: case TO_CHAR:
case TO_DATE:
min = 1; min = 1;
max = 3; max = 3;
break; break;
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=Skalární vnořený dotaz obsahuje více než jeden řádek 90053=Skalární vnořený dotaz obsahuje více než jeden řádek
90054=Neplatné použití agregátní funkce {0} 90054=Neplatné použití agregátní funkce {0}
90055=Nepodporované šifrování {0} 90055=Nepodporované šifrování {0}
90056=Function {0}: Invalid date format: {1}
90057=Omezení {0} nenalezeno 90057=Omezení {0} nenalezeno
90058=Vkládání nebo vrácení změn není povoleno uvnitř triggeru 90058=Vkládání nebo vrácení změn není povoleno uvnitř triggeru
90059=Dvojsmyslný název sloupce {0} 90059=Dvojsmyslný název sloupce {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=Skalar-Unterabfrage enthält mehr als eine Zeile 90053=Skalar-Unterabfrage enthält mehr als eine Zeile
90054=Ungültige Verwendung der Aggregat Funktion {0} 90054=Ungültige Verwendung der Aggregat Funktion {0}
90055=Chiffre nicht unterstützt: {0} 90055=Chiffre nicht unterstützt: {0}
90056=Funktion {0}: Ungültiges TO_DATE Format: {1}
90057=Bedingung {0} nicht gefunden 90057=Bedingung {0} nicht gefunden
90058=Innerhalb eines Triggers sind Commit und Rollback ist nicht erlaubt 90058=Innerhalb eines Triggers sind Commit und Rollback ist nicht erlaubt
90059=Mehrdeutiger Feldname {0} 90059=Mehrdeutiger Feldname {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=Scalar subquery contains more than one row 90053=Scalar subquery contains more than one row
90054=Invalid use of aggregate function {0} 90054=Invalid use of aggregate function {0}
90055=Unsupported cipher {0} 90055=Unsupported cipher {0}
90056=Function {0}: Invalid date format: {1}
90057=Constraint {0} not found 90057=Constraint {0} not found
90058=Commit or rollback is not allowed within a trigger 90058=Commit or rollback is not allowed within a trigger
90059=Ambiguous column name {0} 90059=Ambiguous column name {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=El Subquery escalar contiene mas de una fila 90053=El Subquery escalar contiene mas de una fila
90054=Uso Invalido de la función de columna agregada {0} 90054=Uso Invalido de la función de columna agregada {0}
90055=Cipher No soportado {0} 90055=Cipher No soportado {0}
90056=Function {0}: Invalid date format: {1}
90057=Constraint {0} no encontrado 90057=Constraint {0} no encontrado
90058=Commit ó rollback no permitido dentro de un trigger 90058=Commit ó rollback no permitido dentro de un trigger
90059=Nombre de columna ambigua {0} 90059=Nombre de columna ambigua {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=数値サブクエリ�複数�行を�ん���� 90053=数値サブクエリ�複数�行を�ん����
90054=集約関数 {0} ��正�使用 90054=集約関数 {0} ��正�使用
90055={0} �未サ�ート�暗��� 90055={0} �未サ�ート�暗���
90056=Function {0}: Invalid date format: {1}
90057=制約 {0} �見��り��ん 90057=制約 {0} �見��り��ん
90058=トリガ内��コミット�ロール�ック�許�れ����ん 90058=トリガ内��コミット�ロール�ック�許�れ����ん
90059=列� {0} ������� 90059=列� {0} �������
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=Skalarna pod-kwerenda zawiera więcej niż jeden wiersz 90053=Skalarna pod-kwerenda zawiera więcej niż jeden wiersz
90054=Nieprawidłowe użycie funkcji agregującej {0} 90054=Nieprawidłowe użycie funkcji agregującej {0}
90055=Nieobsługiwany szyfr {0} 90055=Nieobsługiwany szyfr {0}
90056=Function {0}: Invalid date format: {1}
90057=Ograniczenie {0} nie istnieje 90057=Ograniczenie {0} nie istnieje
90058=Zatwierdzenie lub wycofanie transakcji nie jest dozwolone w wyzwalaczu 90058=Zatwierdzenie lub wycofanie transakcji nie jest dozwolone w wyzwalaczu
90059=Niejednoznaczna nazwa kolumny {0} 90059=Niejednoznaczna nazwa kolumny {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=A Subquery contém mais de uma linha 90053=A Subquery contém mais de uma linha
90054=Uso inválido da função {0} agregada 90054=Uso inválido da função {0} agregada
90055=Cipher {0} não é suportado 90055=Cipher {0} não é suportado
90056=Function {0}: Invalid date format: {1}
90057=Restrição {0} não foi encontrada 90057=Restrição {0} não foi encontrada
90058=#Commit or rollback is not allowed within a trigger 90058=#Commit or rollback is not allowed within a trigger
90059=Nome da coluna {0} é ambíguo. 90059=Nome da coluna {0} é ambíguo.
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=Подзапро� выбирает более одной �троки 90053=Подзапро� выбирает более одной �троки
90054=�екорректное и�пользование агрегирующей функции {0} 90054=�екорректное и�пользование агрегирующей функции {0}
90055=Метод шифровани� {0} не поддерживает�� 90055=Метод шифровани� {0} не поддерживает��
90056=Function {0}: Invalid date format: {1}
90057=Ограничение {0} не найдено 90057=Ограничение {0} не найдено
90058=Commit или rollback внутри триггера не допу�кает�� 90058=Commit или rollback внутри триггера не допу�кает��
90059=�еоднозначное им� �толбца {0} 90059=�еоднозначное им� �толбца {0}
......
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
90053=标��查询(Scalar subquery)包�多于一行结果 90053=标��查询(Scalar subquery)包�多于一行结果
90054=�法使用��函数 {0} 90054=�法使用��函数 {0}
90055=�支�的加密算法 {0} 90055=�支�的加密算法 {0}
90056=Function {0}: Invalid date format: {1}
90057=约� {0} 找�到 90057=约� {0} 找�到
90058=�交或回滚�能办函触�器 90058=�交或回滚�能办函触�器
90059=�明确的字段� {0} 90059=�明确的字段� {0}
......
...@@ -10,11 +10,9 @@ package org.h2.util; ...@@ -10,11 +10,9 @@ package org.h2.util;
import java.sql.Date; import java.sql.Date;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.*;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -583,6 +581,59 @@ public class DateTimeUtils { ...@@ -583,6 +581,59 @@ public class DateTimeUtils {
} }
} }
@SuppressWarnings("serial")
private static final Map<String, String> DATE_FORMAT_REGEXPS = new HashMap<String, String>() {
{
put("^\\d{8}[^\\d]*", "yyyyMMdd");
put("^\\d{1,2}\\.\\d{1,2}\\.\\d{4}[^\\d]*", "dd.MM.yyyy");
put("^\\d{4}\\.\\d{1,2}\\.\\d{1,2}[^\\d]*", "yyyy.MM.dd");
put("^\\d{4}-\\d{1,2}-\\d{1,2}[^\\d]*", "yyyy-MM-dd");
put("^\\d{1,2}-\\d{1,2}-\\d{4}[^\\d]*", "dd-MM-yyyy");
put("^\\d{1,2}/\\d{1,2}/\\d{4}[^\\d]*", "dd/MM/yyyy");
put("^\\d{4}/\\d{1,2}/\\d{1,2}[^\\d]*", "yyyy/MM/dd");
put("^\\d{1,2}/\\d{1,2}/\\d{4}[^\\d]*", "dd/MM/yyyy");
put("^\\d{1,2}-[^\\d]{3}-\\d{4}[^\\d]*", "dd-MMM-yyyy");
put("^\\d{4}-[^\\d]{3}-\\d{1,2}[^\\d]*", "yyyy-MMM-dd");
put("^.{2}\\s.{3}\\s\\d{1,2}\\s\\d{1,2}\\:\\d{1,2}\\:\\d{1,2}\\s.{3}\\s\\d{4}$",
"EEE MMM dd hh:mm:ss z yyyy");
}
};
/**
* Determine SimpleDateFormat pattern matching with the given date string. Returns null if format is unknown. You can
* simply extend DateUtil with more formats if needed.
*
* @param dateString
* The date string to determine the SimpleDateFormat pattern for.
* @return The matching SimpleDateFormat pattern, or null if format is unknown.
*/
private static String determineDateFormat(final String dateString) {
for (String regexp : DATE_FORMAT_REGEXPS.keySet()) {
if (dateString.toLowerCase().matches(regexp)) {
return DATE_FORMAT_REGEXPS.get(regexp);
}
}
return null; // Unknown format.
}
/**
* Parse date-string in "Best Effort" (BE) manner.
* Uses a predefined list of date patterns to parse a string into a date.
*/
public static java.util.Date parseDateBestEffort(final String dateStr) {
String dateFormat = determineDateFormat(dateStr);
if (dateFormat == null) {
// The source does not contain a date that can be parsed.
throw DbException.get(ErrorCode.PARSE_ERROR_1, "Invalid date. " + dateStr);
}
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
try {
return formatter.parse(dateStr);
} catch (ParseException e) {
throw DbException.get(ErrorCode.PARSE_ERROR_1, e, "Invalid date. " + dateStr);
}
}
/** /**
* Parses a date using a format string. * Parses a date using a format string.
* *
......
package org.h2.util;
import java.sql.Timestamp;
import java.util.List;
import org.h2.util.ToDateTokenizer.FormatTokenEnum;
/**
* Emulates Oracle's TO_DATE function.<br>
* Main class
*/
public class ToDate {
public enum ToDateFunctionName {
TO_DATE("DD MON YYYY"), TO_TIMESTAMP("DD MON YYYY HH:MI:SS");
private final String defaultFormatStr;
ToDateFunctionName(final String defaultFormatStr) {
this.defaultFormatStr = defaultFormatStr;
}
String getDefaultFormatStr() {
return defaultFormatStr;
}
};
public static Timestamp TO_DATE(final String input, final String format) {
ToDateParams parsed = parse(new ToDateParams(ToDateFunctionName.TO_DATE, input, format));
return parsed.getResultingTimestamp();
}
public static Timestamp TO_TIMESTAMP(final String input, final String format) {
ToDateParams parsed = parse(new ToDateParams(ToDateFunctionName.TO_TIMESTAMP, input, format));
return parsed.getResultingTimestamp();
}
/**
* Parse the format-string with passed token of {@link FormatTokenEnum}}.<br>
* if token matches return true otherwise false.
*/
private static ToDateParams parse(final ToDateParams p) {
while (p.hasToParseData()) {
List<FormatTokenEnum> tokenList = FormatTokenEnum.getTokensInQuestion(p);
if (tokenList.isEmpty()) {
p.removeFirstChar();
continue;
}
boolean foundAnToken = false;
for (FormatTokenEnum token : tokenList) {
if (token.parseFormatStrWithToken(p)) {
foundAnToken = true;
break;
}
}
if (!foundAnToken) {
p.removeFirstChar();
continue;
}
}
return p;
}
}
\ No newline at end of file
package org.h2.util;
import static java.lang.String.format;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Calendar;
import org.h2.util.ToDate.ToDateFunctionName;
/**
* Emulates Oracle's TO_DATE function.<br>
* This class holds and handles the input data form the TO_DATE-method
*/
class ToDateParams {
private final String unmodifiedInputStr;
private final String unmodifiedFormatStr;
private final ToDateFunctionName functionName;
private String inputStr;
private String formatStr;
private final Calendar resultCalendar = (Calendar) Calendar.getInstance().clone();
private Integer nanos = null;
/**
* @param input the input date with the date-time info
* @param format the format of date-time info
* @param functionName one of [TO_DATE, TO_TIMESTAMP] (both share the same code)
*/
ToDateParams(final ToDateFunctionName functionName, final String input, final String format) {
// reset calendar - default oracle behaviour
resultCalendar.set(Calendar.YEAR, 1970);
resultCalendar.set(Calendar.MONTH, Calendar.getInstance().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();
unmodifiedInputStr = inputStr; // Keep a copy
if (format == null || format.isEmpty()) {
formatStr = functionName.getDefaultFormatStr(); // default Oracle format.
} else {
formatStr = format.trim();
}
unmodifiedFormatStr = formatStr; // Keep a copy
}
Date getResultingDate() {
return new Date(getResultCalendar().getTimeInMillis());
}
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;
}
Calendar getResultCalendar() {
return resultCalendar;
}
String getInputStr() {
return inputStr;
}
String getFormatStr() {
return formatStr;
}
ToDateFunctionName getFunctionName() {
return functionName;
}
void setNanos(final int nanos) {
this.nanos = nanos;
}
boolean hasToParseData() {
return formatStr.length() > 0;
}
void removeFirstChar() {
if (!formatStr.isEmpty()) {
formatStr = formatStr.substring(1);
}
if (!inputStr.isEmpty()) {
inputStr = inputStr.substring(1);
}
}
void remove(final String toIgnore) {
if (toIgnore != null) {
int trimLeng = toIgnore.length();
formatStr = formatStr.substring(trimLeng);
if (inputStr.length() >= trimLeng) {
inputStr = inputStr.substring(trimLeng);
}
}
}
void remove(final String intputFragmentStr, final String formatFragment) {
if (intputFragmentStr != null && inputStr.length() >= intputFragmentStr.length()) {
inputStr = inputStr.substring(intputFragmentStr.length());
}
if (formatFragment != null && formatStr.length() >= formatFragment.length()) {
formatStr = formatStr.substring(formatFragment.length());
}
}
@Override
public String toString() {
int inputStrLeng = inputStr.length();
int orgInputLeng = unmodifiedInputStr.length();
int currentInputPos = orgInputLeng - inputStrLeng;
int restInputLeng = inputStrLeng <= 0 ? inputStrLeng : inputStrLeng - 1;
int orgFormatLeng = unmodifiedFormatStr.length();
int currentFormatPos = orgFormatLeng - formatStr.length();
StringBuilder sb = new StringBuilder();
sb.append(format("\n %s('%s', '%s')", functionName, unmodifiedInputStr, unmodifiedFormatStr));
sb.append(format("\n %s^%s , %s^ <-- Parsing failed at this point", //
format("%" + (functionName.name().length() + currentInputPos) + "s", ""),
restInputLeng <= 0 ? "" : format("%" + restInputLeng + "s", ""),
currentFormatPos <= 0 ? "" : format("%" + currentFormatPos + "s", "")));
return sb.toString();
}
}
\ No newline at end of file
package org.h2.util;
import static java.lang.String.format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.h2.api.ErrorCode;
import org.h2.message.DbException;
/**
* Emulates Oracle's TO_DATE function.<br>
* This class knows all about the TO_DATE-format conventions and how to parse the corresponding data
*/
class ToDateTokenizer {
private static final Pattern PATTERN_Number = Pattern.compile("^([+-]?[0-9]+)");
private static final Pattern PATTERN_4_Digit = Pattern.compile("^([+-]?[0-9]{4})");
private static final Pattern PATTERN_3_Digit = Pattern.compile("^([+-]?[0-9]{3})");
private static final Pattern PATTERN_2_Digit = Pattern.compile("^([+-]?[0-9]{2})");
private static final Pattern PATTERN_2_DigitOrLess = Pattern.compile("^([+-]?[0-9][0-9]?)");
private static final Pattern PATTERN_1_Digit = Pattern.compile("^([+-]?[0-9])");
private static final Pattern PATTERN_FF = Pattern.compile("^(FF[0-9]?)", Pattern.CASE_INSENSITIVE);
private static final Pattern PATTERN_AM_PM = Pattern.compile("^(AM|A\\.M\\.|PM|P\\.M\\.)", Pattern.CASE_INSENSITIVE);
private static final Pattern PATTERN_BC_AD = Pattern.compile("^(BC|B\\.C\\.|AD|A\\.D\\.)", Pattern.CASE_INSENSITIVE);
private static final YearParslet PARSLET_Year = new YearParslet();
private static final MonthParslet PARSLET_Month = new MonthParslet();
//private static final WeekParslet PARSLET_Week = new WeekParslet();
private static final DayParslet PARSLET_Day = new DayParslet();
private static final TimeParslet PARSLET_Time = new TimeParslet();
static enum FormatTokenEnum {
YYYY(PARSLET_Year) // 4-digit year
, SYYYY(PARSLET_Year) // 4-digit year with sign (- = B.C.)
, IYYY(PARSLET_Year) // 4-digit year based on the ISO standard (?)
, YYY(PARSLET_Year) //
, IYY(PARSLET_Year) //
, YY(PARSLET_Year) //
, IY(PARSLET_Year) //
, SCC(PARSLET_Year) // Two-digit century with with sign (- = B.C.)
, CC(PARSLET_Year) // Two-digit century.
, RRRR(PARSLET_Year) // 2-digit -> 4-digit year 0-49 -> 20xx , 50-99 -> 19xx
, RR(PARSLET_Year) // last 2-digit of the year using "current" century value.
, BC_AD(PARSLET_Year, PATTERN_BC_AD) // Meridian indicator
, MONTH(PARSLET_Month) // Full Name of month
, MON(PARSLET_Month) // Abbreviated name of month.
, MM(PARSLET_Month) // Month (01-12; JAN = 01).
, RM(PARSLET_Month) // Roman numeral month (I-XII; JAN = I).
//, WW(PARSLET_Week) // Week of year (1-53)
//, IW(PARSLET_Week) // Week of year (1-52 or 1-53) based on the ISO standard.
, DDD(PARSLET_Day) // Day of year (1-366).
, DAY(PARSLET_Day) // Name of day.
, DD(PARSLET_Day) // Day of month (1-31).
, DY(PARSLET_Day) // Abbreviated name of day.
, HH24(PARSLET_Time) //
, HH12(PARSLET_Time) //
, HH(PARSLET_Time) // Hour of day (1-12).
, MI(PARSLET_Time) // Min
, SSSSS(PARSLET_Time) // Seconds past midnight (0-86399)
, SS(PARSLET_Time) //
, FF(PARSLET_Time, PATTERN_FF) // Fractional seconds
, TZH(PARSLET_Time) // Time zone hour.
, TZM(PARSLET_Time) // Time zone minute.
, TZR(PARSLET_Time) // Time zone region ID
, TZD(PARSLET_Time) // Daylight savings information. Example: PST (for US/Pacific standard time);
, AM_PM(PARSLET_Time, PATTERN_AM_PM) // Meridian indicator
, EE(PARSLET_Year) // NOT supported yet - Full era name (Japanese Imperial, ROC Official, and Thai Buddha calendars).
, E(PARSLET_Year) // NOT supported yet - Abbreviated era name (Japanese Imperial, ROC Official, and Thai Buddha calendars).
, Y(PARSLET_Year) //
, I(PARSLET_Year) //
, Q(PARSLET_Month) // Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
//, W(PARSLET_Week) // Week of month (1-5)
, D(PARSLET_Day) // Day of week (1-7).
, J(PARSLET_Day); // NOT supported yet - Julian day; the number of days since Jan 1, 4712 BC.
private final static Map<Character, List<FormatTokenEnum>> cache = new HashMap<Character, List<FormatTokenEnum>>(FormatTokenEnum.values().length);
private final ToDateParslet toDateParslet;
private final Pattern patternToUse;
FormatTokenEnum(final ToDateParslet toDateParslet, final Pattern patternToUse) {
this.toDateParslet = toDateParslet;
this.patternToUse = patternToUse;
}
FormatTokenEnum(final ToDateParslet toDateParslet) {
this.toDateParslet = toDateParslet;
patternToUse = Pattern.compile(format("^(%s)", name()), Pattern.CASE_INSENSITIVE);
}
private static List<FormatTokenEnum> EMPTY_LIST = new ArrayList<FormatTokenEnum>(0);
/**
* OPTIMISATION: Only return a list of {@link FormatTokenEnum} that share the same 1st char
* using the 1st char of the 'to parse' formatStr. Or return empty list if no match.
*/
static List<FormatTokenEnum> getTokensInQuestion(final ToDateParams params) {
List<FormatTokenEnum> result = EMPTY_LIST;
if (cache.size() <= 0) {
initCache();
}
String formatStr = params.getFormatStr();
if (formatStr != null && formatStr.length() > 0) {
Character key = Character.toUpperCase(formatStr.charAt(0));
result = cache.get(key);
}
if (result == null) {
result = EMPTY_LIST;
}
return result;
}
private static synchronized void initCache() {
if (cache.size() <= 0) {
for (FormatTokenEnum token : FormatTokenEnum.values()) {
List<Character> tokenKeys = new ArrayList<Character>();
if(token.name().contains("_")) {
String[] tokens = token.name().split("_");
for(String tokenLets : tokens) {
tokenKeys.add(tokenLets.toUpperCase().charAt(0));
}
} else {
tokenKeys.add(token.name().toUpperCase().charAt(0));
}
for(Character tokenKey : tokenKeys) {
List<FormatTokenEnum> l = cache.get(tokenKey);
if (l == null) {
l = new ArrayList<FormatTokenEnum>(1);
cache.put(tokenKey, l);
}
l.add(token);
}
}
}
}
/**
* Parse the format-string with passed token of {@link FormatTokenEnum}}.<br>
* if token matches return true otherwise false.
*/
boolean parseFormatStrWithToken(final ToDateParams params) {
Matcher matcher = patternToUse.matcher(params.getFormatStr());
boolean foundToken = matcher.find();
if (foundToken) {
String formatTokenStr = matcher.group(1);
toDateParslet.parse(params, this, formatTokenStr);
}
return foundToken;
}
}
/**
* Interface of the classes that can parse a specialized small bit of the TO_DATE format-string
*/
interface ToDateParslet {
ToDateParams parse(ToDateParams params, FormatTokenEnum formatTokenEnum, String formatTokenStr);
}
/**
*
*/
private static final class YearParslet implements ToDateParslet {
@Override
public ToDateParams parse(final ToDateParams params, final FormatTokenEnum formatTokenEnum,
final String formatTokenStr) {
final Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case SYYYY:
case YYYY:
case IYYY:
inputFragmentStr = matchStringOrDie(PATTERN_4_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// 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);
break;
case YYY:
case IYY:
inputFragmentStr = matchStringOrDie(PATTERN_3_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// 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);
break;
case RRRR:
inputFragmentStr = matchStringOrDie(PATTERN_2_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
dateNr += dateNr < 50 ? 2000 : 1900;
result.set(Calendar.YEAR, dateNr);
break;
case RR:
Calendar calendar = Calendar.getInstance();
int cc = (calendar.get(Calendar.YEAR) / 100);
inputFragmentStr = matchStringOrDie(PATTERN_2_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr) + cc * 100;
result.set(Calendar.YEAR, dateNr);
break;
case EE /*NOT supported yet*/:
throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name()));
break;
case E /*NOT supported yet*/:
throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name()));
break;
case YY:
case IY:
inputFragmentStr = matchStringOrDie(PATTERN_2_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// 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);
break;
case SCC:
case CC:
inputFragmentStr = matchStringOrDie(PATTERN_2_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr) * 100;
result.set(Calendar.YEAR, dateNr);
break;
case Y:
case I:
inputFragmentStr = matchStringOrDie(PATTERN_1_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// 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);
break;
case BC_AD:
inputFragmentStr = matchStringOrDie(PATTERN_BC_AD, params, formatTokenEnum);
if(inputFragmentStr.toUpperCase().startsWith("B")) {
result.set(Calendar.ERA, GregorianCalendar.BC);
}
else {
result.set(Calendar.ERA, GregorianCalendar.AD);
}
break;
default:
throw new IllegalArgumentException(format("%s: Internal Error. Unhandled case: %s", this.getClass()
.getSimpleName(), formatTokenEnum));
}
params.remove(inputFragmentStr, formatTokenStr);
return params;
}
}
/**
*
*/
private static final class MonthParslet implements ToDateParslet {
private static String[] ROMAN_Month = { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI",
"XII" };
@Override
public ToDateParams parse(final ToDateParams params, final FormatTokenEnum formatTokenEnum,
final String formatTokenStr) {
final Calendar result = params.getResultCalendar();
final String s = params.getInputStr();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case MONTH:
inputFragmentStr = setByName(result, params, Calendar.MONTH, Calendar.LONG);
break;
case Q /*NOT supported yet*/:
throwException(params, format("token '%s' not supported jet.", formatTokenEnum.name()));
break;
case MON:
inputFragmentStr = setByName(result, params, Calendar.MONTH, Calendar.SHORT);
break;
case MM:
// Note: In Calendar Month go from 0 - 11
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.MONTH, dateNr - 1);
break;
case RM:
dateNr = 0;
for (String monthName : ROMAN_Month) {
dateNr++;
int leng = monthName.length();
if (s.length() >= leng && monthName.equalsIgnoreCase(s.substring(0, leng))) {
result.set(Calendar.MONTH, dateNr);
inputFragmentStr = monthName;
break;
}
}
if (inputFragmentStr == null || inputFragmentStr.isEmpty()) {
throwException(params,
format("Issue happend when parsing token '%s'. Expected one of: %s",
formatTokenEnum.name(), Arrays.toString(ROMAN_Month)));
}
break;
default:
throw new IllegalArgumentException(format("%s: Internal Error. Unhandled case: %s", this.getClass()
.getSimpleName(), formatTokenEnum));
}
params.remove(inputFragmentStr, formatTokenStr);
return params;
}
}
/**
* Week parsing can be used by TO_CHAR but not by TO_DATE
*
private static final class WeekParslet implements ToDateParslet {
@Override
public ToDateParams parse(final ToDateParams params, final FormatTokenEnum formatTokenEnum,
final String formatTokenStr) {
final Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case WW:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// The first week of the month, as defined by
// getFirstDayOfWeek() and getMinimalDaysInFirstWeek(),
result.set(Calendar.WEEK_OF_YEAR, dateNr);
break;
case IW:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// Build set the calendar to ISO8601 (see
// http://en.wikipedia.org/wiki/ISO_8601_week_number)
result.setMinimalDaysInFirstWeek(4);
result.setFirstDayOfWeek(Calendar.MONDAY);
result.set(Calendar.WEEK_OF_YEAR, dateNr);
break;
case W:
inputFragmentStr = matchStringOrDie(PATTERN_1_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
// The first week of the month, as defined by
// getFirstDayOfWeek() and getMinimalDaysInFirstWeek(),
result.set(Calendar.WEEK_OF_MONTH, dateNr);
break;
default:
throw new IllegalArgumentException(format("%s: Internal Error. Unhandled case: %s", this.getClass()
.getSimpleName(), formatTokenEnum));
}
params.remove(inputFragmentStr, formatTokenStr);
return params;
}
}
*/
/**
*
*/
private static final class DayParslet implements ToDateParslet {
@Override
public ToDateParams parse(final ToDateParams params, final FormatTokenEnum formatTokenEnum,
final String formatTokenStr) {
final Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case DDD:
inputFragmentStr = matchStringOrDie(PATTERN_Number, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_YEAR, dateNr);
break;
case DD:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr);
break;
case D:
inputFragmentStr = matchStringOrDie(PATTERN_1_Digit, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr);
break;
case DAY:
inputFragmentStr = setByName(result, params, Calendar.DAY_OF_WEEK, Calendar.LONG);
break;
case DY:
inputFragmentStr = setByName(result, params, Calendar.DAY_OF_WEEK, Calendar.SHORT);
break;
case J:
inputFragmentStr = matchStringOrDie(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));
}
break;
default:
throw new IllegalArgumentException(format("%s: Internal Error. Unhandled case: %s", this.getClass()
.getSimpleName(), formatTokenEnum));
}
params.remove(inputFragmentStr, formatTokenStr);
return params;
}
}
private static int MILLIS_in_hour = 60 * 60 * 1000;
/**
*
*/
private static final class TimeParslet implements ToDateParslet {
@Override
public ToDateParams parse(final ToDateParams params, final FormatTokenEnum formatTokenEnum,
final String formatTokenStr) {
final Calendar result = params.getResultCalendar();
String inputFragmentStr = null;
int dateNr = 0;
switch (formatTokenEnum) {
case HH24:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, dateNr);
break;
case HH12:
case HH:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.HOUR, dateNr);
break;
case MI:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.MINUTE, dateNr);
break;
case SS:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.SECOND, dateNr);
break;
case SSSSS:
inputFragmentStr = matchStringOrDie(PATTERN_Number, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, 0);
result.set(Calendar.MINUTE, 0);
result.set(Calendar.SECOND, dateNr);
break;
case FF: //
inputFragmentStr = matchStringOrDie(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);
break;
case AM_PM:
inputFragmentStr = matchStringOrDie(PATTERN_AM_PM, params, formatTokenEnum);
if(inputFragmentStr.toUpperCase().startsWith("A")) {
result.set(Calendar.AM_PM, Calendar.AM);
}
else {
result.set(Calendar.AM_PM, Calendar.PM);
}
break;
case TZH:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
TimeZone tz = result.getTimeZone();
int offsetMillis = tz.getRawOffset();
offsetMillis = (offsetMillis / MILLIS_in_hour) * MILLIS_in_hour; // purge min and sec
tz.setRawOffset(offsetMillis + dateNr);
result.setTimeZone(tz);
break;
case TZM:
inputFragmentStr = matchStringOrDie(PATTERN_2_DigitOrLess, params, formatTokenEnum);
dateNr = parseInt(inputFragmentStr);
tz = result.getTimeZone();
offsetMillis = tz.getRawOffset();
offsetMillis = offsetMillis % MILLIS_in_hour; // purge hour
tz.setRawOffset(dateNr * MILLIS_in_hour + offsetMillis);
result.setTimeZone(tz);
break;
case TZR: // Example: US/Pacific
final 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()));
break;
default:
throw new IllegalArgumentException(format("%s: Internal Error. Unhandled case: %s", this.getClass()
.getSimpleName(), formatTokenEnum));
}
params.remove(inputFragmentStr, formatTokenStr);
return params;
}
}
// ========== PRIVATE ===================
private static int parseInt(final String s) {
int result = 0;
if (s.length() > 0 && s.charAt(0) == '+') {
result = Integer.parseInt(s.substring(1));
} else {
result = Integer.parseInt(s);
}
return result;
}
private static String matchStringOrDie(final Pattern p, final ToDateParams params, final Enum<?> aEnum) {
final String s = params.getInputStr();
Matcher matcher = p.matcher(s);
if (!matcher.find()) {
throwException(params, format("Issue happend when parsing token '%s'", aEnum.name()));
}
return matcher.group(1);
}
private static String setByName(final Calendar c, final ToDateParams params, final int field, final int style) {
String inputFragmentStr = null;
String s = params.getInputStr();
Map<String, Integer> timeStringMap = c.getDisplayNames(field, style, Locale.getDefault());
for (String dayName : timeStringMap.keySet()) {
int leng = dayName.length();
if (dayName.equalsIgnoreCase(s.substring(0, leng))) {
c.set(field, timeStringMap.get(dayName));
inputFragmentStr = dayName;
break;
}
}
if (inputFragmentStr == null || inputFragmentStr.isEmpty()) {
throwException(params, format("Tryed to parse one of '%s' but failed (may be an internal error?)",
timeStringMap.keySet()));
}
return inputFragmentStr;
}
private static void throwException(final ToDateParams params, final String errorStr) {
throw DbException.get(
ErrorCode.INVALID_TO_DATE_FORMAT,
params.getFunctionName().name(),
format(" %s. Details: %s", errorStr, params));
}
}
\ No newline at end of file
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.samples;
import org.h2.tools.DeleteDbFiles;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* A very simple class that shows how to load the driver, create a database,
* create a table, and insert some data.
*/
public class ToDate {
/**
* Called when ran from command line.
*
* @param args ignored
*/
public static void main(String... args) throws Exception {
// delete the database named 'test' in the user home directory
DeleteDbFiles.execute("~", "test", true);
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:~/test");
Statement stat = conn.createStatement();
stat.execute("create table ToDateTest(id int primary key, start_date datetime, end_date datetime)");
stat.execute("insert into ToDateTest values(1, TO_DATE('2015-11-13', 'yyyy-MM-DD'), TO_DATE('2015-12-15', 'YYYY-MM-DD'))");
stat.execute("insert into ToDateTest values(2, TO_DATE('2015-12-12 00:00:00', 'yyyy-MM-DD HH24:MI:ss'), TO_DATE('2015-12-16 15:00:00', 'YYYY-MM-DD HH24:MI:ss'))");
stat.execute("insert into ToDateTest values(3, TO_DATE('2015-12-12 08:00 A.M.', 'yyyy-MM-DD HH:MI AM'), TO_DATE('2015-12-17 08:00 P.M.', 'YYYY-MM-DD HH:MI AM'))");
stat.execute("insert into ToDateTest values(4, TO_DATE(substr('2015-12-12 08:00 A.M.', 1, 10), 'yyyy-MM-DD'), TO_DATE('2015-12-17 08:00 P.M.', 'YYYY-MM-DD HH:MI AM'))");
ResultSet rs = stat.executeQuery("select * from ToDateTest");
while (rs.next()) {
System.out.println("Start date: " + dateToString(rs.getTimestamp("start_date")));
System.out.println("End date: " + dateToString(rs.getTimestamp("end_date")));
System.out.println();
}
stat.close();
conn.close();
}
private static String dateToString(Date date) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
}
...@@ -27,6 +27,8 @@ import java.sql.SQLException; ...@@ -27,6 +27,8 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Currency; import java.util.Currency;
...@@ -42,6 +44,7 @@ import org.h2.api.AggregateFunction; ...@@ -42,6 +44,7 @@ import org.h2.api.AggregateFunction;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.jdbc.JdbcSQLException; import org.h2.jdbc.JdbcSQLException;
import org.h2.message.DbException;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.test.ap.TestAnnotationProcessor; import org.h2.test.ap.TestAnnotationProcessor;
...@@ -49,6 +52,7 @@ import org.h2.tools.SimpleResultSet; ...@@ -49,6 +52,7 @@ import org.h2.tools.SimpleResultSet;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.ToDate;
import org.h2.value.Value; import org.h2.value.Value;
/** /**
...@@ -72,6 +76,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -72,6 +76,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
deleteDb("functions"); deleteDb("functions");
testToDate();
testToDateException();
testDataType(); testDataType();
testVersion(); testVersion();
testFunctionTable(); testFunctionTable();
...@@ -1275,6 +1281,123 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1275,6 +1281,123 @@ public class TestFunctions extends TestBase implements AggregateFunction {
String.format("SELECT ORA_HASH('%s', 0, 0) FROM DUAL", testStr)); String.format("SELECT ORA_HASH('%s', 0, 0) FROM DUAL", testStr));
} }
private void testToDateException() {
try {
ToDate.TO_DATE("1979-ThisWillFail-12", "YYYY-MM-DD");
} catch (Exception e) {
assertEquals(DbException.class.getSimpleName(), e.getClass().getSimpleName());
}
}
private void testToDate() throws SQLException, ParseException {
final int curMonth = Calendar.getInstance().get(Calendar.MONTH);
Date date = null;
date = new SimpleDateFormat("yyyy-MM-dd").parse("1979-11-12");
assertEquals(date, ToDate.TO_DATE("1979-11-12", "YYYY-MM-DD"));
assertEquals(date, ToDate.TO_DATE("1979/11/12", "YYYY/MM/DD"));
assertEquals(date, ToDate.TO_DATE("1979,11,12", "YYYY,MM,DD"));
assertEquals(date, ToDate.TO_DATE("1979.11.12", "YYYY.MM.DD"));
assertEquals(date, ToDate.TO_DATE("1979;11;12", "YYYY;MM;DD"));
assertEquals(date, ToDate.TO_DATE("1979:11:12", "YYYY:MM:DD"));
date = new SimpleDateFormat("yyyy").parse("1979");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("1979", "YYYY"));
assertEquals(date, ToDate.TO_DATE("1979 AD", "YYYY AD"));
assertEquals(date, ToDate.TO_DATE("1979 A.D.", "YYYY A.D."));
assertEquals(date, ToDate.TO_DATE("1979 A.D.", "YYYY BC"));
assertEquals(date, ToDate.TO_DATE("1979", "IYYY"));
assertEquals(date, ToDate.TO_DATE("+1979", "SYYYY"));
assertEquals(date, ToDate.TO_DATE("79", "RRRR"));
date = new SimpleDateFormat("yyyy-mm").parse("1970-12");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("12", "MI"));
date = new SimpleDateFormat("yyyy-MM").parse("1970-11");
assertEquals(date, ToDate.TO_DATE("11", "MM"));
assertEquals(date, ToDate.TO_DATE("11", "Mm"));
assertEquals(date, ToDate.TO_DATE("11", "mM"));
assertEquals(date, ToDate.TO_DATE("11", "mm"));
assertEquals(date, ToDate.TO_DATE("XI", "RM"));
date = new SimpleDateFormat("yyyy").parse("9");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("9", "Y"));
assertEquals(date, ToDate.TO_DATE("9", "I"));
date = new SimpleDateFormat("yyyy").parse("79");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("79", "YY"));
assertEquals(date, ToDate.TO_DATE("79", "IY"));
date = new SimpleDateFormat("yyyy").parse("979");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("979", "YYY"));
assertEquals(date, ToDate.TO_DATE("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");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("0100 BC", "YYYY BC"));
assertEquals(date, ToDate.TO_DATE("0100 B.C.", "YYYY B.C."));
assertEquals(date, ToDate.TO_DATE("100 BC", "YYY BC"));
assertEquals(date, ToDate.TO_DATE("-0100", "SYYYY"));
assertEquals(date, ToDate.TO_DATE("-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");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("01 BC", "YY BC"));
assertEquals(date, ToDate.TO_DATE("1 BC", "Y BC"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("08:12 AM", "HH:MI AM"));
assertEquals(date, ToDate.TO_DATE("08:12 A.M.", "HH:MI A.M."));
assertEquals(date, ToDate.TO_DATE("08:12", "HH24:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("08:12", "HH:MI"));
assertEquals(date, ToDate.TO_DATE("08:12", "HH12:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:34");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("08:12:34", "HH:MI:SS"));
date = new SimpleDateFormat("ss").parse("34");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("34", "SS"));
date = new SimpleDateFormat("yyyy hh:mm:ss").parse("1970 08:12:34");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("29554", "SSSSS"));
date = new SimpleDateFormat("yyyy hh:mm:ss SSS").parse("1970 08:12:34 550");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("08:12:34 550", "HH:MI:SS FF"));
assertEquals(date, ToDate.TO_DATE("08:12:34 55", "HH:MI:SS FF2"));
date = new SimpleDateFormat("hh:mm:ss").parse("14:04:00");
date.setMonth(curMonth);
assertEquals(date, ToDate.TO_DATE("02:04 P.M.", "HH:MI p.M."));
assertEquals(date, ToDate.TO_DATE("02:04 PM", "HH:MI PM"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-12-12");
assertEquals(date, ToDate.TO_DATE("12", "DD"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-11-12");
assertEquals(date, ToDate.TO_DATE("316", "DDD"));
assertEquals(date, ToDate.TO_DATE("316", "DdD"));
assertEquals(date, ToDate.TO_DATE("316", "dDD"));
assertEquals(date, ToDate.TO_DATE("316", "ddd"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("2013-01-29");
assertEquals(date, ToDate.TO_DATE("113029", "J"));
}
private void testToCharFromDateTime() throws SQLException { private void testToCharFromDateTime() throws SQLException {
deleteDb("functions"); deleteDb("functions");
Connection conn = getConnection("functions"); Connection conn = getConnection("functions");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论