Skip to content

Commit ff0db15

Browse files
authored
Cleanup some Inefficiencies in JavaDateFormatter (#84922) (#89797)
Fixes a couple of smaller inefficiencies that I found in profiling and otherwise. Iteration over the parsers is faster if we just use an array. Make some of the object construction more efficient since formats aren't always constants, like in e.g. the index name resolver. Fix non-static initializer block running useless puts into the static mutable map on every instantiation no need to even have the map, just inline what it does and save some code, indirection and the iteration over the mutable map backports #84922
1 parent 756e8c6 commit ff0db15

File tree

2 files changed

+80
-75
lines changed

2 files changed

+80
-75
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2192,7 +2192,7 @@ static DateFormatter forPattern(String input) {
21922192
* - If no time is given, the start of the day is used
21932193
* - If no month of the year is found, the first day of the year is used
21942194
* - If an iso based weekyear is found, but not week is specified, the first monday
2195-
* of the new year is chosen (reataining BWC to joda time)
2195+
* of the new year is chosen (retaining BWC with joda time)
21962196
* - If an iso based weekyear is found and an iso based weekyear week, the start
21972197
* of the day is used
21982198
*

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

Lines changed: 79 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,25 @@
1717
import java.time.format.DateTimeParseException;
1818
import java.time.temporal.ChronoField;
1919
import java.time.temporal.TemporalAccessor;
20-
import java.time.temporal.TemporalField;
2120
import java.util.ArrayList;
22-
import java.util.Arrays;
23-
import java.util.Collection;
2421
import java.util.Collections;
25-
import java.util.HashMap;
2622
import java.util.List;
2723
import java.util.Locale;
28-
import java.util.Map;
2924
import java.util.Objects;
3025
import java.util.function.Consumer;
31-
import java.util.stream.Collectors;
26+
import java.util.function.UnaryOperator;
3227

3328
class JavaDateFormatter implements DateFormatter {
3429

35-
// base fields which should be used for default parsing, when we round up for date math
36-
private static final Map<TemporalField, Long> ROUND_UP_BASE_FIELDS = new HashMap<>(6);
37-
38-
{
39-
ROUND_UP_BASE_FIELDS.put(ChronoField.MONTH_OF_YEAR, 1L);
40-
ROUND_UP_BASE_FIELDS.put(ChronoField.DAY_OF_MONTH, 1L);
41-
ROUND_UP_BASE_FIELDS.put(ChronoField.HOUR_OF_DAY, 23L);
42-
ROUND_UP_BASE_FIELDS.put(ChronoField.MINUTE_OF_HOUR, 59L);
43-
ROUND_UP_BASE_FIELDS.put(ChronoField.SECOND_OF_MINUTE, 59L);
44-
ROUND_UP_BASE_FIELDS.put(ChronoField.NANO_OF_SECOND, 999_999_999L);
45-
}
46-
4730
private final String format;
4831
private final DateTimeFormatter printer;
49-
private final List<DateTimeFormatter> parsers;
50-
private final JavaDateFormatter roundupParser;
32+
private final DateTimeFormatter[] parsers;
33+
private final RoundUpFormatter roundupParser;
5134

52-
static class RoundUpFormatter extends JavaDateFormatter {
35+
private static final class RoundUpFormatter extends JavaDateFormatter {
5336

54-
RoundUpFormatter(String format, List<DateTimeFormatter> roundUpParsers) {
55-
super(format, firstFrom(roundUpParsers), null, roundUpParsers);
56-
}
57-
58-
private static DateTimeFormatter firstFrom(List<DateTimeFormatter> roundUpParsers) {
59-
return roundUpParsers.get(0);
37+
RoundUpFormatter(String format, DateTimeFormatter[] roundUpParsers) {
38+
super(format, roundUpParsers[0], (RoundUpFormatter) null, roundUpParsers);
6039
}
6140

6241
@Override
@@ -67,7 +46,18 @@ JavaDateFormatter getRoundupParser() {
6746

6847
// named formatters use default roundUpParser
6948
JavaDateFormatter(String format, DateTimeFormatter printer, DateTimeFormatter... parsers) {
70-
this(format, printer, builder -> ROUND_UP_BASE_FIELDS.forEach(builder::parseDefaulting), parsers);
49+
this(
50+
format,
51+
printer,
52+
// set up base fields which should be used for default parsing, when we round up for date math
53+
builder -> builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1L)
54+
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1L)
55+
.parseDefaulting(ChronoField.HOUR_OF_DAY, 23L)
56+
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 59L)
57+
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 59L)
58+
.parseDefaulting(ChronoField.NANO_OF_SECOND, 999_999_999L),
59+
parsers
60+
);
7161
}
7262

7363
// subclasses override roundUpParser
@@ -80,24 +70,28 @@ JavaDateFormatter getRoundupParser() {
8070
if (printer == null) {
8171
throw new IllegalArgumentException("printer may not be null");
8272
}
83-
long distinctZones = Arrays.stream(parsers).map(DateTimeFormatter::getZone).distinct().count();
84-
if (distinctZones > 1) {
85-
throw new IllegalArgumentException("formatters must have the same time zone");
86-
}
87-
long distinctLocales = Arrays.stream(parsers).map(DateTimeFormatter::getLocale).distinct().count();
88-
if (distinctLocales > 1) {
89-
throw new IllegalArgumentException("formatters must have the same locale");
90-
}
9173
this.printer = printer;
9274
this.format = format;
75+
this.parsers = parsersArray(printer, parsers);
76+
this.roundupParser = createRoundUpParser(format, roundupParserConsumer, locale(), this.parsers);
77+
}
9378

79+
private static DateTimeFormatter[] parsersArray(DateTimeFormatter printer, DateTimeFormatter... parsers) {
9480
if (parsers.length == 0) {
95-
this.parsers = Collections.singletonList(printer);
96-
} else {
97-
this.parsers = Arrays.asList(parsers);
81+
return new DateTimeFormatter[] { printer };
82+
}
83+
final ZoneId zoneId = parsers[0].getZone();
84+
final Locale locale = parsers[0].getLocale();
85+
for (int i = 1; i < parsers.length; i++) {
86+
final DateTimeFormatter parser = parsers[i];
87+
if (Objects.equals(parser.getZone(), zoneId) == false) {
88+
throw new IllegalArgumentException("formatters must have the same time zone");
89+
}
90+
if (Objects.equals(parser.getLocale(), locale) == false) {
91+
throw new IllegalArgumentException("formatters must have the same locale");
92+
}
9893
}
99-
List<DateTimeFormatter> roundUp = createRoundUpParser(format, roundupParserConsumer);
100-
this.roundupParser = new RoundUpFormatter(format, roundUp);
94+
return parsers;
10195
}
10296

10397
/**
@@ -109,16 +103,19 @@ JavaDateFormatter getRoundupParser() {
109103
* <code>DateFormatters</code>.
110104
* This means that we need to also have multiple RoundUp parsers.
111105
*/
112-
private List<DateTimeFormatter> createRoundUpParser(String format, Consumer<DateTimeFormatterBuilder> roundupParserConsumer) {
106+
private static RoundUpFormatter createRoundUpParser(
107+
String format,
108+
Consumer<DateTimeFormatterBuilder> roundupParserConsumer,
109+
Locale locale,
110+
DateTimeFormatter[] parsers
111+
) {
113112
if (format.contains("||") == false) {
114-
List<DateTimeFormatter> roundUpParsers = new ArrayList<>();
115-
for (DateTimeFormatter parser : this.parsers) {
113+
return new RoundUpFormatter(format, mapParsers(parser -> {
116114
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
117115
builder.append(parser);
118116
roundupParserConsumer.accept(builder);
119-
roundUpParsers.add(builder.toFormatter(locale()));
120-
}
121-
return roundUpParsers;
117+
return builder.toFormatter(locale);
118+
}, parsers));
122119
}
123120
return null;
124121
}
@@ -136,22 +133,26 @@ public static DateFormatter combined(String input, List<DateFormatter> formatter
136133
if (printer == null) {
137134
printer = javaDateFormatter.getPrinter();
138135
}
139-
parsers.addAll(javaDateFormatter.getParsers());
140-
roundUpParsers.addAll(javaDateFormatter.getRoundupParser().getParsers());
136+
Collections.addAll(parsers, javaDateFormatter.parsers);
137+
Collections.addAll(roundUpParsers, javaDateFormatter.getRoundupParser().parsers);
141138
}
142139

143-
return new JavaDateFormatter(input, printer, roundUpParsers, parsers);
140+
return new JavaDateFormatter(
141+
input,
142+
printer,
143+
roundUpParsers.toArray(new DateTimeFormatter[0]),
144+
parsers.toArray(new DateTimeFormatter[0])
145+
);
144146
}
145147

146-
private JavaDateFormatter(
147-
String format,
148-
DateTimeFormatter printer,
149-
List<DateTimeFormatter> roundUpParsers,
150-
List<DateTimeFormatter> parsers
151-
) {
148+
private JavaDateFormatter(String format, DateTimeFormatter printer, DateTimeFormatter[] roundUpParsers, DateTimeFormatter[] parsers) {
149+
this(format, printer, new RoundUpFormatter(format, roundUpParsers), parsers);
150+
}
151+
152+
private JavaDateFormatter(String format, DateTimeFormatter printer, RoundUpFormatter roundupParser, DateTimeFormatter[] parsers) {
152153
this.format = format;
153154
this.printer = printer;
154-
this.roundupParser = roundUpParsers != null ? new RoundUpFormatter(format, roundUpParsers) : null;
155+
this.roundupParser = roundupParser;
155156
this.parsers = parsers;
156157
}
157158

@@ -191,7 +192,7 @@ public TemporalAccessor parse(String input) {
191192
* @throws DateTimeParseException when unable to parse with any parsers
192193
*/
193194
private TemporalAccessor doParse(String input) {
194-
if (parsers.size() > 1) {
195+
if (parsers.length > 1) {
195196
for (DateTimeFormatter formatter : parsers) {
196197
ParsePosition pos = new ParsePosition(0);
197198
Object object = formatter.toFormat().parseObject(input, pos);
@@ -201,7 +202,7 @@ private TemporalAccessor doParse(String input) {
201202
}
202203
throw new DateTimeParseException("Failed to parse with all enclosed parsers", input, 0);
203204
}
204-
return this.parsers.get(0).parse(input);
205+
return this.parsers[0].parse(input);
205206
}
206207

207208
private boolean parsingSucceeded(Object object, String input, ParsePosition pos) {
@@ -214,12 +215,7 @@ public DateFormatter withZone(ZoneId zoneId) {
214215
if (zoneId.equals(zone())) {
215216
return this;
216217
}
217-
List<DateTimeFormatter> parsers = this.parsers.stream().map(p -> p.withZone(zoneId)).collect(Collectors.toList());
218-
List<DateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
219-
.stream()
220-
.map(p -> p.withZone(zoneId))
221-
.collect(Collectors.toList());
222-
return new JavaDateFormatter(format, printer.withZone(zoneId), roundUpParsers, parsers);
218+
return mapParsers(p -> p.withZone(zoneId));
223219
}
224220

225221
@Override
@@ -228,12 +224,24 @@ public DateFormatter withLocale(Locale locale) {
228224
if (locale.equals(locale())) {
229225
return this;
230226
}
231-
List<DateTimeFormatter> parsers = this.parsers.stream().map(p -> p.withLocale(locale)).collect(Collectors.toList());
232-
List<DateTimeFormatter> roundUpParsers = this.roundupParser.getParsers()
233-
.stream()
234-
.map(p -> p.withLocale(locale))
235-
.collect(Collectors.toList());
236-
return new JavaDateFormatter(format, printer.withLocale(locale), roundUpParsers, parsers);
227+
return mapParsers(p -> p.withLocale(locale));
228+
}
229+
230+
private JavaDateFormatter mapParsers(UnaryOperator<DateTimeFormatter> mapping) {
231+
return new JavaDateFormatter(
232+
format,
233+
mapping.apply(printer),
234+
mapParsers(mapping, ((JavaDateFormatter) this.roundupParser).parsers),
235+
mapParsers(mapping, this.parsers)
236+
);
237+
}
238+
239+
private static DateTimeFormatter[] mapParsers(UnaryOperator<DateTimeFormatter> mapping, DateTimeFormatter[] parsers) {
240+
DateTimeFormatter[] res = new DateTimeFormatter[parsers.length];
241+
for (int i = 0; i < parsers.length; i++) {
242+
res[i] = mapping.apply(parsers[i]);
243+
}
244+
return res;
237245
}
238246

239247
@Override
@@ -283,7 +291,4 @@ public String toString() {
283291
return String.format(Locale.ROOT, "format[%s] locale[%s]", format, locale());
284292
}
285293

286-
Collection<DateTimeFormatter> getParsers() {
287-
return parsers;
288-
}
289294
}

0 commit comments

Comments
 (0)