Skip to content

Commit 829215c

Browse files
CaptainDredgePrabhat Sharma
and
Prabhat Sharma
authored
Added New DateTime parser implementation (opensearch-project#11465)
Signed-off-by: Prabhat Sharma <ptsharma@amazon.com> Co-authored-by: Prabhat Sharma <ptsharma@amazon.com>
1 parent 76f7e73 commit 829215c

10 files changed

+796
-37
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
133133
- Bump OpenTelemetry from 1.32.0 to 1.34.1 ([#11891](https://github.com/opensearch-project/OpenSearch/pull/11891))
134134
- Support index level allocation filtering for searchable snapshot index ([#11563](https://github.com/opensearch-project/OpenSearch/pull/11563))
135135
- Add `org.opensearch.rest.MethodHandlers` and `RestController#getAllHandlers` ([11876](https://github.com/opensearch-project/OpenSearch/pull/11876))
136+
- New DateTime format for RFC3339 compatible date fields ([#11465](https://github.com/opensearch-project/OpenSearch/pull/11465))
136137

137138
### Dependencies
138139
- Bumps jetty version to 9.4.52.v20230823 to fix GMS-2023-1857 ([#9822](https://github.com/opensearch-project/OpenSearch/pull/9822))

NOTICE.txt

+3
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ Foundation (http://www.apache.org/).
1010

1111
This product includes software developed by
1212
Joda.org (http://www.joda.org/).
13+
14+
This product includes software developed by
15+
Morten Haraldsen (ethlo) (https://github.com/ethlo) under the Apache License, version 2.0.

server/src/main/java/org/opensearch/common/time/DateFormatters.java

+37
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,41 @@ public class DateFormatters {
12991299
.withResolverStyle(ResolverStyle.STRICT)
13001300
);
13011301

1302+
/**
1303+
* Returns RFC 3339 a popular ISO 8601 profile compatible date time formatter and parser.
1304+
* This is not fully compatible to the existing spec, its more linient and closely follows w3c note on datetime
1305+
*/
1306+
1307+
public static final DateFormatter RFC3339_LENIENT_DATE_FORMATTER = new JavaDateFormatter(
1308+
"rfc3339_lenient",
1309+
new OpenSearchDateTimeFormatter(STRICT_DATE_OPTIONAL_TIME_PRINTER),
1310+
new RFC3339CompatibleDateTimeFormatter(
1311+
new DateTimeFormatterBuilder().append(DATE_FORMATTER)
1312+
.optionalStart()
1313+
.appendLiteral('T')
1314+
.appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE)
1315+
.appendLiteral(':')
1316+
.appendValue(MINUTE_OF_HOUR, 1, 2, SignStyle.NOT_NEGATIVE)
1317+
.optionalStart()
1318+
.appendLiteral(':')
1319+
.appendValue(SECOND_OF_MINUTE, 1, 2, SignStyle.NOT_NEGATIVE)
1320+
.optionalStart()
1321+
.appendFraction(NANO_OF_SECOND, 1, 9, true)
1322+
.optionalEnd()
1323+
.optionalStart()
1324+
.appendLiteral(',')
1325+
.appendFraction(NANO_OF_SECOND, 1, 9, false)
1326+
.optionalEnd()
1327+
.optionalStart()
1328+
.appendOffsetId()
1329+
.optionalEnd()
1330+
.optionalEnd()
1331+
.optionalEnd()
1332+
.toFormatter(Locale.ROOT)
1333+
.withResolverStyle(ResolverStyle.STRICT)
1334+
)
1335+
);
1336+
13021337
private static final DateTimeFormatter HOUR_MINUTE_SECOND_FORMATTER = new DateTimeFormatterBuilder().append(HOUR_MINUTE_FORMATTER)
13031338
.appendLiteral(":")
13041339
.appendValue(SECOND_OF_MINUTE, 1, 2, SignStyle.NOT_NEGATIVE)
@@ -2152,6 +2187,8 @@ static DateFormatter forPattern(String input) {
21522187
return STRICT_YEAR_MONTH;
21532188
} else if (FormatNames.STRICT_YEAR_MONTH_DAY.matches(input)) {
21542189
return STRICT_YEAR_MONTH_DAY;
2190+
} else if (FormatNames.RFC3339_LENIENT.matches(input)) {
2191+
return RFC3339_LENIENT_DATE_FORMATTER;
21552192
} else {
21562193
try {
21572194
return new JavaDateFormatter(

server/src/main/java/org/opensearch/common/time/FormatNames.java

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
*/
4545
public enum FormatNames {
4646
ISO8601(null, "iso8601"),
47+
RFC3339_LENIENT(null, "rfc3339_lenient"),
4748
BASIC_DATE("basicDate", "basic_date"),
4849
BASIC_DATE_TIME("basicDateTime", "basic_date_time"),
4950
BASIC_DATE_TIME_NO_MILLIS("basicDateTimeNoMillis", "basic_date_time_no_millis"),

server/src/main/java/org/opensearch/common/time/JavaDateFormatter.java

+58-32
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.opensearch.core.common.Strings;
3737

3838
import java.text.ParsePosition;
39+
import java.time.DateTimeException;
3940
import java.time.ZoneId;
4041
import java.time.format.DateTimeFormatter;
4142
import java.time.format.DateTimeFormatterBuilder;
@@ -52,7 +53,6 @@
5253
import java.util.Locale;
5354
import java.util.Map;
5455
import java.util.Objects;
55-
import java.util.concurrent.CopyOnWriteArrayList;
5656
import java.util.function.BiConsumer;
5757
import java.util.stream.Collectors;
5858

@@ -70,11 +70,11 @@ class JavaDateFormatter implements DateFormatter {
7070

7171
private final String format;
7272
private final String printFormat;
73-
private final DateTimeFormatter printer;
74-
private final List<DateTimeFormatter> parsers;
73+
private final OpenSearchDateTimePrinter printer;
74+
private final List<OpenSearchDateTimeFormatter> parsers;
7575
private final JavaDateFormatter roundupParser;
7676
private final Boolean canCacheLastParsedFormatter;
77-
private volatile DateTimeFormatter lastParsedformatter = null;
77+
private volatile OpenSearchDateTimeFormatter lastParsedformatter = null;
7878

7979
/**
8080
* A round up formatter
@@ -83,11 +83,11 @@ class JavaDateFormatter implements DateFormatter {
8383
*/
8484
static class RoundUpFormatter extends JavaDateFormatter {
8585

86-
RoundUpFormatter(String format, List<DateTimeFormatter> roundUpParsers) {
86+
RoundUpFormatter(String format, List<OpenSearchDateTimeFormatter> roundUpParsers) {
8787
super(format, firstFrom(roundUpParsers), null, roundUpParsers);
8888
}
8989

90-
private static DateTimeFormatter firstFrom(List<DateTimeFormatter> roundUpParsers) {
90+
private static OpenSearchDateTimeFormatter firstFrom(List<OpenSearchDateTimeFormatter> roundUpParsers) {
9191
return roundUpParsers.get(0);
9292
}
9393

@@ -101,14 +101,18 @@ JavaDateFormatter getRoundupParser() {
101101
JavaDateFormatter(
102102
String format,
103103
String printFormat,
104-
DateTimeFormatter printer,
104+
OpenSearchDateTimePrinter printer,
105105
Boolean canCacheLastParsedFormatter,
106-
DateTimeFormatter... parsers
106+
OpenSearchDateTimeFormatter... parsers
107107
) {
108108
this(format, printFormat, printer, ROUND_UP_BASE_FIELDS, canCacheLastParsedFormatter, parsers);
109109
}
110110

111111
JavaDateFormatter(String format, DateTimeFormatter printer, DateTimeFormatter... parsers) {
112+
this(format, format, wrapFormatter(printer), false, wrapAllFormatters(parsers));
113+
}
114+
115+
JavaDateFormatter(String format, OpenSearchDateTimePrinter printer, OpenSearchDateTimeFormatter... parsers) {
112116
this(format, format, printer, false, parsers);
113117
}
114118

@@ -127,19 +131,19 @@ JavaDateFormatter getRoundupParser() {
127131
JavaDateFormatter(
128132
String format,
129133
String printFormat,
130-
DateTimeFormatter printer,
134+
OpenSearchDateTimePrinter printer,
131135
BiConsumer<DateTimeFormatterBuilder, DateTimeFormatter> roundupParserConsumer,
132136
Boolean canCacheLastParsedFormatter,
133-
DateTimeFormatter... parsers
137+
OpenSearchDateTimeFormatter... parsers
134138
) {
135139
if (printer == null) {
136140
throw new IllegalArgumentException("printer may not be null");
137141
}
138-
long distinctZones = Arrays.stream(parsers).map(DateTimeFormatter::getZone).distinct().count();
142+
long distinctZones = Arrays.stream(parsers).map(OpenSearchDateTimeFormatter::getZone).distinct().count();
139143
if (distinctZones > 1) {
140144
throw new IllegalArgumentException("formatters must have the same time zone");
141145
}
142-
long distinctLocales = Arrays.stream(parsers).map(DateTimeFormatter::getLocale).distinct().count();
146+
long distinctLocales = Arrays.stream(parsers).map(OpenSearchDateTimeFormatter::getLocale).distinct().count();
143147
if (distinctLocales > 1) {
144148
throw new IllegalArgumentException("formatters must have the same locale");
145149
}
@@ -149,12 +153,12 @@ JavaDateFormatter getRoundupParser() {
149153
this.canCacheLastParsedFormatter = canCacheLastParsedFormatter;
150154

151155
if (parsers.length == 0) {
152-
this.parsers = Collections.singletonList(printer);
156+
this.parsers = Collections.singletonList((OpenSearchDateTimeFormatter) printer);
153157
} else {
154158
this.parsers = Arrays.asList(parsers);
155159
}
156160
List<DateTimeFormatter> roundUp = createRoundUpParser(format, roundupParserConsumer);
157-
this.roundupParser = new RoundUpFormatter(format, roundUp);
161+
this.roundupParser = new RoundUpFormatter(format, wrapAllFormatters(roundUp));
158162
}
159163

160164
JavaDateFormatter(
@@ -163,7 +167,7 @@ JavaDateFormatter getRoundupParser() {
163167
BiConsumer<DateTimeFormatterBuilder, DateTimeFormatter> roundupParserConsumer,
164168
DateTimeFormatter... parsers
165169
) {
166-
this(format, format, printer, roundupParserConsumer, false, parsers);
170+
this(format, format, wrapFormatter(printer), roundupParserConsumer, false, wrapAllFormatters(parsers));
167171
}
168172

169173
/**
@@ -181,7 +185,8 @@ private List<DateTimeFormatter> createRoundUpParser(
181185
) {
182186
if (format.contains("||") == false) {
183187
List<DateTimeFormatter> roundUpParsers = new ArrayList<>();
184-
for (DateTimeFormatter parser : this.parsers) {
188+
for (OpenSearchDateTimeFormatter customparser : this.parsers) {
189+
DateTimeFormatter parser = customparser.getFormatter();
185190
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
186191
builder.append(parser);
187192
roundupParserConsumer.accept(builder, parser);
@@ -201,12 +206,12 @@ public static DateFormatter combined(
201206
assert formatters.size() > 0;
202207
assert printFormatter != null;
203208

204-
List<DateTimeFormatter> parsers = new ArrayList<>(formatters.size());
205-
List<DateTimeFormatter> roundUpParsers = new ArrayList<>(formatters.size());
209+
List<OpenSearchDateTimeFormatter> parsers = new ArrayList<>(formatters.size());
210+
List<OpenSearchDateTimeFormatter> roundUpParsers = new ArrayList<>(formatters.size());
206211

207212
assert printFormatter instanceof JavaDateFormatter;
208213
JavaDateFormatter javaPrintFormatter = (JavaDateFormatter) printFormatter;
209-
DateTimeFormatter printer = javaPrintFormatter.getPrinter();
214+
OpenSearchDateTimePrinter printer = javaPrintFormatter.getPrinter();
210215
for (DateFormatter formatter : formatters) {
211216
assert formatter instanceof JavaDateFormatter;
212217
JavaDateFormatter javaDateFormatter = (JavaDateFormatter) formatter;
@@ -227,9 +232,9 @@ public static DateFormatter combined(
227232
private JavaDateFormatter(
228233
String format,
229234
String printFormat,
230-
DateTimeFormatter printer,
231-
List<DateTimeFormatter> roundUpParsers,
232-
List<DateTimeFormatter> parsers,
235+
OpenSearchDateTimePrinter printer,
236+
List<OpenSearchDateTimeFormatter> roundUpParsers,
237+
List<OpenSearchDateTimeFormatter> parsers,
233238
Boolean canCacheLastParsedFormatter
234239
) {
235240
this.format = format;
@@ -245,6 +250,15 @@ private JavaDateFormatter(
245250
DateTimeFormatter printer,
246251
List<DateTimeFormatter> roundUpParsers,
247252
List<DateTimeFormatter> parsers
253+
) {
254+
this(format, format, wrapFormatter(printer), wrapAllFormatters(roundUpParsers), wrapAllFormatters(parsers), false);
255+
}
256+
257+
private JavaDateFormatter(
258+
String format,
259+
OpenSearchDateTimePrinter printer,
260+
List<OpenSearchDateTimeFormatter> roundUpParsers,
261+
List<OpenSearchDateTimeFormatter> parsers
248262
) {
249263
this(format, format, printer, roundUpParsers, parsers, false);
250264
}
@@ -253,7 +267,7 @@ JavaDateFormatter getRoundupParser() {
253267
return roundupParser;
254268
}
255269

256-
DateTimeFormatter getPrinter() {
270+
OpenSearchDateTimePrinter getPrinter() {
257271
return printer;
258272
}
259273

@@ -265,7 +279,7 @@ public TemporalAccessor parse(String input) {
265279

266280
try {
267281
return doParse(input);
268-
} catch (DateTimeParseException e) {
282+
} catch (DateTimeException e) {
269283
throw new IllegalArgumentException("failed to parse date field [" + input + "] with format [" + format + "]", e);
270284
}
271285
}
@@ -289,14 +303,14 @@ private TemporalAccessor doParse(String input) {
289303
Object object = null;
290304
if (canCacheLastParsedFormatter && lastParsedformatter != null) {
291305
ParsePosition pos = new ParsePosition(0);
292-
object = lastParsedformatter.toFormat().parseObject(input, pos);
306+
object = lastParsedformatter.parseObject(input, pos);
293307
if (parsingSucceeded(object, input, pos)) {
294308
return (TemporalAccessor) object;
295309
}
296310
}
297-
for (DateTimeFormatter formatter : parsers) {
311+
for (OpenSearchDateTimeFormatter formatter : parsers) {
298312
ParsePosition pos = new ParsePosition(0);
299-
object = formatter.toFormat().parseObject(input, pos);
313+
object = formatter.parseObject(input, pos);
300314
if (parsingSucceeded(object, input, pos)) {
301315
lastParsedformatter = formatter;
302316
return (TemporalAccessor) object;
@@ -312,16 +326,28 @@ private boolean parsingSucceeded(Object object, String input, ParsePosition pos)
312326
return object != null && pos.getIndex() == input.length();
313327
}
314328

329+
private static OpenSearchDateTimeFormatter wrapFormatter(DateTimeFormatter formatter) {
330+
return new OpenSearchDateTimeFormatter(formatter);
331+
}
332+
333+
private static OpenSearchDateTimeFormatter[] wrapAllFormatters(DateTimeFormatter... formatters) {
334+
return Arrays.stream(formatters).map(JavaDateFormatter::wrapFormatter).toArray(OpenSearchDateTimeFormatter[]::new);
335+
}
336+
337+
private static List<OpenSearchDateTimeFormatter> wrapAllFormatters(List<DateTimeFormatter> formatters) {
338+
return formatters.stream().map(JavaDateFormatter::wrapFormatter).collect(Collectors.toList());
339+
}
340+
315341
@Override
316342
public DateFormatter withZone(ZoneId zoneId) {
317343
// shortcurt to not create new objects unnecessarily
318344
if (zoneId.equals(zone())) {
319345
return this;
320346
}
321-
List<DateTimeFormatter> parsers = new CopyOnWriteArrayList<>(
347+
List<OpenSearchDateTimeFormatter> parsers = new ArrayList<>(
322348
this.parsers.stream().map(p -> p.withZone(zoneId)).collect(Collectors.toList())
323349
);
324-
List<DateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
350+
List<OpenSearchDateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
325351
.stream()
326352
.map(p -> p.withZone(zoneId))
327353
.collect(Collectors.toList());
@@ -334,10 +360,10 @@ public DateFormatter withLocale(Locale locale) {
334360
if (locale.equals(locale())) {
335361
return this;
336362
}
337-
List<DateTimeFormatter> parsers = new CopyOnWriteArrayList<>(
363+
List<OpenSearchDateTimeFormatter> parsers = new ArrayList<>(
338364
this.parsers.stream().map(p -> p.withLocale(locale)).collect(Collectors.toList())
339365
);
340-
List<DateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
366+
List<OpenSearchDateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
341367
.stream()
342368
.map(p -> p.withLocale(locale))
343369
.collect(Collectors.toList());
@@ -396,7 +422,7 @@ public String toString() {
396422
return String.format(Locale.ROOT, "format[%s] locale[%s]", format, locale());
397423
}
398424

399-
Collection<DateTimeFormatter> getParsers() {
425+
Collection<OpenSearchDateTimeFormatter> getParsers() {
400426
return parsers;
401427
}
402428
}

0 commit comments

Comments
 (0)