From 884b5063a4ef1ed2e4c53e1c4fe9c13e7c9ddadb Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 11 Feb 2019 17:10:33 +0300 Subject: [PATCH] Create ISO8601 joda compatible java time formatter (#38434) The existing formatter being used was not on par with the joda formatter as it was missing the ability to parse a comma as a separator between seconds and milliseconds. While a real iso8601 would be much more complex, this might be sufficient for some more use-cases. The ingest date formatter now also uses the iso8601 formatter by default. Closes #38345 --- .../ingest/common/DateFormat.java | 2 +- .../common/time/DateFormatters.java | 41 ++++++++++++++++++- .../joda/JavaJodaTimeDuellingTests.java | 18 ++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateFormat.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateFormat.java index 7301ad8a9e9..ae3bc6b5d78 100644 --- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateFormat.java +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateFormat.java @@ -45,7 +45,7 @@ enum DateFormat { Iso8601 { @Override Function getFunction(String format, ZoneId timezone, Locale locale) { - return (date) -> DateFormatters.from(DateFormatter.forPattern("strict_date_time").parse(date)).withZoneSameInstant(timezone); + return (date) -> DateFormatters.from(DateFormatter.forPattern("iso8601").parse(date)).withZoneSameInstant(timezone); } }, Unix { diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java index 1cbaaeb80b8..957f632973d 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java @@ -175,6 +175,43 @@ public class DateFormatters { private static final DateFormatter STRICT_DATE_OPTIONAL_TIME_NANOS = new JavaDateFormatter("strict_date_optional_time_nanos", STRICT_DATE_OPTIONAL_TIME_PRINTER_NANOS, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS); + /** + * Returns a ISO 8601 compatible date time formatter and parser. + * This is not fully compatible to the existing spec, which would require far more edge cases, but merely compatible with the + * existing joda time ISO data formater + */ + private static final DateFormatter ISO_8601 = new JavaDateFormatter("iso8601", STRICT_DATE_OPTIONAL_TIME_PRINTER, + new DateTimeFormatterBuilder() + .append(STRICT_YEAR_MONTH_DAY_FORMATTER) + .optionalStart() + .appendLiteral('T') + .optionalStart() + .appendValue(HOUR_OF_DAY, 2, 2, SignStyle.NOT_NEGATIVE) + .optionalStart() + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2, 2, SignStyle.NOT_NEGATIVE) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2, 2, SignStyle.NOT_NEGATIVE) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 1, 9, true) + .optionalEnd() + .optionalStart() + .appendLiteral(",") + .appendFraction(NANO_OF_SECOND, 1, 9, false) + .optionalEnd() + .optionalEnd() + .optionalStart() + .appendZoneOrOffsetId() + .optionalEnd() + .optionalStart() + .append(TIME_ZONE_FORMATTER_NO_COLON) + .optionalEnd() + .optionalEnd() + .optionalEnd() + .optionalEnd() + .toFormatter(Locale.ROOT)); + ///////////////////////////////////////// // // BEGIN basic time formatters @@ -1363,7 +1400,9 @@ public class DateFormatters { throw new IllegalArgumentException("No date pattern provided"); } - if ("basicDate".equals(input) || "basic_date".equals(input)) { + if ("iso8601".equals(input)) { + return ISO_8601; + } else if ("basicDate".equals(input) || "basic_date".equals(input)) { return BASIC_DATE; } else if ("basicDateTime".equals(input) || "basic_date_time".equals(input)) { return BASIC_DATE_TIME; diff --git a/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java b/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java index cd92061ae25..b5fcf1c7f30 100644 --- a/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java +++ b/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java @@ -25,6 +25,7 @@ import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.test.ESTestCase; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.joda.time.format.ISODateTimeFormat; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -665,6 +666,23 @@ public class JavaJodaTimeDuellingTests extends ESTestCase { } } + // the iso 8601 parser is available via Joda.forPattern(), so we have to test this slightly differently + public void testIso8601Parsers() { + String format = "iso8601"; + org.joda.time.format.DateTimeFormatter isoFormatter = ISODateTimeFormat.dateTimeParser().withZone(DateTimeZone.UTC); + JodaDateFormatter jodaFormatter = new JodaDateFormatter(format, isoFormatter, isoFormatter); + DateFormatter javaFormatter = DateFormatter.forPattern(format); + + assertSameDate("2018-10-10T", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11:12", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11:12.123", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11:12.123Z", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11:12,123", format, jodaFormatter, javaFormatter); + assertSameDate("2018-10-10T10:11:12,123Z", format, jodaFormatter, javaFormatter); + } + private void assertSamePrinterOutput(String format, ZonedDateTime javaDate, DateTime jodaDate) { assertThat(jodaDate.getMillis(), is(javaDate.toInstant().toEpochMilli())); String javaTimeOut = DateFormatter.forPattern(format).format(javaDate);