diff --git a/docs/changelog/138316.yaml b/docs/changelog/138316.yaml new file mode 100644 index 0000000000000..d891bf170f6b0 --- /dev/null +++ b/docs/changelog/138316.yaml @@ -0,0 +1,5 @@ +pr: 138316 +summary: '`DateDiff` timezone support' +area: ES|QL +type: feature +issues: [] diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec index 92e51dfab858e..b7d4cda7185aa 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/date.csv-spec @@ -380,6 +380,28 @@ ROW end_23 = TO_DATETIME("2023-12-31T23:59:59.999Z"), // end::evalDateDiffYearForDocs-result[] ; +dateDiffOffsetTimezone +required_capability: date_diff_timezone_support + +set time_zone="+03:00"\; +ROW diff = DATE_DIFF("months", "2010-10-07T00:00:00-03:00"::date, "2010-11-06T23:01:00-04:00"::date) +; + +diff:integer +1 +; + +dateDiffDstTimezone +required_capability: date_diff_timezone_support + +set time_zone="America/Goose_Bay"\; +ROW diff = DATE_DIFF("months", "2010-10-07T00:00:00-03:00"::date, "2010-11-06T23:01:00-04:00"::date) +; + +diff:integer +0 +; + evalDateParseWithSimpleDate row a = "2023-02-01" | eval b = date_parse("yyyy-MM-dd", a) | keep b; diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisEvaluator.java index 06b75750220e8..6181c3428a6a5 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.IntBlock; @@ -35,17 +36,20 @@ public final class DateDiffConstantMillisEvaluator implements EvalOperator.Expre private final EvalOperator.ExpressionEvaluator endTimestamp; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffConstantMillisEvaluator(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator startTimestamp, - EvalOperator.ExpressionEvaluator endTimestamp, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestamp, ZoneId zoneId, DriverContext driverContext) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -103,7 +107,7 @@ public IntBlock eval(int positionCount, LongBlock startTimestampBlock, long startTimestamp = startTimestampBlock.getLong(startTimestampBlock.getFirstValueIndex(p)); long endTimestamp = endTimestampBlock.getLong(endTimestampBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processMillis(this.datePartFieldUnit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processMillis(this.datePartFieldUnit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -120,7 +124,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampVector, long startTimestamp = startTimestampVector.getLong(p); long endTimestamp = endTimestampVector.getLong(p); try { - result.appendInt(DateDiff.processMillis(this.datePartFieldUnit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processMillis(this.datePartFieldUnit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -132,7 +136,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampVector, @Override public String toString() { - return "DateDiffConstantMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffConstantMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } @Override @@ -161,23 +165,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestamp; + private final ZoneId zoneId; + public Factory(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator.Factory startTimestamp, - EvalOperator.ExpressionEvaluator.Factory endTimestamp) { + EvalOperator.ExpressionEvaluator.Factory endTimestamp, ZoneId zoneId) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; } @Override public DateDiffConstantMillisEvaluator get(DriverContext context) { - return new DateDiffConstantMillisEvaluator(source, datePartFieldUnit, startTimestamp.get(context), endTimestamp.get(context), context); + return new DateDiffConstantMillisEvaluator(source, datePartFieldUnit, startTimestamp.get(context), endTimestamp.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffConstantMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffConstantMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisNanosEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisNanosEvaluator.java index 0215abf52cca6..2f5bc4e4f67c5 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisNanosEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantMillisNanosEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.IntBlock; @@ -35,17 +36,21 @@ public final class DateDiffConstantMillisNanosEvaluator implements EvalOperator. private final EvalOperator.ExpressionEvaluator endTimestampNanos; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffConstantMillisNanosEvaluator(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator startTimestampMillis, - EvalOperator.ExpressionEvaluator endTimestampNanos, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestampNanos, ZoneId zoneId, + DriverContext driverContext) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestampMillis = startTimestampMillis; this.endTimestampNanos = endTimestampNanos; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -103,7 +108,7 @@ public IntBlock eval(int positionCount, LongBlock startTimestampMillisBlock, long startTimestampMillis = startTimestampMillisBlock.getLong(startTimestampMillisBlock.getFirstValueIndex(p)); long endTimestampNanos = endTimestampNanosBlock.getLong(endTimestampNanosBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processMillisNanos(this.datePartFieldUnit, startTimestampMillis, endTimestampNanos)); + result.appendInt(DateDiff.processMillisNanos(this.datePartFieldUnit, startTimestampMillis, endTimestampNanos, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -120,7 +125,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampMillisVector, long startTimestampMillis = startTimestampMillisVector.getLong(p); long endTimestampNanos = endTimestampNanosVector.getLong(p); try { - result.appendInt(DateDiff.processMillisNanos(this.datePartFieldUnit, startTimestampMillis, endTimestampNanos)); + result.appendInt(DateDiff.processMillisNanos(this.datePartFieldUnit, startTimestampMillis, endTimestampNanos, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -132,7 +137,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampMillisVector, @Override public String toString() { - return "DateDiffConstantMillisNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + "]"; + return "DateDiffConstantMillisNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + ", zoneId=" + zoneId + "]"; } @Override @@ -161,23 +166,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestampNanos; + private final ZoneId zoneId; + public Factory(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator.Factory startTimestampMillis, - EvalOperator.ExpressionEvaluator.Factory endTimestampNanos) { + EvalOperator.ExpressionEvaluator.Factory endTimestampNanos, ZoneId zoneId) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestampMillis = startTimestampMillis; this.endTimestampNanos = endTimestampNanos; + this.zoneId = zoneId; } @Override public DateDiffConstantMillisNanosEvaluator get(DriverContext context) { - return new DateDiffConstantMillisNanosEvaluator(source, datePartFieldUnit, startTimestampMillis.get(context), endTimestampNanos.get(context), context); + return new DateDiffConstantMillisNanosEvaluator(source, datePartFieldUnit, startTimestampMillis.get(context), endTimestampNanos.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffConstantMillisNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + "]"; + return "DateDiffConstantMillisNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosEvaluator.java index aaf8c1e722ed6..7eabca292c848 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.IntBlock; @@ -35,17 +36,20 @@ public final class DateDiffConstantNanosEvaluator implements EvalOperator.Expres private final EvalOperator.ExpressionEvaluator endTimestamp; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffConstantNanosEvaluator(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator startTimestamp, - EvalOperator.ExpressionEvaluator endTimestamp, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestamp, ZoneId zoneId, DriverContext driverContext) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -103,7 +107,7 @@ public IntBlock eval(int positionCount, LongBlock startTimestampBlock, long startTimestamp = startTimestampBlock.getLong(startTimestampBlock.getFirstValueIndex(p)); long endTimestamp = endTimestampBlock.getLong(endTimestampBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processNanos(this.datePartFieldUnit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processNanos(this.datePartFieldUnit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -120,7 +124,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampVector, long startTimestamp = startTimestampVector.getLong(p); long endTimestamp = endTimestampVector.getLong(p); try { - result.appendInt(DateDiff.processNanos(this.datePartFieldUnit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processNanos(this.datePartFieldUnit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -132,7 +136,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampVector, @Override public String toString() { - return "DateDiffConstantNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffConstantNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } @Override @@ -161,23 +165,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestamp; + private final ZoneId zoneId; + public Factory(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator.Factory startTimestamp, - EvalOperator.ExpressionEvaluator.Factory endTimestamp) { + EvalOperator.ExpressionEvaluator.Factory endTimestamp, ZoneId zoneId) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; } @Override public DateDiffConstantNanosEvaluator get(DriverContext context) { - return new DateDiffConstantNanosEvaluator(source, datePartFieldUnit, startTimestamp.get(context), endTimestamp.get(context), context); + return new DateDiffConstantNanosEvaluator(source, datePartFieldUnit, startTimestamp.get(context), endTimestamp.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffConstantNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffConstantNanosEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosMillisEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosMillisEvaluator.java index e1cc2252e4db6..04fc5005dbba3 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosMillisEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffConstantNanosMillisEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.IntBlock; @@ -35,17 +36,21 @@ public final class DateDiffConstantNanosMillisEvaluator implements EvalOperator. private final EvalOperator.ExpressionEvaluator endTimestampMillis; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffConstantNanosMillisEvaluator(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator startTimestampNanos, - EvalOperator.ExpressionEvaluator endTimestampMillis, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestampMillis, ZoneId zoneId, + DriverContext driverContext) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestampNanos = startTimestampNanos; this.endTimestampMillis = endTimestampMillis; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -103,7 +108,7 @@ public IntBlock eval(int positionCount, LongBlock startTimestampNanosBlock, long startTimestampNanos = startTimestampNanosBlock.getLong(startTimestampNanosBlock.getFirstValueIndex(p)); long endTimestampMillis = endTimestampMillisBlock.getLong(endTimestampMillisBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processNanosMillis(this.datePartFieldUnit, startTimestampNanos, endTimestampMillis)); + result.appendInt(DateDiff.processNanosMillis(this.datePartFieldUnit, startTimestampNanos, endTimestampMillis, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -120,7 +125,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampNanosVector, long startTimestampNanos = startTimestampNanosVector.getLong(p); long endTimestampMillis = endTimestampMillisVector.getLong(p); try { - result.appendInt(DateDiff.processNanosMillis(this.datePartFieldUnit, startTimestampNanos, endTimestampMillis)); + result.appendInt(DateDiff.processNanosMillis(this.datePartFieldUnit, startTimestampNanos, endTimestampMillis, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -132,7 +137,7 @@ public IntBlock eval(int positionCount, LongVector startTimestampNanosVector, @Override public String toString() { - return "DateDiffConstantNanosMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + "]"; + return "DateDiffConstantNanosMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + ", zoneId=" + zoneId + "]"; } @Override @@ -161,23 +166,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestampMillis; + private final ZoneId zoneId; + public Factory(Source source, DateDiff.Part datePartFieldUnit, EvalOperator.ExpressionEvaluator.Factory startTimestampNanos, - EvalOperator.ExpressionEvaluator.Factory endTimestampMillis) { + EvalOperator.ExpressionEvaluator.Factory endTimestampMillis, ZoneId zoneId) { this.source = source; this.datePartFieldUnit = datePartFieldUnit; this.startTimestampNanos = startTimestampNanos; this.endTimestampMillis = endTimestampMillis; + this.zoneId = zoneId; } @Override public DateDiffConstantNanosMillisEvaluator get(DriverContext context) { - return new DateDiffConstantNanosMillisEvaluator(source, datePartFieldUnit, startTimestampNanos.get(context), endTimestampMillis.get(context), context); + return new DateDiffConstantNanosMillisEvaluator(source, datePartFieldUnit, startTimestampNanos.get(context), endTimestampMillis.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffConstantNanosMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + "]"; + return "DateDiffConstantNanosMillisEvaluator[" + "datePartFieldUnit=" + datePartFieldUnit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisEvaluator.java index 0f26acc3c7ec7..5c8a879cef4bb 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; @@ -38,17 +39,20 @@ public final class DateDiffMillisEvaluator implements EvalOperator.ExpressionEva private final EvalOperator.ExpressionEvaluator endTimestamp; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffMillisEvaluator(Source source, EvalOperator.ExpressionEvaluator unit, EvalOperator.ExpressionEvaluator startTimestamp, - EvalOperator.ExpressionEvaluator endTimestamp, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestamp, ZoneId zoneId, DriverContext driverContext) { this.source = source; this.unit = unit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -126,7 +130,7 @@ public IntBlock eval(int positionCount, BytesRefBlock unitBlock, LongBlock start long startTimestamp = startTimestampBlock.getLong(startTimestampBlock.getFirstValueIndex(p)); long endTimestamp = endTimestampBlock.getLong(endTimestampBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processMillis(unit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processMillis(unit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -145,7 +149,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, long startTimestamp = startTimestampVector.getLong(p); long endTimestamp = endTimestampVector.getLong(p); try { - result.appendInt(DateDiff.processMillis(unit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processMillis(unit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -157,7 +161,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, @Override public String toString() { - return "DateDiffMillisEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffMillisEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } @Override @@ -186,23 +190,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestamp; + private final ZoneId zoneId; + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory unit, EvalOperator.ExpressionEvaluator.Factory startTimestamp, - EvalOperator.ExpressionEvaluator.Factory endTimestamp) { + EvalOperator.ExpressionEvaluator.Factory endTimestamp, ZoneId zoneId) { this.source = source; this.unit = unit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; } @Override public DateDiffMillisEvaluator get(DriverContext context) { - return new DateDiffMillisEvaluator(source, unit.get(context), startTimestamp.get(context), endTimestamp.get(context), context); + return new DateDiffMillisEvaluator(source, unit.get(context), startTimestamp.get(context), endTimestamp.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffMillisEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffMillisEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisNanosEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisNanosEvaluator.java index cdf867bf04740..c0642e0a5390a 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisNanosEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffMillisNanosEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; @@ -38,17 +39,21 @@ public final class DateDiffMillisNanosEvaluator implements EvalOperator.Expressi private final EvalOperator.ExpressionEvaluator endTimestampNanos; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffMillisNanosEvaluator(Source source, EvalOperator.ExpressionEvaluator unit, EvalOperator.ExpressionEvaluator startTimestampMillis, - EvalOperator.ExpressionEvaluator endTimestampNanos, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestampNanos, ZoneId zoneId, + DriverContext driverContext) { this.source = source; this.unit = unit; this.startTimestampMillis = startTimestampMillis; this.endTimestampNanos = endTimestampNanos; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -126,7 +131,7 @@ public IntBlock eval(int positionCount, BytesRefBlock unitBlock, long startTimestampMillis = startTimestampMillisBlock.getLong(startTimestampMillisBlock.getFirstValueIndex(p)); long endTimestampNanos = endTimestampNanosBlock.getLong(endTimestampNanosBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processMillisNanos(unit, startTimestampMillis, endTimestampNanos)); + result.appendInt(DateDiff.processMillisNanos(unit, startTimestampMillis, endTimestampNanos, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -145,7 +150,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, long startTimestampMillis = startTimestampMillisVector.getLong(p); long endTimestampNanos = endTimestampNanosVector.getLong(p); try { - result.appendInt(DateDiff.processMillisNanos(unit, startTimestampMillis, endTimestampNanos)); + result.appendInt(DateDiff.processMillisNanos(unit, startTimestampMillis, endTimestampNanos, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -157,7 +162,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, @Override public String toString() { - return "DateDiffMillisNanosEvaluator[" + "unit=" + unit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + "]"; + return "DateDiffMillisNanosEvaluator[" + "unit=" + unit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + ", zoneId=" + zoneId + "]"; } @Override @@ -186,23 +191,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestampNanos; + private final ZoneId zoneId; + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory unit, EvalOperator.ExpressionEvaluator.Factory startTimestampMillis, - EvalOperator.ExpressionEvaluator.Factory endTimestampNanos) { + EvalOperator.ExpressionEvaluator.Factory endTimestampNanos, ZoneId zoneId) { this.source = source; this.unit = unit; this.startTimestampMillis = startTimestampMillis; this.endTimestampNanos = endTimestampNanos; + this.zoneId = zoneId; } @Override public DateDiffMillisNanosEvaluator get(DriverContext context) { - return new DateDiffMillisNanosEvaluator(source, unit.get(context), startTimestampMillis.get(context), endTimestampNanos.get(context), context); + return new DateDiffMillisNanosEvaluator(source, unit.get(context), startTimestampMillis.get(context), endTimestampNanos.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffMillisNanosEvaluator[" + "unit=" + unit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + "]"; + return "DateDiffMillisNanosEvaluator[" + "unit=" + unit + ", startTimestampMillis=" + startTimestampMillis + ", endTimestampNanos=" + endTimestampNanos + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosEvaluator.java index 726d8bbcfc140..b04264220aa7e 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; @@ -38,17 +39,20 @@ public final class DateDiffNanosEvaluator implements EvalOperator.ExpressionEval private final EvalOperator.ExpressionEvaluator endTimestamp; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffNanosEvaluator(Source source, EvalOperator.ExpressionEvaluator unit, EvalOperator.ExpressionEvaluator startTimestamp, - EvalOperator.ExpressionEvaluator endTimestamp, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestamp, ZoneId zoneId, DriverContext driverContext) { this.source = source; this.unit = unit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -126,7 +130,7 @@ public IntBlock eval(int positionCount, BytesRefBlock unitBlock, LongBlock start long startTimestamp = startTimestampBlock.getLong(startTimestampBlock.getFirstValueIndex(p)); long endTimestamp = endTimestampBlock.getLong(endTimestampBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processNanos(unit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processNanos(unit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -145,7 +149,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, long startTimestamp = startTimestampVector.getLong(p); long endTimestamp = endTimestampVector.getLong(p); try { - result.appendInt(DateDiff.processNanos(unit, startTimestamp, endTimestamp)); + result.appendInt(DateDiff.processNanos(unit, startTimestamp, endTimestamp, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -157,7 +161,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, @Override public String toString() { - return "DateDiffNanosEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffNanosEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } @Override @@ -186,23 +190,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestamp; + private final ZoneId zoneId; + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory unit, EvalOperator.ExpressionEvaluator.Factory startTimestamp, - EvalOperator.ExpressionEvaluator.Factory endTimestamp) { + EvalOperator.ExpressionEvaluator.Factory endTimestamp, ZoneId zoneId) { this.source = source; this.unit = unit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; + this.zoneId = zoneId; } @Override public DateDiffNanosEvaluator get(DriverContext context) { - return new DateDiffNanosEvaluator(source, unit.get(context), startTimestamp.get(context), endTimestamp.get(context), context); + return new DateDiffNanosEvaluator(source, unit.get(context), startTimestamp.get(context), endTimestamp.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffNanosEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + "]"; + return "DateDiffNanosEvaluator[" + "unit=" + unit + ", startTimestamp=" + startTimestamp + ", endTimestamp=" + endTimestamp + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosMillisEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosMillisEvaluator.java index 6bc87e9cae751..6712a566b9868 100644 --- a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosMillisEvaluator.java +++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffNanosMillisEvaluator.java @@ -7,6 +7,7 @@ import java.lang.IllegalArgumentException; import java.lang.Override; import java.lang.String; +import java.time.ZoneId; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.compute.data.Block; @@ -38,17 +39,21 @@ public final class DateDiffNanosMillisEvaluator implements EvalOperator.Expressi private final EvalOperator.ExpressionEvaluator endTimestampMillis; + private final ZoneId zoneId; + private final DriverContext driverContext; private Warnings warnings; public DateDiffNanosMillisEvaluator(Source source, EvalOperator.ExpressionEvaluator unit, EvalOperator.ExpressionEvaluator startTimestampNanos, - EvalOperator.ExpressionEvaluator endTimestampMillis, DriverContext driverContext) { + EvalOperator.ExpressionEvaluator endTimestampMillis, ZoneId zoneId, + DriverContext driverContext) { this.source = source; this.unit = unit; this.startTimestampNanos = startTimestampNanos; this.endTimestampMillis = endTimestampMillis; + this.zoneId = zoneId; this.driverContext = driverContext; } @@ -126,7 +131,7 @@ public IntBlock eval(int positionCount, BytesRefBlock unitBlock, long startTimestampNanos = startTimestampNanosBlock.getLong(startTimestampNanosBlock.getFirstValueIndex(p)); long endTimestampMillis = endTimestampMillisBlock.getLong(endTimestampMillisBlock.getFirstValueIndex(p)); try { - result.appendInt(DateDiff.processNanosMillis(unit, startTimestampNanos, endTimestampMillis)); + result.appendInt(DateDiff.processNanosMillis(unit, startTimestampNanos, endTimestampMillis, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -145,7 +150,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, long startTimestampNanos = startTimestampNanosVector.getLong(p); long endTimestampMillis = endTimestampMillisVector.getLong(p); try { - result.appendInt(DateDiff.processNanosMillis(unit, startTimestampNanos, endTimestampMillis)); + result.appendInt(DateDiff.processNanosMillis(unit, startTimestampNanos, endTimestampMillis, this.zoneId)); } catch (IllegalArgumentException | InvalidArgumentException e) { warnings().registerException(e); result.appendNull(); @@ -157,7 +162,7 @@ public IntBlock eval(int positionCount, BytesRefVector unitVector, @Override public String toString() { - return "DateDiffNanosMillisEvaluator[" + "unit=" + unit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + "]"; + return "DateDiffNanosMillisEvaluator[" + "unit=" + unit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + ", zoneId=" + zoneId + "]"; } @Override @@ -186,23 +191,26 @@ static class Factory implements EvalOperator.ExpressionEvaluator.Factory { private final EvalOperator.ExpressionEvaluator.Factory endTimestampMillis; + private final ZoneId zoneId; + public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory unit, EvalOperator.ExpressionEvaluator.Factory startTimestampNanos, - EvalOperator.ExpressionEvaluator.Factory endTimestampMillis) { + EvalOperator.ExpressionEvaluator.Factory endTimestampMillis, ZoneId zoneId) { this.source = source; this.unit = unit; this.startTimestampNanos = startTimestampNanos; this.endTimestampMillis = endTimestampMillis; + this.zoneId = zoneId; } @Override public DateDiffNanosMillisEvaluator get(DriverContext context) { - return new DateDiffNanosMillisEvaluator(source, unit.get(context), startTimestampNanos.get(context), endTimestampMillis.get(context), context); + return new DateDiffNanosMillisEvaluator(source, unit.get(context), startTimestampNanos.get(context), endTimestampMillis.get(context), zoneId, context); } @Override public String toString() { - return "DateDiffNanosMillisEvaluator[" + "unit=" + unit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + "]"; + return "DateDiffNanosMillisEvaluator[" + "unit=" + unit + ", startTimestampNanos=" + startTimestampNanos + ", endTimestampMillis=" + endTimestampMillis + ", zoneId=" + zoneId + "]"; } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index d7683fe379d06..679908cce205e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -1299,6 +1299,11 @@ public enum Cap { */ DATE_TRUNC_TIMEZONE_SUPPORT(Build.current().isSnapshot()), + /** + * Support timezones in DATE_DIFF. + */ + DATE_DIFF_TIMEZONE_SUPPORT(Build.current().isSnapshot()), + /** * (Re)Added EXPLAIN command */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java index efe8e17497a38..207cc4bfa26e0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java @@ -437,7 +437,7 @@ private static FunctionDefinition[][] functions() { def(UrlDecode.class, UrlDecode::new, "url_decode") }, // date new FunctionDefinition[] { - def(DateDiff.class, DateDiff::new, "date_diff"), + def(DateDiff.class, tric(DateDiff::new), "date_diff"), def(DateExtract.class, DateExtract::new, "date_extract"), def(DateFormat.class, DateFormat::new, "date_format"), def(DateParse.class, DateParse::new, "date_parse"), @@ -1261,7 +1261,11 @@ protected interface BinaryConfigurationAwareBuilder { * Build a {@linkplain FunctionDefinition} for a ternary function that is configuration aware. */ @SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do - protected FunctionDefinition def(Class function, TernaryConfigurationAwareBuilder ctorRef, String... names) { + protected static FunctionDefinition def( + Class function, + TernaryConfigurationAwareBuilder ctorRef, + String... names + ) { FunctionBuilder builder = (source, children, cfg) -> { checkIsOptionalTriFunction(function, children.size()); return ctorRef.build(source, children.get(0), children.get(1), children.size() == 3 ? children.get(2) : null, cfg); @@ -1363,6 +1367,10 @@ private static BiFunction uni(BiFunc return function; } + private static UnaryConfigurationAwareBuilder unic(UnaryConfigurationAwareBuilder function) { + return function; + } + private static BinaryBuilder bi(BinaryBuilder function) { return function; } @@ -1375,6 +1383,10 @@ private static TernaryBuilder tri(TernaryBuilder func return function; } + private static TernaryConfigurationAwareBuilder tric(TernaryConfigurationAwareBuilder function) { + return function; + } + private static QuaternaryBuilder quad(QuaternaryBuilder function) { return function; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiff.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiff.java index 2437fdc307415..a0137f1f52939 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiff.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiff.java @@ -25,8 +25,9 @@ import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; -import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlConfigurationFunction; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; +import org.elasticsearch.xpack.esql.session.Configuration; import java.io.IOException; import java.time.Instant; @@ -54,7 +55,7 @@ * in multiples of the unit specified in the first argument. * If the second argument (start) is greater than the third argument (end), then negative values are returned. */ -public class DateDiff extends EsqlScalarFunction { +public class DateDiff extends EsqlConfigurationFunction { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "DateDiff", DateDiff::new); public static final ZoneId UTC = org.elasticsearch.xpack.esql.core.util.DateUtils.UTC; @@ -175,9 +176,10 @@ public DateDiff( name = "endTimestamp", type = { "date", "date_nanos" }, description = "A string representing an end timestamp" - ) Expression endTimestamp + ) Expression endTimestamp, + Configuration configuration ) { - super(source, List.of(unit, startTimestamp, endTimestamp)); + super(source, List.of(unit, startTimestamp, endTimestamp), configuration); this.unit = unit; this.startTimestamp = startTimestamp; this.endTimestamp = endTimestamp; @@ -188,7 +190,8 @@ private DateDiff(StreamInput in) throws IOException { Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), in.readNamedWriteable(Expression.class), - in.readNamedWriteable(Expression.class) + in.readNamedWriteable(Expression.class), + ((PlanStreamInput) in).configuration() ); } @@ -218,53 +221,57 @@ Expression endTimestamp() { } @Evaluator(extraName = "ConstantMillis", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processMillis(@Fixed Part datePartFieldUnit, long startTimestamp, long endTimestamp) throws IllegalArgumentException { - ZonedDateTime zdtStart = ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTimestamp), UTC); - ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(Instant.ofEpochMilli(endTimestamp), UTC); + static int processMillis(@Fixed Part datePartFieldUnit, long startTimestamp, long endTimestamp, @Fixed ZoneId zoneId) + throws IllegalArgumentException { + ZonedDateTime zdtStart = ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTimestamp), zoneId); + ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(Instant.ofEpochMilli(endTimestamp), zoneId); return datePartFieldUnit.diff(zdtStart, zdtEnd); } @Evaluator(extraName = "Millis", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processMillis(BytesRef unit, long startTimestamp, long endTimestamp) throws IllegalArgumentException { - return processMillis(Part.resolve(unit.utf8ToString()), startTimestamp, endTimestamp); + static int processMillis(BytesRef unit, long startTimestamp, long endTimestamp, @Fixed ZoneId zoneId) throws IllegalArgumentException { + return processMillis(Part.resolve(unit.utf8ToString()), startTimestamp, endTimestamp, zoneId); } @Evaluator(extraName = "ConstantNanos", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processNanos(@Fixed Part datePartFieldUnit, long startTimestamp, long endTimestamp) throws IllegalArgumentException { - ZonedDateTime zdtStart = ZonedDateTime.ofInstant(DateUtils.toInstant(startTimestamp), UTC); - ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(DateUtils.toInstant(endTimestamp), UTC); + static int processNanos(@Fixed Part datePartFieldUnit, long startTimestamp, long endTimestamp, @Fixed ZoneId zoneId) + throws IllegalArgumentException { + ZonedDateTime zdtStart = ZonedDateTime.ofInstant(DateUtils.toInstant(startTimestamp), zoneId); + ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(DateUtils.toInstant(endTimestamp), zoneId); return datePartFieldUnit.diff(zdtStart, zdtEnd); } @Evaluator(extraName = "Nanos", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processNanos(BytesRef unit, long startTimestamp, long endTimestamp) throws IllegalArgumentException { - return processNanos(Part.resolve(unit.utf8ToString()), startTimestamp, endTimestamp); + static int processNanos(BytesRef unit, long startTimestamp, long endTimestamp, @Fixed ZoneId zoneId) throws IllegalArgumentException { + return processNanos(Part.resolve(unit.utf8ToString()), startTimestamp, endTimestamp, zoneId); } @Evaluator(extraName = "ConstantNanosMillis", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processNanosMillis(@Fixed Part datePartFieldUnit, long startTimestampNanos, long endTimestampMillis) + static int processNanosMillis(@Fixed Part datePartFieldUnit, long startTimestampNanos, long endTimestampMillis, @Fixed ZoneId zoneId) throws IllegalArgumentException { - ZonedDateTime zdtStart = ZonedDateTime.ofInstant(DateUtils.toInstant(startTimestampNanos), UTC); - ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(Instant.ofEpochMilli(endTimestampMillis), UTC); + ZonedDateTime zdtStart = ZonedDateTime.ofInstant(DateUtils.toInstant(startTimestampNanos), zoneId); + ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(Instant.ofEpochMilli(endTimestampMillis), zoneId); return datePartFieldUnit.diff(zdtStart, zdtEnd); } @Evaluator(extraName = "NanosMillis", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processNanosMillis(BytesRef unit, long startTimestampNanos, long endTimestampMillis) throws IllegalArgumentException { - return processNanosMillis(Part.resolve(unit.utf8ToString()), startTimestampNanos, endTimestampMillis); + static int processNanosMillis(BytesRef unit, long startTimestampNanos, long endTimestampMillis, @Fixed ZoneId zoneId) + throws IllegalArgumentException { + return processNanosMillis(Part.resolve(unit.utf8ToString()), startTimestampNanos, endTimestampMillis, zoneId); } @Evaluator(extraName = "ConstantMillisNanos", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processMillisNanos(@Fixed Part datePartFieldUnit, long startTimestampMillis, long endTimestampNanos) + static int processMillisNanos(@Fixed Part datePartFieldUnit, long startTimestampMillis, long endTimestampNanos, @Fixed ZoneId zoneId) throws IllegalArgumentException { - ZonedDateTime zdtStart = ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTimestampMillis), UTC); - ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(DateUtils.toInstant(endTimestampNanos), UTC); + ZonedDateTime zdtStart = ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTimestampMillis), zoneId); + ZonedDateTime zdtEnd = ZonedDateTime.ofInstant(DateUtils.toInstant(endTimestampNanos), zoneId); return datePartFieldUnit.diff(zdtStart, zdtEnd); } @Evaluator(extraName = "MillisNanos", warnExceptions = { IllegalArgumentException.class, InvalidArgumentException.class }) - static int processMillisNanos(BytesRef unit, long startTimestampMillis, long endTimestampNanos) throws IllegalArgumentException { - return processMillisNanos(Part.resolve(unit.utf8ToString()), startTimestampMillis, endTimestampNanos); + static int processMillisNanos(BytesRef unit, long startTimestampMillis, long endTimestampNanos, @Fixed ZoneId zoneId) + throws IllegalArgumentException { + return processMillisNanos(Part.resolve(unit.utf8ToString()), startTimestampMillis, endTimestampNanos, zoneId); } @FunctionalInterface @@ -273,7 +280,8 @@ ExpressionEvaluator.Factory build( Source source, ExpressionEvaluator.Factory unitsEvaluator, ExpressionEvaluator.Factory startTimestampEvaluator, - ExpressionEvaluator.Factory endTimestampEvaluator + ExpressionEvaluator.Factory endTimestampEvaluator, + ZoneId zoneId ); } @@ -283,20 +291,32 @@ ExpressionEvaluator.Factory build( Source source, Part unitsEvaluator, ExpressionEvaluator.Factory startTimestampEvaluator, - ExpressionEvaluator.Factory endTimestampEvaluator + ExpressionEvaluator.Factory endTimestampEvaluator, + ZoneId zoneId ); } @Override public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { + ZoneId zoneId = configuration().zoneId(); if (startTimestamp.dataType() == DATETIME && endTimestamp.dataType() == DATETIME) { - return toEvaluator(toEvaluator, DateDiffConstantMillisEvaluator.Factory::new, DateDiffMillisEvaluator.Factory::new); + return toEvaluator(toEvaluator, DateDiffConstantMillisEvaluator.Factory::new, DateDiffMillisEvaluator.Factory::new, zoneId); } else if (startTimestamp.dataType() == DATE_NANOS && endTimestamp.dataType() == DATE_NANOS) { - return toEvaluator(toEvaluator, DateDiffConstantNanosEvaluator.Factory::new, DateDiffNanosEvaluator.Factory::new); + return toEvaluator(toEvaluator, DateDiffConstantNanosEvaluator.Factory::new, DateDiffNanosEvaluator.Factory::new, zoneId); } else if (startTimestamp.dataType() == DATE_NANOS && endTimestamp.dataType() == DATETIME) { - return toEvaluator(toEvaluator, DateDiffConstantNanosMillisEvaluator.Factory::new, DateDiffNanosMillisEvaluator.Factory::new); + return toEvaluator( + toEvaluator, + DateDiffConstantNanosMillisEvaluator.Factory::new, + DateDiffNanosMillisEvaluator.Factory::new, + zoneId + ); } else if (startTimestamp.dataType() == DATETIME && endTimestamp.dataType() == DATE_NANOS) { - return toEvaluator(toEvaluator, DateDiffConstantMillisNanosEvaluator.Factory::new, DateDiffMillisNanosEvaluator.Factory::new); + return toEvaluator( + toEvaluator, + DateDiffConstantMillisNanosEvaluator.Factory::new, + DateDiffMillisNanosEvaluator.Factory::new, + zoneId + ); } throw new UnsupportedOperationException( "Invalid types [" @@ -311,7 +331,8 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { private ExpressionEvaluator.Factory toEvaluator( ToEvaluator toEvaluator, DateDiffConstantFactory constantFactory, - DateDiffFactory dateDiffFactory + DateDiffFactory dateDiffFactory, + ZoneId zoneId ) { ExpressionEvaluator.Factory startTimestampEvaluator = toEvaluator.apply(startTimestamp); ExpressionEvaluator.Factory endTimestampEvaluator = toEvaluator.apply(endTimestamp); @@ -319,13 +340,13 @@ private ExpressionEvaluator.Factory toEvaluator( if (unit.foldable()) { try { Part datePartField = Part.resolve(BytesRefs.toString(unit.fold(toEvaluator.foldCtx()))); - return constantFactory.build(source(), datePartField, startTimestampEvaluator, endTimestampEvaluator); + return constantFactory.build(source(), datePartField, startTimestampEvaluator, endTimestampEvaluator, zoneId); } catch (IllegalArgumentException e) { throw new InvalidArgumentException("invalid unit format for [{}]: {}", sourceText(), e.getMessage()); } } ExpressionEvaluator.Factory unitEvaluator = toEvaluator.apply(unit); - return dateDiffFactory.build(source(), unitEvaluator, startTimestampEvaluator, endTimestampEvaluator); + return dateDiffFactory.build(source(), unitEvaluator, startTimestampEvaluator, endTimestampEvaluator, zoneId); } @Override @@ -358,11 +379,11 @@ public DataType dataType() { @Override public Expression replaceChildren(List newChildren) { - return new DateDiff(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2)); + return new DateDiff(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2), configuration()); } @Override protected NodeInfo info() { - return NodeInfo.create(this, DateDiff::new, children().get(0), children().get(1), children().get(2)); + return NodeInfo.create(this, DateDiff::new, children().get(0), children().get(1), children().get(2), configuration()); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffErrorTests.java index 7f70c6e8cd372..2a8f797a90e10 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffErrorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffErrorTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.date; +import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -27,7 +28,7 @@ protected List cases() { @Override protected Expression build(Source source, List args) { - return new DateDiff(source, args.get(0), args.get(1), args.get(2)); + return new DateDiff(source, args.get(0), args.get(1), args.get(2), EsqlTestUtils.TEST_CFG); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffFunctionTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffFunctionTests.java index 7380ac08f85a2..1d16e9f162a4e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffFunctionTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffFunctionTests.java @@ -10,6 +10,8 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.test.ESTestCase; +import java.time.ZoneOffset; + import static org.hamcrest.Matchers.containsString; /** @@ -20,7 +22,7 @@ public class DateDiffFunctionTests extends ESTestCase { public void testDateDiffFunctionErrorUnitNotValid() { IllegalArgumentException e = expectThrows( IllegalArgumentException.class, - () -> DateDiff.processMillis(new BytesRef("sseconds"), 0, 0) + () -> DateDiff.processMillis(new BytesRef("sseconds"), 0, 0, ZoneOffset.UTC) ); assertThat( e.getMessage(), @@ -30,7 +32,10 @@ public void testDateDiffFunctionErrorUnitNotValid() { ) ); - e = expectThrows(IllegalArgumentException.class, () -> DateDiff.processMillis(new BytesRef("not-valid-unit"), 0, 0)); + e = expectThrows( + IllegalArgumentException.class, + () -> DateDiff.processMillis(new BytesRef("not-valid-unit"), 0, 0, ZoneOffset.UTC) + ); assertThat( e.getMessage(), containsString( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffSerializationTests.java index f1f8d1b0f8dad..063361bd0e693 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffSerializationTests.java @@ -20,7 +20,7 @@ protected DateDiff createTestInstance() { Expression unit = randomChild(); Expression startTimestamp = randomChild(); Expression endTimestamp = randomChild(); - return new DateDiff(source, unit, startTimestamp, endTimestamp); + return new DateDiff(source, unit, startTimestamp, endTimestamp, configuration()); } @Override @@ -34,6 +34,6 @@ protected DateDiff mutateInstance(DateDiff instance) throws IOException { case 1 -> startTimestamp = randomValueOtherThan(startTimestamp, AbstractExpressionSerializationTests::randomChild); case 2 -> endTimestamp = randomValueOtherThan(endTimestamp, AbstractExpressionSerializationTests::randomChild); } - return new DateDiff(source, unit, startTimestamp, endTimestamp); + return new DateDiff(source, unit, startTimestamp, endTimestamp, configuration()); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java index e34a9ad92f7e7..b4218fe6b235c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java @@ -15,81 +15,120 @@ import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; -import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase; +import org.elasticsearch.xpack.esql.session.Configuration; import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; +import java.util.stream.Stream; +import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomConfiguration; +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.TEST_SOURCE; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; -public class DateDiffTests extends AbstractScalarFunctionTestCase { +public class DateDiffTests extends AbstractConfigurationFunctionTestCase { public DateDiffTests(@Name("TestCase") Supplier testCaseSupplier) { this.testCase = testCaseSupplier.get(); } @ParametersFactory public static Iterable parameters() { - List suppliers = new ArrayList<>(); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:30Z"), Instant.parse("2023-12-05T10:45:00Z"), "seconds", 88170)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-12T00:01:01Z"), Instant.parse("2024-12-12T00:01:01Z"), "year", 1)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-12T00:01:01.001Z"), Instant.parse("2024-12-12T00:01:01Z"), "year", 0)); - suppliers.addAll( - makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "nanoseconds", 1000000000) - ); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "ns", 1000000000)); - suppliers.addAll( - makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "microseconds", 1000000) - ); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "mcs", 1000000)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "milliseconds", 1000)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "ms", 1000)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "seconds", 1)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "ss", 1)); - suppliers.addAll(makeSuppliers(Instant.parse("2023-12-04T10:15:00Z"), Instant.parse("2023-12-04T10:15:01Z"), "s", 1)); + String zdtStart = "2023-12-04T10:15:00Z"; + String zdtEnd = "2024-12-04T10:15:01Z"; + + List.of( + /// + /// Timezone independent + /// + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "nanoseconds", 1000000000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "ns", 1000000000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "microseconds", 1000000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "mcs", 1000000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "milliseconds", 1000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "ms", 1000), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "seconds", 1), + makeSuppliers("2023-12-04T10:15:30Z", "2023-12-05T10:45:00Z", "Z", "seconds", 88170), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "ss", 1), + makeSuppliers("2023-12-04T10:15:00Z", "2023-12-04T10:15:01Z", null, "s", 1), + + makeSuppliers(zdtStart, zdtEnd, null, "minutes", 527040), + makeSuppliers(zdtStart, zdtEnd, null, "mi", 527040), + makeSuppliers(zdtStart, zdtEnd, null, "n", 527040), + makeSuppliers(zdtStart, zdtEnd, null, "hours", 8784), + makeSuppliers(zdtStart, zdtEnd, null, "hh", 8784), - Instant zdtStart = Instant.parse("2023-12-04T10:15:00Z"); - Instant zdtEnd = Instant.parse("2024-12-04T10:15:01Z"); + /// + /// UTC + /// + // 2024 is a leap year, so the dates are 366 days apart + makeSuppliers(zdtStart, zdtEnd, "Z", "weekdays", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "dw", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "days", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "dd", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "d", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "dy", 366), + makeSuppliers(zdtStart, zdtEnd, "Z", "y", 366), - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "minutes", 527040)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "mi", 527040)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "n", 527040)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "hours", 8784)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "hh", 8784)); + makeSuppliers(zdtStart, zdtEnd, "Z", "weeks", 52), + makeSuppliers(zdtStart, zdtEnd, "Z", "wk", 52), + makeSuppliers(zdtStart, zdtEnd, "Z", "ww", 52), + makeSuppliers(zdtStart, zdtEnd, "Z", "months", 12), + makeSuppliers(zdtStart, zdtEnd, "Z", "mm", 12), + makeSuppliers(zdtStart, zdtEnd, "Z", "m", 12), + makeSuppliers(zdtStart, zdtEnd, "Z", "quarters", 4), + makeSuppliers(zdtStart, zdtEnd, "Z", "qq", 4), + makeSuppliers(zdtStart, zdtEnd, "Z", "q", 4), + makeSuppliers(zdtStart, zdtEnd, "Z", "years", 1), + makeSuppliers(zdtStart, zdtEnd, "Z", "yyyy", 1), + makeSuppliers(zdtStart, zdtEnd, "Z", "yy", 1), + makeSuppliers("2023-12-12T00:01:01Z", "2024-12-12T00:01:01Z", "Z", "year", 1), + makeSuppliers("2023-12-12T00:01:01.001Z", "2024-12-12T00:01:01Z", "Z", "year", 0) + ).forEach(suppliers::addAll); - // 2024 is a leap year, so the dates are 366 days apart - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "weekdays", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "dw", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "days", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "dd", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "d", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "dy", 366)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "y", 366)); + /// + /// DST timezones: Cases where the result doesn't change + /// + List.of("Z", "Europe/Paris", "America/Goose_Bay") + .forEach( + timezone -> List.of( + // Europe/Paris, 1h DST + // - +1 -> +2 at 2025-03-30T02:00:00+01:00 + makeSuppliers("2025-03-29T01:00:00+01:00", "2025-03-30T03:00:00+02:00", timezone, "days", 1), + // - +2 -> +1 at 2025-10-26T03:00:00+02:, + makeSuppliers("2025-10-25T02:00:00+02:00", "2025-10-26T02:00:00+01:00", timezone, "days", 1), - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "weeks", 52)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "wk", 52)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "ww", 52)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "months", 12)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "mm", 12)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "m", 12)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "quarters", 4)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "qq", 4)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "q", 4)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "years", 1)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "yyyy", 1)); - suppliers.addAll(makeSuppliers(zdtStart, zdtEnd, "yy", 1)); + // America/Goose_Bay, midnight DST: -3 to -4 at 2010-11-07T00:01:00-03:00) + makeSuppliers("2010-11-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", timezone, "minutes", 1), + makeSuppliers("2010-11-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", timezone, "hours", 0), + makeSuppliers("2010-11-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", timezone, "days", 0) + ).forEach(suppliers::addAll) + ); + + /// + /// DST timezones: Cases where the result changes + /// + List.of( + // America/Goose_Bay, midnight DST: -3 to -4 at 2010-11-07T00:01:00-03:00) + makeSuppliers("2010-11-06T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "America/Goose_Bay", "days", 0), + makeSuppliers("2010-11-06T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "Z", "days", 1), + makeSuppliers("2010-10-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "America/Goose_Bay", "months", 0), + makeSuppliers("2010-10-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "Z", "months", 1), + makeSuppliers("2009-10-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "America/Goose_Bay", "months", 12), + makeSuppliers("2009-10-07T00:00:00-03:00", "2010-11-06T23:01:00-04:00", "Z", "months", 13) + ).forEach(suppliers::addAll); // Error cases - Instant zdtStart2 = Instant.parse("2023-12-04T10:15:00Z"); - Instant zdtEnd2 = Instant.parse("2023-12-04T10:20:00Z"); suppliers.addAll( - makeSuppliers( - zdtStart2, - zdtEnd2, + makeSuppliersForWarning( + "2023-12-04T10:15:00Z", + "2023-12-04T10:20:00Z", "nanoseconds", "Line 1:1: org.elasticsearch.xpack.esql.core.InvalidArgumentException: [300000000000] out of [integer] range" ) @@ -98,175 +137,209 @@ public static Iterable parameters() { return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers); } - private static List makeSuppliers(Instant startTimestamp, Instant endTimestamp, String unit, int expected) { - // Units as Keyword case - return List.of( - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.KEYWORD, DataType.DATETIME, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") - ), - "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.KEYWORD, DataType.DATE_NANOS, DataType.DATE_NANOS), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), - new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), - new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") - ), - "DateDiffNanosEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.KEYWORD, DataType.DATE_NANOS, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), - new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") - ), - "DateDiffNanosMillisEvaluator[unit=Attribute[channel=0], startTimestampNanos=Attribute[channel=1], " - + "endTimestampMillis=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.KEYWORD, DataType.DATETIME, DataType.DATE_NANOS), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") + private static List makeSuppliers( + String startTimestampString, + String endTimestampString, + String timezone, + String unit, + int expected + ) { + Instant startTimestamp = Instant.parse(startTimestampString); + Instant endTimestamp = Instant.parse(endTimestampString); + + Supplier configurationSupplier = () -> timezone == null + ? randomConfiguration() + : configurationForTimezone(ZoneId.of(timezone)); + + return Stream.of(DataType.KEYWORD, DataType.TEXT) + .flatMap( + unitType -> Stream.of( + new TestCaseSupplier( + "DateDiff(" + + unit + + "<" + + unitType + + ">, " + + timezone + + ", " + + startTimestampString + + ", " + + endTimestampString + + ") == " + + expected, + List.of(unitType, DataType.DATETIME, DataType.DATETIME), + () -> { + Configuration configuration = configurationSupplier.get(); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), unitType, "unit"), + new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), + new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + ), + "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " + + "endTimestamp=Attribute[channel=2], zoneId=" + + configuration.zoneId() + + "]", + DataType.INTEGER, + equalTo(expected) + ).withConfiguration(TEST_SOURCE, configuration); + } ), - "DateDiffMillisNanosEvaluator[unit=Attribute[channel=0], startTimestampMillis=Attribute[channel=1], " - + "endTimestampNanos=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - // Units as text case - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.TEXT, DataType.DATETIME, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + new TestCaseSupplier( + "DateDiff(" + + unit + + "<" + + unitType + + ">, " + + timezone + + ", " + + startTimestampString + + ", " + + endTimestampString + + ") == " + + expected, + List.of(unitType, DataType.DATE_NANOS, DataType.DATE_NANOS), + () -> { + Configuration configuration = configurationSupplier.get(); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), unitType, "unit"), + new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), + new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") + ), + "DateDiffNanosEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " + + "endTimestamp=Attribute[channel=2], zoneId=" + + configuration.zoneId() + + "]", + DataType.INTEGER, + equalTo(expected) + ).withConfiguration(TEST_SOURCE, configuration); + } ), - "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.TEXT, DataType.DATE_NANOS, DataType.DATE_NANOS), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), - new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), - new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") + new TestCaseSupplier( + "DateDiff(" + + unit + + "<" + + unitType + + ">, " + + timezone + + ", " + + startTimestampString + + ", " + + endTimestampString + + ") == " + + expected, + List.of(unitType, DataType.DATE_NANOS, DataType.DATETIME), + () -> { + Configuration configuration = configurationSupplier.get(); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), unitType, "unit"), + new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), + new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + ), + "DateDiffNanosMillisEvaluator[unit=Attribute[channel=0], startTimestampNanos=Attribute[channel=1], " + + "endTimestampMillis=Attribute[channel=2], zoneId=" + + configuration.zoneId() + + "]", + DataType.INTEGER, + equalTo(expected) + ).withConfiguration(TEST_SOURCE, configuration); + } ), - "DateDiffNanosEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.TEXT, DataType.DATE_NANOS, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), - new TestCaseSupplier.TypedData(DateUtils.toLong(startTimestamp), DataType.DATE_NANOS, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") - ), - "DateDiffNanosMillisEvaluator[unit=Attribute[channel=0], startTimestampNanos=Attribute[channel=1], " - + "endTimestampMillis=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) - ) - ), - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") == " + expected, - List.of(DataType.TEXT, DataType.DATETIME, DataType.DATE_NANOS), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") - ), - "DateDiffMillisNanosEvaluator[unit=Attribute[channel=0], startTimestampMillis=Attribute[channel=1], " - + "endTimestampNanos=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(expected) + new TestCaseSupplier( + "DateDiff(" + + unit + + "<" + + unitType + + ">, " + + timezone + + ", " + + startTimestampString + + ", " + + endTimestampString + + ") == " + + expected, + List.of(unitType, DataType.DATETIME, DataType.DATE_NANOS), + () -> { + Configuration configuration = configurationSupplier.get(); + return new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), unitType, "unit"), + new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), + new TestCaseSupplier.TypedData(DateUtils.toLong(endTimestamp), DataType.DATE_NANOS, "endTimestamp") + ), + "DateDiffMillisNanosEvaluator[unit=Attribute[channel=0], startTimestampMillis=Attribute[channel=1], " + + "endTimestampNanos=Attribute[channel=2], zoneId=" + + configuration.zoneId() + + "]", + DataType.INTEGER, + equalTo(expected) + ).withConfiguration(TEST_SOURCE, configuration); + } + ) ) ) - ); + .toList(); } - private static List makeSuppliers(Instant startTimestamp, Instant endTimestamp, String unit, String warning) { - // Units as Keyword case - return List.of( - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") -> warning ", - List.of(DataType.KEYWORD, DataType.DATETIME, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") - ), - "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(null) - ).withWarning("Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.") - .withWarning(warning) - ), - // Units as text case - new TestCaseSupplier( - "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") -> warning ", - List.of(DataType.TEXT, DataType.DATETIME, DataType.DATETIME), - () -> new TestCaseSupplier.TestCase( - List.of( - new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), - new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), - new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + private static List makeSuppliersForWarning( + String startTimestampString, + String endTimestampString, + String unit, + String warning + ) { + Instant startTimestamp = Instant.parse(startTimestampString); + Instant endTimestamp = Instant.parse(endTimestampString); + + return Stream.of(DataType.KEYWORD, DataType.TEXT) + .flatMap( + unitType -> Stream.of( + new TestCaseSupplier( + "warning -> " + unit + ", " + startTimestamp + ", " + endTimestamp, + List.of(DataType.KEYWORD, DataType.DATETIME, DataType.DATETIME), + () -> new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.KEYWORD, "unit"), + new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), + new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + ), + startsWith( + "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " + + "endTimestamp=Attribute[channel=2], zoneId=" + ), + DataType.INTEGER, + equalTo(null) + ).withWarning("Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.") + .withWarning(warning) ), - "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " - + "endTimestamp=Attribute[channel=2]]", - DataType.INTEGER, - equalTo(null) - ).withWarning("Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.") - .withWarning(warning) + // Units as text case + new TestCaseSupplier( + "DateDiff(" + unit + ", " + startTimestamp + ", " + endTimestamp + ") -> warning ", + List.of(DataType.TEXT, DataType.DATETIME, DataType.DATETIME), + () -> new TestCaseSupplier.TestCase( + List.of( + new TestCaseSupplier.TypedData(new BytesRef(unit), DataType.TEXT, "unit"), + new TestCaseSupplier.TypedData(startTimestamp.toEpochMilli(), DataType.DATETIME, "startTimestamp"), + new TestCaseSupplier.TypedData(endTimestamp.toEpochMilli(), DataType.DATETIME, "endTimestamp") + ), + startsWith( + "DateDiffMillisEvaluator[unit=Attribute[channel=0], startTimestamp=Attribute[channel=1], " + + "endTimestamp=Attribute[channel=2], zoneId=" + ), + DataType.INTEGER, + equalTo(null) + ).withWarning("Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded.") + .withWarning(warning) + ) + ) ) - ); + .toList(); } @Override - protected Expression build(Source source, List args) { - return new DateDiff(source, args.get(0), args.get(1), args.get(2)); + protected Expression buildWithConfiguration(Source source, List args, Configuration configuration) { + return new DateDiff(source, args.get(0), args.get(1), args.get(2), configuration); // TODO: Add config here } @Override