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

Merge pull request #851 from katzyn/datetime

Reimplement DATEDIFF without a Calendar
......@@ -3685,6 +3685,7 @@ Returns the the number of crossed unit boundaries between two timestamps.
This method returns a long.
The string indicates the unit.
The same units as in the EXTRACT function are supported.
If timestamps have time zone offset component it is ignored.
","
DATEDIFF('YEAR', T1.CREATED, T2.CREATED)
"
......@@ -3722,7 +3723,7 @@ DAY_OF_YEAR(CREATED)
"
"Functions (Time and Date)","EXTRACT","
EXTRACT ( { YEAR | YY | MONTH | MM | WEEK | DAY | DD | DAY_OF_YEAR
EXTRACT ( { YEAR | YY | MONTH | MM | WEEK | ISO_WEEK | DAY | DD | DAY_OF_YEAR
| DOY | HOUR | HH | MINUTE | MI | SECOND | SS | MILLISECOND | MS }
FROM timestamp )
","
......
......@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
......@@ -175,6 +174,7 @@ public class Function extends Expression implements FunctionCall {
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);
......@@ -1489,8 +1489,7 @@ public class Function extends Expression implements FunctionCall {
v0.getString(), v1.getLong(), v2.getTimestamp()));
break;
case DATE_DIFF:
result = ValueLong.get(datediff(
v0.getString(), v1.getTimestamp(), v2.getTimestamp()));
result = ValueLong.get(datediff(v0.getString(), v1, v2));
break;
case EXTRACT: {
int field = getDatePart(v0.getString());
......@@ -1863,79 +1862,72 @@ public class Function extends Expression implements FunctionCall {
* </pre>
*
* @param part the part
* @param d1 the first date
* @param d2 the second date
* @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, Timestamp d1, Timestamp d2) {
private static long datediff(String part, Value v1, Value v2) {
int field = getDatePart(part);
Calendar calendar = DateTimeUtils.createGregorianCalendar();
long t1 = d1.getTime(), t2 = d2.getTime();
// need to convert to UTC, otherwise we get inconsistent results with
// certain time zones (those that are 30 minutes off)
TimeZone zone = calendar.getTimeZone();
calendar.setTime(d1);
t1 += zone.getOffset(calendar.get(Calendar.ERA),
calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.DAY_OF_WEEK),
calendar.get(Calendar.MILLISECOND));
calendar.setTime(d2);
t2 += zone.getOffset(calendar.get(Calendar.ERA),
calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.DAY_OF_WEEK),
calendar.get(Calendar.MILLISECOND));
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 MILLISECOND:
return t2 - t1;
case SECOND:
case MINUTE:
case HOUR:
case DAY_OF_YEAR:
case WEEK: {
// first 'normalize' the numbers so both are not negative
long hour = 60 * 60 * 1000;
long add = Math.min(t1 / hour * hour, t2 / hour * hour);
t1 -= add;
t2 -= add;
long timeNanos1 = a1[1];
long timeNanos2 = a2[1];
switch (field) {
case MILLISECOND:
return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY
+ (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000);
case SECOND:
return t2 / 1000 - t1 / 1000;
return (absolute2 - absolute1) * 86_400
+ (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000);
case MINUTE:
return t2 / (60 * 1000) - t1 / (60 * 1000);
return (absolute2 - absolute1) * 1_440
+ (timeNanos2 / 60_000_000_000L - timeNanos1 / 60_000_000_000L);
case HOUR:
return t2 / hour - t1 / hour;
case DAY_OF_YEAR:
return t2 / (hour * 24) - t1 / (hour * 24);
case WEEK:
return t2 / (hour * 24 * 7) - t1 / (hour * 24 * 7);
default:
throw DbException.throwInternalError("field:" + field);
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:
return t2 / (24 * 60 * 60 * 1000) - t1 / (24 * 60 * 60 * 1000);
case DAY_OF_YEAR:
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 YEAR:
return DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1);
default:
break;
}
calendar = DateTimeUtils.createGregorianCalendar(DateTimeUtils.UTC);
calendar.setTimeInMillis(t1);
int year1 = calendar.get(Calendar.YEAR);
int month1 = calendar.get(Calendar.MONTH);
calendar.setTimeInMillis(t2);
int year2 = calendar.get(Calendar.YEAR);
int month2 = calendar.get(Calendar.MONTH);
int result = year2 - year1;
if (field == MONTH) {
return 12 * result + (month2 - month1);
} else if (field == YEAR) {
return result;
} else {
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 substring(String s, int start, int length) {
int len = s.length();
start--;
......
......@@ -562,33 +562,47 @@ public class DateTimeUtils {
}
/**
* Get the specified field of a date, however with years normalized to
* positive or negative, and month starting with 1.
* Extracts date value and nanos of day from the specified value.
*
* @param date the date value
* @param field the field type, see {@link Function} for constants
* @return the value
* @param value
* value to extract fields from
* @return array with date value and nanos of day
*/
public static int getDatePart(Value date, int field) {
public static long[] dateAndTimeFromValue(Value value) {
long dateValue = EPOCH_DATE_VALUE;
long timeNanos = 0;
if (date instanceof ValueTimestamp) {
ValueTimestamp v = (ValueTimestamp) date;
if (value instanceof ValueTimestamp) {
ValueTimestamp v = (ValueTimestamp) value;
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
} else if (date instanceof ValueDate) {
dateValue = ((ValueDate) date).getDateValue();
} else if (date instanceof ValueTime) {
timeNanos = ((ValueTime) date).getNanos();
} else if (date instanceof ValueTimestampTimeZone) {
ValueTimestampTimeZone v = (ValueTimestampTimeZone) date;
} else if (value instanceof ValueDate) {
dateValue = ((ValueDate) value).getDateValue();
} else if (value instanceof ValueTime) {
timeNanos = ((ValueTime) value).getNanos();
} else if (value instanceof ValueTimestampTimeZone) {
ValueTimestampTimeZone v = (ValueTimestampTimeZone) value;
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
} else {
ValueTimestamp v = (ValueTimestamp) date.convertTo(Value.TIMESTAMP);
ValueTimestamp v = (ValueTimestamp) value.convertTo(Value.TIMESTAMP);
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
}
return new long[] {dateValue, timeNanos};
}
/**
* 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 getDatePart(Value date, int field) {
long[] a = dateAndTimeFromValue(date);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) {
case Function.YEAR:
return yearFromDateValue(dateValue);
......
......@@ -158,3 +158,39 @@ call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 0
> -------------
> 3408134399000
> rows: 1
SELECT DATEDIFF('WEEK', DATE '2018-02-02', DATE '2018-02-03'), DATEDIFF('ISO_WEEK', DATE '2018-02-02', DATE '2018-02-03');
> 0 0
> - -
> 0 0
> rows: 1
SELECT DATEDIFF('WEEK', DATE '2018-02-03', DATE '2018-02-04'), DATEDIFF('ISO_WEEK', DATE '2018-02-03', DATE '2018-02-04');
> 1 0
> - -
> 1 0
> rows: 1
SELECT DATEDIFF('WEEK', DATE '2018-02-04', DATE '2018-02-05'), DATEDIFF('ISO_WEEK', DATE '2018-02-04', DATE '2018-02-05');
> 0 1
> - -
> 0 1
> rows: 1
SELECT DATEDIFF('WEEK', DATE '2018-02-05', DATE '2018-02-06'), DATEDIFF('ISO_WEEK', DATE '2018-02-05', DATE '2018-02-06');
> 0 0
> - -
> 0 0
> rows: 1
SELECT DATEDIFF('WEEK', DATE '1969-12-27', DATE '1969-12-28'), DATEDIFF('ISO_WEEK', DATE '1969-12-27', DATE '1969-12-28');
> 1 0
> - -
> 1 0
> rows: 1
SELECT DATEDIFF('WEEK', DATE '1969-12-28', DATE '1969-12-29'), DATEDIFF('ISO_WEEK', DATE '1969-12-28', DATE '1969-12-29');
> 0 1
> - -
> 0 1
> rows: 1
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论