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. ...@@ -3685,6 +3685,7 @@ Returns the the number of crossed unit boundaries between two timestamps.
This method returns a long. This method returns a long.
The string indicates the unit. The string indicates the unit.
The same units as in the EXTRACT function are supported. 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) DATEDIFF('YEAR', T1.CREATED, T2.CREATED)
" "
...@@ -3722,7 +3723,7 @@ DAY_OF_YEAR(CREATED) ...@@ -3722,7 +3723,7 @@ DAY_OF_YEAR(CREATED)
" "
"Functions (Time and Date)","EXTRACT"," "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 } | DOY | HOUR | HH | MINUTE | MI | SECOND | SS | MILLISECOND | MS }
FROM timestamp ) FROM timestamp )
"," ","
......
...@@ -20,7 +20,6 @@ import java.util.ArrayList; ...@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
...@@ -175,6 +174,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -175,6 +174,7 @@ public class Function extends Expression implements FunctionCall {
DATE_PART.put("WW", WEEK); DATE_PART.put("WW", WEEK);
DATE_PART.put("WK", WEEK); DATE_PART.put("WK", WEEK);
DATE_PART.put("WEEK", WEEK); DATE_PART.put("WEEK", WEEK);
DATE_PART.put("ISO_WEEK", ISO_WEEK);
DATE_PART.put("DAY", DAY_OF_MONTH); DATE_PART.put("DAY", DAY_OF_MONTH);
DATE_PART.put("DD", DAY_OF_MONTH); DATE_PART.put("DD", DAY_OF_MONTH);
DATE_PART.put("D", DAY_OF_MONTH); DATE_PART.put("D", DAY_OF_MONTH);
...@@ -1489,8 +1489,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1489,8 +1489,7 @@ public class Function extends Expression implements FunctionCall {
v0.getString(), v1.getLong(), v2.getTimestamp())); v0.getString(), v1.getLong(), v2.getTimestamp()));
break; break;
case DATE_DIFF: case DATE_DIFF:
result = ValueLong.get(datediff( result = ValueLong.get(datediff(v0.getString(), v1, v2));
v0.getString(), v1.getTimestamp(), v2.getTimestamp()));
break; break;
case EXTRACT: { case EXTRACT: {
int field = getDatePart(v0.getString()); int field = getDatePart(v0.getString());
...@@ -1863,79 +1862,72 @@ public class Function extends Expression implements FunctionCall { ...@@ -1863,79 +1862,72 @@ public class Function extends Expression implements FunctionCall {
* </pre> * </pre>
* *
* @param part the part * @param part the part
* @param d1 the first date * @param v1 the first date-time value
* @param d2 the second date * @param v2 the second date-time value
* @return the number of crossed boundaries * @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); int field = getDatePart(part);
Calendar calendar = DateTimeUtils.createGregorianCalendar(); long[] a1 = DateTimeUtils.dateAndTimeFromValue(v1);
long t1 = d1.getTime(), t2 = d2.getTime(); long dateValue1 = a1[0];
// need to convert to UTC, otherwise we get inconsistent results with long absolute1 = DateTimeUtils.absoluteDayFromDateValue(dateValue1);
// certain time zones (those that are 30 minutes off) long[] a2 = DateTimeUtils.dateAndTimeFromValue(v2);
TimeZone zone = calendar.getTimeZone(); long dateValue2 = a2[0];
calendar.setTime(d1); long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2);
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));
switch (field) { switch (field) {
case MILLISECOND: case MILLISECOND:
return t2 - t1;
case SECOND: case SECOND:
case MINUTE: case MINUTE:
case HOUR: case HOUR:
case DAY_OF_YEAR: long timeNanos1 = a1[1];
case WEEK: { long timeNanos2 = a2[1];
// 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;
switch (field) { switch (field) {
case MILLISECOND:
return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY
+ (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000);
case SECOND: case SECOND:
return t2 / 1000 - t1 / 1000; return (absolute2 - absolute1) * 86_400
+ (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000);
case MINUTE: 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: case HOUR:
return t2 / hour - t1 / hour; return (absolute2 - absolute1) * 24
case DAY_OF_YEAR: + (timeNanos2 / 3_600_000_000_000L - timeNanos1 / 3_600_000_000_000L);
return t2 / (hour * 24) - t1 / (hour * 24);
case WEEK:
return t2 / (hour * 24 * 7) - t1 / (hour * 24 * 7);
default:
throw DbException.throwInternalError("field:" + field);
} }
} // Fake fall-through
//$FALL-THROUGH$
case DAY_OF_MONTH: 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: 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); 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) { private static String substring(String s, int start, int length) {
int len = s.length(); int len = s.length();
start--; start--;
......
...@@ -562,33 +562,47 @@ public class DateTimeUtils { ...@@ -562,33 +562,47 @@ public class DateTimeUtils {
} }
/** /**
* Get the specified field of a date, however with years normalized to * Extracts date value and nanos of day from the specified value.
* positive or negative, and month starting with 1.
* *
* @param date the date value * @param value
* @param field the field type, see {@link Function} for constants * value to extract fields from
* @return the value * @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 dateValue = EPOCH_DATE_VALUE;
long timeNanos = 0; long timeNanos = 0;
if (date instanceof ValueTimestamp) { if (value instanceof ValueTimestamp) {
ValueTimestamp v = (ValueTimestamp) date; ValueTimestamp v = (ValueTimestamp) value;
dateValue = v.getDateValue(); dateValue = v.getDateValue();
timeNanos = v.getTimeNanos(); timeNanos = v.getTimeNanos();
} else if (date instanceof ValueDate) { } else if (value instanceof ValueDate) {
dateValue = ((ValueDate) date).getDateValue(); dateValue = ((ValueDate) value).getDateValue();
} else if (date instanceof ValueTime) { } else if (value instanceof ValueTime) {
timeNanos = ((ValueTime) date).getNanos(); timeNanos = ((ValueTime) value).getNanos();
} else if (date instanceof ValueTimestampTimeZone) { } else if (value instanceof ValueTimestampTimeZone) {
ValueTimestampTimeZone v = (ValueTimestampTimeZone) date; ValueTimestampTimeZone v = (ValueTimestampTimeZone) value;
dateValue = v.getDateValue(); dateValue = v.getDateValue();
timeNanos = v.getTimeNanos(); timeNanos = v.getTimeNanos();
} else { } else {
ValueTimestamp v = (ValueTimestamp) date.convertTo(Value.TIMESTAMP); ValueTimestamp v = (ValueTimestamp) value.convertTo(Value.TIMESTAMP);
dateValue = v.getDateValue(); dateValue = v.getDateValue();
timeNanos = v.getTimeNanos(); 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) { switch (field) {
case Function.YEAR: case Function.YEAR:
return yearFromDateValue(dateValue); return yearFromDateValue(dateValue);
......
...@@ -158,3 +158,39 @@ call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 0 ...@@ -158,3 +158,39 @@ call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 0
> ------------- > -------------
> 3408134399000 > 3408134399000
> rows: 1 > 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论