2424import org .elasticsearch .xpack .esql .expression .function .FunctionInfo ;
2525import org .elasticsearch .xpack .esql .expression .function .OptionalArgument ;
2626import 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 ;
2729import org .elasticsearch .xpack .esql .expression .function .scalar .EsqlScalarFunction ;
2830import org .elasticsearch .xpack .esql .io .stream .PlanStreamInput ;
2931
3032import java .io .IOException ;
33+ import java .util .ArrayList ;
34+ import java .util .Arrays ;
3135import java .util .List ;
36+ import java .util .Locale ;
37+ import java .util .Objects ;
38+ import java .util .TimeZone ;
3239
3340import static org .elasticsearch .common .time .DateFormatter .forPattern ;
3441import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
3845import static org .elasticsearch .xpack .esql .type .EsqlDataTypeConverter .DEFAULT_DATE_TIME_FORMATTER ;
3946import 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