11
11
import static org .opensearch .sql .data .type .ExprCoreType .TIME ;
12
12
import static org .opensearch .sql .data .type .ExprCoreType .TIMESTAMP ;
13
13
14
- import java .time .LocalDateTime ;
15
- import java .time .temporal .TemporalAccessor ;
16
14
import java .util .List ;
17
15
import java .util .Objects ;
18
16
import java .util .stream .Collectors ;
19
17
import lombok .EqualsAndHashCode ;
20
18
import org .opensearch .common .time .DateFormatter ;
21
- import org .opensearch .common .time .DateFormatters ;
22
19
import org .opensearch .common .time .FormatNames ;
23
20
import org .opensearch .sql .data .type .ExprCoreType ;
24
21
import org .opensearch .sql .data .type .ExprType ;
@@ -31,13 +28,13 @@ public class OpenSearchDateType extends OpenSearchDataType {
31
28
32
29
private static final OpenSearchDateType instance = new OpenSearchDateType ();
33
30
34
- // numeric formats which support full datetime
31
+ /** Numeric formats which support full datetime. */
35
32
public static final List <FormatNames > SUPPORTED_NAMED_NUMERIC_FORMATS = List .of (
36
33
FormatNames .EPOCH_MILLIS ,
37
34
FormatNames .EPOCH_SECOND
38
35
);
39
36
40
- // list of named formats which support full datetime
37
+ /** List of named formats which support full datetime. */
41
38
public static final List <FormatNames > SUPPORTED_NAMED_DATETIME_FORMATS = List .of (
42
39
FormatNames .ISO8601 ,
43
40
FormatNames .BASIC_DATE_TIME ,
@@ -78,7 +75,7 @@ public class OpenSearchDateType extends OpenSearchDataType {
78
75
FormatNames .STRICT_WEEK_DATE_TIME_NO_MILLIS
79
76
);
80
77
81
- // list of named formats that only support year/month/day
78
+ /** List of named formats that only support year/month/day. */
82
79
public static final List <FormatNames > SUPPORTED_NAMED_DATE_FORMATS = List .of (
83
80
FormatNames .BASIC_DATE ,
84
81
FormatNames .BASIC_ORDINAL_DATE ,
@@ -94,8 +91,8 @@ public class OpenSearchDateType extends OpenSearchDataType {
94
91
FormatNames .STRICT_WEEKYEAR_WEEK_DAY
95
92
);
96
93
97
- // list of named formats which produce incomplete date,
98
- // e.g. 1 or 2 are missing from tuple year/month/day
94
+ /** list of named formats which produce incomplete date,
95
+ * e.g. 1 or 2 are missing from tuple year/month/day. */
99
96
public static final List <FormatNames > SUPPORTED_NAMED_INCOMPLETE_DATE_FORMATS = List .of (
100
97
FormatNames .YEAR_MONTH ,
101
98
FormatNames .STRICT_YEAR_MONTH ,
@@ -108,7 +105,7 @@ public class OpenSearchDateType extends OpenSearchDataType {
108
105
FormatNames .STRICT_WEEKYEAR
109
106
);
110
107
111
- // list of named formats that only support hour/minute/second
108
+ /** List of named formats that only support hour/minute/second. */
112
109
public static final List <FormatNames > SUPPORTED_NAMED_TIME_FORMATS = List .of (
113
110
FormatNames .BASIC_TIME ,
114
111
FormatNames .BASIC_TIME_NO_MILLIS ,
@@ -134,6 +131,11 @@ public class OpenSearchDateType extends OpenSearchDataType {
134
131
FormatNames .STRICT_T_TIME_NO_MILLIS
135
132
);
136
133
134
+ /** Formatter symbols which used to format time or date correspondingly.
135
+ * {@link java.time.format.DateTimeFormatter}. */
136
+ private static final String CUSTOM_FORMAT_TIME_SYMBOLS = "nNASsmHkKha" ;
137
+ private static final String CUSTOM_FORMAT_DATE_SYMBOLS = "FecEWwYqQgdMLDyuG" ;
138
+
137
139
@ EqualsAndHashCode .Exclude
138
140
String formatString ;
139
141
@@ -179,7 +181,6 @@ public List<DateFormatter> getAllNamedFormatters() {
179
181
180
182
/**
181
183
* Retrieves a list of numeric formatters that format for dates.
182
- *
183
184
* @return a list of DateFormatters that can be used to parse a Date.
184
185
*/
185
186
public List <DateFormatter > getNumericNamedFormatters () {
@@ -191,6 +192,26 @@ public List<DateFormatter> getNumericNamedFormatters() {
191
192
.map (DateFormatter ::forPattern ).collect (Collectors .toList ());
192
193
}
193
194
195
+ /**
196
+ * Retrieves a list of custom formats defined by the user.
197
+ * @return a list of formats as strings that can be used to parse a Date/Time/Timestamp.
198
+ */
199
+ public List <String > getAllCustomFormats () {
200
+ return getFormatList ().stream ()
201
+ .filter (format -> FormatNames .forName (format ) == null )
202
+ .map (format -> {
203
+ try {
204
+ DateFormatter .forPattern (format );
205
+ return format ;
206
+ } catch (Exception ignored ) {
207
+ // parsing failed
208
+ return null ;
209
+ }
210
+ })
211
+ .filter (Objects ::nonNull )
212
+ .collect (Collectors .toList ());
213
+ }
214
+
194
215
/**
195
216
* Retrieves a list of custom formatters defined by the user.
196
217
* @return a list of DateFormatters that can be used to parse a Date/Time/Timestamp.
@@ -212,7 +233,6 @@ public List<DateFormatter> getAllCustomFormatters() {
212
233
213
234
/**
214
235
* Retrieves a list of named formatters that format for dates.
215
- *
216
236
* @return a list of DateFormatters that can be used to parse a Date.
217
237
*/
218
238
public List <DateFormatter > getDateNamedFormatters () {
@@ -226,7 +246,6 @@ public List<DateFormatter> getDateNamedFormatters() {
226
246
227
247
/**
228
248
* Retrieves a list of named formatters that format for Times.
229
- *
230
249
* @return a list of DateFormatters that can be used to parse a Time.
231
250
*/
232
251
public List <DateFormatter > getTimeNamedFormatters () {
@@ -240,7 +259,6 @@ public List<DateFormatter> getTimeNamedFormatters() {
240
259
241
260
/**
242
261
* Retrieves a list of named formatters that format for DateTimes.
243
- *
244
262
* @return a list of DateFormatters that can be used to parse a DateTime.
245
263
*/
246
264
public List <DateFormatter > getDateTimeNamedFormatters () {
@@ -252,39 +270,26 @@ public List<DateFormatter> getDateTimeNamedFormatters() {
252
270
.map (DateFormatter ::forPattern ).collect (Collectors .toList ());
253
271
}
254
272
255
- private ExprCoreType getExprTypeFromCustomFormats (List <DateFormatter > formats ) {
273
+ private ExprCoreType getExprTypeFromCustomFormats (List <String > formats ) {
256
274
boolean isDate = false ;
257
275
boolean isTime = false ;
258
276
259
- LocalDateTime sampleDateTime = LocalDateTime .now ();
260
- // Unfortunately, there is no public API to get info from the formatter object,
261
- // whether it parses date or time or datetime. The workaround is:
262
- // Converting a sample DateTime object by each formatter to string and back.
263
- // Double-converted sample will be also DateTime, but if formatter parses
264
- // time part only, date part would be lost. And vice versa.
265
- // This trick allows us to get matching data type for every custom formatter.
266
- // Unfortunately, this involves parsing a string, once per query, per column, per format.
267
- // Overhead performance is equal to parsing extra row of result set (extra doc).
268
- // Could be cached in scope of #1783 https://github.com/opensearch-project/sql/issues/1783.
269
-
270
277
for (var format : formats ) {
271
- TemporalAccessor ta = format .parse (format .format (sampleDateTime ));
272
- LocalDateTime parsedSample = sampleDateTime ;
273
- try {
274
- // TODO do we need withZoneSameInstant or withZoneSameLocal?
275
- parsedSample = DateFormatters .from (ta ).toLocalDateTime ();
276
- } catch (Exception ignored ) {
277
- // Can't convert to a DateTime - format does not represent a complete date or time
278
- continue ;
278
+ if (!isTime ) {
279
+ for (var symbol : CUSTOM_FORMAT_TIME_SYMBOLS .toCharArray ()) {
280
+ if (format .contains (String .valueOf (symbol ))) {
281
+ isTime = true ;
282
+ break ;
283
+ }
284
+ }
279
285
}
280
-
281
286
if (!isDate ) {
282
- isDate = parsedSample . toLocalDate (). equals ( sampleDateTime . toLocalDate ());
283
- }
284
- if (! isTime ) {
285
- // Second and Second fraction part are optional and may miss in some formats, trim it.
286
- isTime = parsedSample . toLocalTime (). withSecond ( 0 ). withNano ( 0 ). equals (
287
- sampleDateTime . toLocalTime (). withSecond ( 0 ). withNano ( 0 ));
287
+ for ( var symbol : CUSTOM_FORMAT_DATE_SYMBOLS . toCharArray ()) {
288
+ if ( format . contains ( String . valueOf ( symbol ))) {
289
+ isDate = true ;
290
+ break ;
291
+ }
292
+ }
288
293
}
289
294
if (isDate && isTime ) {
290
295
return TIMESTAMP ;
@@ -298,7 +303,7 @@ private ExprCoreType getExprTypeFromCustomFormats(List<DateFormatter> formats) {
298
303
return TIME ;
299
304
}
300
305
301
- // Incomplete formats: can't be converted to DATE nor TIME, for example `year` (year only)
306
+ // Incomplete or incorrect formats: can't be converted to DATE nor TIME, for example `year`
302
307
return TIMESTAMP ;
303
308
}
304
309
@@ -316,7 +321,7 @@ private ExprCoreType getExprTypeFromFormatString(String formatString) {
316
321
return TIMESTAMP ;
317
322
}
318
323
319
- List <DateFormatter > customFormatters = getAllCustomFormatters ();
324
+ List <String > customFormatters = getAllCustomFormats ();
320
325
if (!customFormatters .isEmpty ()) {
321
326
ExprCoreType customFormatType = getExprTypeFromCustomFormats (customFormatters );
322
327
ExprCoreType combinedByDefaultFormats = customFormatType ;
0 commit comments