From 64bc8a6799c8400da99b038e58361ab34c23b976 Mon Sep 17 00:00:00 2001 From: Fang Xing Date: Fri, 15 Mar 2024 23:09:08 -0400 Subject: [PATCH] centralize datetime string conversion --- .../src/main/resources/date.csv-spec | 4 +- .../xpack/esql/action/PositionToXContent.java | 5 +- .../xpack/esql/action/ResponseValueUtils.java | 8 +- .../xpack/esql/analysis/Analyzer.java | 5 +- .../function/scalar/convert/ToDatetime.java | 5 +- .../function/scalar/convert/ToIP.java | 4 +- .../function/scalar/convert/ToString.java | 5 +- .../function/scalar/date/DateExtract.java | 19 ++--- .../function/scalar/date/DateFormat.java | 18 +++-- .../function/scalar/date/DateParse.java | 10 +-- .../function/scalar/math/AutoBucket.java | 20 ++--- .../planner/EsqlExpressionTranslators.java | 6 +- .../esql/type/EsqlDataTypeConverter.java | 79 ++++++++++++++++++- .../scalar/convert/ToDatetimeTests.java | 37 ++++++--- 14 files changed, 159 insertions(+), 66 deletions(-) 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 f56cba7031def..fc775a65b15f9 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 @@ -167,14 +167,14 @@ birth_date:date |bd:date 1964-06-02T00:00:00.000Z|1964-06-02T00:00:00.000Z ; -convertFromString +convertFromString#[skip:-8.13.99, reason: default date formatter is changed in 8.14] // tag::to_datetime-str[] ROW string = ["1953-09-02T00:00:00.000Z", "1964-06-02T00:00:00.000Z", "1964-06-02 00:00:00"] | EVAL datetime = TO_DATETIME(string) // end::to_datetime-str[] ; warning:Line 2:19: evaluation of [TO_DATETIME(string)] failed, treating result as null. Only first 20 failures recorded. -warning:Line 2:19: java.lang.IllegalArgumentException: failed to parse date field [1964-06-02 00:00:00] with format [yyyy-MM-dd'T'HH:mm:ss.SSS'Z'] +warning:Line 2:19: java.lang.IllegalArgumentException: failed to parse date field [1964-06-02 00:00:00] with format [strict_date_optional_time] // tag::to_datetime-str-result[] string:keyword |datetime:date diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/PositionToXContent.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/PositionToXContent.java index 669b22883fd8c..d1e0b12aff79d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/PositionToXContent.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/PositionToXContent.java @@ -27,7 +27,8 @@ import java.io.IOException; -import static org.elasticsearch.xpack.ql.util.DateUtils.UTC_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeLongToString; import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsNumber; import static org.elasticsearch.xpack.ql.util.SpatialCoordinateTypes.CARTESIAN; import static org.elasticsearch.xpack.ql.util.SpatialCoordinateTypes.GEO; @@ -116,7 +117,7 @@ protected XContentBuilder valueToXContent(XContentBuilder builder, ToXContent.Pa protected XContentBuilder valueToXContent(XContentBuilder builder, ToXContent.Params params, int valueIndex) throws IOException { long longVal = ((LongBlock) block).getLong(valueIndex); - return builder.value(UTC_DATE_TIME_FORMATTER.formatMillis(longVal)); + return builder.value(convertDatetimeLongToString(longVal, ESQL_DEFAULT_DATE_TIME_FORMATTER)); } }; case "geo_point", "geo_shape" -> new PositionToXContent(block) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/ResponseValueUtils.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/ResponseValueUtils.java index d5dc12357f3fe..21f31bbec1b94 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/ResponseValueUtils.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/ResponseValueUtils.java @@ -38,7 +38,9 @@ import java.util.List; import java.util.Map; -import static org.elasticsearch.xpack.ql.util.DateUtils.UTC_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeLongToString; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeStringToLong; import static org.elasticsearch.xpack.ql.util.NumericUtils.asLongUnsigned; import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsNumber; import static org.elasticsearch.xpack.ql.util.SpatialCoordinateTypes.CARTESIAN; @@ -97,7 +99,7 @@ private static Object valueAt(String dataType, Block block, int offset, BytesRef } case "date" -> { long longVal = ((LongBlock) block).getLong(offset); - yield UTC_DATE_TIME_FORMATTER.formatMillis(longVal); + yield convertDatetimeLongToString(longVal, ESQL_DEFAULT_DATE_TIME_FORMATTER); } case "boolean" -> ((BooleanBlock) block).getBoolean(offset); case "version" -> new Version(((BytesRefBlock) block).getBytesRef(offset, scratch)).toString(); @@ -143,7 +145,7 @@ static Page valuesToPage(BlockFactory blockFactory, List columns, Li ); case "ip" -> ((BytesRefBlock.Builder) builder).appendBytesRef(parseIP(value.toString())); case "date" -> { - long longVal = UTC_DATE_TIME_FORMATTER.parseMillis(value.toString()); + long longVal = convertDatetimeStringToLong(value.toString(), ESQL_DEFAULT_DATE_TIME_FORMATTER); ((LongBlock.Builder) builder).appendLong(longVal); } case "boolean" -> ((BooleanBlock.Builder) builder).appendBoolean(((Boolean) value)); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 3d5ee7d7507ef..78f6094c455f6 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -9,7 +9,6 @@ import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; import org.elasticsearch.xpack.esql.VerificationException; @@ -80,6 +79,8 @@ import static java.util.Collections.singletonList; import static org.elasticsearch.common.logging.LoggerMessageFormat.format; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.LIMIT; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeStringToLong; import static org.elasticsearch.xpack.ql.analyzer.AnalyzerRules.resolveFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME; import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD; @@ -750,7 +751,7 @@ private static Expression stringToDate(Expression stringExpression) { Long millis = null; // TODO: better control over this string format - do we want this to be flexible or always redirect folks to use date parsing try { - millis = str == null ? null : DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parseMillis(str); + millis = str == null ? null : convertDatetimeStringToLong(str, ESQL_DEFAULT_DATE_TIME_FORMATTER); } catch (Exception ex) { // in case of exception, millis will be null which will trigger an error } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java index 1ff8bc39e36f4..eecfeca8407a5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java @@ -11,7 +11,6 @@ import org.elasticsearch.compute.ann.ConvertEvaluator; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; -import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; import org.elasticsearch.xpack.ql.tree.Source; @@ -20,6 +19,8 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeStringToLong; import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME; import static org.elasticsearch.xpack.ql.type.DataTypes.DOUBLE; import static org.elasticsearch.xpack.ql.type.DataTypes.INTEGER; @@ -70,6 +71,6 @@ protected NodeInfo info() { @ConvertEvaluator(extraName = "FromString", warnExceptions = { IllegalArgumentException.class }) static long fromKeyword(BytesRef in) { - return DateParse.process(in, DateParse.DEFAULT_FORMATTER); + return convertDatetimeStringToLong(in.utf8ToString(), ESQL_DEFAULT_DATE_TIME_FORMATTER); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java index fc6a5f5c69afa..1109ce3a54690 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java @@ -19,10 +19,10 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.EsqlConverter.STRING_TO_IP; import static org.elasticsearch.xpack.ql.type.DataTypes.IP; import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD; import static org.elasticsearch.xpack.ql.type.DataTypes.TEXT; -import static org.elasticsearch.xpack.ql.util.StringUtils.parseIP; public class ToIP extends AbstractConvertFunction { @@ -59,6 +59,6 @@ protected NodeInfo info() { @ConvertEvaluator(extraName = "FromString", warnExceptions = { IllegalArgumentException.class }) static BytesRef fromKeyword(BytesRef asString) { - return parseIP(asString.utf8ToString()); + return (BytesRef) STRING_TO_IP.convert(asString); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java index 688996dd1db00..44f84e68f0233 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeLongToString; import static org.elasticsearch.xpack.esql.type.EsqlDataTypes.CARTESIAN_POINT; import static org.elasticsearch.xpack.esql.type.EsqlDataTypes.CARTESIAN_SHAPE; import static org.elasticsearch.xpack.esql.type.EsqlDataTypes.GEO_POINT; @@ -36,7 +38,6 @@ import static org.elasticsearch.xpack.ql.type.DataTypes.TEXT; import static org.elasticsearch.xpack.ql.type.DataTypes.UNSIGNED_LONG; import static org.elasticsearch.xpack.ql.type.DataTypes.VERSION; -import static org.elasticsearch.xpack.ql.util.DateUtils.UTC_DATE_TIME_FORMATTER; import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsNumber; import static org.elasticsearch.xpack.ql.util.SpatialCoordinateTypes.CARTESIAN; import static org.elasticsearch.xpack.ql.util.SpatialCoordinateTypes.GEO; @@ -117,7 +118,7 @@ static BytesRef fromIP(BytesRef ip) { @ConvertEvaluator(extraName = "FromDatetime") static BytesRef fromDatetime(long datetime) { - return new BytesRef(UTC_DATE_TIME_FORMATTER.formatMillis(datetime)); + return new BytesRef(convertDatetimeLongToString(datetime, ESQL_DEFAULT_DATE_TIME_FORMATTER)); } @ConvertEvaluator(extraName = "FromDouble") diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateExtract.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateExtract.java index 4ee178852fcd4..9af1cbc765bff 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateExtract.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateExtract.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.date; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.compute.ann.Evaluator; import org.elasticsearch.compute.ann.Fixed; import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator; @@ -24,13 +23,13 @@ import org.elasticsearch.xpack.ql.type.DataType; import org.elasticsearch.xpack.ql.type.DataTypes; -import java.time.Instant; import java.time.ZoneId; import java.time.temporal.ChronoField; import java.util.List; -import java.util.Locale; import java.util.function.Function; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.EsqlConverter.STRING_TO_CHRONO_FIELD; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.extractLongChronoField; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isDate; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact; @@ -76,13 +75,8 @@ private ChronoField chronoField() { // TODO: move the slimmed down code here to toEvaluator? if (chronoField == null) { Expression field = children().get(0); - if (field.foldable() && field.dataType() == DataTypes.KEYWORD) { - try { - BytesRef br = BytesRefs.toBytesRef(field.fold()); - chronoField = ChronoField.valueOf(br.utf8ToString().toUpperCase(Locale.ROOT)); - } catch (Exception e) { - return null; - } + if (field.foldable()) { + chronoField = (ChronoField) STRING_TO_CHRONO_FIELD.convert(field); } } return chronoField; @@ -90,13 +84,12 @@ private ChronoField chronoField() { @Evaluator(warnExceptions = { IllegalArgumentException.class }) static long process(long value, BytesRef chronoField, @Fixed ZoneId zone) { - ChronoField chrono = ChronoField.valueOf(chronoField.utf8ToString().toUpperCase(Locale.ROOT)); - return Instant.ofEpochMilli(value).atZone(zone).getLong(chrono); + return extractLongChronoField(value, chronoField, zone); } @Evaluator(extraName = "Constant") static long process(long value, @Fixed ChronoField chronoField, @Fixed ZoneId zone) { - return Instant.ofEpochMilli(value).atZone(zone).getLong(chronoField); + return extractLongChronoField(value, chronoField, zone); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormat.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormat.java index 60e1aabed3cdd..f994e3badc3dc 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormat.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateFormat.java @@ -28,11 +28,12 @@ import java.util.Locale; import java.util.function.Function; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.convertDatetimeLongToString; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.FIRST; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.SECOND; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isDate; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact; -import static org.elasticsearch.xpack.ql.util.DateUtils.UTC_DATE_TIME_FORMATTER; public class DateFormat extends EsqlConfigurationFunction implements OptionalArgument { @@ -83,19 +84,24 @@ public boolean foldable() { @Evaluator(extraName = "Constant") static BytesRef process(long val, @Fixed DateFormatter formatter) { - return new BytesRef(formatter.formatMillis(val)); + return new BytesRef(convertDatetimeLongToString(val, formatter)); } @Evaluator static BytesRef process(long val, BytesRef formatter, @Fixed Locale locale) { - return process(val, toFormatter(formatter, locale)); + return new BytesRef(convertDatetimeLongToString(val, toFormatter(formatter, locale))); } @Override public ExpressionEvaluator.Factory toEvaluator(Function toEvaluator) { var fieldEvaluator = toEvaluator.apply(field); if (format == null) { - return dvrCtx -> new DateFormatConstantEvaluator(source(), fieldEvaluator.get(dvrCtx), UTC_DATE_TIME_FORMATTER, dvrCtx); + return dvrCtx -> new DateFormatConstantEvaluator( + source(), + fieldEvaluator.get(dvrCtx), + ESQL_DEFAULT_DATE_TIME_FORMATTER, + dvrCtx + ); } if (format.dataType() != DataTypes.KEYWORD) { throw new IllegalArgumentException("unsupported data type for format [" + format.dataType() + "]"); @@ -115,7 +121,9 @@ public ExpressionEvaluator.Factory toEvaluator(Function EsqlDataTypeConverter.parseTemporalAmount(x, EsqlDataTypes.DATE_PERIOD)), - STRING_TO_TIME_DURATION(x -> EsqlDataTypeConverter.parseTemporalAmount(x, EsqlDataTypes.TIME_DURATION)); + STRING_TO_TIME_DURATION(x -> EsqlDataTypeConverter.parseTemporalAmount(x, EsqlDataTypes.TIME_DURATION)), + STRING_DATETIME_TO_LONG(x -> EsqlDataTypeConverter.foldExpressionToLong((Expression) x)), + STRING_TO_CHRONO_FIELD(x -> EsqlDataTypeConverter.foldExpressionToChronoField((Expression) x)), + STRING_TO_IP(x -> EsqlDataTypeConverter.convertStringToIP((BytesRef) x)); private static final String NAME = "esql-converter"; private final Function converter; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java index 808249a01969a..7a60af378110a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java @@ -13,7 +13,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; -import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.Source; import org.elasticsearch.xpack.ql.type.DataTypes; @@ -25,6 +24,7 @@ import java.util.function.Supplier; import static java.util.Collections.emptyList; +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ESQL_DEFAULT_DATE_TIME_FORMATTER; public class ToDatetimeTests extends AbstractFunctionTestCase { public ToDatetimeTests(@Name("TestCase") Supplier testCaseSupplier) { @@ -103,7 +103,7 @@ public static Iterable parameters() { "Line -1:-1: java.lang.IllegalArgumentException: " + (bytesRef.utf8ToString().isEmpty() ? "cannot parse empty datetime" - : ("failed to parse date field [" + bytesRef.utf8ToString() + "] with format [yyyy-MM-dd'T'HH:mm:ss.SSS'Z']")) + : ("failed to parse date field [" + bytesRef.utf8ToString() + "] with format [strict_date_optional_time]")) ) ); TestCaseSupplier.unary( @@ -113,12 +113,12 @@ public static Iterable parameters() { new TestCaseSupplier.TypedDataSupplier( "", // millis past "0001-01-01T00:00:00.000Z" to match the default formatter - () -> new BytesRef(randomDateString(-62135596800000L, Long.MAX_VALUE)), + () -> new BytesRef(randomDateString(-62135596800000L, 253402300799999L)), DataTypes.KEYWORD ) ), DataTypes.DATETIME, - bytesRef -> DateParse.DEFAULT_FORMATTER.parseMillis(((BytesRef) bytesRef).utf8ToString()), + bytesRef -> ESQL_DEFAULT_DATE_TIME_FORMATTER.parseMillis(((BytesRef) bytesRef).utf8ToString()), emptyList() ); TestCaseSupplier.unary( @@ -138,7 +138,27 @@ public static Iterable parameters() { "Line -1:-1: evaluation of [] failed, treating result as null. Only first 20 failures recorded.", "Line -1:-1: java.lang.IllegalArgumentException: failed to parse date field [" + ((BytesRef) bytesRef).utf8ToString() - + "] with format [yyyy-MM-dd'T'HH:mm:ss.SSS'Z']" + + "] with format [strict_date_optional_time]" + ) + ); + TestCaseSupplier.unary( + suppliers, + "ToDatetimeFromStringEvaluator[field=" + read + "]", + List.of( + new TestCaseSupplier.TypedDataSupplier( + "", + // millis before "0001-01-01T00:00:00.000Z" + () -> new BytesRef(randomDateString(253402300800000L, Long.MAX_VALUE)), + DataTypes.KEYWORD + ) + ), + DataTypes.DATETIME, + bytesRef -> null, + bytesRef -> List.of( + "Line -1:-1: evaluation of [] failed, treating result as null. Only first 20 failures recorded.", + "Line -1:-1: java.lang.IllegalArgumentException: failed to parse date field [" + + ((BytesRef) bytesRef).utf8ToString() + + "] with format [strict_date_optional_time]" ) ); @@ -146,12 +166,7 @@ public static Iterable parameters() { } private static String randomDateString(long from, long to) { - String result = Instant.ofEpochMilli(randomLongBetween(from, to)).toString(); - if (result.matches(".*:..Z")) { - // it's a zero millisecond date string, Instant.toString() will strip the milliseconds (and the parsing will fail) - return result.replace("Z", ".000Z"); - } - return result; + return Instant.ofEpochMilli(randomLongBetween(from, to)).toString(); } @Override