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

Merge pull request #981 from katzyn/datetime

Reorganize date-time functions
...@@ -147,6 +147,7 @@ import org.h2.table.Table; ...@@ -147,6 +147,7 @@ import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.table.TableFilter.TableFilterVisitor; import org.h2.table.TableFilter.TableFilterVisitor;
import org.h2.table.TableView; import org.h2.table.TableView;
import org.h2.util.DateTimeFunctions;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.ParserUtil; import org.h2.util.ParserUtil;
...@@ -2798,7 +2799,7 @@ public class Parser { ...@@ -2798,7 +2799,7 @@ public class Parser {
} }
case Function.DATE_ADD: case Function.DATE_ADD:
case Function.DATE_DIFF: { case Function.DATE_DIFF: {
if (Function.isDatePart(currentToken)) { if (DateTimeFunctions.isDatePart(currentToken)) {
function.setParameter(0, function.setParameter(0,
ValueExpression.get(ValueString.get(currentToken))); ValueExpression.get(ValueString.get(currentToken)));
read(); read();
......
...@@ -10,16 +10,12 @@ import java.io.IOException; ...@@ -10,16 +10,12 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.DateFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
...@@ -44,6 +40,7 @@ import org.h2.table.Table; ...@@ -44,6 +40,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.DateTimeFunctions;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
...@@ -60,7 +57,6 @@ import org.h2.value.ValueArray; ...@@ -60,7 +57,6 @@ import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
import org.h2.value.ValueBytes; import org.h2.value.ValueBytes;
import org.h2.value.ValueDate; import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble; import org.h2.value.ValueDouble;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
...@@ -150,14 +146,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -150,14 +146,8 @@ public class Function extends Expression implements FunctionCall {
private static final long PRECISION_UNKNOWN = -1; private static final long PRECISION_UNKNOWN = -1;
private static final HashMap<String, FunctionInfo> FUNCTIONS = new HashMap<>(); private static final HashMap<String, FunctionInfo> FUNCTIONS = new HashMap<>();
private static final HashMap<String, Integer> DATE_PART = new HashMap<>();
private static final char[] SOUNDEX_INDEX = new char[128]; private static final char[] SOUNDEX_INDEX = new char[128];
/**
* English names of months and week days.
*/
private static volatile String[][] MONTHS_AND_WEEKS;
protected Expression[] args; protected Expression[] args;
private final FunctionInfo info; private final FunctionInfo info;
...@@ -168,54 +158,6 @@ public class Function extends Expression implements FunctionCall { ...@@ -168,54 +158,6 @@ public class Function extends Expression implements FunctionCall {
private final Database database; private final Database database;
static { static {
// DATE_PART
DATE_PART.put("SQL_TSI_YEAR", YEAR);
DATE_PART.put("YEAR", YEAR);
DATE_PART.put("YYYY", YEAR);
DATE_PART.put("YY", YEAR);
DATE_PART.put("SQL_TSI_MONTH", MONTH);
DATE_PART.put("MONTH", MONTH);
DATE_PART.put("MM", MONTH);
DATE_PART.put("M", MONTH);
DATE_PART.put("QUARTER", QUARTER);
DATE_PART.put("SQL_TSI_WEEK", WEEK);
DATE_PART.put("WW", WEEK);
DATE_PART.put("WK", WEEK);
DATE_PART.put("WEEK", WEEK);
DATE_PART.put("ISO_WEEK", ISO_WEEK);
DATE_PART.put("DAY", DAY_OF_MONTH);
DATE_PART.put("DD", DAY_OF_MONTH);
DATE_PART.put("D", DAY_OF_MONTH);
DATE_PART.put("SQL_TSI_DAY", DAY_OF_MONTH);
DATE_PART.put("DAY_OF_WEEK", DAY_OF_WEEK);
DATE_PART.put("DAYOFWEEK", DAY_OF_WEEK);
DATE_PART.put("DOW", DAY_OF_WEEK);
DATE_PART.put("ISO_DAY_OF_WEEK", ISO_DAY_OF_WEEK);
DATE_PART.put("DAYOFYEAR", DAY_OF_YEAR);
DATE_PART.put("DAY_OF_YEAR", DAY_OF_YEAR);
DATE_PART.put("DY", DAY_OF_YEAR);
DATE_PART.put("DOY", DAY_OF_YEAR);
DATE_PART.put("SQL_TSI_HOUR", HOUR);
DATE_PART.put("HOUR", HOUR);
DATE_PART.put("HH", HOUR);
DATE_PART.put("SQL_TSI_MINUTE", MINUTE);
DATE_PART.put("MINUTE", MINUTE);
DATE_PART.put("MI", MINUTE);
DATE_PART.put("N", MINUTE);
DATE_PART.put("SQL_TSI_SECOND", SECOND);
DATE_PART.put("SECOND", SECOND);
DATE_PART.put("SS", SECOND);
DATE_PART.put("S", SECOND);
DATE_PART.put("MILLISECOND", MILLISECOND);
DATE_PART.put("MS", MILLISECOND);
DATE_PART.put("EPOCH", EPOCH);
DATE_PART.put("MICROSECOND", MICROSECOND);
DATE_PART.put("MCS", MICROSECOND);
DATE_PART.put("NANOSECOND", NANOSECOND);
DATE_PART.put("NS", NANOSECOND);
DATE_PART.put("TIMEZONE_HOUR", TIMEZONE_HOUR);
DATE_PART.put("TIMEZONE_MINUTE", TIMEZONE_MINUTE);
// SOUNDEX_INDEX // SOUNDEX_INDEX
String index = "7AEIOUY8HW1BFPV2CGJKQSXZ3DT4L5MN6R"; String index = "7AEIOUY8HW1BFPV2CGJKQSXZ3DT4L5MN6R";
char number = 0; char number = 0;
...@@ -853,7 +795,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -853,7 +795,7 @@ public class Function extends Expression implements FunctionCall {
break; break;
case DAY_NAME: { case DAY_NAME: {
int dayOfWeek = DateTimeUtils.getSundayDayOfWeek(DateTimeUtils.dateAndTimeFromValue(v0)[0]); int dayOfWeek = DateTimeUtils.getSundayDayOfWeek(DateTimeUtils.dateAndTimeFromValue(v0)[0]);
result = ValueString.get(getMonthsAndWeeks(1)[dayOfWeek], result = ValueString.get(DateTimeFunctions.getMonthsAndWeeks(1)[dayOfWeek],
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
} }
...@@ -870,11 +812,11 @@ public class Function extends Expression implements FunctionCall { ...@@ -870,11 +812,11 @@ public class Function extends Expression implements FunctionCall {
case SECOND: case SECOND:
case WEEK: case WEEK:
case YEAR: case YEAR:
result = ValueInt.get(getIntDatePart(v0, info.type)); result = ValueInt.get(DateTimeFunctions.getIntDatePart(v0, info.type));
break; break;
case MONTH_NAME: { case MONTH_NAME: {
int month = DateTimeUtils.monthFromDateValue(DateTimeUtils.dateAndTimeFromValue(v0)[0]); int month = DateTimeUtils.monthFromDateValue(DateTimeUtils.dateAndTimeFromValue(v0)[0]);
result = ValueString.get(getMonthsAndWeeks(0)[month - 1], result = ValueString.get(DateTimeFunctions.getMonthsAndWeeks(0)[month - 1],
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
} }
...@@ -1470,7 +1412,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1470,7 +1412,7 @@ public class Function extends Expression implements FunctionCall {
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 = DateTimeFunctions.dateadd("MONTH", v1.getInt(), v0);
break; break;
case TO_TIMESTAMP_TZ: case TO_TIMESTAMP_TZ:
result = ToDateParser.toTimestampTz(v0.getString(), result = ToDateParser.toTimestampTz(v0.getString(),
...@@ -1489,10 +1431,10 @@ public class Function extends Expression implements FunctionCall { ...@@ -1489,10 +1431,10 @@ public class Function extends Expression implements FunctionCall {
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
case DATE_ADD: case DATE_ADD:
result = dateadd(v0.getString(), v1.getLong(), v2); result = DateTimeFunctions.dateadd(v0.getString(), v1.getLong(), v2);
break; break;
case DATE_DIFF: case DATE_DIFF:
result = ValueLong.get(datediff(v0.getString(), v1, v2)); result = ValueLong.get(DateTimeFunctions.datediff(v0.getString(), v1, v2));
break; break;
case DATE_TRUNC: case DATE_TRUNC:
// Retrieve the time unit (e.g. 'day', 'microseconds', etc.) // Retrieve the time unit (e.g. 'day', 'microseconds', etc.)
...@@ -1501,60 +1443,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -1501,60 +1443,9 @@ public class Function extends Expression implements FunctionCall {
result = DateTimeUtils.truncateDate(timeUnit, v1); result = DateTimeUtils.truncateDate(timeUnit, v1);
break; break;
case EXTRACT: { case EXTRACT:
int field = getDatePart(v0.getString()); result = DateTimeFunctions.extract(v0.getString(), v1);
if (field != EPOCH) {
result = ValueInt.get(getIntDatePart(v1, field));
} else {
// Case where we retrieve the EPOCH time.
// First we retrieve the dateValue and his time in nanoseconds.
long[] a = DateTimeUtils.dateAndTimeFromValue(v1);
long dateValue = a[0];
long timeNanos = a[1];
// We compute the time in nanoseconds and the total number of days.
BigDecimal timeNanosBigDecimal = new BigDecimal(timeNanos);
BigDecimal numberOfDays = new BigDecimal(DateTimeUtils.absoluteDayFromDateValue(dateValue));
BigDecimal nanosSeconds = new BigDecimal(1_000_000_000);
BigDecimal secondsPerDay = new BigDecimal(DateTimeUtils.SECONDS_PER_DAY);
// Case where the value is of type time e.g. '10:00:00'
if (v1 instanceof ValueTime) {
// In order to retrieve the EPOCH time we only have to convert the time
// in nanoseconds (previously retrieved) in seconds.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds));
} else if (v1 instanceof ValueDate) {
// Case where the value is of type date '2000:01:01', we have to retrieve the
// total number of days and multiply it by the number of seconds in a day.
result = ValueDecimal.get(numberOfDays.multiply(secondsPerDay));
} else if (v1 instanceof ValueTimestampTimeZone) {
// Case where the value is a of type ValueTimestampTimeZone
// ('2000:01:01 10:00:00+05').
// We retrieve the time zone offset in minutes
ValueTimestampTimeZone v = (ValueTimestampTimeZone) v1;
BigDecimal timeZoneOffsetSeconds = new BigDecimal(v.getTimeZoneOffsetMins() * 60);
// Sum the time in nanoseconds and the total number of days in seconds
// and adding the timeZone offset in seconds.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds)
.add(numberOfDays.multiply(secondsPerDay)).subtract(timeZoneOffsetSeconds));
} else {
// By default, we have the date and the time ('2000:01:01 10:00:00') if no type
// is given.
// We just have to sum the time in nanoseconds and the total number of days in
// seconds.
result = ValueDecimal
.get(timeNanosBigDecimal.divide(nanosSeconds).add(numberOfDays.multiply(secondsPerDay)));
}
}
break; break;
}
case FORMATDATETIME: { case FORMATDATETIME: {
if (v0 == ValueNull.INSTANCE || v1 == ValueNull.INSTANCE) { if (v0 == ValueNull.INSTANCE || v1 == ValueNull.INSTANCE) {
result = ValueNull.INSTANCE; result = ValueNull.INSTANCE;
...@@ -1567,7 +1458,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1567,7 +1458,7 @@ public class Function extends Expression implements FunctionCall {
tz = DateTimeUtils.timeZoneNameFromOffsetMins( tz = DateTimeUtils.timeZoneNameFromOffsetMins(
((ValueTimestampTimeZone) v0).getTimeZoneOffsetMins()); ((ValueTimestampTimeZone) v0).getTimeZoneOffsetMins());
} }
result = ValueString.get(DateTimeUtils.formatDateTime( result = ValueString.get(DateTimeFunctions.formatDateTime(
v0.getTimestamp(), v1.getString(), locale, tz), v0.getTimestamp(), v1.getString(), locale, tz),
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
} }
...@@ -1581,7 +1472,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1581,7 +1472,7 @@ public class Function extends Expression implements FunctionCall {
null : v2 == ValueNull.INSTANCE ? null : v2.getString(); null : v2 == ValueNull.INSTANCE ? null : v2.getString();
String tz = v3 == null ? String tz = v3 == null ?
null : v3 == ValueNull.INSTANCE ? null : v3.getString(); null : v3 == ValueNull.INSTANCE ? null : v3.getString();
java.util.Date d = DateTimeUtils.parseDateTime( java.util.Date d = DateTimeFunctions.parseDateTime(
v0.getString(), v1.getString(), locale, tz); v0.getString(), v1.getString(), locale, tz);
result = ValueTimestamp.fromMillis(d.getTime()); result = ValueTimestamp.fromMillis(d.getTime());
} }
...@@ -1849,244 +1740,6 @@ public class Function extends Expression implements FunctionCall { ...@@ -1849,244 +1740,6 @@ public class Function extends Expression implements FunctionCall {
return bytes; return bytes;
} }
/**
* Check if a given string is a valid date part string.
*
* @param part the string
* @return true if it is
*/
public static boolean isDatePart(String part) {
Integer p = DATE_PART.get(StringUtils.toUpperEnglish(part));
return p != null;
}
private static int getDatePart(String part) {
Integer p = DATE_PART.get(StringUtils.toUpperEnglish(part));
if (p == null) {
throw DbException.getInvalidValueException("date part", part);
}
return p.intValue();
}
private static Value dateadd(String part, long count, Value v) {
int field = getDatePart(part);
if (field != MILLISECOND && field != MICROSECOND && field != NANOSECOND &&
(count > Integer.MAX_VALUE || count < Integer.MIN_VALUE)) {
throw DbException.getInvalidValueException("DATEADD count", count);
}
boolean withDate = !(v instanceof ValueTime);
boolean withTime = !(v instanceof ValueDate);
boolean forceTimestamp = false;
long[] a = DateTimeUtils.dateAndTimeFromValue(v);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) {
case QUARTER:
count *= 3;
//$FALL-THROUGH$
case YEAR:
case MONTH: {
if (!withDate) {
throw DbException.getInvalidValueException("DATEADD time part", part);
}
long year = DateTimeUtils.yearFromDateValue(dateValue);
long month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
if (field == YEAR) {
year += count;
} else {
month += count;
}
dateValue = DateTimeUtils.dateValueFromDenormalizedDate(year, month, day);
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
}
case WEEK:
case ISO_WEEK:
count *= 7;
//$FALL-THROUGH$
case DAY_OF_WEEK:
case ISO_DAY_OF_WEEK:
case DAY_OF_MONTH:
case DAY_OF_YEAR:
if (!withDate) {
throw DbException.getInvalidValueException("DATEADD time part", part);
}
dateValue = DateTimeUtils.dateValueFromAbsoluteDay(
DateTimeUtils.absoluteDayFromDateValue(dateValue) + count);
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
case HOUR:
count *= 3_600_000_000_000L;
break;
case MINUTE:
count *= 60_000_000_000L;
break;
case SECOND:
case EPOCH:
count *= 1_000_000_000;
break;
case MILLISECOND:
count *= 1_000_000;
break;
case MICROSECOND:
count *= 1_000;
break;
case NANOSECOND:
break;
case TIMEZONE_HOUR:
count *= 60;
//$FALL-THROUGH$
case TIMEZONE_MINUTE: {
if (!(v instanceof ValueTimestampTimeZone)) {
throw DbException.getUnsupportedException("DATEADD " + part);
}
count += ((ValueTimestampTimeZone) v).getTimeZoneOffsetMins();
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) count);
}
default:
throw DbException.getUnsupportedException("DATEADD " + part);
}
if (!withTime) {
// Treat date as timestamp at the start of this date
forceTimestamp = true;
}
timeNanos += count;
if (timeNanos >= DateTimeUtils.NANOS_PER_DAY || timeNanos < 0) {
long d;
if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) {
d = timeNanos / DateTimeUtils.NANOS_PER_DAY;
} else {
d = (timeNanos - DateTimeUtils.NANOS_PER_DAY + 1) / DateTimeUtils.NANOS_PER_DAY;
}
timeNanos -= d * DateTimeUtils.NANOS_PER_DAY;
return DateTimeUtils.dateTimeToValue(v,
DateTimeUtils.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + d),
timeNanos, forceTimestamp);
}
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
}
/**
* Calculate the number of crossed unit boundaries between two timestamps.
* This method is supported for MS SQL Server compatibility.
* <pre>
* DATEDIFF(YEAR, '2004-12-31', '2005-01-01') = 1
* </pre>
*
* @param part the part
* @param v1 the first date-time value
* @param v2 the second date-time value
* @return the number of crossed boundaries
*/
private static long datediff(String part, Value v1, Value v2) {
int field = getDatePart(part);
long[] a1 = DateTimeUtils.dateAndTimeFromValue(v1);
long dateValue1 = a1[0];
long absolute1 = DateTimeUtils.absoluteDayFromDateValue(dateValue1);
long[] a2 = DateTimeUtils.dateAndTimeFromValue(v2);
long dateValue2 = a2[0];
long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2);
switch (field) {
case NANOSECOND:
case MICROSECOND:
case MILLISECOND:
case SECOND:
case EPOCH:
case MINUTE:
case HOUR:
long timeNanos1 = a1[1];
long timeNanos2 = a2[1];
switch (field) {
case NANOSECOND:
return (absolute2 - absolute1) * DateTimeUtils.NANOS_PER_DAY
+ (timeNanos2 - timeNanos1);
case MICROSECOND:
return (absolute2 - absolute1) * (DateTimeUtils.MILLIS_PER_DAY * 1_000)
+ (timeNanos2 / 1_000 - timeNanos1 / 1_000);
case MILLISECOND:
return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY
+ (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000);
case SECOND:
case EPOCH:
return (absolute2 - absolute1) * 86_400
+ (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000);
case MINUTE:
return (absolute2 - absolute1) * 1_440
+ (timeNanos2 / 60_000_000_000L - timeNanos1 / 60_000_000_000L);
case HOUR:
return (absolute2 - absolute1) * 24
+ (timeNanos2 / 3_600_000_000_000L - timeNanos1 / 3_600_000_000_000L);
}
// Fake fall-through
//$FALL-THROUGH$
case DAY_OF_MONTH:
case DAY_OF_YEAR:
case DAY_OF_WEEK:
case ISO_DAY_OF_WEEK:
return absolute2 - absolute1;
case WEEK:
return weekdiff(absolute1, absolute2, 0);
case ISO_WEEK:
return weekdiff(absolute1, absolute2, 1);
case MONTH:
return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 12
+ DateTimeUtils.monthFromDateValue(dateValue2) - DateTimeUtils.monthFromDateValue(dateValue1);
case QUARTER:
return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 4
+ (DateTimeUtils.monthFromDateValue(dateValue2) - 1) / 3
- (DateTimeUtils.monthFromDateValue(dateValue1) - 1) / 3;
case YEAR:
return DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1);
case TIMEZONE_HOUR:
case TIMEZONE_MINUTE: {
int offsetMinutes1;
if (v1 instanceof ValueTimestampTimeZone) {
offsetMinutes1 = ((ValueTimestampTimeZone) v1).getTimeZoneOffsetMins();
} else {
offsetMinutes1 = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue1, a1[1]);
}
int offsetMinutes2;
if (v2 instanceof ValueTimestampTimeZone) {
offsetMinutes2 = ((ValueTimestampTimeZone) v2).getTimeZoneOffsetMins();
} else {
offsetMinutes2 = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue2, a2[1]);
}
if (field == TIMEZONE_HOUR) {
return (offsetMinutes2 / 60) - (offsetMinutes1 / 60);
} else {
return offsetMinutes2 - offsetMinutes1;
}
}
default:
throw DbException.getUnsupportedException("DATEDIFF " + part);
}
}
private static long weekdiff(long absolute1, long absolute2, int firstDayOfWeek) {
absolute1 += 4 - firstDayOfWeek;
long r1 = absolute1 / 7;
if (absolute1 < 0 && (r1 * 7 != absolute1)) {
r1--;
}
absolute2 += 4 - firstDayOfWeek;
long r2 = absolute2 / 7;
if (absolute2 < 0 && (r2 * 7 != absolute2)) {
r2--;
}
return r2 - r1;
}
private static String[] getMonthsAndWeeks(int field) {
String[][] result = MONTHS_AND_WEEKS;
if (result == null) {
result = new String[2][];
DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.ENGLISH);
result[0] = dfs.getMonths();
result[1] = dfs.getWeekdays();
MONTHS_AND_WEEKS = result;
}
return result[field];
}
private static String substring(String s, int start, int length) { private static String substring(String s, int start, int length) {
int len = s.length(); int len = s.length();
start--; start--;
...@@ -2920,69 +2573,6 @@ public class Function extends Expression implements FunctionCall { ...@@ -2920,69 +2573,6 @@ public class Function extends Expression implements FunctionCall {
} }
} }
/**
* Get the specified field of a date, however with years normalized to
* positive or negative, and month starting with 1.
*
* @param date the date value
* @param field the field type, see {@link Function} for constants
* @return the value
*/
public static int getIntDatePart(Value date, int field) {
long[] a = DateTimeUtils.dateAndTimeFromValue(date);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) {
case YEAR:
return DateTimeUtils.yearFromDateValue(dateValue);
case MONTH:
return DateTimeUtils.monthFromDateValue(dateValue);
case DAY_OF_MONTH:
return DateTimeUtils.dayFromDateValue(dateValue);
case HOUR:
return (int) (timeNanos / 3_600_000_000_000L % 24);
case MINUTE:
return (int) (timeNanos / 60_000_000_000L % 60);
case SECOND:
return (int) (timeNanos / 1_000_000_000 % 60);
case MILLISECOND:
return (int) (timeNanos / 1_000_000 % 1_000);
case MICROSECOND:
return (int) (timeNanos / 1_000 % 1_000_000);
case NANOSECOND:
return (int) (timeNanos % 1_000_000_000);
case DAY_OF_YEAR:
return DateTimeUtils.getDayOfYear(dateValue);
case DAY_OF_WEEK:
return DateTimeUtils.getSundayDayOfWeek(dateValue);
case WEEK:
GregorianCalendar gc = DateTimeUtils.getCalendar();
return DateTimeUtils.getWeekOfYear(dateValue, gc.getFirstDayOfWeek() - 1, gc.getMinimalDaysInFirstWeek());
case QUARTER:
return (DateTimeUtils.monthFromDateValue(dateValue) - 1) / 3 + 1;
case ISO_YEAR:
return DateTimeUtils.getIsoWeekYear(dateValue);
case ISO_WEEK:
return DateTimeUtils.getIsoWeekOfYear(dateValue);
case ISO_DAY_OF_WEEK:
return DateTimeUtils.getIsoDayOfWeek(dateValue);
case TIMEZONE_HOUR:
case TIMEZONE_MINUTE: {
int offsetMinutes;
if (date instanceof ValueTimestampTimeZone) {
offsetMinutes = ((ValueTimestampTimeZone) date).getTimeZoneOffsetMins();
} else {
offsetMinutes = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue, timeNanos);
}
if (field == TIMEZONE_HOUR) {
return offsetMinutes / 60;
}
return offsetMinutes % 60;
}
}
throw DbException.getUnsupportedException("getDatePart(" + date + ", " + field + ')');
}
@Override @Override
public Expression[] getArgs() { public Expression[] getArgs() {
return args; return args;
......
/*
* Copyright 2004-2018 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.util;
import static org.h2.expression.Function.DAY_OF_MONTH;
import static org.h2.expression.Function.DAY_OF_WEEK;
import static org.h2.expression.Function.DAY_OF_YEAR;
import static org.h2.expression.Function.EPOCH;
import static org.h2.expression.Function.HOUR;
import static org.h2.expression.Function.ISO_DAY_OF_WEEK;
import static org.h2.expression.Function.ISO_WEEK;
import static org.h2.expression.Function.ISO_YEAR;
import static org.h2.expression.Function.MICROSECOND;
import static org.h2.expression.Function.MILLISECOND;
import static org.h2.expression.Function.MINUTE;
import static org.h2.expression.Function.MONTH;
import static org.h2.expression.Function.NANOSECOND;
import static org.h2.expression.Function.QUARTER;
import static org.h2.expression.Function.SECOND;
import static org.h2.expression.Function.TIMEZONE_HOUR;
import static org.h2.expression.Function.TIMEZONE_MINUTE;
import static org.h2.expression.Function.WEEK;
import static org.h2.expression.Function.YEAR;
import java.math.BigDecimal;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
import org.h2.api.ErrorCode;
import org.h2.expression.Function;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueInt;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestampTimeZone;
/**
* Date and time functions.
*/
public final class DateTimeFunctions {
private static final HashMap<String, Integer> DATE_PART = new HashMap<>();
/**
* English names of months and week days.
*/
private static volatile String[][] MONTHS_AND_WEEKS;
static {
// DATE_PART
DATE_PART.put("SQL_TSI_YEAR", YEAR);
DATE_PART.put("YEAR", YEAR);
DATE_PART.put("YYYY", YEAR);
DATE_PART.put("YY", YEAR);
DATE_PART.put("SQL_TSI_MONTH", MONTH);
DATE_PART.put("MONTH", MONTH);
DATE_PART.put("MM", MONTH);
DATE_PART.put("M", MONTH);
DATE_PART.put("QUARTER", QUARTER);
DATE_PART.put("SQL_TSI_WEEK", WEEK);
DATE_PART.put("WW", WEEK);
DATE_PART.put("WK", WEEK);
DATE_PART.put("WEEK", WEEK);
DATE_PART.put("ISO_WEEK", ISO_WEEK);
DATE_PART.put("DAY", DAY_OF_MONTH);
DATE_PART.put("DD", DAY_OF_MONTH);
DATE_PART.put("D", DAY_OF_MONTH);
DATE_PART.put("SQL_TSI_DAY", DAY_OF_MONTH);
DATE_PART.put("DAY_OF_WEEK", DAY_OF_WEEK);
DATE_PART.put("DAYOFWEEK", DAY_OF_WEEK);
DATE_PART.put("DOW", DAY_OF_WEEK);
DATE_PART.put("ISO_DAY_OF_WEEK", ISO_DAY_OF_WEEK);
DATE_PART.put("DAYOFYEAR", DAY_OF_YEAR);
DATE_PART.put("DAY_OF_YEAR", DAY_OF_YEAR);
DATE_PART.put("DY", DAY_OF_YEAR);
DATE_PART.put("DOY", DAY_OF_YEAR);
DATE_PART.put("SQL_TSI_HOUR", HOUR);
DATE_PART.put("HOUR", HOUR);
DATE_PART.put("HH", HOUR);
DATE_PART.put("SQL_TSI_MINUTE", MINUTE);
DATE_PART.put("MINUTE", MINUTE);
DATE_PART.put("MI", MINUTE);
DATE_PART.put("N", MINUTE);
DATE_PART.put("SQL_TSI_SECOND", SECOND);
DATE_PART.put("SECOND", SECOND);
DATE_PART.put("SS", SECOND);
DATE_PART.put("S", SECOND);
DATE_PART.put("MILLISECOND", MILLISECOND);
DATE_PART.put("MS", MILLISECOND);
DATE_PART.put("EPOCH", EPOCH);
DATE_PART.put("MICROSECOND", MICROSECOND);
DATE_PART.put("MCS", MICROSECOND);
DATE_PART.put("NANOSECOND", NANOSECOND);
DATE_PART.put("NS", NANOSECOND);
DATE_PART.put("TIMEZONE_HOUR", TIMEZONE_HOUR);
DATE_PART.put("TIMEZONE_MINUTE", TIMEZONE_MINUTE);
}
/**
* DATEADD function.
*
* @param part
* name of date-time part
* @param count
* count to add
* @param v
* value to add to
* @return result
*/
public static Value dateadd(String part, long count, Value v) {
int field = getDatePart(part);
if (field != MILLISECOND && field != MICROSECOND && field != NANOSECOND
&& (count > Integer.MAX_VALUE || count < Integer.MIN_VALUE)) {
throw DbException.getInvalidValueException("DATEADD count", count);
}
boolean withDate = !(v instanceof ValueTime);
boolean withTime = !(v instanceof ValueDate);
boolean forceTimestamp = false;
long[] a = DateTimeUtils.dateAndTimeFromValue(v);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) {
case QUARTER:
count *= 3;
//$FALL-THROUGH$
case YEAR:
case MONTH: {
if (!withDate) {
throw DbException.getInvalidValueException("DATEADD time part", part);
}
long year = DateTimeUtils.yearFromDateValue(dateValue);
long month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
if (field == YEAR) {
year += count;
} else {
month += count;
}
dateValue = DateTimeUtils.dateValueFromDenormalizedDate(year, month, day);
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
}
case WEEK:
case ISO_WEEK:
count *= 7;
//$FALL-THROUGH$
case DAY_OF_WEEK:
case ISO_DAY_OF_WEEK:
case DAY_OF_MONTH:
case DAY_OF_YEAR:
if (!withDate) {
throw DbException.getInvalidValueException("DATEADD time part", part);
}
dateValue = DateTimeUtils
.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + count);
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
case HOUR:
count *= 3_600_000_000_000L;
break;
case MINUTE:
count *= 60_000_000_000L;
break;
case SECOND:
case EPOCH:
count *= 1_000_000_000;
break;
case MILLISECOND:
count *= 1_000_000;
break;
case MICROSECOND:
count *= 1_000;
break;
case NANOSECOND:
break;
case TIMEZONE_HOUR:
count *= 60;
//$FALL-THROUGH$
case TIMEZONE_MINUTE: {
if (!(v instanceof ValueTimestampTimeZone)) {
throw DbException.getUnsupportedException("DATEADD " + part);
}
count += ((ValueTimestampTimeZone) v).getTimeZoneOffsetMins();
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) count);
}
default:
throw DbException.getUnsupportedException("DATEADD " + part);
}
if (!withTime) {
// Treat date as timestamp at the start of this date
forceTimestamp = true;
}
timeNanos += count;
if (timeNanos >= DateTimeUtils.NANOS_PER_DAY || timeNanos < 0) {
long d;
if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) {
d = timeNanos / DateTimeUtils.NANOS_PER_DAY;
} else {
d = (timeNanos - DateTimeUtils.NANOS_PER_DAY + 1) / DateTimeUtils.NANOS_PER_DAY;
}
timeNanos -= d * DateTimeUtils.NANOS_PER_DAY;
return DateTimeUtils.dateTimeToValue(v,
DateTimeUtils.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + d),
timeNanos, forceTimestamp);
}
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
}
/**
* Calculate the number of crossed unit boundaries between two timestamps. This
* method is supported for MS SQL Server compatibility.
*
* <pre>
* DATEDIFF(YEAR, '2004-12-31', '2005-01-01') = 1
* </pre>
*
* @param part
* the part
* @param v1
* the first date-time value
* @param v2
* the second date-time value
* @return the number of crossed boundaries
*/
public static long datediff(String part, Value v1, Value v2) {
int field = getDatePart(part);
long[] a1 = DateTimeUtils.dateAndTimeFromValue(v1);
long dateValue1 = a1[0];
long absolute1 = DateTimeUtils.absoluteDayFromDateValue(dateValue1);
long[] a2 = DateTimeUtils.dateAndTimeFromValue(v2);
long dateValue2 = a2[0];
long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2);
switch (field) {
case NANOSECOND:
case MICROSECOND:
case MILLISECOND:
case SECOND:
case EPOCH:
case MINUTE:
case HOUR:
long timeNanos1 = a1[1];
long timeNanos2 = a2[1];
switch (field) {
case NANOSECOND:
return (absolute2 - absolute1) * DateTimeUtils.NANOS_PER_DAY + (timeNanos2 - timeNanos1);
case MICROSECOND:
return (absolute2 - absolute1) * (DateTimeUtils.MILLIS_PER_DAY * 1_000)
+ (timeNanos2 / 1_000 - timeNanos1 / 1_000);
case MILLISECOND:
return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY
+ (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000);
case SECOND:
case EPOCH:
return (absolute2 - absolute1) * 86_400 + (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000);
case MINUTE:
return (absolute2 - absolute1) * 1_440 + (timeNanos2 / 60_000_000_000L - timeNanos1 / 60_000_000_000L);
case HOUR:
return (absolute2 - absolute1) * 24
+ (timeNanos2 / 3_600_000_000_000L - timeNanos1 / 3_600_000_000_000L);
}
// Fake fall-through
// $FALL-THROUGH$
case DAY_OF_MONTH:
case DAY_OF_YEAR:
case DAY_OF_WEEK:
case ISO_DAY_OF_WEEK:
return absolute2 - absolute1;
case WEEK:
return weekdiff(absolute1, absolute2, 0);
case ISO_WEEK:
return weekdiff(absolute1, absolute2, 1);
case MONTH:
return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 12
+ DateTimeUtils.monthFromDateValue(dateValue2) - DateTimeUtils.monthFromDateValue(dateValue1);
case QUARTER:
return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 4
+ (DateTimeUtils.monthFromDateValue(dateValue2) - 1) / 3
- (DateTimeUtils.monthFromDateValue(dateValue1) - 1) / 3;
case YEAR:
return DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1);
case TIMEZONE_HOUR:
case TIMEZONE_MINUTE: {
int offsetMinutes1;
if (v1 instanceof ValueTimestampTimeZone) {
offsetMinutes1 = ((ValueTimestampTimeZone) v1).getTimeZoneOffsetMins();
} else {
offsetMinutes1 = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue1, a1[1]);
}
int offsetMinutes2;
if (v2 instanceof ValueTimestampTimeZone) {
offsetMinutes2 = ((ValueTimestampTimeZone) v2).getTimeZoneOffsetMins();
} else {
offsetMinutes2 = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue2, a2[1]);
}
if (field == TIMEZONE_HOUR) {
return (offsetMinutes2 / 60) - (offsetMinutes1 / 60);
} else {
return offsetMinutes2 - offsetMinutes1;
}
}
default:
throw DbException.getUnsupportedException("DATEDIFF " + part);
}
}
/**
* Extracts specified field from the specified date-time value.
*
* @param part
* the date part
* @param value
* the date-time value
* @return extracted field
*/
public static Value extract(String part, Value value) {
Value result;
int field = getDatePart(part);
if (field != EPOCH) {
result = ValueInt.get(getIntDatePart(value, field));
} else {
// Case where we retrieve the EPOCH time.
// First we retrieve the dateValue and his time in nanoseconds.
long[] a = DateTimeUtils.dateAndTimeFromValue(value);
long dateValue = a[0];
long timeNanos = a[1];
// We compute the time in nanoseconds and the total number of days.
BigDecimal timeNanosBigDecimal = new BigDecimal(timeNanos);
BigDecimal numberOfDays = new BigDecimal(DateTimeUtils.absoluteDayFromDateValue(dateValue));
BigDecimal nanosSeconds = new BigDecimal(1_000_000_000);
BigDecimal secondsPerDay = new BigDecimal(DateTimeUtils.SECONDS_PER_DAY);
// Case where the value is of type time e.g. '10:00:00'
if (value instanceof ValueTime) {
// In order to retrieve the EPOCH time we only have to convert the time
// in nanoseconds (previously retrieved) in seconds.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds));
} else if (value instanceof ValueDate) {
// Case where the value is of type date '2000:01:01', we have to retrieve the
// total number of days and multiply it by the number of seconds in a day.
result = ValueDecimal.get(numberOfDays.multiply(secondsPerDay));
} else if (value instanceof ValueTimestampTimeZone) {
// Case where the value is a of type ValueTimestampTimeZone
// ('2000:01:01 10:00:00+05').
// We retrieve the time zone offset in minutes
ValueTimestampTimeZone v = (ValueTimestampTimeZone) value;
BigDecimal timeZoneOffsetSeconds = new BigDecimal(v.getTimeZoneOffsetMins() * 60);
// Sum the time in nanoseconds and the total number of days in seconds
// and adding the timeZone offset in seconds.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds)
.add(numberOfDays.multiply(secondsPerDay)).subtract(timeZoneOffsetSeconds));
} else {
// By default, we have the date and the time ('2000:01:01 10:00:00') if no type
// is given.
// We just have to sum the time in nanoseconds and the total number of days in
// seconds.
result = ValueDecimal
.get(timeNanosBigDecimal.divide(nanosSeconds).add(numberOfDays.multiply(secondsPerDay)));
}
}
return result;
}
/**
* Formats a date using a format string.
*
* @param date
* the date to format
* @param format
* the format string
* @param locale
* the locale
* @param timeZone
* the timezone
* @return the formatted date
*/
public static String formatDateTime(java.util.Date date, String format, String locale, String timeZone) {
SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone);
synchronized (dateFormat) {
return dateFormat.format(date);
}
}
private static SimpleDateFormat getDateFormat(String format, String locale, String timeZone) {
try {
// currently, a new instance is create for each call
// however, could cache the last few instances
SimpleDateFormat df;
if (locale == null) {
df = new SimpleDateFormat(format);
} else {
Locale l = new Locale(locale);
df = new SimpleDateFormat(format, l);
}
if (timeZone != null) {
df.setTimeZone(TimeZone.getTimeZone(timeZone));
}
return df;
} catch (Exception e) {
throw DbException.get(ErrorCode.PARSE_ERROR_1, e, format + "/" + locale + "/" + timeZone);
}
}
private static int getDatePart(String part) {
Integer p = DATE_PART.get(StringUtils.toUpperEnglish(part));
if (p == null) {
throw DbException.getInvalidValueException("date part", part);
}
return p.intValue();
}
/**
* Get the specified field of a date, however with years normalized to positive
* or negative, and month starting with 1.
*
* @param date
* the date value
* @param field
* the field type, see {@link Function} for constants
* @return the value
*/
public static int getIntDatePart(Value date, int field) {
long[] a = DateTimeUtils.dateAndTimeFromValue(date);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) {
case YEAR:
return DateTimeUtils.yearFromDateValue(dateValue);
case MONTH:
return DateTimeUtils.monthFromDateValue(dateValue);
case DAY_OF_MONTH:
return DateTimeUtils.dayFromDateValue(dateValue);
case HOUR:
return (int) (timeNanos / 3_600_000_000_000L % 24);
case MINUTE:
return (int) (timeNanos / 60_000_000_000L % 60);
case SECOND:
return (int) (timeNanos / 1_000_000_000 % 60);
case MILLISECOND:
return (int) (timeNanos / 1_000_000 % 1_000);
case MICROSECOND:
return (int) (timeNanos / 1_000 % 1_000_000);
case NANOSECOND:
return (int) (timeNanos % 1_000_000_000);
case DAY_OF_YEAR:
return DateTimeUtils.getDayOfYear(dateValue);
case DAY_OF_WEEK:
return DateTimeUtils.getSundayDayOfWeek(dateValue);
case WEEK:
GregorianCalendar gc = DateTimeUtils.getCalendar();
return DateTimeUtils.getWeekOfYear(dateValue, gc.getFirstDayOfWeek() - 1, gc.getMinimalDaysInFirstWeek());
case QUARTER:
return (DateTimeUtils.monthFromDateValue(dateValue) - 1) / 3 + 1;
case ISO_YEAR:
return DateTimeUtils.getIsoWeekYear(dateValue);
case ISO_WEEK:
return DateTimeUtils.getIsoWeekOfYear(dateValue);
case ISO_DAY_OF_WEEK:
return DateTimeUtils.getIsoDayOfWeek(dateValue);
case TIMEZONE_HOUR:
case TIMEZONE_MINUTE: {
int offsetMinutes;
if (date instanceof ValueTimestampTimeZone) {
offsetMinutes = ((ValueTimestampTimeZone) date).getTimeZoneOffsetMins();
} else {
offsetMinutes = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue, timeNanos);
}
if (field == TIMEZONE_HOUR) {
return offsetMinutes / 60;
}
return offsetMinutes % 60;
}
}
throw DbException.getUnsupportedException("getDatePart(" + date + ", " + field + ')');
}
/**
* Return names of month or weeks.
*
* @param field
* 0 for months, 1 for weekdays
* @return names of month or weeks
*/
public static String[] getMonthsAndWeeks(int field) {
String[][] result = MONTHS_AND_WEEKS;
if (result == null) {
result = new String[2][];
DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.ENGLISH);
result[0] = dfs.getMonths();
result[1] = dfs.getWeekdays();
MONTHS_AND_WEEKS = result;
}
return result[field];
}
/**
* Check if a given string is a valid date part string.
*
* @param part
* the string
* @return true if it is
*/
public static boolean isDatePart(String part) {
return DATE_PART.containsKey(StringUtils.toUpperEnglish(part));
}
/**
* Parses a date using a format string.
*
* @param date
* the date to parse
* @param format
* the parsing format
* @param locale
* the locale
* @param timeZone
* the timeZone
* @return the parsed date
*/
public static java.util.Date parseDateTime(String date, String format, String locale, String timeZone) {
SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone);
try {
synchronized (dateFormat) {
return dateFormat.parse(date);
}
} catch (Exception e) {
// ParseException
throw DbException.get(ErrorCode.PARSE_ERROR_1, e, date);
}
}
private static long weekdiff(long absolute1, long absolute2, int firstDayOfWeek) {
absolute1 += 4 - firstDayOfWeek;
long r1 = absolute1 / 7;
if (absolute1 < 0 && (r1 * 7 != absolute1)) {
r1--;
}
absolute2 += 4 - firstDayOfWeek;
long r2 = absolute2 / 7;
if (absolute2 < 0 && (r2 * 7 != absolute2)) {
r2--;
}
return r2 - r1;
}
private DateTimeFunctions() {
}
}
...@@ -9,12 +9,9 @@ package org.h2.util; ...@@ -9,12 +9,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.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.api.ErrorCode;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -58,7 +55,7 @@ public class DateTimeUtils { ...@@ -58,7 +55,7 @@ public class DateTimeUtils {
/** /**
* Date value for 1970-01-01. * Date value for 1970-01-01.
*/ */
private static final int EPOCH_DATE_VALUE = (1970 << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1; public static final int EPOCH_DATE_VALUE = (1970 << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1;
private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31, private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31,
30, 31, 31, 30, 31, 30, 31 }; 30, 31, 31, 30, 31, 30, 31 };
...@@ -744,8 +741,7 @@ public class DateTimeUtils { ...@@ -744,8 +741,7 @@ public class DateTimeUtils {
* @return number of day in year * @return number of day in year
*/ */
public static int getDayOfYear(long dateValue) { public static int getDayOfYear(long dateValue) {
int year = yearFromDateValue(dateValue); return (int) (absoluteDayFromDateValue(dateValue) - absoluteDayFromYear(yearFromDateValue(dateValue))) + 1;
return (int) (absoluteDayFromDateValue(dateValue) - absoluteDayFromDateValue(dateValue(year, 1, 1))) + 1;
} }
/** /**
...@@ -825,7 +821,7 @@ public class DateTimeUtils { ...@@ -825,7 +821,7 @@ public class DateTimeUtils {
} }
private static long getWeekOfYearBase(int year, int firstDayOfWeek, int minimalDaysInFirstWeek) { private static long getWeekOfYearBase(int year, int firstDayOfWeek, int minimalDaysInFirstWeek) {
long first = absoluteDayFromDateValue(dateValue(year, 1, 1)); long first = absoluteDayFromYear(year);
int daysInFirstWeek = 8 - getDayOfWeekFromAbsolute(first, firstDayOfWeek); int daysInFirstWeek = 8 - getDayOfWeekFromAbsolute(first, firstDayOfWeek);
long base = first + daysInFirstWeek; long base = first + daysInFirstWeek;
if (daysInFirstWeek >= minimalDaysInFirstWeek) { if (daysInFirstWeek >= minimalDaysInFirstWeek) {
...@@ -860,67 +856,6 @@ public class DateTimeUtils { ...@@ -860,67 +856,6 @@ public class DateTimeUtils {
return year; return year;
} }
/**
* Formats a date using a format string.
*
* @param date the date to format
* @param format the format string
* @param locale the locale
* @param timeZone the timezone
* @return the formatted date
*/
public static String formatDateTime(java.util.Date date, String format,
String locale, String timeZone) {
SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone);
synchronized (dateFormat) {
return dateFormat.format(date);
}
}
/**
* Parses a date using a format string.
*
* @param date the date to parse
* @param format the parsing format
* @param locale the locale
* @param timeZone the timeZone
* @return the parsed date
*/
public static java.util.Date parseDateTime(String date, String format,
String locale, String timeZone) {
SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone);
try {
synchronized (dateFormat) {
return dateFormat.parse(date);
}
} catch (Exception e) {
// ParseException
throw DbException.get(ErrorCode.PARSE_ERROR_1, e, date);
}
}
private static SimpleDateFormat getDateFormat(String format, String locale,
String timeZone) {
try {
// currently, a new instance is create for each call
// however, could cache the last few instances
SimpleDateFormat df;
if (locale == null) {
df = new SimpleDateFormat(format);
} else {
Locale l = new Locale(locale);
df = new SimpleDateFormat(format, l);
}
if (timeZone != null) {
df.setTimeZone(TimeZone.getTimeZone(timeZone));
}
return df;
} catch (Exception e) {
throw DbException.get(ErrorCode.PARSE_ERROR_1, e,
format + "/" + locale + "/" + timeZone);
}
}
/** /**
* Returns number of days in month. * Returns number of days in month.
* *
...@@ -1230,6 +1165,26 @@ public class DateTimeUtils { ...@@ -1230,6 +1165,26 @@ public class DateTimeUtils {
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) offsetMins); return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) offsetMins);
} }
/**
* Calculate the absolute day for a January, 1 of the specified year.
*
* @param year
* the year
* @return the absolute day
*/
public static long absoluteDayFromYear(long year) {
year--;
long a = ((year * 1461L) >> 2) - 719_177;
if (year < 1582) {
// Julian calendar
a += 13;
} else if (year < 1900 || year > 2099) {
// Gregorian calendar (slow mode)
a += (year / 400) - (year / 100) + 15;
}
return a;
}
/** /**
* Calculate the absolute day from an encoded date value. * Calculate the absolute day from an encoded date value.
* *
...@@ -1244,11 +1199,11 @@ public class DateTimeUtils { ...@@ -1244,11 +1199,11 @@ public class DateTimeUtils {
y--; y--;
m += 12; m += 12;
} }
long a = ((y * 2922L) >> 3) + DAYS_OFFSET[m - 3] + d - 719_484; long a = ((y * 1461L) >> 2) + DAYS_OFFSET[m - 3] + d - 719_484;
if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1015))) { if (y <= 1582 && ((y < 1582) || (m * 100 + d < 10_15))) {
// Julian calendar (cutover at 1582-10-04 / 1582-10-15) // Julian calendar (cutover at 1582-10-04 / 1582-10-15)
a += 13; a += 13;
} else if (y < 1901 || y > 2099) { } else if (y < 1900 || y > 2099) {
// Gregorian calendar (slow mode) // Gregorian calendar (slow mode)
a += (y / 400) - (y / 100) + 15; a += (y / 400) - (y / 100) + 15;
} }
...@@ -1270,8 +1225,8 @@ public class DateTimeUtils { ...@@ -1270,8 +1225,8 @@ public class DateTimeUtils {
y--; y--;
m += 12; m += 12;
} }
long a = ((y * 2922L) >> 3) + DAYS_OFFSET[m - 3] + d - 719_484; long a = ((y * 1461L) >> 2) + DAYS_OFFSET[m - 3] + d - 719_484;
if (y < 1901 || y > 2099) { if (y < 1900 || y > 2099) {
// Slow mode // Slow mode
a += (y / 400) - (y / 100) + 15; a += (y / 400) - (y / 100) + 15;
} }
...@@ -1286,7 +1241,7 @@ public class DateTimeUtils { ...@@ -1286,7 +1241,7 @@ public class DateTimeUtils {
*/ */
public static long dateValueFromAbsoluteDay(long absoluteDay) { public static long dateValueFromAbsoluteDay(long absoluteDay) {
long d = absoluteDay + 719_468; long d = absoluteDay + 719_468;
long y100 = 0, offset; long y100, offset;
if (d > 578_040) { if (d > 578_040) {
// Gregorian calendar // Gregorian calendar
long y400 = d / 146_097; long y400 = d / 146_097;
...@@ -1296,6 +1251,7 @@ public class DateTimeUtils { ...@@ -1296,6 +1251,7 @@ public class DateTimeUtils {
offset = y400 * 400 + y100 * 100; offset = y400 * 400 + y100 * 100;
} else { } else {
// Julian calendar // Julian calendar
y100 = 0;
d += 292_200_000_002L; d += 292_200_000_002L;
offset = -800_000_000; offset = -800_000_000;
} }
...@@ -1339,14 +1295,13 @@ public class DateTimeUtils { ...@@ -1339,14 +1295,13 @@ public class DateTimeUtils {
if (day < getDaysInMonth(year, month)) { if (day < getDaysInMonth(year, month)) {
return dateValue + 1; return dateValue + 1;
} }
day = 1;
if (month < 12) { if (month < 12) {
month++; month++;
} else { } else {
month = 1; month = 1;
year++; year++;
} }
return dateValue(year, month, day); return dateValue(year, month, 1);
} }
/** /**
......
...@@ -91,8 +91,7 @@ public class ToDateParser { ...@@ -91,8 +91,7 @@ public class ToDateParser {
} }
if (doyValid) { if (doyValid) {
dateValue = DateTimeUtils.dateValueFromAbsoluteDay( dateValue = DateTimeUtils.dateValueFromAbsoluteDay(
DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(year, 1, 1)) DateTimeUtils.absoluteDayFromYear(year) + dayOfYear - 1);
+ dayOfYear - 1);
} else { } else {
int month = this.month; int month = this.month;
if (month == 0) { if (month == 0) {
......
...@@ -845,8 +845,7 @@ public abstract class Value { ...@@ -845,8 +845,7 @@ public abstract class Value {
case TIME: case TIME:
// because the time has set the date to 1970-01-01, // because the time has set the date to 1970-01-01,
// this will be the result // this will be the result
return ValueDate.fromDateValue( return ValueDate.fromDateValue(DateTimeUtils.EPOCH_DATE_VALUE);
DateTimeUtils.dateValue(1970, 1, 1));
case TIMESTAMP: case TIMESTAMP:
return ValueDate.fromDateValue( return ValueDate.fromDateValue(
((ValueTimestamp) this).getDateValue()); ((ValueTimestamp) this).getDateValue());
......
...@@ -26,7 +26,6 @@ public class TestClearReferences extends TestBase { ...@@ -26,7 +26,6 @@ public class TestClearReferences extends TestBase {
"org.h2.compress.CompressLZF.cachedHashTable", "org.h2.compress.CompressLZF.cachedHashTable",
"org.h2.engine.DbSettings.defaultSettings", "org.h2.engine.DbSettings.defaultSettings",
"org.h2.engine.SessionRemote.sessionFactory", "org.h2.engine.SessionRemote.sessionFactory",
"org.h2.expression.Function.MONTHS_AND_WEEKS",
"org.h2.jdbcx.JdbcDataSourceFactory.cachedTraceSystem", "org.h2.jdbcx.JdbcDataSourceFactory.cachedTraceSystem",
"org.h2.store.RecoverTester.instance", "org.h2.store.RecoverTester.instance",
"org.h2.store.fs.FilePath.defaultProvider", "org.h2.store.fs.FilePath.defaultProvider",
...@@ -37,6 +36,7 @@ public class TestClearReferences extends TestBase { ...@@ -37,6 +36,7 @@ public class TestClearReferences extends TestBase {
"org.h2.tools.CompressTool.cachedBuffer", "org.h2.tools.CompressTool.cachedBuffer",
"org.h2.util.CloseWatcher.queue", "org.h2.util.CloseWatcher.queue",
"org.h2.util.CloseWatcher.refs", "org.h2.util.CloseWatcher.refs",
"org.h2.util.DateTimeFunctions.MONTHS_AND_WEEKS",
"org.h2.util.DateTimeUtils.timeZone", "org.h2.util.DateTimeUtils.timeZone",
"org.h2.util.MathUtils.cachedSecureRandom", "org.h2.util.MathUtils.cachedSecureRandom",
"org.h2.util.NetUtils.cachedLocalAddress", "org.h2.util.NetUtils.cachedLocalAddress",
......
...@@ -371,6 +371,9 @@ public class TestDate extends TestBase { ...@@ -371,6 +371,9 @@ public class TestDate extends TestBase {
if (abs != next && next != Long.MIN_VALUE) { if (abs != next && next != Long.MIN_VALUE) {
assertEquals(abs, next); assertEquals(abs, next);
} }
if (m == 1 && d == 1) {
assertEquals(abs, DateTimeUtils.absoluteDayFromYear(y));
}
next = abs + 1; next = abs + 1;
long d2 = DateTimeUtils.dateValueFromAbsoluteDay(abs); long d2 = DateTimeUtils.dateValueFromAbsoluteDay(abs);
assertEquals(date, d2); assertEquals(date, d2);
......
...@@ -13,7 +13,7 @@ import java.util.Random; ...@@ -13,7 +13,7 @@ import java.util.Random;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.test.utils.AssertThrows; import org.h2.test.utils.AssertThrows;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeFunctions;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
/** /**
...@@ -85,7 +85,7 @@ public class TestStringUtils extends TestBase { ...@@ -85,7 +85,7 @@ public class TestStringUtils extends TestBase {
StringUtils.xmlText("Rand&Blue")); StringUtils.xmlText("Rand&Blue"));
assertEquals("&lt;&lt;[[[]]]&gt;&gt;", assertEquals("&lt;&lt;[[[]]]&gt;&gt;",
StringUtils.xmlCData("<<[[[]]]>>")); StringUtils.xmlCData("<<[[[]]]>>"));
Date dt = DateTimeUtils.parseDateTime( Date dt = DateTimeFunctions.parseDateTime(
"2001-02-03 04:05:06 GMT", "2001-02-03 04:05:06 GMT",
"yyyy-MM-dd HH:mm:ss z", "en", "GMT"); "yyyy-MM-dd HH:mm:ss z", "en", "GMT");
String s = StringUtils.xmlStartDoc() String s = StringUtils.xmlStartDoc()
...@@ -99,10 +99,10 @@ public class TestStringUtils extends TestBase { ...@@ -99,10 +99,10 @@ public class TestStringUtils extends TestBase {
+ StringUtils.xmlNode("description", null, "H2 Database Engine") + StringUtils.xmlNode("description", null, "H2 Database Engine")
+ StringUtils.xmlNode("language", null, "en-us") + StringUtils.xmlNode("language", null, "en-us")
+ StringUtils.xmlNode("pubDate", null, + StringUtils.xmlNode("pubDate", null,
DateTimeUtils.formatDateTime(dt, DateTimeFunctions.formatDateTime(dt,
"EEE, d MMM yyyy HH:mm:ss z", "en", "GMT")) "EEE, d MMM yyyy HH:mm:ss z", "en", "GMT"))
+ StringUtils.xmlNode("lastBuildDate", null, + StringUtils.xmlNode("lastBuildDate", null,
DateTimeUtils.formatDateTime(dt, DateTimeFunctions.formatDateTime(dt,
"EEE, d MMM yyyy HH:mm:ss z", "en", "GMT")) "EEE, d MMM yyyy HH:mm:ss z", "en", "GMT"))
+ StringUtils.xmlNode("item", null, + StringUtils.xmlNode("item", null,
StringUtils.xmlNode("title", null, StringUtils.xmlNode("title", null,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论