SQL: Implement DATE_PARSE function for parsing strings into DATE values (#57391) (#59699)

Implement DATE_PARSE(<date_str>, <pattern_str>) function
which allows to parse a date string according to the specified
pattern into a date object. The patterns allowed are those of
java.time.format.DateTimeFormatter.

Closes #54962

Co-authored-by: Marios Trivyzas <matriv@users.noreply.github.com>
Co-authored-by: Patrick Jiang(白泽) <dreamlike.sky@foxmail.com>

(cherry picked from commit 647a413d9b21bd3938f1716bb19f8407e1334125)
This commit is contained in:
Marios Trivyzas 2020-07-16 18:24:30 +03:00 committed by GitHub
parent 305b46c7cd
commit c7efbc1b83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 350 additions and 24 deletions

View File

@ -404,6 +404,50 @@ include-tagged::{sql-specs}/docs/docs.csv-spec[dateDiffDateTimeMinutes]
include-tagged::{sql-specs}/docs/docs.csv-spec[dateDiffDateMinutes]
--------------------------------------------------
[[sql-functions-datetime-dateparse]]
==== `DATE_PARSE`
.Synopsis:
[source, sql]
--------------------------------------------------
DATE_PARSE(
string_exp, <1>
string_exp) <2>
--------------------------------------------------
*Input*:
<1> date expression as a string
<2> parsing pattern
*Output*: date
*Description*: Returns a date by parsing the 1st argument using the format specified in the 2nd argument. The parsing
format pattern used is the one from
https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/time/format/DateTimeFormatter.html[`java.time.format.DateTimeFormatter`].
If any of the two arguments is `null` or an empty string, then `null` is returned.
[NOTE]
If the parsing pattern does not contain all valid date units (e.g. 'HH:mm:ss', 'dd-MM HH:mm:ss', etc.) an error is returned
as the function needs to return a value of `date` type which will contain date part.
[source, sql]
--------------------------------------------------
include-tagged::{sql-specs}/docs/docs.csv-spec[dateParse1]
--------------------------------------------------
[NOTE]
====
The resulting `date` will have the time zone specified by the user through the
<<sql-rest-fields-timezone,`time_zone`>>/<<jdbc-cfg-timezone,`timezone`>> REST/driver parameters
with no conversion applied.
[source, sql]
--------------------------------------------------
include-tagged::{sql-specs}/docs/docs.csv-spec[dateParse2]
--------------------------------------------------
====
[[sql-functions-datetime-datetimeformat]]
==== `DATETIME_FORMAT`

View File

@ -55,6 +55,7 @@
** <<sql-functions-current-timestamp>>
** <<sql-functions-datetime-add>>
** <<sql-functions-datetime-diff>>
** <<sql-functions-datetime-dateparse>>
** <<sql-functions-datetime-datetimeformat>>
** <<sql-functions-datetime-datetimeparse>>
** <<sql-functions-datetime-timeparse>>

View File

@ -51,6 +51,7 @@ DATETIME_PARSE |SCALAR
DATETRUNC |SCALAR
DATE_ADD |SCALAR
DATE_DIFF |SCALAR
DATE_PARSE |SCALAR
DATE_PART |SCALAR
DATE_TRUNC |SCALAR
DAY |SCALAR

View File

@ -164,3 +164,105 @@ SELECT MAX(salary) FROM test_emp GROUP BY TODAY();
---------------
74999
;
selectDateParse
schema::date1:date
SELECT DATE_PARSE('07/04/2020', 'dd/MM/uuuu') AS date1;
date1
------------
2020-04-07
;
selectDateParseWithField
schema::birth_date:ts|dp_birth_date:date
SELECT birth_date, DATE_PARSE(DATETIME_FORMAT(birth_date, 'MM/dd/ HH uuuu'), concat(gender, 'M/dd/ HH uuuu')) AS dp_birth_date
FROM test_emp WHERE gender = 'M' AND emp_no BETWEEN 10037 AND 10052 ORDER BY emp_no;
birth_date | dp_birth_date
-------------------------+----------------
1963-07-22 00:00:00.000Z | 1963-07-22
1960-07-20 00:00:00.000Z | 1960-07-20
1959-10-01 00:00:00.000Z | 1959-10-01
null | null
null | null
null | null
null | null
null | null
1958-05-21 00:00:00.000Z | 1958-05-21
1953-07-28 00:00:00.000Z | 1953-07-28
1961-02-26 00:00:00.000Z | 1961-02-26
;
dateParseWhere
schema::birth_date:ts|dp_birth_date:date
SELECT birth_date, DATE_PARSE(DATETIME_FORMAT(birth_date, 'MM_dd_uuuu'), 'MM_dd_uuuu') AS dp_birth_date
FROM test_emp WHERE dp_birth_date > '1963-10-20'::date ORDER BY emp_no;
birth_date | dp_birth_date
-------------------------+----------------
1964-06-02 00:00:00.000Z | 1964-06-02
1963-11-26 00:00:00.000Z | 1963-11-26
1964-04-18 00:00:00.000Z | 1964-04-18
1964-10-18 00:00:00.000Z | 1964-10-18
1964-06-11 00:00:00.000Z | 1964-06-11
1965-01-03 00:00:00.000Z | 1965-01-03
;
dateParseOrderBy
schema::birth_date:ts|dp_birth_date:date
SELECT birth_date, DATE_PARSE(DATETIME_FORMAT(birth_date, 'MM/dd/uuuu'), 'MM/dd/uuuu') AS dp_birth_date
FROM test_emp ORDER BY 2 DESC NULLS LAST LIMIT 10;
birth_date | dp_birth_date
-------------------------+---------------
1965-01-03 00:00:00.000Z | 1965-01-03
1964-10-18 00:00:00.000Z | 1964-10-18
1964-06-11 00:00:00.000Z | 1964-06-11
1964-06-02 00:00:00.000Z | 1964-06-02
1964-04-18 00:00:00.000Z | 1964-04-18
1963-11-26 00:00:00.000Z | 1963-11-26
1963-09-09 00:00:00.000Z | 1963-09-09
1963-07-22 00:00:00.000Z | 1963-07-22
1963-06-07 00:00:00.000Z | 1963-06-07
1963-06-01 00:00:00.000Z | 1963-06-01
;
dateParseGroupBy
schema::count:l|df_birth_date:s
SELECT count(*) AS count, DATETIME_FORMAT(DATE_PARSE(DATETIME_FORMAT(birth_date, 'dd/MM/uuuu'), 'dd/MM/uuuu'), 'MM') AS df_birth_date
FROM test_emp GROUP BY df_birth_date ORDER BY 1 DESC, 2 DESC NULLS LAST LIMIT 10;
count | df_birth_date
-------+---------------
10 | 09
10 | 05
10 | null
9 | 10
9 | 07
8 | 11
8 | 04
8 | 02
7 | 12
7 | 06
;
dateParseHaving
schema::max:ts|df_birth_date:s
SELECT MAX(birth_date) AS max, DATETIME_FORMAT(birth_date, 'MM') AS df_birth_date FROM test_emp GROUP BY df_birth_date
HAVING DATE_PARSE(DATETIME_FORMAT(MAX(birth_date), 'dd/MM/uuuu'), 'dd/MM/uuuu') > '1961-10-20'::date ORDER BY 1 DESC NULLS LAST;
max | df_birth_date
-------------------------+---------------
1965-01-03 00:00:00.000Z | 01
1964-10-18 00:00:00.000Z | 10
1964-06-11 00:00:00.000Z | 06
1964-04-18 00:00:00.000Z | 04
1963-11-26 00:00:00.000Z | 11
1963-09-09 00:00:00.000Z | 09
1963-07-22 00:00:00.000Z | 07
1963-03-21 00:00:00.000Z | 03
1962-12-29 00:00:00.000Z | 12
null | null
;

View File

@ -247,6 +247,7 @@ DATETIME_PARSE |SCALAR
DATETRUNC |SCALAR
DATE_ADD |SCALAR
DATE_DIFF |SCALAR
DATE_PARSE |SCALAR
DATE_PART |SCALAR
DATE_TRUNC |SCALAR
DAY |SCALAR
@ -2906,6 +2907,31 @@ schema::time:time
// end::timeParse3
;
dateParse1
schema::date:date
// tag::dateParse1
SELECT DATE_PARSE('07/04/2020', 'dd/MM/uuuu') AS "date";
date
-----------
2020-04-07
// end::dateParse1
;
dateParse2-Ignore
schema::date:date
// tag::dateParse2
{
"query" : "SELECT DATE_PARSE('07/04/2020', 'dd/MM/uuuu') AS \"date\"",
"time_zone" : "Europe/Athens"
}
date
------------
2020-04-07T00:00:00.000+03:00
// end::dateParse2
;
datePartDateTimeYears
// tag::datePartDateTimeYears
SELECT DATE_PART('year', '2019-09-22T11:22:33.123Z'::datetime) AS "years";

View File

@ -35,6 +35,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.CurrentTi
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateAdd;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateDiff;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DatePart;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateParse;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFormat;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeParse;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTrunc;
@ -176,7 +177,8 @@ public class SqlFunctionRegistry extends FunctionRegistry {
def(DayOfYear.class, DayOfYear::new, "DAY_OF_YEAR", "DAYOFYEAR", "DOY"),
def(DateAdd.class, DateAdd::new, "DATEADD", "DATE_ADD", "TIMESTAMPADD", "TIMESTAMP_ADD"),
def(DateDiff.class, DateDiff::new, "DATEDIFF", "DATE_DIFF", "TIMESTAMPDIFF", "TIMESTAMP_DIFF"),
def(DatePart.class, DatePart::new, "DATEPART", "DATE_PART"),
def(DateParse.class, DateParse::new, "DATE_PARSE"),
def(DatePart.class, DatePart::new, "DATEPART", "DATE_PART"),
def(DateTimeFormat.class, DateTimeFormat::new, "DATETIME_FORMAT"),
def(DateTimeParse.class, DateTimeParse::new, "DATETIME_PARSE"),
def(DateTrunc.class, DateTrunc::new, "DATETRUNC", "DATE_TRUNC"),

View File

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.function.scalar.BinaryScalarFunction;
import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataType;
import org.elasticsearch.xpack.sql.type.SqlDataTypes;
import java.time.ZoneId;
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeParseProcessor.Parser.DATE;
public class DateParse extends BaseDateTimeParseFunction {
public DateParse(Source source, Expression datePart, Expression timestamp, ZoneId zoneId) {
super(source, datePart, timestamp, zoneId);
}
@Override
protected DateTimeParseProcessor.Parser parser() {
return DATE;
}
@Override
protected NodeInfo.NodeCtor3<Expression, Expression, ZoneId, BaseDateTimeParseFunction> ctorForInfo() {
return DateParse::new;
}
@Override
protected BinaryScalarFunction replaceChildren(Expression timestamp, Expression pattern) {
return new DateParse(source(), timestamp, pattern, zoneId());
}
@Override
public DataType dataType() {
return SqlDataTypes.DATE;
}
@Override
protected String scriptMethodName() {
return "dateParse";
}
}

View File

@ -8,11 +8,15 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.ql.type.DataType;
import org.elasticsearch.xpack.ql.type.DataTypes;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.type.SqlDataTypes;
import org.elasticsearch.xpack.sql.util.DateUtils;
import java.io.IOException;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
@ -30,15 +34,16 @@ import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
public class DateTimeParseProcessor extends BinaryDateTimeProcessor {
public enum Parser {
DATE_TIME("datetime", ZonedDateTime::from, LocalDateTime::from),
TIME("time", OffsetTime::from, LocalTime::from);
DATE_TIME(DataTypes.DATETIME, ZonedDateTime::from, LocalDateTime::from),
TIME(SqlDataTypes.TIME, OffsetTime::from, LocalTime::from),
DATE(SqlDataTypes.DATE, LocalDate::from, (TemporalAccessor ta) -> {throw new DateTimeException("InvalidDate");});
private final BiFunction<String, String, TemporalAccessor> parser;
private final String parseType;
Parser(String parseType, TemporalQuery<?>... queries) {
this.parseType = parseType;
Parser(DataType parseType, TemporalQuery<?>... queries) {
this.parseType = parseType.typeName();
this.parser = (timestampStr, pattern) -> DateTimeFormatter.ofPattern(pattern, Locale.ROOT)
.parseBest(timestampStr, queries);
}

View File

@ -280,6 +280,10 @@ public class InternalSqlScriptUtils extends InternalQlScriptUtils {
return DateTruncProcessor.process(truncateTo, asDateTime(dateTimeOrInterval), ZoneId.of(tzId));
}
public static Object dateParse(String dateField, String pattern, String tzId) {
return Parser.DATE.parse(dateField, pattern, ZoneId.of(tzId));
}
public static Integer datePart(String dateField, Object dateTime, String tzId) {
return (Integer) DatePartProcessor.process(dateField, asDateTime(dateTime), ZoneId.of(tzId));
}

View File

@ -177,7 +177,11 @@ public final class DateUtils {
nano = nano - nano % (int) Math.pow(10, (9 - precision));
return nano;
}
public static ZonedDateTime atTimeZone(LocalDate ld, ZoneId zoneId) {
return ld.atStartOfDay(zoneId);
}
public static ZonedDateTime atTimeZone(LocalDateTime ldt, ZoneId zoneId) {
return ZonedDateTime.ofInstant(ldt, zoneId.getRules().getValidOffsets(ldt).get(0), zoneId);
}
@ -205,6 +209,8 @@ public final class DateUtils {
return atTimeZone((OffsetTime) ta, zoneId);
} else if (ta instanceof LocalTime) {
return atTimeZone((LocalTime) ta, zoneId);
} else if (ta instanceof LocalDate) {
return atTimeZone((LocalDate) ta, zoneId);
} else {
return ta;
}

View File

@ -132,6 +132,7 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
ZonedDateTime dateAdd(String, Integer, Object, String)
Integer dateDiff(String, Object, Object, String)
def dateTrunc(String, Object, String)
def dateParse(String, String, String)
Integer datePart(String, Object, String)
String dateTimeFormat(Object, String, String)
def dateTimeParse(String, String, String)

View File

@ -43,6 +43,12 @@ public class DateTimeParsePipeTests extends AbstractNodeTestCase<DateTimeParsePi
randomStringLiteral(),
randomZone()
).makePipe());
functions.add(new DateParse(
randomSource(),
randomStringLiteral(),
randomStringLiteral(),
randomZone()
).makePipe());
return (DateTimeParsePipe) randomFrom(functions);
}

View File

@ -22,6 +22,7 @@ import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTest
import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.dateTime;
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.time;
import static org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeTestUtils.date;
public class DateTimeParseProcessorTests extends AbstractSqlWireSerializingTestCase<DateTimeParseProcessor> {
@ -87,7 +88,7 @@ public class DateTimeParseProcessorTests extends AbstractSqlWireSerializingTestC
() -> new DateTimeParse(Source.EMPTY, l("2020-04-07"), l("MM/dd"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid datetime string [2020-04-07] or pattern [MM/dd] is received; Text '2020-04-07' could not be parsed at index 2",
"Invalid datetime string [2020-04-07] or pattern [MM/dd] is received; Text '2020-04-07' could not be parsed at index 2",
siae.getMessage()
);
@ -113,43 +114,97 @@ public class DateTimeParseProcessorTests extends AbstractSqlWireSerializingTestC
public void testTimeInvalidInputs() {
SqlIllegalArgumentException siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l(10), randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null)
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l(10), randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals("A string is required; received [10]", siae.getMessage());
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, randomStringLiteral(), l(20), randomZone()).makePipe().asProcessor().process(null)
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, randomStringLiteral(), l(20), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals("A string is required; received [20]", siae.getMessage());
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("11:04:07"), l("invalid"), randomZone()).makePipe().asProcessor().process(null)
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("11:04:07"), l("invalid"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid time string [11:04:07] or pattern [invalid] is received; Unknown pattern letter: i",
"Invalid time string [11:04:07] or pattern [invalid] is received; Unknown pattern letter: i",
siae.getMessage()
);
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("11:04:07"), l("HH:mm"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid time string [11:04:07] or pattern [HH:mm] is received; " +
"Text '11:04:07' could not be parsed, unparsed text found at index 5",
siae.getMessage()
);
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("07/05/2020"), l("dd/MM/uuuu"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid time string [07/05/2020] or pattern [dd/MM/uuuu] is received; Unable to convert parsed text into [time]",
siae.getMessage()
);
}
public void testDateInvalidInputs() {
SqlIllegalArgumentException siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, l(10), randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals("A string is required; received [10]", siae.getMessage());
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, randomStringLiteral(), l(20), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals("A string is required; received [20]", siae.getMessage());
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, l("07/05/2020"), l("invalid"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid date string [07/05/2020] or pattern [invalid] is received; Unknown pattern letter: i",
siae.getMessage()
);
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("11:04:07"), l("HH:mm"), randomZone()).makePipe().asProcessor().process(null)
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, l("07/05/2020"), l("dd/MM"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid time string [11:04:07] or pattern [HH:mm] is received; " +
"Text '11:04:07' could not be parsed, unparsed text found at index 5",
siae.getMessage()
"Invalid date string [07/05/2020] or pattern [dd/MM] is received; " +
"Text '07/05/2020' could not be parsed, unparsed text found at index 5",
siae.getMessage()
);
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new TimeParse(Source.EMPTY, l("07/05/2020"), l("dd/MM/uuuu"), randomZone()).makePipe().asProcessor().process(null)
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, l("11:04:07"), l("HH:mm:ss"), randomZone()).makePipe().asProcessor().process(null)
);
assertEquals(
"Invalid time string [07/05/2020] or pattern [dd/MM/uuuu] is received; Unable to convert parsed text into [time]",
siae.getMessage()
"Invalid date string [11:04:07] or pattern [HH:mm:ss] is received; Unable to convert parsed text into [date]",
siae.getMessage()
);
siae = expectThrows(
SqlIllegalArgumentException.class,
() -> new DateParse(Source.EMPTY, l("05/2020 11:04:07"), l("MM/uuuu HH:mm:ss"), randomZone())
.makePipe()
.asProcessor()
.process(null)
);
assertEquals(
"Invalid date string [05/2020 11:04:07] or pattern [MM/uuuu HH:mm:ss] is received; Unable to convert parsed text into [date]",
siae.getMessage()
);
}
@ -164,6 +219,11 @@ public class DateTimeParseProcessorTests extends AbstractSqlWireSerializingTestC
assertNull(new TimeParse(Source.EMPTY, randomStringLiteral(), l(""), randomZone()).makePipe().asProcessor().process(null));
assertNull(new TimeParse(Source.EMPTY, NULL, randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null));
assertNull(new TimeParse(Source.EMPTY, l(""), randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null));
// DateParse
assertNull(new DateParse(Source.EMPTY, randomStringLiteral(), NULL, randomZone()).makePipe().asProcessor().process(null));
assertNull(new DateParse(Source.EMPTY, randomStringLiteral(), l(""), randomZone()).makePipe().asProcessor().process(null));
assertNull(new DateParse(Source.EMPTY, NULL, randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null));
assertNull(new DateParse(Source.EMPTY, l(""), randomStringLiteral(), randomZone()).makePipe().asProcessor().process(null));
}
public void testParsing() {
@ -203,6 +263,19 @@ public class DateTimeParseProcessorTests extends AbstractSqlWireSerializingTestC
.asProcessor()
.process(null)
);
// DateParse
assertEquals(
date(2020, 4, 7, zoneId),
new DateParse(Source.EMPTY, l("07/04/2020"), l("dd/MM/uuuu"), zoneId).makePipe()
.asProcessor()
.process(null)
);
assertEquals(
date(2020, 4, 7, zoneId),
new DateParse(Source.EMPTY, l("07/04/2020 12:12:00"), l("dd/MM/uuuu HH:mm:ss"), zoneId).makePipe()
.asProcessor()
.process(null)
);
assertEquals(
time(10, 20, 30, 123456789, ZoneOffset.of("+05:30"), zoneId),
new TimeParse(Source.EMPTY, l("16/06/2020 10:20:30.123456789 +0530"), l("dd/MM/uuuu HH:mm:ss.SSSSSSSSS xx"), zoneId).makePipe()

View File

@ -10,6 +10,7 @@ import org.elasticsearch.xpack.sql.util.DateUtils;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDate;
import java.time.OffsetTime;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -64,6 +65,10 @@ public class DateTimeTestUtils {
return OffsetTime.of(lt, zoneId.getRules().getValidOffsets(ldt).get(0));
}
public static ZonedDateTime date(int year, int month, int day, ZoneId zoneId) {
return LocalDate.of(year, month, day).atStartOfDay(zoneId);
}
static ZonedDateTime nowWithMillisResolution() {
Clock millisResolutionClock = Clock.tick(Clock.systemUTC(), Duration.ofMillis(1));
return ZonedDateTime.now(millisResolutionClock);