Skip to content

Commit 75df484

Browse files
authored
[7.17] Fix date rounding for date math parsing backport(#90458) (#90635)
* Fix date rounding for date math parsing (#90458) in #89693 the rounding logic was only applied when a field was present on a pattern. This is incorrect as for dates like "2020" we want to default to "2020-01-01T23:59:59.999..." when rounding is enabled. This commit always applies monthOfYear or dayofMonth defaulting (when rounding enabled) except when the dayOfYear is set closes #90187 backport(#90458) (cherry picked from commit 3f3a95e)
1 parent 974669e commit 75df484

File tree

4 files changed

+156
-3
lines changed

4 files changed

+156
-3
lines changed

docs/changelog/90458.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 90458
2+
summary: Fix date rounding for date math parsing
3+
area: Infra/Core
4+
type: bug
5+
issues:
6+
- 90187
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
setup:
2+
- skip:
3+
version: " - 8.5.99"
4+
reason: awaits backports
5+
- do:
6+
indices.create:
7+
index: dates_year_only
8+
body:
9+
mappings:
10+
properties:
11+
date:
12+
type: date
13+
format: uuuu
14+
15+
- do:
16+
bulk:
17+
refresh: true
18+
body:
19+
- '{ "index" : { "_index" : "dates_year_only", "_id" : "first" } }'
20+
- '{"date" : "1900", "field" : 1 }'
21+
- '{ "index" : { "_index" : "dates_year_only", "_id" : "second" } }'
22+
- '{"date" : "2022", "field" : 1 }'
23+
- '{ "index" : { "_index" : "dates_year_only", "_id" : "third" } }'
24+
- '{"date" : "2022", "field" : 2 }'
25+
- '{ "index" : { "_index" : "dates_year_only", "_id" : "fourth" } }'
26+
- '{"date" : "1500", "field" : 2 }'
27+
28+
- do:
29+
indices.create:
30+
index: dates
31+
body:
32+
mappings:
33+
properties:
34+
date:
35+
type: date
36+
37+
- do:
38+
bulk:
39+
refresh: true
40+
body:
41+
- '{ "index" : { "_index" : "dates", "_id" : "first" } }'
42+
- '{"date" : "1900-01-01T12:12:12.123456789Z", "field" : 1 }'
43+
- '{ "index" : { "_index" : "dates", "_id" : "second" } }'
44+
- '{"date" : "2022-01-01T12:12:12.123456789Z", "field" : 1 }'
45+
- '{ "index" : { "_index" : "dates", "_id" : "third" } }'
46+
- '{"date" : "2022-01-03T12:12:12.123456789Z", "field" : 2 }'
47+
- '{ "index" : { "_index" : "dates", "_id" : "fourth" } }'
48+
- '{"date" : "1500-01-01T12:12:12.123456789Z", "field" : 2 }'
49+
- '{ "index" : { "_index" : "dates", "_id" : "fifth" } }'
50+
- '{"date" : "1500-01-05T12:12:12.123456789Z", "field" : 2 }'
51+
52+
---
53+
"test range query for all docs with year uuuu":
54+
- do:
55+
search:
56+
rest_total_hits_as_int: true
57+
index: dates
58+
body:
59+
query:
60+
range:
61+
date:
62+
gte: 1000
63+
lte: 2023
64+
format: uuuu
65+
66+
- match: { hits.total: 5 }
67+
- length: { hits.hits: 5 }
68+
69+
---
70+
"test match query gte and lt for single result with year uuuu":
71+
- do:
72+
search:
73+
rest_total_hits_as_int: true
74+
index: dates
75+
body:
76+
query:
77+
range:
78+
date:
79+
gte: 1500 #1500-01-01T00:00:00
80+
lte: 1500 #1500-01-01T23:59:59
81+
format: uuuu
82+
83+
- match: { hits.total: 1 }
84+
- length: { hits.hits: 1 }
85+
- match: { hits.hits.0._id: "fourth" }
86+
87+
---
88+
"test match query gte and lte with year uuuu":
89+
- do:
90+
search:
91+
rest_total_hits_as_int: true
92+
index: dates
93+
body:
94+
query:
95+
range:
96+
date:
97+
gte: 1500
98+
lte: 2000
99+
format: uuuu
100+
101+
- match: { hits.total: 3 }
102+
- length: { hits.hits: 3 }
103+
- match: { hits.hits.0._id: "first" }
104+
- match: { hits.hits.1._id: "fourth" }
105+
- match: { hits.hits.2._id: "fifth" }
106+
107+
---
108+
"test match query with year uuuu":
109+
- do:
110+
search:
111+
rest_total_hits_as_int: true
112+
index: dates_year_only
113+
body:
114+
query:
115+
match:
116+
date:
117+
query: "1500"
118+
119+
- match: { hits.total: 1 }
120+
- length: { hits.hits: 1 }
121+
- match: { hits.hits.0._id: "fourth" }

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.time.format.DateTimeFormatterBuilder;
1717
import java.time.format.DateTimeParseException;
1818
import java.time.temporal.ChronoField;
19+
import java.time.temporal.IsoFields;
1920
import java.time.temporal.TemporalAccessor;
2021
import java.util.ArrayList;
2122
import java.util.Collections;
@@ -41,12 +42,17 @@ class JavaDateFormatter implements DateFormatter {
4142
*/
4243
private static final BiConsumer<DateTimeFormatterBuilder, DateTimeFormatter> DEFAULT_ROUND_UP = (builder, parser) -> {
4344
String parserAsString = parser.toString();
44-
if (parserAsString.contains(ChronoField.MONTH_OF_YEAR.toString())) {
45+
if (parserAsString.contains(ChronoField.DAY_OF_YEAR.toString())) {
46+
builder.parseDefaulting(ChronoField.DAY_OF_YEAR, 1L);
47+
// TODO ideally we should make defaulting for weekbased year here too,
48+
// but this will not work when locale is changed
49+
// weekbased rounding relies on DateFormatters#localDateFromWeekBasedDate
50+
// Applying month of year or dayOfMonth when weekbased fields are used will result in a conflict
51+
} else if (parserAsString.contains(IsoFields.WEEK_BASED_YEAR.toString()) == false) {
4552
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1L);
46-
}
47-
if (parserAsString.contains(ChronoField.DAY_OF_MONTH.toString())) {
4853
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1L);
4954
}
55+
5056
if (parserAsString.contains(ChronoField.CLOCK_HOUR_OF_AMPM.toString())) {
5157
builder.parseDefaulting(ChronoField.CLOCK_HOUR_OF_AMPM, 11L);
5258
builder.parseDefaulting(ChronoField.AMPM_OF_DAY, 1L);

server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,26 @@ public void testRoundupFormatterWithEpochDates() {
324324
assertRoundupFormatter("uuuu-MM-dd'T'HH:mm:ss.SSS||epoch_second", "1234567890", 1234567890999L);
325325
}
326326

327+
public void testYearWithoutMonthRoundUp() {
328+
assertRoundupFormatter("1500", "1500-01-01T23:59:59.999", "uuuu");
329+
assertRoundupFormatter("2022", "2022-01-01T23:59:59.999", "uuuu");
330+
assertRoundupFormatter("2022", "2022-01-01T23:59:59.999", "yyyy");
331+
assumeFalse(
332+
"won't work in jdk8 " + "because SPI mechanism is not looking at classpath - needs ISOCalendarDataProvider in jre's ext/libs",
333+
JavaVersion.current().equals(JavaVersion.parse("8"))
334+
);
335+
// cannot reliably default week based years due to locale changing. This is always using the same locale anyway
336+
// See JavaDateFormatter javadocs
337+
assertRoundupFormatter("2022", "2022-01-03T23:59:59.999", "YYYY");
338+
}
339+
340+
private void assertRoundupFormatter(String input, String expectedDate, String format) {
341+
long expectedMillis = DateFormatters.from(DateFormatter.forPattern("strict_date_optional_time").parse(expectedDate))
342+
.toInstant()
343+
.toEpochMilli();
344+
assertRoundupFormatter(format, input, expectedMillis);
345+
}
346+
327347
private void assertRoundupFormatter(String format, String input, long expectedMilliSeconds) {
328348
JavaDateFormatter dateFormatter = (JavaDateFormatter) DateFormatter.forPattern(format);
329349
dateFormatter.parse(input);

0 commit comments

Comments
 (0)