Skip to content

Commit cf933b1

Browse files
authored
Allow parsing timezone without fully provided time (#50178)
strict_date_optional_time changes to have optional minute part. It already allowed optional second and fraction of second part. This allows parsing 2018-01-01T00+01 , 2018-01-01T00:00+01 , 2018-01-01T00:00:00+01 , 2018-01-01T00:00:00.000+01 It won't allow parsing a timezone without an hour part as this is not allowed by iso8601 spec closes #49351
1 parent 40801af commit cf933b1

File tree

2 files changed

+105
-41
lines changed

2 files changed

+105
-41
lines changed

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

+25-25
Original file line numberDiff line numberDiff line change
@@ -107,31 +107,31 @@ public class DateFormatters {
107107
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER = new DateTimeFormatterBuilder()
108108
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
109109
.optionalStart()
110-
.appendLiteral('T')
111-
.optionalStart()
112-
.appendValue(HOUR_OF_DAY, 2, 2, SignStyle.NOT_NEGATIVE)
113-
.optionalStart()
114-
.appendLiteral(':')
115-
.appendValue(MINUTE_OF_HOUR, 2, 2, SignStyle.NOT_NEGATIVE)
116-
.optionalStart()
117-
.appendLiteral(':')
118-
.appendValue(SECOND_OF_MINUTE, 2, 2, SignStyle.NOT_NEGATIVE)
119-
.optionalStart()
120-
.appendFraction(NANO_OF_SECOND, 1, 9, true)
121-
.optionalEnd()
122-
.optionalStart()
123-
.appendLiteral(',')
124-
.appendFraction(NANO_OF_SECOND, 1, 9, false)
125-
.optionalEnd()
126-
.optionalEnd()
127-
.optionalStart()
128-
.appendZoneOrOffsetId()
129-
.optionalEnd()
130-
.optionalStart()
131-
.append(TIME_ZONE_FORMATTER_NO_COLON)
132-
.optionalEnd()
133-
.optionalEnd()
134-
.optionalEnd()
110+
.appendLiteral('T')
111+
.optionalStart()
112+
.appendValue(HOUR_OF_DAY, 2, 2, SignStyle.NOT_NEGATIVE)
113+
.optionalStart()
114+
.appendLiteral(':')
115+
.appendValue(MINUTE_OF_HOUR, 2, 2, SignStyle.NOT_NEGATIVE)
116+
.optionalStart()
117+
.appendLiteral(':')
118+
.appendValue(SECOND_OF_MINUTE, 2, 2, SignStyle.NOT_NEGATIVE)
119+
.optionalStart()
120+
.appendFraction(NANO_OF_SECOND, 1, 9, true)
121+
.optionalEnd()
122+
.optionalStart()
123+
.appendLiteral(',')
124+
.appendFraction(NANO_OF_SECOND, 1, 9, false)
125+
.optionalEnd()
126+
.optionalEnd()
127+
.optionalEnd()
128+
.optionalStart()
129+
.appendZoneOrOffsetId()
130+
.optionalEnd()
131+
.optionalStart()
132+
.append(TIME_ZONE_FORMATTER_NO_COLON)
133+
.optionalEnd()
134+
.optionalEnd()
135135
.optionalEnd()
136136
.toFormatter(Locale.ROOT)
137137
.withResolverStyle(ResolverStyle.STRICT);

server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java

+80-16
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
import org.elasticsearch.common.time.DateFormatter;
2525
import org.elasticsearch.common.time.DateFormatters;
2626
import org.elasticsearch.common.time.DateMathParser;
27+
import org.elasticsearch.common.util.LocaleUtils;
2728
import org.elasticsearch.test.ESTestCase;
2829
import org.joda.time.DateTime;
2930
import org.joda.time.DateTimeZone;
3031
import org.joda.time.format.ISODateTimeFormat;
32+
import org.junit.BeforeClass;
3133

34+
import java.time.Instant;
3235
import java.time.LocalDateTime;
3336
import java.time.ZoneId;
3437
import java.time.ZoneOffset;
@@ -46,9 +49,47 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
4649
protected boolean enableWarningsCheck() {
4750
return false;
4851
}
52+
@BeforeClass
53+
public static void checkJvmProperties(){
54+
assert ("SPI,COMPAT".equals(System.getProperty("java.locale.providers")))
55+
: "`-Djava.locale.providers=SPI,COMPAT` needs to be set";
56+
}
57+
58+
public void testTimezoneParsing() {
59+
/** this testcase won't work in joda. See comment in {@link #testPartialTimeParsing()}
60+
* assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time");
61+
*/
62+
assertSameDateAs("2016-11-30T00+01", "strict_date_optional_time", "strict_date_optional_time");
63+
assertSameDateAs("2016-11-30T00+0100", "strict_date_optional_time", "strict_date_optional_time");
64+
assertSameDateAs("2016-11-30T00+01:00", "strict_date_optional_time", "strict_date_optional_time");
65+
}
66+
67+
public void testPartialTimeParsing() {
68+
/*
69+
This does not work in Joda as it reports 2016-11-30T01:00:00Z
70+
because StrictDateOptionalTime confuses +01 with an hour (which is a signed fixed length digit)
71+
assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time");
72+
ES java.time implementation does not suffer from this,
73+
but we intentionally not allow parsing timezone without an time part as it is not allowed in iso8601
74+
*/
75+
assertJavaTimeParseException("2016-11-30T+01","strict_date_optional_time");
76+
77+
assertSameDateAs("2016-11-30T12+01", "strict_date_optional_time", "strict_date_optional_time");
78+
assertSameDateAs("2016-11-30T12:00+01", "strict_date_optional_time", "strict_date_optional_time");
79+
assertSameDateAs("2016-11-30T12:00:00+01", "strict_date_optional_time", "strict_date_optional_time");
80+
assertSameDateAs("2016-11-30T12:00:00.000+01", "strict_date_optional_time", "strict_date_optional_time");
81+
82+
//without timezone
83+
assertSameDateAs("2016-11-30T", "strict_date_optional_time", "strict_date_optional_time");
84+
assertSameDateAs("2016-11-30T12", "strict_date_optional_time", "strict_date_optional_time");
85+
assertSameDateAs("2016-11-30T12:00", "strict_date_optional_time", "strict_date_optional_time");
86+
assertSameDateAs("2016-11-30T12:00:00", "strict_date_optional_time", "strict_date_optional_time");
87+
assertSameDateAs("2016-11-30T12:00:00.000", "strict_date_optional_time", "strict_date_optional_time");
88+
}
89+
4990
// date_optional part of a parser names "strict_date_optional_time" or "date_optional"time
5091
// means that date part can be partially parsed.
51-
public void testPartialParsing() {
92+
public void testPartialDateParsing() {
5293
assertSameDateAs("2001", "strict_date_optional_time_nanos", "strict_date_optional_time");
5394
assertSameDateAs("2001-01", "strict_date_optional_time_nanos", "strict_date_optional_time");
5495
assertSameDateAs("2001-01-01", "strict_date_optional_time_nanos", "strict_date_optional_time");
@@ -167,19 +208,17 @@ public void testCustomTimeFormats() {
167208
assertSameDate("Nov 24 01:29:01 -0800", "MMM dd HH:mm:ss Z");
168209
}
169210

170-
// this test requires tests to run with -Djava.locale.providers=COMPAT in order to work
171-
// public void testCustomLocales() {
172-
//
173-
// // also ensure that locale based dates are the same
174-
// assertSameDate("Di., 05 Dez. 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
175-
// assertSameDate("Mi., 06 Dez. 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
176-
// assertSameDate("Do., 07 Dez. 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
177-
// assertSameDate("Fr., 08 Dez. 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
178-
//
179-
// DateTime dateTimeNow = DateTime.now(DateTimeZone.UTC);
180-
// ZonedDateTime javaTimeNow = Instant.ofEpochMilli(dateTimeNow.getMillis()).atZone(ZoneOffset.UTC);
181-
// assertSamePrinterOutput("E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"), javaTimeNow, dateTimeNow);
182-
// }
211+
public void testCustomLocales() {
212+
// also ensure that locale based dates are the same
213+
assertSameDate("Di, 05 Dez 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
214+
assertSameDate("Mi, 06 Dez 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
215+
assertSameDate("Do, 07 Dez 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
216+
assertSameDate("Fr, 08 Dez 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"));
217+
218+
DateTime dateTimeNow = DateTime.now(DateTimeZone.UTC);
219+
ZonedDateTime javaTimeNow = Instant.ofEpochMilli(dateTimeNow.getMillis()).atZone(ZoneOffset.UTC);
220+
assertSamePrinterOutput("E, d MMM yyyy HH:mm:ss Z", javaTimeNow, dateTimeNow, LocaleUtils.parse("de"));
221+
}
183222

184223
public void testDuellingFormatsValidParsing() {
185224
assertSameDate("1522332219", "epoch_second");
@@ -859,9 +898,28 @@ public void testParsingMissingTimezone() {
859898
}
860899

861900
private void assertSamePrinterOutput(String format, ZonedDateTime javaDate, DateTime jodaDate) {
901+
DateFormatter dateFormatter = DateFormatter.forPattern(format);
902+
JodaDateFormatter jodaDateFormatter = Joda.forPattern(format);
903+
904+
assertSamePrinterOutput(format, javaDate, jodaDate, dateFormatter, jodaDateFormatter);
905+
}
906+
907+
private void assertSamePrinterOutput(String format, ZonedDateTime javaDate, DateTime jodaDate, Locale locale) {
908+
DateFormatter dateFormatter = DateFormatter.forPattern(format).withLocale(locale);
909+
DateFormatter jodaDateFormatter = Joda.forPattern(format).withLocale(locale);
910+
911+
assertSamePrinterOutput(format, javaDate, jodaDate, dateFormatter, jodaDateFormatter);
912+
}
913+
914+
private void assertSamePrinterOutput(String format,
915+
ZonedDateTime javaDate,
916+
DateTime jodaDate,
917+
DateFormatter dateFormatter,
918+
DateFormatter jodaDateFormatter) {
919+
String javaTimeOut = dateFormatter.format(javaDate);
920+
String jodaTimeOut = jodaDateFormatter.formatJoda(jodaDate);
921+
862922
assertThat(jodaDate.getMillis(), is(javaDate.toInstant().toEpochMilli()));
863-
String javaTimeOut = DateFormatter.forPattern(format).format(javaDate);
864-
String jodaTimeOut = Joda.forPattern(format).formatJoda(jodaDate);
865923

866924
if (JavaVersion.current().getVersion().get(0) == 8 && javaTimeOut.endsWith(".0")
867925
&& (format.equals("epoch_second") || format.equals("epoch_millis"))) {
@@ -880,6 +938,12 @@ private void assertSameDate(String input, String format) {
880938
assertSameDate(input, format, jodaFormatter, javaFormatter);
881939
}
882940

941+
private void assertSameDate(String input, String format, Locale locale) {
942+
DateFormatter jodaFormatter = Joda.forPattern(format).withLocale(locale);
943+
DateFormatter javaFormatter = DateFormatter.forPattern(format).withLocale(locale);
944+
assertSameDate(input, format, jodaFormatter, javaFormatter);
945+
}
946+
883947
private void assertSameDate(String input, String format, DateFormatter jodaFormatter, DateFormatter javaFormatter) {
884948
DateTime jodaDateTime = jodaFormatter.parseJoda(input);
885949

0 commit comments

Comments
 (0)