提交 04c2fd11 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Reimplement DATEADD without a Calendar and correct its return type

上级 f011b5bc
...@@ -14,7 +14,6 @@ import java.nio.charset.StandardCharsets; ...@@ -14,7 +14,6 @@ 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.sql.Timestamp;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
...@@ -1485,8 +1484,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1485,8 +1484,7 @@ public class Function extends Expression implements FunctionCall {
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
case DATE_ADD: case DATE_ADD:
result = ValueTimestamp.get(dateadd( result = dateadd(v0.getString(), v1.getLong(), v2);
v0.getString(), v1.getLong(), v2.getTimestamp()));
break; break;
case DATE_DIFF: case DATE_DIFF:
result = ValueLong.get(datediff(v0.getString(), v1, v2)); result = ValueLong.get(datediff(v0.getString(), v1, v2));
...@@ -1805,53 +1803,79 @@ public class Function extends Expression implements FunctionCall { ...@@ -1805,53 +1803,79 @@ public class Function extends Expression implements FunctionCall {
return p.intValue(); return p.intValue();
} }
private static Timestamp dateadd(String part, long count, Timestamp d) { private static Value dateadd(String part, long count, Value v) {
int field = getDatePart(part); int field = getDatePart(part);
//v = v.convertTo(Value.TIMESTAMP);
if (field != MILLISECOND && count > Integer.MAX_VALUE || count < Integer.MIN_VALUE) {
throw DbException.getInvalidValueException("DATEADD count", count);
}
boolean withDate = !(v instanceof ValueTime), withTime = !(v instanceof ValueDate), forceTimestamp = false;
long[] a = DateTimeUtils.dateAndTimeFromValue(v);
long dateValue = a[0];
long timeNanos = a[1];
switch (field) { switch (field) {
case YEAR: case YEAR:
field = Calendar.YEAR; case MONTH: {
break; if (!withDate) {
case MONTH: throw DbException.getInvalidValueException("DATEADD timestamp", v);
field = Calendar.MONTH; }
break; 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 DAY_OF_MONTH: case DAY_OF_MONTH:
field = Calendar.DAY_OF_MONTH;
break;
case DAY_OF_YEAR: case DAY_OF_YEAR:
field = Calendar.DAY_OF_YEAR; if (!withDate) {
break; throw DbException.getInvalidValueException("DATEADD timestamp", v);
case WEEK: }
field = Calendar.WEEK_OF_YEAR; dateValue = DateTimeUtils.dateValueFromAbsoluteDay(
break; DateTimeUtils.absoluteDayFromDateValue(dateValue) + count);
return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
case HOUR: case HOUR:
field = Calendar.HOUR_OF_DAY; count *= 3_600_000_000_000L;
break; break;
case MINUTE: case MINUTE:
field = Calendar.MINUTE; count *= 60_000_000_000L;
break; break;
case SECOND: case SECOND:
field = Calendar.SECOND; count *= 1_000_000_000;
break;
case MILLISECOND:
count *= 1_000_000;
break; break;
case MILLISECOND: {
Timestamp ts = new Timestamp(d.getTime() + count);
ts.setNanos(ts.getNanos() + (d.getNanos() % 1000000));
return ts;
}
default: default:
throw DbException.getUnsupportedException("DATEADD " + part); throw DbException.getUnsupportedException("DATEADD " + part);
} }
// We allow long for manipulating the millisecond component, if (!withTime) {
// for the rest we only allow int. // Treat date as timestamp at the start of this date
if (count > Integer.MAX_VALUE) { forceTimestamp = true;
throw DbException.getInvalidValueException("DATEADD count", count); }
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);
} }
Calendar calendar = DateTimeUtils.createGregorianCalendar(); return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos, forceTimestamp);
int nanos = d.getNanos() % 1000000;
calendar.setTime(d);
calendar.add(field, (int) count);
Timestamp ts = new Timestamp(calendar.getTimeInMillis());
ts.setNanos(ts.getNanos() + nanos);
return ts;
} }
/** /**
......
...@@ -591,6 +591,40 @@ public class DateTimeUtils { ...@@ -591,6 +591,40 @@ public class DateTimeUtils {
return new long[] {dateValue, timeNanos}; return new long[] {dateValue, timeNanos};
} }
/**
* Creates a new date-time value with the same type as original value. If
* original value is a ValueTimestampTimeZone, returned value will have the same
* time zone offset as original value.
*
* @param original
* original value
* @param dateValue
* date value for the returned value
* @param timeNanos
* nanos of day for the returned value
* @param forceTimestamp
* if {@code true} return ValueTimestamp if original argument is
* ValueDate or ValueTime
* @return new value with specified date value and nanos of day
*/
public static Value dateTimeToValue(Value original, long dateValue, long timeNanos, boolean forceTimestamp) {
if (!(original instanceof ValueTimestamp)) {
if (!forceTimestamp) {
if (original instanceof ValueDate) {
return ValueDate.fromDateValue(dateValue);
}
if (original instanceof ValueTime) {
return ValueTime.fromNanos(timeNanos);
}
}
if (original instanceof ValueTimestampTimeZone) {
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos,
((ValueTimestampTimeZone) original).getTimeZoneOffsetMins());
}
}
return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos);
}
/** /**
* Get the specified field of a date, however with years normalized to * Get the specified field of a date, however with years normalized to
* positive or negative, and month starting with 1. * positive or negative, and month starting with 1.
......
...@@ -81,7 +81,7 @@ select d + t, t + d - t x from test; ...@@ -81,7 +81,7 @@ select d + t, t + d - t x from test;
select 1 + d + 1, d - 1, 2 + ts + 2, ts - 2 from test; select 1 + d + 1, d - 1, 2 + ts + 2, ts - 2 from test;
> DATEADD('DAY', 1, DATEADD('DAY', 1, D)) DATEADD('DAY', -1, D) DATEADD('DAY', 2, DATEADD('DAY', 2, TS)) DATEADD('DAY', -2, TS) > DATEADD('DAY', 1, DATEADD('DAY', 1, D)) DATEADD('DAY', -1, D) DATEADD('DAY', 2, DATEADD('DAY', 2, TS)) DATEADD('DAY', -2, TS)
> --------------------------------------- --------------------- ---------------------------------------- ---------------------- > --------------------------------------- --------------------- ---------------------------------------- ----------------------
> 2001-01-03 00:00:00.0 2000-12-31 00:00:00.0 2010-01-05 00:00:00.0 2009-12-30 00:00:00.0 > 2001-01-03 2000-12-31 2010-01-05 00:00:00.0 2009-12-30 00:00:00.0
> rows: 1 > rows: 1
select 1 + d + t + 1 from test; select 1 + d + t + 1 from test;
...@@ -104,3 +104,24 @@ call dateadd('MS', 1, TIMESTAMP '2001-02-03 04:05:06.789001'); ...@@ -104,3 +104,24 @@ call dateadd('MS', 1, TIMESTAMP '2001-02-03 04:05:06.789001');
> -------------------------------------- > --------------------------------------
> 2001-02-03 04:05:06.790001 > 2001-02-03 04:05:06.790001
> rows: 1 > rows: 1
SELECT DATEADD('HOUR', 1, DATE '2010-01-20');
> TIMESTAMP '2010-01-20 01:00:00.0'
> ---------------------------------
> 2010-01-20 01:00:00.0
> rows: 1
SELECT DATEADD('MINUTE', 30, TIME '12:30:55');
> TIME '13:00:55'
> ---------------
> 13:00:55
> rows: 1
SELECT DATEADD('DAY', 1, TIME '12:30:55');
> exception
SELECT DATEADD('DAY', 10, TIMESTAMP WITH TIME ZONE '2000-01-05 15:00:30.123456789-10');
> TIMESTAMP WITH TIME ZONE '2000-01-15 15:00:30.123456789-10'
> -----------------------------------------------------------
> 2000-01-15 15:00:30.123456789-10
> rows: 1
...@@ -7586,9 +7586,9 @@ SELECT * FROM TEST; ...@@ -7586,9 +7586,9 @@ SELECT * FROM TEST;
SELECT XD+1, XD-1, XD-XD FROM TEST; SELECT XD+1, XD-1, XD-XD FROM TEST;
> DATEADD('DAY', 1, XD) DATEADD('DAY', -1, XD) DATEDIFF('DAY', XD, XD) > DATEADD('DAY', 1, XD) DATEADD('DAY', -1, XD) DATEDIFF('DAY', XD, XD)
> --------------------- ---------------------- ----------------------- > --------------------- ---------------------- -----------------------
> 0001-02-04 00:00:00.0 0001-02-02 00:00:00.0 0 > 0001-02-04 0001-02-02 0
> 0004-05-07 00:00:00.0 0004-05-05 00:00:00.0 0 > 0004-05-07 0004-05-05 0
> 2000-01-01 00:00:00.0 1999-12-30 00:00:00.0 0 > 2000-01-01 1999-12-30 0
> null null null > null null null
> rows: 4 > rows: 4
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论