Skip to content

Commit acc9837

Browse files
committed
date parse settings
1 parent 87d866d commit acc9837

File tree

3 files changed

+86
-7
lines changed

3 files changed

+86
-7
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,13 +1065,17 @@ protected static <T extends Function> FunctionDefinition def(Class<T> function,
10651065
if (children.size() > 4 || children.size() < 2) {
10661066
throw new QlIllegalArgumentException("expects minimum two, maximum four arguments");
10671067
}
1068+
} else if (ThreeOptionalArguments.class.isAssignableFrom(function)) {
1069+
if (children.size() > 4 || children.isEmpty()) {
1070+
throw new QlIllegalArgumentException("expects minimum one, maximum four arguments");
1071+
}
10681072
} else if (children.size() != 4) {
10691073
throw new QlIllegalArgumentException("expects exactly four arguments");
10701074
}
10711075
return ctorRef.build(
10721076
source,
10731077
children.get(0),
1074-
children.get(1),
1078+
children.size() > 1 ? children.get(1) : null,
10751079
children.size() > 2 ? children.get(2) : null,
10761080
children.size() > 3 ? children.get(3) : null
10771081
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function;
9+
10+
/**
11+
* Marker interface indicating that a function accepts three optional arguments (the last three).
12+
* This is used by the {@link EsqlFunctionRegistry} to perform validation of function declaration.
13+
*/
14+
public interface ThreeOptionalArguments {
15+
16+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParse.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@
2424
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
2525
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
2626
import org.elasticsearch.xpack.esql.expression.function.Param;
27+
import org.elasticsearch.xpack.esql.expression.function.ThreeOptionalArguments;
28+
import org.elasticsearch.xpack.esql.expression.function.TwoOptionalArguments;
2729
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
2830
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
2931

3032
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
3135
import java.util.List;
36+
import java.util.Locale;
37+
import java.util.Objects;
38+
import java.util.TimeZone;
3239

3340
import static org.elasticsearch.common.time.DateFormatter.forPattern;
3441
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
@@ -38,7 +45,7 @@
3845
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.DEFAULT_DATE_TIME_FORMATTER;
3946
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToLong;
4047

41-
public class DateParse extends EsqlScalarFunction implements OptionalArgument {
48+
public class DateParse extends EsqlScalarFunction implements ThreeOptionalArguments {
4249
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
4350
Expression.class,
4451
"DateParse",
@@ -47,6 +54,8 @@ public class DateParse extends EsqlScalarFunction implements OptionalArgument {
4754

4855
private final Expression field;
4956
private final Expression format;
57+
private final Expression locale;
58+
private final Expression timezone;
5059

5160
@FunctionInfo(
5261
returnType = "date",
@@ -63,17 +72,46 @@ public DateParse(
6372
name = "dateString",
6473
type = { "keyword", "text" },
6574
description = "Date expression as a string. If `null` or an empty string, the function returns `null`."
66-
) Expression second
75+
) Expression second,
76+
@Param(
77+
name="dateLocale",
78+
type = { "keyword", "text" },
79+
description = "The locale to parse with"
80+
) Expression third,
81+
@Param(
82+
name="dateTimezone",
83+
type = { "keyword", "text" },
84+
description = "The timezone to parse with"
85+
) Expression forth
6786
) {
68-
super(source, second != null ? List.of(first, second) : List.of(first));
87+
super(source, fields(first, second, third, forth));
6988
this.field = second != null ? second : first;
7089
this.format = second != null ? first : null;
90+
this.locale = third;
91+
this.timezone = forth;
92+
}
93+
94+
private static List<Expression> fields(Expression field, Expression format, Expression locale, Expression timezone) {
95+
List<Expression> list = new ArrayList<>(3);
96+
list.add(field);
97+
if (format != null) {
98+
list.add(format);
99+
}
100+
if (locale != null) {
101+
list.add(locale);
102+
}
103+
if (timezone != null) {
104+
list.add(timezone);
105+
}
106+
return list;
71107
}
72108

73109
private DateParse(StreamInput in) throws IOException {
74110
this(
75111
Source.readFrom((PlanStreamInput) in),
76112
in.readNamedWriteable(Expression.class),
113+
in.readOptionalNamedWriteable(Expression.class),
114+
in.readOptionalNamedWriteable(Expression.class),
77115
in.readOptionalNamedWriteable(Expression.class)
78116
);
79117
}
@@ -82,7 +120,9 @@ private DateParse(StreamInput in) throws IOException {
82120
public void writeTo(StreamOutput out) throws IOException {
83121
source().writeTo(out);
84122
out.writeNamedWriteable(children().get(0));
85-
out.writeOptionalNamedWriteable(children().size() == 2 ? children().get(1) : null);
123+
out.writeOptionalNamedWriteable(children().size() > 1 ? children().get(1) : null);
124+
out.writeOptionalNamedWriteable(children().size() > 2 ? children().get(2) : null);
125+
out.writeOptionalNamedWriteable(children().size() > 3 ? children().get(3) : null);
86126
}
87127

88128
@Override
@@ -141,9 +181,23 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
141181
if (DataType.isString(format.dataType()) == false) {
142182
throw new IllegalArgumentException("unsupported data type for date_parse [" + format.dataType() + "]");
143183
}
184+
String localeAsString = locale == null ? null : ((BytesRef) locale.fold(toEvaluator.foldCtx())).utf8ToString();
185+
Locale locale = localeAsString == null ? null : Locale.forLanguageTag(localeAsString);
186+
if (localeAsString != null && locale == null) {
187+
throw new IllegalArgumentException("unsupported locale [" + localeAsString + "]");
188+
}
189+
190+
String timezoneAsString = timezone == null ? null : ((BytesRef) timezone.fold(toEvaluator.foldCtx())).utf8ToString();
191+
TimeZone timezone = timezoneAsString == null ? null : TimeZone.getTimeZone(timezoneAsString);
144192
if (format.foldable()) {
145193
try {
146194
DateFormatter formatter = toFormatter(format.fold(toEvaluator.foldCtx()));
195+
if (locale != null) {
196+
formatter = formatter.withLocale(locale);
197+
}
198+
if (timezone != null) {
199+
formatter = formatter.withZone(timezone.toZoneId());
200+
}
147201
return new DateParseConstantEvaluator.Factory(source(), fieldEvaluator, formatter);
148202
} catch (IllegalArgumentException e) {
149203
throw new InvalidArgumentException(e, "invalid date pattern for [{}]: {}", sourceText(), e.getMessage());
@@ -159,13 +213,18 @@ private static DateFormatter toFormatter(Object format) {
159213

160214
@Override
161215
public Expression replaceChildren(List<Expression> newChildren) {
162-
return new DateParse(source(), newChildren.get(0), newChildren.size() > 1 ? newChildren.get(1) : null);
216+
return new DateParse(
217+
source(),
218+
newChildren.get(0),
219+
newChildren.size() > 1 ? newChildren.get(1) : null,
220+
newChildren.size() > 2 ? newChildren.get(2) : null,
221+
newChildren.size() > 3 ? newChildren.get(3) : null);
163222
}
164223

165224
@Override
166225
protected NodeInfo<? extends Expression> info() {
167226
Expression first = format != null ? format : field;
168227
Expression second = format != null ? field : null;
169-
return NodeInfo.create(this, DateParse::new, first, second);
228+
return NodeInfo.create(this, DateParse::new, first, second, locale, timezone);
170229
}
171230
}

0 commit comments

Comments
 (0)