From faa3c1624136ba075c737bce312f570564b9ba35 Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Fri, 14 Sep 2018 13:55:16 +0200 Subject: [PATCH] Core: Add DateFormatter interface for java time parsing (#33467) The existing approach used date formatters when a format based string like `date_time||epoch_millis` was used, instead of the custom code. In order to properly solve this, a new interface called `DateFormatter` has been added, which now can be implemented for custom formatters. Currently there are two implementations, one using java time and one doing the epoch_millis formatter, which simply parses a number and then converts it to a date in UTC timezone. The DateFormatter interface now also has a method to retrieve the name of the formatter pattern, which is needed for mapping changes anyway. The existing `CompoundDateTimeFormatter` class has been removed, the name was not really nice anyway. One more minor change is the fact, that the new java time using FormatDateFormatter does not try to parse the date with its printer implementation first (which might be a strict one and fail), but a printer can now be specified in addition. This saves one potential failure/exception when parsing less strict dates. If only a printer is specified, the printer will also be used as a parser. --- .../cluster/metadata/IndexGraveyard.java | 5 +- .../cluster/routing/UnassignedInfo.java | 5 +- .../java/org/elasticsearch/common/Table.java | 4 +- .../common/time/DateFormatter.java | 133 +++++++ .../common/time/DateFormatters.java | 341 +++++++++--------- .../common/time/DateMathParser.java | 8 +- .../common/time/EpochMillisDateFormatter.java | 73 ++++ ...eFormatter.java => JavaDateFormatter.java} | 77 ++-- .../XContentElasticsearchExtension.java | 8 +- .../elasticsearch/monitor/jvm/HotThreads.java | 4 +- .../rest/action/cat/RestSnapshotAction.java | 4 +- .../rest/action/cat/RestTasksAction.java | 4 +- .../elasticsearch/snapshots/SnapshotInfo.java | 4 +- .../joda/JavaJodaTimeDuellingTests.java | 6 +- .../common/time/DateFormattersTests.java | 19 +- .../common/time/DateMathParserTests.java | 6 +- .../bucket/histogram/DateHistogramTests.java | 4 +- 17 files changed, 456 insertions(+), 249 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/common/time/DateFormatter.java create mode 100644 server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java rename server/src/main/java/org/elasticsearch/common/time/{CompoundDateTimeFormatter.java => JavaDateFormatter.java} (59%) diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexGraveyard.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexGraveyard.java index 8d0ad8efb7f..7a43ce31d33 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexGraveyard.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexGraveyard.java @@ -28,7 +28,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ContextParser; import org.elasticsearch.common.xcontent.ObjectParser; @@ -368,8 +368,7 @@ public final class IndexGraveyard implements MetaData.Custom { TOMBSTONE_PARSER.declareString((b, s) -> {}, new ParseField(DELETE_DATE_KEY)); } - static final CompoundDateTimeFormatter FORMATTER = - DateFormatters.forPattern("strict_date_optional_time").withZone(ZoneOffset.UTC); + static final DateFormatter FORMATTER = DateFormatters.forPattern("strict_date_optional_time").withZone(ZoneOffset.UTC); static ContextParser getParser() { return (parser, context) -> TOMBSTONE_PARSER.apply(parser, null).build(); diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java b/server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java index ad715500a9e..21885d1788c 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java @@ -31,7 +31,7 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContentFragment; @@ -48,8 +48,7 @@ import java.util.Objects; */ public final class UnassignedInfo implements ToXContentFragment, Writeable { - public static final CompoundDateTimeFormatter DATE_TIME_FORMATTER = - DateFormatters.forPattern("dateOptionalTime").withZone(ZoneOffset.UTC); + public static final DateFormatter DATE_TIME_FORMATTER = DateFormatters.forPattern("dateOptionalTime").withZone(ZoneOffset.UTC); public static final Setting INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING = Setting.positiveTimeSetting("index.unassigned.node_left.delayed_timeout", TimeValue.timeValueMinutes(1), Property.Dynamic, diff --git a/server/src/main/java/org/elasticsearch/common/Table.java b/server/src/main/java/org/elasticsearch/common/Table.java index 13d13066e16..a41fd267329 100644 --- a/server/src/main/java/org/elasticsearch/common/Table.java +++ b/server/src/main/java/org/elasticsearch/common/Table.java @@ -19,7 +19,7 @@ package org.elasticsearch.common; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import java.time.Instant; @@ -85,7 +85,7 @@ public class Table { return this; } - private static final CompoundDateTimeFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); + private static final DateFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); public Table startRow() { if (headers.isEmpty()) { diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java new file mode 100644 index 00000000000..d16662b23b9 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.time; + +import java.time.ZoneId; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public interface DateFormatter { + + /** + * Try to parse input to a java time TemporalAccessor + * @param input An arbitrary string resembling the string representation of a date or time + * @throws DateTimeParseException If parsing fails, this exception will be thrown. + * Note that it can contained suppressed exceptions when several formatters failed parse this value + * @return The java time object containing the parsed input + */ + TemporalAccessor parse(String input); + + /** + * Create a copy of this formatter that is configured to parse dates in the specified time zone + * + * @param zoneId The time zone to act on + * @return A copy of the date formatter this has been called on + */ + DateFormatter withZone(ZoneId zoneId); + + /** + * Print the supplied java time accessor in a string based representation according to this formatter + * + * @param accessor The temporal accessor used to format + * @return The string result for the formatting + */ + String format(TemporalAccessor accessor); + + /** + * A name based format for this formatter. Can be one of the registered formatters like epoch_millis or + * a configured format like HH:mm:ss + * + * @return The name of this formatter + */ + String pattern(); + + /** + * Configure a formatter using default fields for a TemporalAccessor that should be used in case + * the supplied date is not having all of those fields + * + * @param fields A Map<TemporalField, Long> of fields to be used as fallbacks + * @return A new date formatter instance, that will use those fields during parsing + */ + DateFormatter parseDefaulting(Map fields); + + /** + * Merge several date formatters into a single one. Useful if you need to have several formatters with + * different formats act as one, for example when you specify a + * format like date_hour||epoch_millis + * + * @param formatters The list of date formatters to be merged together + * @return The new date formtter containing the specified date formatters + */ + static DateFormatter merge(DateFormatter ... formatters) { + return new MergedDateFormatter(formatters); + } + + class MergedDateFormatter implements DateFormatter { + + private final String format; + private final DateFormatter[] formatters; + + MergedDateFormatter(DateFormatter ... formatters) { + this.formatters = formatters; + this.format = Arrays.stream(formatters).map(DateFormatter::pattern).collect(Collectors.joining("||")); + } + + @Override + public TemporalAccessor parse(String input) { + DateTimeParseException failure = null; + for (DateFormatter formatter : formatters) { + try { + return formatter.parse(input); + } catch (DateTimeParseException e) { + if (failure == null) { + failure = e; + } else { + failure.addSuppressed(e); + } + } + } + throw failure; + } + + @Override + public DateFormatter withZone(ZoneId zoneId) { + return new MergedDateFormatter(Arrays.stream(formatters).map(f -> f.withZone(zoneId)).toArray(DateFormatter[]::new)); + } + + @Override + public String format(TemporalAccessor accessor) { + return formatters[0].format(accessor); + } + + @Override + public String pattern() { + return format; + } + + @Override + public DateFormatter parseDefaulting(Map fields) { + return new MergedDateFormatter(Arrays.stream(formatters).map(f -> f.parseDefaulting(fields)).toArray(DateFormatter[]::new)); + } + } +} 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 69b8cb0c85b..5f687651344 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java @@ -25,12 +25,10 @@ import java.time.DateTimeException; import java.time.DayOfWeek; import java.time.Instant; import java.time.LocalDate; -import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; import java.time.format.ResolverStyle; import java.time.format.SignStyle; import java.time.temporal.ChronoField; @@ -38,9 +36,6 @@ import java.time.temporal.IsoFields; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjusters; import java.time.temporal.WeekFields; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; import java.util.Locale; import static java.time.temporal.ChronoField.DAY_OF_MONTH; @@ -106,8 +101,9 @@ public class DateFormatters { /** * Returns a generic ISO datetime parser where the date is mandatory and the time is optional. */ - private static final CompoundDateTimeFormatter STRICT_DATE_OPTIONAL_TIME = - new CompoundDateTimeFormatter(STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_2); + private static final DateFormatter STRICT_DATE_OPTIONAL_TIME = + new JavaDateFormatter("strict_date_optional_time", STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, + STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_2); private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1 = new DateTimeFormatterBuilder() .append(STRICT_YEAR_MONTH_DAY_FORMATTER) @@ -140,8 +136,9 @@ public class DateFormatters { /** * Returns a generic ISO datetime parser where the date is mandatory and the time is optional with nanosecond resolution. */ - private static final CompoundDateTimeFormatter STRICT_DATE_OPTIONAL_TIME_NANOS = - new CompoundDateTimeFormatter(STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_2); + private static final DateFormatter STRICT_DATE_OPTIONAL_TIME_NANOS = new JavaDateFormatter("strict_date_optional_time_nanos", + STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1, + STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_2); ///////////////////////////////////////// // @@ -162,7 +159,8 @@ public class DateFormatters { * Returns a basic formatter for a two digit hour of day, two digit minute * of hour, two digit second of minute, and time zone offset (HHmmssZ). */ - private static final CompoundDateTimeFormatter BASIC_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_TIME_NO_MILLIS = new JavaDateFormatter("basic_time_no_millis", + new DateTimeFormatterBuilder().append(BASIC_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_TIME_NO_MILLIS_BASE).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -186,7 +184,7 @@ public class DateFormatters { * of hour, two digit second of minute, three digit millis, and time zone * offset (HHmmss.SSSZ). */ - private static final CompoundDateTimeFormatter BASIC_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_TIME = new JavaDateFormatter("basic_time", new DateTimeFormatterBuilder().append(BASIC_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_TIME_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_TIME_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) @@ -203,7 +201,7 @@ public class DateFormatters { * of hour, two digit second of minute, three digit millis, and time zone * offset prefixed by 'T' ('T'HHmmss.SSSZ). */ - private static final CompoundDateTimeFormatter BASIC_T_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_T_TIME = new JavaDateFormatter("basic_t_time", new DateTimeFormatterBuilder().append(BASIC_T_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_T_TIME_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_T_TIME_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) @@ -214,11 +212,11 @@ public class DateFormatters { * of hour, two digit second of minute, and time zone offset prefixed by 'T' * ('T'HHmmssZ). */ - private static final CompoundDateTimeFormatter BASIC_T_TIME_NO_MILLIS = new CompoundDateTimeFormatter( - new DateTimeFormatterBuilder().appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) - .appendZoneOrOffsetId().toFormatter(Locale.ROOT), - new DateTimeFormatterBuilder().appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) - .append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) + private static final DateFormatter BASIC_T_TIME_NO_MILLIS = new JavaDateFormatter("basic_t_time_no_millis", + new DateTimeFormatterBuilder().appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), + new DateTimeFormatterBuilder().appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), + new DateTimeFormatterBuilder().appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE).append(TIME_ZONE_FORMATTER_NO_COLON) + .toFormatter(Locale.ROOT) ); private static final DateTimeFormatter BASIC_YEAR_MONTH_DAY_FORMATTER = new DateTimeFormatterBuilder() @@ -241,7 +239,7 @@ public class DateFormatters { * Returns a basic formatter that combines a basic date and time, separated * by a 'T' (yyyyMMdd'T'HHmmss.SSSZ). */ - private static final CompoundDateTimeFormatter BASIC_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_DATE_TIME = new JavaDateFormatter("basic_date_time", new DateTimeFormatterBuilder().append(BASIC_DATE_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_DATE_TIME_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_DATE_TIME_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) @@ -254,7 +252,9 @@ public class DateFormatters { * Returns a basic formatter that combines a basic date and time without millis, * separated by a 'T' (yyyyMMdd'T'HHmmssZ). */ - private static final CompoundDateTimeFormatter BASIC_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_DATE_TIME_NO_MILLIS = new JavaDateFormatter("basic_t_time_no_millis", + new DateTimeFormatterBuilder().append(BASIC_DATE_T).append(BASIC_TIME_NO_MILLIS_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_DATE_T).append(BASIC_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_DATE_T).append(BASIC_TIME_NO_MILLIS_BASE) @@ -265,14 +265,14 @@ public class DateFormatters { * Returns a formatter for a full ordinal date, using a four * digit year and three digit dayOfYear (yyyyDDD). */ - private static final CompoundDateTimeFormatter BASIC_ORDINAL_DATE = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_ORDINAL_DATE = new JavaDateFormatter("basic_ordinal_date", DateTimeFormatter.ofPattern("yyyyDDD", Locale.ROOT)); /* * Returns a formatter for a full ordinal date and time, using a four * digit year and three digit dayOfYear (yyyyDDD'T'HHmmss.SSSZ). */ - private static final CompoundDateTimeFormatter BASIC_ORDINAL_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_ORDINAL_DATE_TIME = new JavaDateFormatter("basic_ordinal_date_time", new DateTimeFormatterBuilder().appendPattern("yyyyDDD").append(BASIC_T_TIME_PRINTER) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendPattern("yyyyDDD").append(BASIC_T_TIME_FORMATTER) @@ -284,7 +284,9 @@ public class DateFormatters { * Returns a formatter for a full ordinal date and time without millis, * using a four digit year and three digit dayOfYear (yyyyDDD'T'HHmmssZ). */ - private static final CompoundDateTimeFormatter BASIC_ORDINAL_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_ORDINAL_DATE_TIME_NO_MILLIS = new JavaDateFormatter("basic_ordinal_date_time_no_millis", + new DateTimeFormatterBuilder().appendPattern("yyyyDDD").appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendPattern("yyyyDDD").appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendPattern("yyyyDDD").appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) @@ -329,14 +331,14 @@ public class DateFormatters { * Returns a basic formatter for a full date as four digit weekyear, two * digit week of weekyear, and one digit day of week (xxxx'W'wwe). */ - private static final CompoundDateTimeFormatter STRICT_BASIC_WEEK_DATE = - new CompoundDateTimeFormatter(STRICT_BASIC_WEEK_DATE_PRINTER, STRICT_BASIC_WEEK_DATE_FORMATTER); + private static final DateFormatter STRICT_BASIC_WEEK_DATE = + new JavaDateFormatter("strict_basic_week_date", STRICT_BASIC_WEEK_DATE_PRINTER, STRICT_BASIC_WEEK_DATE_FORMATTER); /* * Returns a basic formatter that combines a basic weekyear date and time * without millis, separated by a 'T' (xxxx'W'wwe'T'HHmmssX). */ - private static final CompoundDateTimeFormatter STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS = new JavaDateFormatter("strict_basic_week_date_no_millis", new DateTimeFormatterBuilder() .append(STRICT_BASIC_WEEK_DATE_PRINTER).append(DateTimeFormatter.ofPattern("'T'HHmmssX", Locale.ROOT)) .toFormatter(Locale.ROOT), @@ -349,7 +351,7 @@ public class DateFormatters { * Returns a basic formatter that combines a basic weekyear date and time, * separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSX). */ - private static final CompoundDateTimeFormatter STRICT_BASIC_WEEK_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_BASIC_WEEK_DATE_TIME = new JavaDateFormatter("strict_basic_week_date_time", new DateTimeFormatterBuilder() .append(STRICT_BASIC_WEEK_DATE_PRINTER) .append(DateTimeFormatter.ofPattern("'T'HHmmss.SSSX", Locale.ROOT)) @@ -363,30 +365,32 @@ public class DateFormatters { /* * An ISO date formatter that formats or parses a date without an offset, such as '2011-12-03'. */ - private static final CompoundDateTimeFormatter STRICT_DATE = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE = new JavaDateFormatter("strict_date", DateTimeFormatter.ISO_LOCAL_DATE.withResolverStyle(ResolverStyle.LENIENT)); /* * A date formatter that formats or parses a date plus an hour without an offset, such as '2011-12-03T01'. */ - private static final CompoundDateTimeFormatter STRICT_DATE_HOUR = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE_HOUR = new JavaDateFormatter("strict_date_hour", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH", Locale.ROOT)); /* * A date formatter that formats or parses a date plus an hour/minute without an offset, such as '2011-12-03T01:10'. */ - private static final CompoundDateTimeFormatter STRICT_DATE_HOUR_MINUTE = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE_HOUR_MINUTE = new JavaDateFormatter("strict_date_hour_minute", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm", Locale.ROOT)); /* * A strict date formatter that formats or parses a date without an offset, such as '2011-12-03'. */ - private static final CompoundDateTimeFormatter STRICT_YEAR_MONTH_DAY = new CompoundDateTimeFormatter(STRICT_YEAR_MONTH_DAY_FORMATTER); + private static final DateFormatter STRICT_YEAR_MONTH_DAY = + new JavaDateFormatter("strict_year_month_day", STRICT_YEAR_MONTH_DAY_FORMATTER); /* * A strict formatter that formats or parses a year and a month, such as '2011-12'. */ - private static final CompoundDateTimeFormatter STRICT_YEAR_MONTH = new CompoundDateTimeFormatter(new DateTimeFormatterBuilder() + private static final DateFormatter STRICT_YEAR_MONTH = new JavaDateFormatter("strict_year_month", + new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .appendLiteral("-") .appendValue(MONTH_OF_YEAR, 2, 2, SignStyle.NOT_NEGATIVE) @@ -395,15 +399,15 @@ public class DateFormatters { /* * A strict formatter that formats or parses a year, such as '2011'. */ - private static final CompoundDateTimeFormatter STRICT_YEAR = new CompoundDateTimeFormatter(new DateTimeFormatterBuilder() + private static final DateFormatter STRICT_YEAR = new JavaDateFormatter("strict_year", new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .toFormatter(Locale.ROOT)); /* * A strict formatter that formats or parses a hour, minute and second, such as '09:43:25'. */ - private static final CompoundDateTimeFormatter STRICT_HOUR_MINUTE_SECOND = - new CompoundDateTimeFormatter(STRICT_HOUR_MINUTE_SECOND_FORMATTER); + private static final DateFormatter STRICT_HOUR_MINUTE_SECOND = + new JavaDateFormatter("strict_hour_minute_second", STRICT_HOUR_MINUTE_SECOND_FORMATTER); private static final DateTimeFormatter STRICT_DATE_FORMATTER = new DateTimeFormatterBuilder() .append(STRICT_YEAR_MONTH_DAY_FORMATTER) @@ -418,7 +422,8 @@ public class DateFormatters { * Returns a formatter that combines a full date and time, separated by a 'T' * (yyyy-MM-dd'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter STRICT_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE_TIME = new JavaDateFormatter("strict_date_time", + new DateTimeFormatterBuilder().append(STRICT_DATE_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_DATE_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_DATE_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -435,7 +440,7 @@ public class DateFormatters { * Returns a formatter for a full ordinal date and time without millis, * using a four digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter STRICT_ORDINAL_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_ORDINAL_DATE_TIME_NO_MILLIS = new JavaDateFormatter("strict_ordinal_date_time_no_millis", new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_NO_MILLIS_BASE) @@ -452,7 +457,9 @@ public class DateFormatters { * Returns a formatter that combines a full date and time without millis, * separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter STRICT_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE_TIME_NO_MILLIS = new JavaDateFormatter("strict_date_time_no_millis", + new DateTimeFormatterBuilder().append(STRICT_DATE_TIME_NO_MILLIS_FORMATTER) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_DATE_TIME_NO_MILLIS_FORMATTER) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_DATE_TIME_NO_MILLIS_FORMATTER) @@ -478,17 +485,19 @@ public class DateFormatters { * NOTE: this is not a strict formatter to retain the joda time based behaviour, * even though it's named like this */ - private static final CompoundDateTimeFormatter STRICT_HOUR_MINUTE_SECOND_MILLIS = - new CompoundDateTimeFormatter(STRICT_HOUR_MINUTE_SECOND_MILLIS_PRINTER, STRICT_HOUR_MINUTE_SECOND_MILLIS_FORMATTER); + private static final DateFormatter STRICT_HOUR_MINUTE_SECOND_MILLIS = + new JavaDateFormatter("strict_hour_minute_second_millis", + STRICT_HOUR_MINUTE_SECOND_MILLIS_PRINTER, STRICT_HOUR_MINUTE_SECOND_MILLIS_FORMATTER); - private static final CompoundDateTimeFormatter STRICT_HOUR_MINUTE_SECOND_FRACTION = STRICT_HOUR_MINUTE_SECOND_MILLIS; + private static final DateFormatter STRICT_HOUR_MINUTE_SECOND_FRACTION = STRICT_HOUR_MINUTE_SECOND_MILLIS; /* * Returns a formatter that combines a full date, two digit hour of day, * two digit minute of hour, two digit second of minute, and three digit * fraction of second (yyyy-MM-dd'T'HH:mm:ss.SSS). */ - private static final CompoundDateTimeFormatter STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION = new JavaDateFormatter( + "strict_date_hour_minute_second_fraction", new DateTimeFormatterBuilder() .append(STRICT_YEAR_MONTH_DAY_FORMATTER) .appendLiteral("T") @@ -503,20 +512,20 @@ public class DateFormatters { .toFormatter(Locale.ROOT) ); - private static final CompoundDateTimeFormatter STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS = STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION; + private static final DateFormatter STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS = STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION; /* * Returns a formatter for a two digit hour of day. (HH) */ - private static final CompoundDateTimeFormatter STRICT_HOUR = - new CompoundDateTimeFormatter(DateTimeFormatter.ofPattern("HH", Locale.ROOT)); + private static final DateFormatter STRICT_HOUR = + new JavaDateFormatter("strict_hour", DateTimeFormatter.ofPattern("HH", Locale.ROOT)); /* * Returns a formatter for a two digit hour of day and two digit minute of * hour. (HH:mm) */ - private static final CompoundDateTimeFormatter STRICT_HOUR_MINUTE = - new CompoundDateTimeFormatter(DateTimeFormatter.ofPattern("HH:mm", Locale.ROOT)); + private static final DateFormatter STRICT_HOUR_MINUTE = + new JavaDateFormatter("strict_hour_minute", DateTimeFormatter.ofPattern("HH:mm", Locale.ROOT)); private static final DateTimeFormatter STRICT_ORDINAL_DATE_TIME_FORMATTER_BASE = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) @@ -535,7 +544,7 @@ public class DateFormatters { * Returns a formatter for a full ordinal date and time, using a four * digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter STRICT_ORDINAL_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_ORDINAL_DATE_TIME = new JavaDateFormatter("strict_ordinal_date_time", new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_FORMATTER_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_FORMATTER_BASE) @@ -566,7 +575,7 @@ public class DateFormatters { * hour, two digit second of minute, three digit fraction of second, and * time zone offset (HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter STRICT_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_TIME = new JavaDateFormatter("strict_time", new DateTimeFormatterBuilder().append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_TIME_FORMATTER_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_TIME_FORMATTER_BASE).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) @@ -577,7 +586,7 @@ public class DateFormatters { * hour, two digit second of minute, three digit fraction of second, and * time zone offset prefixed by 'T' ('T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter STRICT_T_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_T_TIME = new JavaDateFormatter("strict_t_time", new DateTimeFormatterBuilder().appendLiteral('T').append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendLiteral('T').append(STRICT_TIME_FORMATTER_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), @@ -597,7 +606,8 @@ public class DateFormatters { * Returns a formatter for a two digit hour of day, two digit minute of * hour, two digit second of minute, and time zone offset (HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter STRICT_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_TIME_NO_MILLIS = new JavaDateFormatter("strict_time_no_millis", + new DateTimeFormatterBuilder().append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(STRICT_TIME_NO_MILLIS_BASE).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -607,7 +617,9 @@ public class DateFormatters { * hour, two digit second of minute, and time zone offset prefixed * by 'T' ('T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter STRICT_T_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_T_TIME_NO_MILLIS = new JavaDateFormatter("strict_t_time_no_millis", + new DateTimeFormatterBuilder().appendLiteral("T").append(STRICT_TIME_NO_MILLIS_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendLiteral("T").append(STRICT_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendLiteral("T").append(STRICT_TIME_NO_MILLIS_BASE) @@ -632,13 +644,15 @@ public class DateFormatters { * Returns a formatter for a full date as four digit weekyear, two digit * week of weekyear, and one digit day of week (xxxx-'W'ww-e). */ - private static final CompoundDateTimeFormatter STRICT_WEEK_DATE = new CompoundDateTimeFormatter(ISO_WEEK_DATE); + private static final DateFormatter STRICT_WEEK_DATE = new JavaDateFormatter("strict_week_date", ISO_WEEK_DATE); /* * Returns a formatter that combines a full weekyear date and time without millis, * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter STRICT_WEEK_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_WEEK_DATE_TIME_NO_MILLIS = new JavaDateFormatter("strict_week_date_time_no_millis", + new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T) + .append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T) .append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T) @@ -649,7 +663,7 @@ public class DateFormatters { * Returns a formatter that combines a full weekyear date and time, * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter STRICT_WEEK_DATE_TIME = new CompoundDateTimeFormatter( + private static final DateFormatter STRICT_WEEK_DATE_TIME = new JavaDateFormatter("strict_week_date_time", new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T).append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T).append(STRICT_TIME_FORMATTER_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), @@ -660,7 +674,7 @@ public class DateFormatters { /* * Returns a formatter for a four digit weekyear */ - private static final CompoundDateTimeFormatter STRICT_WEEKYEAR = new CompoundDateTimeFormatter(new DateTimeFormatterBuilder() + private static final DateFormatter STRICT_WEEKYEAR = new JavaDateFormatter("strict_weekyear", new DateTimeFormatterBuilder() .appendValue(WeekFields.ISO.weekBasedYear(), 4, 10, SignStyle.EXCEEDS_PAD) .toFormatter(Locale.ROOT)); @@ -674,13 +688,15 @@ public class DateFormatters { * Returns a formatter for a four digit weekyear and two digit week of * weekyear. (xxxx-'W'ww) */ - private static final CompoundDateTimeFormatter STRICT_WEEKYEAR_WEEK = new CompoundDateTimeFormatter(STRICT_WEEKYEAR_WEEK_FORMATTER); + private static final DateFormatter STRICT_WEEKYEAR_WEEK = + new JavaDateFormatter("strict_weekyear_week", STRICT_WEEKYEAR_WEEK_FORMATTER); /* * Returns a formatter for a four digit weekyear, two digit week of * weekyear, and one digit day of week. (xxxx-'W'ww-e) */ - private static final CompoundDateTimeFormatter STRICT_WEEKYEAR_WEEK_DAY = new CompoundDateTimeFormatter(new DateTimeFormatterBuilder() + private static final DateFormatter STRICT_WEEKYEAR_WEEK_DAY = new JavaDateFormatter("strict_weekyear_week_day", + new DateTimeFormatterBuilder() .append(STRICT_WEEKYEAR_WEEK_FORMATTER) .appendLiteral("-") .appendValue(WeekFields.ISO.dayOfWeek()) @@ -691,14 +707,14 @@ public class DateFormatters { * two digit minute of hour, and two digit second of * minute. (yyyy-MM-dd'T'HH:mm:ss) */ - private static final CompoundDateTimeFormatter STRICT_DATE_HOUR_MINUTE_SECOND = - new CompoundDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT)); + private static final DateFormatter STRICT_DATE_HOUR_MINUTE_SECOND = new JavaDateFormatter("strict_date_hour_minute_second", + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT)); /* * A basic formatter for a full date as four digit year, two digit * month of year, and two digit day of month (yyyyMMdd). */ - private static final CompoundDateTimeFormatter BASIC_DATE = new CompoundDateTimeFormatter( + private static final DateFormatter BASIC_DATE = new JavaDateFormatter("basic_date", new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NORMAL) .appendValue(MONTH_OF_YEAR, 2, 2, SignStyle.NOT_NEGATIVE) @@ -723,7 +739,7 @@ public class DateFormatters { * Returns a formatter for a full ordinal date, using a four * digit year and three digit dayOfYear (yyyy-DDD). */ - private static final CompoundDateTimeFormatter STRICT_ORDINAL_DATE = new CompoundDateTimeFormatter(STRICT_ORDINAL_DATE_FORMATTER); + private static final DateFormatter STRICT_ORDINAL_DATE = new JavaDateFormatter("strict_ordinal_date", STRICT_ORDINAL_DATE_FORMATTER); ///////////////////////////////////////// // @@ -759,7 +775,8 @@ public class DateFormatters { * a date formatter with optional time, being very lenient, format is * yyyy-MM-dd'T'HH:mm:ss.SSSZ */ - private static final CompoundDateTimeFormatter DATE_OPTIONAL_TIME = new CompoundDateTimeFormatter(STRICT_DATE_OPTIONAL_TIME.printer, + private static final DateFormatter DATE_OPTIONAL_TIME = new JavaDateFormatter("date_optional_time", + STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, new DateTimeFormatterBuilder() .append(DATE_FORMATTER) .optionalStart() @@ -834,8 +851,8 @@ public class DateFormatters { * Returns a formatter for a full ordinal date, using a four * digit year and three digit dayOfYear (yyyy-DDD). */ - private static final CompoundDateTimeFormatter ORDINAL_DATE = - new CompoundDateTimeFormatter(ORDINAL_DATE_PRINTER, ORDINAL_DATE_FORMATTER); + private static final DateFormatter ORDINAL_DATE = + new JavaDateFormatter("ordinal_date", ORDINAL_DATE_PRINTER, ORDINAL_DATE_FORMATTER); private static final DateTimeFormatter TIME_NO_MILLIS_FORMATTER = new DateTimeFormatterBuilder() .appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE) @@ -864,70 +881,32 @@ public class DateFormatters { /* * Returns a formatter for a four digit weekyear. (YYYY) */ - private static final CompoundDateTimeFormatter WEEK_YEAR = new CompoundDateTimeFormatter( + private static final DateFormatter WEEK_YEAR = new JavaDateFormatter("week_year", new DateTimeFormatterBuilder().appendValue(WeekFields.ISO.weekBasedYear()).toFormatter(Locale.ROOT)); /* * Returns a formatter for a four digit weekyear. (uuuu) */ - private static final CompoundDateTimeFormatter YEAR = new CompoundDateTimeFormatter( + private static final DateFormatter YEAR = new JavaDateFormatter("year", new DateTimeFormatterBuilder().appendValue(ChronoField.YEAR).toFormatter(Locale.ROOT)); /* * Returns a formatter for parsing the seconds since the epoch */ - private static final CompoundDateTimeFormatter EPOCH_SECOND = new CompoundDateTimeFormatter( + private static final DateFormatter EPOCH_SECOND = new JavaDateFormatter("epoch_second", new DateTimeFormatterBuilder().appendValue(ChronoField.INSTANT_SECONDS).toFormatter(Locale.ROOT)); /* - * Returns a formatter for parsing the milliseconds since the epoch - * This one needs a custom implementation, because the standard date formatter can not parse negative values - * or anything +- 999 milliseconds around the epoch - * - * This implementation just resorts to parsing the input directly to an Instant by trying to parse a number. + * Parses the milliseconds since/before the epoch */ - private static final DateTimeFormatter EPOCH_MILLIS_FORMATTER = new DateTimeFormatterBuilder() - .appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NEVER) - .appendValue(ChronoField.MILLI_OF_SECOND, 3) - .toFormatter(Locale.ROOT); - - private static final class EpochDateTimeFormatter extends CompoundDateTimeFormatter { - - private EpochDateTimeFormatter() { - super(EPOCH_MILLIS_FORMATTER); - } - - private EpochDateTimeFormatter(ZoneId zoneId) { - super(EPOCH_MILLIS_FORMATTER.withZone(zoneId)); - } - - @Override - public TemporalAccessor parse(String input) { - try { - return Instant.ofEpochMilli(Long.valueOf(input)).atZone(ZoneOffset.UTC); - } catch (NumberFormatException e) { - throw new DateTimeParseException("invalid number", input, 0, e); - } - } - - @Override - public CompoundDateTimeFormatter withZone(ZoneId zoneId) { - return new EpochDateTimeFormatter(zoneId); - } - - @Override - public String format(TemporalAccessor accessor) { - return String.valueOf(Instant.from(accessor).toEpochMilli()); - } - } - - private static final CompoundDateTimeFormatter EPOCH_MILLIS = new EpochDateTimeFormatter(); + private static final DateFormatter EPOCH_MILLIS = EpochMillisDateFormatter.INSTANCE; /* * Returns a formatter that combines a full date and two digit hour of * day. (yyyy-MM-dd'T'HH) */ - private static final CompoundDateTimeFormatter DATE_HOUR = new CompoundDateTimeFormatter(STRICT_DATE_HOUR.printer, + private static final DateFormatter DATE_HOUR = new JavaDateFormatter("date_hour", + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH", Locale.ROOT), new DateTimeFormatterBuilder() .append(DATE_FORMATTER) .appendLiteral("T") @@ -940,8 +919,8 @@ public class DateFormatters { * fraction of second (yyyy-MM-dd'T'HH:mm:ss.SSS). Parsing will parse up * to 3 fractional second digits. */ - private static final CompoundDateTimeFormatter DATE_HOUR_MINUTE_SECOND_MILLIS = - new CompoundDateTimeFormatter( + private static final DateFormatter DATE_HOUR_MINUTE_SECOND_MILLIS = + new JavaDateFormatter("date_hour_minute_second_millis", new DateTimeFormatterBuilder() .append(STRICT_YEAR_MONTH_DAY_FORMATTER) .appendLiteral("T") @@ -953,13 +932,14 @@ public class DateFormatters { .append(HOUR_MINUTE_SECOND_MILLIS_FORMATTER) .toFormatter(Locale.ROOT)); - private static final CompoundDateTimeFormatter DATE_HOUR_MINUTE_SECOND_FRACTION = DATE_HOUR_MINUTE_SECOND_MILLIS; + private static final DateFormatter DATE_HOUR_MINUTE_SECOND_FRACTION = DATE_HOUR_MINUTE_SECOND_MILLIS; /* * Returns a formatter that combines a full date, two digit hour of day, * and two digit minute of hour. (yyyy-MM-dd'T'HH:mm) */ - private static final CompoundDateTimeFormatter DATE_HOUR_MINUTE = new CompoundDateTimeFormatter(STRICT_DATE_HOUR_MINUTE.printer, + private static final DateFormatter DATE_HOUR_MINUTE = new JavaDateFormatter("date_hour_minute", + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm", Locale.ROOT), new DateTimeFormatterBuilder() .append(DATE_FORMATTER) .appendLiteral("T") @@ -971,8 +951,8 @@ public class DateFormatters { * two digit minute of hour, and two digit second of * minute. (yyyy-MM-dd'T'HH:mm:ss) */ - private static final CompoundDateTimeFormatter DATE_HOUR_MINUTE_SECOND = new CompoundDateTimeFormatter( - STRICT_DATE_HOUR_MINUTE_SECOND.printer, + private static final DateFormatter DATE_HOUR_MINUTE_SECOND = new JavaDateFormatter("date_hour_minute_second", + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT), new DateTimeFormatterBuilder() .append(DATE_FORMATTER) .appendLiteral("T") @@ -994,8 +974,8 @@ public class DateFormatters { * Returns a formatter that combines a full date and time, separated by a 'T' * (yyyy-MM-dd'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter DATE_TIME = new CompoundDateTimeFormatter( - STRICT_DATE_TIME.printer, + private static final DateFormatter DATE_TIME = new JavaDateFormatter("date_time", + STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, new DateTimeFormatterBuilder().append(DATE_TIME_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(DATE_TIME_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -1004,20 +984,22 @@ public class DateFormatters { * Returns a basic formatter for a full date as four digit weekyear, two * digit week of weekyear, and one digit day of week (YYYY'W'wwe). */ - private static final CompoundDateTimeFormatter BASIC_WEEK_DATE = - new CompoundDateTimeFormatter(STRICT_BASIC_WEEK_DATE.printer, BASIC_WEEK_DATE_FORMATTER); + private static final DateFormatter BASIC_WEEK_DATE = + new JavaDateFormatter("basic_week_date", STRICT_BASIC_WEEK_DATE_PRINTER, BASIC_WEEK_DATE_FORMATTER); /* * Returns a formatter for a full date as four digit year, two digit month * of year, and two digit day of month (yyyy-MM-dd). */ - private static final CompoundDateTimeFormatter DATE = new CompoundDateTimeFormatter(STRICT_DATE.printer, DATE_FORMATTER); + private static final DateFormatter DATE = new JavaDateFormatter("date", + DateTimeFormatter.ISO_LOCAL_DATE.withResolverStyle(ResolverStyle.LENIENT), + DATE_FORMATTER); // only the formatter, nothing optional here private static final DateTimeFormatter DATE_TIME_NO_MILLIS_PRINTER = new DateTimeFormatterBuilder() - .append(STRICT_DATE.printer) + .append(DateTimeFormatter.ISO_LOCAL_DATE.withResolverStyle(ResolverStyle.LENIENT)) .appendLiteral('T') - .append(STRICT_HOUR_MINUTE.printer) + .appendPattern("HH:mm") .appendLiteral(':') .appendValue(SECOND_OF_MINUTE, 2, 2, SignStyle.NOT_NEGATIVE) .appendZoneId() @@ -1037,7 +1019,8 @@ public class DateFormatters { * Returns a formatter that combines a full date and time without millis, but with a timezone that can be optional * separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZ). */ - private static final CompoundDateTimeFormatter DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter(DATE_TIME_NO_MILLIS_PRINTER, + private static final DateFormatter DATE_TIME_NO_MILLIS = new JavaDateFormatter("date_time_no_millis", + DATE_TIME_NO_MILLIS_PRINTER, new DateTimeFormatterBuilder().append(DATE_TIME_PREFIX).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(DATE_TIME_PREFIX).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(DATE_TIME_PREFIX) @@ -1051,21 +1034,21 @@ public class DateFormatters { * hour, two digit second of minute, and three digit fraction of * second (HH:mm:ss.SSS). */ - private static final CompoundDateTimeFormatter HOUR_MINUTE_SECOND_MILLIS = - new CompoundDateTimeFormatter(STRICT_HOUR_MINUTE_SECOND_FRACTION.printer, HOUR_MINUTE_SECOND_MILLIS_FORMATTER); + private static final DateFormatter HOUR_MINUTE_SECOND_MILLIS = new JavaDateFormatter("hour_minute_second_millis", + STRICT_HOUR_MINUTE_SECOND_MILLIS_PRINTER, HOUR_MINUTE_SECOND_MILLIS_FORMATTER); /* * Returns a formatter for a two digit hour of day and two digit minute of * hour. (HH:mm) */ - private static final CompoundDateTimeFormatter HOUR_MINUTE = - new CompoundDateTimeFormatter(STRICT_HOUR_MINUTE.printer, HOUR_MINUTE_FORMATTER); + private static final DateFormatter HOUR_MINUTE = + new JavaDateFormatter("hour_minute", DateTimeFormatter.ofPattern("HH:mm", Locale.ROOT), HOUR_MINUTE_FORMATTER); /* * A strict formatter that formats or parses a hour, minute and second, such as '09:43:25'. */ - private static final CompoundDateTimeFormatter HOUR_MINUTE_SECOND = new CompoundDateTimeFormatter( - STRICT_HOUR_MINUTE_SECOND.printer, + private static final DateFormatter HOUR_MINUTE_SECOND = new JavaDateFormatter("hour_minute_second", + STRICT_HOUR_MINUTE_SECOND_FORMATTER, new DateTimeFormatterBuilder() .append(HOUR_MINUTE_FORMATTER) .appendLiteral(":") @@ -1076,8 +1059,8 @@ public class DateFormatters { /* * Returns a formatter for a two digit hour of day. (HH) */ - private static final CompoundDateTimeFormatter HOUR = new CompoundDateTimeFormatter( - STRICT_HOUR.printer, + private static final DateFormatter HOUR = new JavaDateFormatter("hour", + DateTimeFormatter.ofPattern("HH", Locale.ROOT), new DateTimeFormatterBuilder().appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).toFormatter(Locale.ROOT) ); @@ -1096,8 +1079,9 @@ public class DateFormatters { * Returns a formatter for a full ordinal date and time, using a four * digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter ORDINAL_DATE_TIME = new CompoundDateTimeFormatter( - STRICT_ORDINAL_DATE_TIME.printer, + private static final DateFormatter ORDINAL_DATE_TIME = new JavaDateFormatter("ordinal_date_time", + new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_FORMATTER_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ORDINAL_DATE_TIME_FORMATTER_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ORDINAL_DATE_TIME_FORMATTER_BASE) @@ -1114,8 +1098,9 @@ public class DateFormatters { * Returns a formatter for a full ordinal date and time without millis, * using a four digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter ORDINAL_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( - STRICT_ORDINAL_DATE_TIME_NO_MILLIS.printer, + private static final DateFormatter ORDINAL_DATE_TIME_NO_MILLIS = new JavaDateFormatter("ordinal_date_time_no_millis", + new DateTimeFormatterBuilder().append(STRICT_ORDINAL_DATE_TIME_NO_MILLIS_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ORDINAL_DATE_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(ORDINAL_DATE_TIME_NO_MILLIS_BASE) @@ -1126,8 +1111,8 @@ public class DateFormatters { * Returns a formatter that combines a full weekyear date and time, * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter WEEK_DATE_TIME = new CompoundDateTimeFormatter( - STRICT_WEEK_DATE_TIME.printer, + private static final DateFormatter WEEK_DATE_TIME = new JavaDateFormatter("week_date_time", + new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T).append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(WEEK_DATE_FORMATTER).appendLiteral("T").append(TIME_PREFIX) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(WEEK_DATE_FORMATTER).appendLiteral("T").append(TIME_PREFIX) @@ -1138,8 +1123,9 @@ public class DateFormatters { * Returns a formatter that combines a full weekyear date and time, * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter WEEK_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( - STRICT_WEEK_DATE_TIME_NO_MILLIS.printer, + private static final DateFormatter WEEK_DATE_TIME_NO_MILLIS = new JavaDateFormatter("week_date_time_no_millis", + new DateTimeFormatterBuilder().append(ISO_WEEK_DATE_T) + .append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(WEEK_DATE_FORMATTER).append(T_TIME_NO_MILLIS_FORMATTER) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(WEEK_DATE_FORMATTER).append(T_TIME_NO_MILLIS_FORMATTER) @@ -1150,8 +1136,11 @@ public class DateFormatters { * Returns a basic formatter that combines a basic weekyear date and time, * separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSX). */ - private static final CompoundDateTimeFormatter BASIC_WEEK_DATE_TIME = new CompoundDateTimeFormatter( - STRICT_BASIC_WEEK_DATE_TIME.printer, + private static final DateFormatter BASIC_WEEK_DATE_TIME = new JavaDateFormatter("basic_week_date_time", + new DateTimeFormatterBuilder() + .append(STRICT_BASIC_WEEK_DATE_PRINTER) + .append(DateTimeFormatter.ofPattern("'T'HHmmss.SSSX", Locale.ROOT)) + .toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_WEEK_DATE_FORMATTER).append(BASIC_T_TIME_FORMATTER) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_WEEK_DATE_FORMATTER).append(BASIC_T_TIME_FORMATTER) @@ -1162,8 +1151,10 @@ public class DateFormatters { * Returns a basic formatter that combines a basic weekyear date and time, * separated by a 'T' (xxxx'W'wwe'T'HHmmssX). */ - private static final CompoundDateTimeFormatter BASIC_WEEK_DATE_TIME_NO_MILLIS = new CompoundDateTimeFormatter( - STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS.printer, + private static final DateFormatter BASIC_WEEK_DATE_TIME_NO_MILLIS = new JavaDateFormatter("basic_week_date_time_no_millis", + new DateTimeFormatterBuilder() + .append(STRICT_BASIC_WEEK_DATE_PRINTER).append(DateTimeFormatter.ofPattern("'T'HHmmssX", Locale.ROOT)) + .toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_WEEK_DATE_FORMATTER).appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(BASIC_WEEK_DATE_FORMATTER).appendLiteral("T").append(BASIC_TIME_NO_MILLIS_BASE) @@ -1175,8 +1166,8 @@ public class DateFormatters { * hour, two digit second of minute, three digit fraction of second, and * time zone offset (HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter TIME = new CompoundDateTimeFormatter( - STRICT_TIME.printer, + private static final DateFormatter TIME = new JavaDateFormatter("time", + new DateTimeFormatterBuilder().append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(TIME_PREFIX).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(TIME_PREFIX).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -1185,8 +1176,8 @@ public class DateFormatters { * Returns a formatter for a two digit hour of day, two digit minute of * hour, two digit second of minute, andtime zone offset (HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter TIME_NO_MILLIS = new CompoundDateTimeFormatter( - STRICT_TIME_NO_MILLIS.printer, + private static final DateFormatter TIME_NO_MILLIS = new JavaDateFormatter("time_no_millis", + new DateTimeFormatterBuilder().append(STRICT_TIME_NO_MILLIS_BASE).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(TIME_NO_MILLIS_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(TIME_NO_MILLIS_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -1196,8 +1187,8 @@ public class DateFormatters { * hour, two digit second of minute, three digit fraction of second, and * time zone offset prefixed by 'T' ('T'HH:mm:ss.SSSZZ). */ - private static final CompoundDateTimeFormatter T_TIME = new CompoundDateTimeFormatter( - STRICT_T_TIME.printer, + private static final DateFormatter T_TIME = new JavaDateFormatter("t_time", + new DateTimeFormatterBuilder().appendLiteral('T').append(STRICT_TIME_PRINTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendLiteral("T").append(TIME_PREFIX) .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendLiteral("T").append(TIME_PREFIX) @@ -1209,8 +1200,9 @@ public class DateFormatters { * hour, two digit second of minute, and time zone offset prefixed * by 'T' ('T'HH:mm:ssZZ). */ - private static final CompoundDateTimeFormatter T_TIME_NO_MILLIS = new CompoundDateTimeFormatter( - STRICT_T_TIME_NO_MILLIS.printer, + private static final DateFormatter T_TIME_NO_MILLIS = new JavaDateFormatter("t_time_no_millis", + new DateTimeFormatterBuilder().appendLiteral("T").append(STRICT_TIME_NO_MILLIS_BASE) + .appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(T_TIME_NO_MILLIS_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().append(T_TIME_NO_MILLIS_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT) ); @@ -1218,16 +1210,20 @@ public class DateFormatters { /* * A strict formatter that formats or parses a year and a month, such as '2011-12'. */ - private static final CompoundDateTimeFormatter YEAR_MONTH = new CompoundDateTimeFormatter( - STRICT_YEAR_MONTH.printer, + private static final DateFormatter YEAR_MONTH = new JavaDateFormatter("year_month", + new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral("-") + .appendValue(MONTH_OF_YEAR, 2, 2, SignStyle.NOT_NEGATIVE) + .toFormatter(Locale.ROOT), new DateTimeFormatterBuilder().appendValue(ChronoField.YEAR).appendLiteral("-").appendValue(MONTH_OF_YEAR).toFormatter(Locale.ROOT) ); /* * A strict date formatter that formats or parses a date without an offset, such as '2011-12-03'. */ - private static final CompoundDateTimeFormatter YEAR_MONTH_DAY = new CompoundDateTimeFormatter( - STRICT_YEAR_MONTH_DAY.printer, + private static final DateFormatter YEAR_MONTH_DAY = new JavaDateFormatter("year_month_day", + STRICT_YEAR_MONTH_DAY_FORMATTER, new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR) .appendLiteral("-") @@ -1241,13 +1237,13 @@ public class DateFormatters { * Returns a formatter for a full date as four digit weekyear, two digit * week of weekyear, and one digit day of week (xxxx-'W'ww-e). */ - private static final CompoundDateTimeFormatter WEEK_DATE = new CompoundDateTimeFormatter(STRICT_WEEK_DATE.printer, WEEK_DATE_FORMATTER); + private static final DateFormatter WEEK_DATE = new JavaDateFormatter("week_date", ISO_WEEK_DATE, WEEK_DATE_FORMATTER); /* * Returns a formatter for a four digit weekyear and two digit week of * weekyear. (xxxx-'W'ww) */ - private static final CompoundDateTimeFormatter WEEKYEAR_WEEK = new CompoundDateTimeFormatter(STRICT_WEEKYEAR_WEEK.printer, + private static final DateFormatter WEEKYEAR_WEEK = new JavaDateFormatter("weekyear_week", STRICT_WEEKYEAR_WEEK_FORMATTER, new DateTimeFormatterBuilder() .appendValue(WeekFields.ISO.weekBasedYear()) .appendLiteral("-W") @@ -1259,8 +1255,12 @@ public class DateFormatters { * Returns a formatter for a four digit weekyear, two digit week of * weekyear, and one digit day of week. (xxxx-'W'ww-e) */ - private static final CompoundDateTimeFormatter WEEKYEAR_WEEK_DAY = new CompoundDateTimeFormatter( - STRICT_WEEKYEAR_WEEK_DAY.printer, + private static final DateFormatter WEEKYEAR_WEEK_DAY = new JavaDateFormatter("weekyear_week_day", + new DateTimeFormatterBuilder() + .append(STRICT_WEEKYEAR_WEEK_FORMATTER) + .appendLiteral("-") + .appendValue(WeekFields.ISO.dayOfWeek()) + .toFormatter(Locale.ROOT), new DateTimeFormatterBuilder() .appendValue(WeekFields.ISO.weekBasedYear()) .appendLiteral("-W") @@ -1276,11 +1276,11 @@ public class DateFormatters { // ///////////////////////////////////////// - public static CompoundDateTimeFormatter forPattern(String input) { + public static DateFormatter forPattern(String input) { return forPattern(input, Locale.ROOT); } - public static CompoundDateTimeFormatter forPattern(String input, Locale locale) { + public static DateFormatter forPattern(String input, Locale locale) { if (Strings.hasLength(input)) { input = input.trim(); } @@ -1452,21 +1452,20 @@ public class DateFormatters { if (formats.length == 1) { return forPattern(formats[0], locale); } else { - Collection parsers = new LinkedHashSet<>(formats.length); - for (String format : formats) { - CompoundDateTimeFormatter dateTimeFormatter = forPattern(format, locale); - try { - parsers.addAll(Arrays.asList(dateTimeFormatter.parsers)); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid format: [" + input + "]: " + e.getMessage(), e); + try { + DateFormatter[] formatters = new DateFormatter[formats.length]; + for (int i = 0; i < formats.length; i++) { + formatters[i] = forPattern(formats[i], locale); } - } - return new CompoundDateTimeFormatter(parsers.toArray(new DateTimeFormatter[0])); + return DateFormatter.merge(formatters); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid format: [" + input + "]: " + e.getMessage(), e); + } } } else { try { - return new CompoundDateTimeFormatter(new DateTimeFormatterBuilder().appendPattern(input).toFormatter(locale)); + return new JavaDateFormatter(input, new DateTimeFormatterBuilder().appendPattern(input).toFormatter(locale)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid format: [" + input + "]: " + e.getMessage(), e); } diff --git a/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java b/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java index 39f6dabbdb2..5e5ecc5bafd 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java @@ -58,10 +58,10 @@ public class DateMathParser { ROUND_UP_BASE_FIELDS.put(ChronoField.MILLI_OF_SECOND, 999L); } - private final CompoundDateTimeFormatter formatter; - private final CompoundDateTimeFormatter roundUpFormatter; + private final DateFormatter formatter; + private final DateFormatter roundUpFormatter; - public DateMathParser(CompoundDateTimeFormatter formatter) { + public DateMathParser(DateFormatter formatter) { Objects.requireNonNull(formatter); this.formatter = formatter; this.roundUpFormatter = formatter.parseDefaulting(ROUND_UP_BASE_FIELDS); @@ -247,7 +247,7 @@ public class DateMathParser { } private long parseDateTime(String value, ZoneId timeZone, boolean roundUpIfNoTime) { - CompoundDateTimeFormatter formatter = roundUpIfNoTime ? this.roundUpFormatter : this.formatter; + DateFormatter formatter = roundUpIfNoTime ? this.roundUpFormatter : this.formatter; try { if (timeZone == null) { return DateFormatters.toZonedDateTime(formatter.parse(value)).toInstant().toEpochMilli(); diff --git a/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java new file mode 100644 index 00000000000..d50cc0cf466 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.time; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.util.Map; + +/** + * This is a special formatter to parse the milliseconds since the epoch. + * There is no way using a native java time date formatter to resemble + * the required behaviour to parse negative milliseconds as well. + * + * This implementation simply tries to convert the input to a long and uses + * this as the milliseconds since the epoch without involving any other + * java time code + */ +class EpochMillisDateFormatter implements DateFormatter { + + public static DateFormatter INSTANCE = new EpochMillisDateFormatter(); + + private EpochMillisDateFormatter() {} + + @Override + public TemporalAccessor parse(String input) { + try { + return Instant.ofEpochMilli(Long.valueOf(input)).atZone(ZoneOffset.UTC); + } catch (NumberFormatException e) { + throw new DateTimeParseException("invalid number", input, 0, e); + } + } + + @Override + public DateFormatter withZone(ZoneId zoneId) { + return this; + } + + @Override + public String format(TemporalAccessor accessor) { + return String.valueOf(Instant.from(accessor).toEpochMilli()); + } + + @Override + public String pattern() { + return "epoch_millis"; + } + + @Override + public DateFormatter parseDefaulting(Map fields) { + return this; + } +} diff --git a/server/src/main/java/org/elasticsearch/common/time/CompoundDateTimeFormatter.java b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java similarity index 59% rename from server/src/main/java/org/elasticsearch/common/time/CompoundDateTimeFormatter.java rename to server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java index 0332c03814d..f68215fde49 100644 --- a/server/src/main/java/org/elasticsearch/common/time/CompoundDateTimeFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + package org.elasticsearch.common.time; import java.time.ZoneId; @@ -27,33 +28,28 @@ import java.time.temporal.TemporalField; import java.util.Arrays; import java.util.Locale; import java.util.Map; -import java.util.function.Consumer; -/** - * wrapper class around java.time.DateTimeFormatter that supports multiple formats for easier parsing, - * and one specific format for printing - */ -public class CompoundDateTimeFormatter { +class JavaDateFormatter implements DateFormatter { - private static final Consumer SAME_TIME_ZONE_VALIDATOR = (parsers) -> { + private final String format; + private final DateTimeFormatter printer; + private final DateTimeFormatter[] parsers; + + JavaDateFormatter(String format, DateTimeFormatter printer, DateTimeFormatter... parsers) { long distinctZones = Arrays.stream(parsers).map(DateTimeFormatter::getZone).distinct().count(); if (distinctZones > 1) { throw new IllegalArgumentException("formatters must have the same time zone"); } - }; - - final DateTimeFormatter printer; - final DateTimeFormatter[] parsers; - - CompoundDateTimeFormatter(DateTimeFormatter ... parsers) { if (parsers.length == 0) { - throw new IllegalArgumentException("at least one date time formatter is required"); + this.parsers = new DateTimeFormatter[]{printer}; + } else { + this.parsers = parsers; } - SAME_TIME_ZONE_VALIDATOR.accept(parsers); - this.printer = parsers[0]; - this.parsers = parsers; + this.format = format; + this.printer = printer; } + @Override public TemporalAccessor parse(String input) { DateTimeParseException failure = null; for (int i = 0; i < parsers.length; i++) { @@ -72,13 +68,8 @@ public class CompoundDateTimeFormatter { throw failure; } - /** - * Configure a specific time zone for a date formatter - * - * @param zoneId The zoneId this formatter shoulduse - * @return The new formatter with all parsers switched to the specified timezone - */ - public CompoundDateTimeFormatter withZone(ZoneId zoneId) { + @Override + public DateFormatter withZone(ZoneId zoneId) { // shortcurt to not create new objects unnecessarily if (zoneId.equals(parsers[0].getZone())) { return this; @@ -89,25 +80,33 @@ public class CompoundDateTimeFormatter { parsersWithZone[i] = parsers[i].withZone(zoneId); } - return new CompoundDateTimeFormatter(parsersWithZone); - } - - /** - * Configure defaults for missing values in a parser, then return a new compound date formatter - */ - CompoundDateTimeFormatter parseDefaulting(Map fields) { - final DateTimeFormatter[] parsersWithDefaulting = new DateTimeFormatter[parsers.length]; - for (int i = 0; i < parsers.length; i++) { - DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append(parsers[i]); - fields.forEach(builder::parseDefaulting); - parsersWithDefaulting[i] = builder.toFormatter(Locale.ROOT); - } - - return new CompoundDateTimeFormatter(parsersWithDefaulting); + return new JavaDateFormatter(format, printer.withZone(zoneId), parsersWithZone); } + @Override public String format(TemporalAccessor accessor) { return printer.format(accessor); } + @Override + public String pattern() { + return format; + } + + @Override + public DateFormatter parseDefaulting(Map fields) { + final DateTimeFormatterBuilder parseDefaultingBuilder = new DateTimeFormatterBuilder().append(printer); + fields.forEach(parseDefaultingBuilder::parseDefaulting); + if (parsers.length == 1 && parsers[0].equals(printer)) { + return new JavaDateFormatter(format, parseDefaultingBuilder.toFormatter(Locale.ROOT)); + } else { + final DateTimeFormatter[] parsersWithDefaulting = new DateTimeFormatter[parsers.length]; + for (int i = 0; i < parsers.length; i++) { + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append(parsers[i]); + fields.forEach(builder::parseDefaulting); + parsersWithDefaulting[i] = builder.toFormatter(Locale.ROOT); + } + return new JavaDateFormatter(format, parseDefaultingBuilder.toFormatter(Locale.ROOT), parsersWithDefaulting); + } + } } diff --git a/server/src/main/java/org/elasticsearch/common/xcontent/XContentElasticsearchExtension.java b/server/src/main/java/org/elasticsearch/common/xcontent/XContentElasticsearchExtension.java index 5793bcf8a0e..684e96f678c 100644 --- a/server/src/main/java/org/elasticsearch/common/xcontent/XContentElasticsearchExtension.java +++ b/server/src/main/java/org/elasticsearch/common/xcontent/XContentElasticsearchExtension.java @@ -21,7 +21,7 @@ package org.elasticsearch.common.xcontent; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; @@ -64,9 +64,9 @@ import java.util.function.Function; public class XContentElasticsearchExtension implements XContentBuilderExtension { public static final DateTimeFormatter DEFAULT_DATE_PRINTER = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); - public static final CompoundDateTimeFormatter DEFAULT_FORMATTER = DateFormatters.forPattern("strict_date_optional_time_nanos"); - public static final CompoundDateTimeFormatter LOCAL_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSS"); - public static final CompoundDateTimeFormatter OFFSET_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSSZZZZZ"); + public static final DateFormatter DEFAULT_FORMATTER = DateFormatters.forPattern("strict_date_optional_time_nanos"); + public static final DateFormatter LOCAL_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSS"); + public static final DateFormatter OFFSET_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSSZZZZZ"); @Override public Map, XContentBuilder.Writer> getXContentWriters() { diff --git a/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java b/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java index 10a3e81163a..7e00aaa7cd9 100644 --- a/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java +++ b/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java @@ -21,7 +21,7 @@ package org.elasticsearch.monitor.jvm; import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.TimeValue; @@ -43,7 +43,7 @@ public class HotThreads { private static final Object mutex = new Object(); - private static final CompoundDateTimeFormatter DATE_TIME_FORMATTER = DateFormatters.forPattern("dateOptionalTime"); + private static final DateFormatter DATE_TIME_FORMATTER = DateFormatters.forPattern("dateOptionalTime"); private int busiestThreads = 3; private TimeValue interval = new TimeValue(500, TimeUnit.MILLISECONDS); diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java index 2da5e432ca3..fb302b1b3b3 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestSnapshotAction.java @@ -25,7 +25,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.Table; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.rest.RestController; @@ -99,7 +99,7 @@ public class RestSnapshotAction extends AbstractCatAction { .endHeaders(); } - private static final CompoundDateTimeFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); + private static final DateFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); private Table buildTable(RestRequest req, GetSnapshotsResponse getSnapshotsResponse) { Table table = getTableWithHeader(req); diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestTasksAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestTasksAction.java index 7d14422b37c..39b3f08dcdc 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestTasksAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestTasksAction.java @@ -27,7 +27,7 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.common.Strings; import org.elasticsearch.common.Table; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.rest.RestController; @@ -125,7 +125,7 @@ public class RestTasksAction extends AbstractCatAction { return table; } - private static final CompoundDateTimeFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); + private static final DateFormatter FORMATTER = DateFormatters.forPattern("HH:mm:ss").withZone(ZoneOffset.UTC); private void buildRow(Table table, boolean fullId, boolean detailed, DiscoveryNodes discoveryNodes, TaskInfo taskInfo) { table.startRow(); diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java index fdbe74d8d4d..38e31519ea1 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotInfo.java @@ -27,7 +27,7 @@ import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ObjectParser; @@ -52,7 +52,7 @@ public final class SnapshotInfo implements Comparable, ToXContent, public static final String CONTEXT_MODE_PARAM = "context_mode"; public static final String CONTEXT_MODE_SNAPSHOT = "SNAPSHOT"; - private static final CompoundDateTimeFormatter DATE_TIME_FORMATTER = DateFormatters.forPattern("strictDateOptionalTime"); + private static final DateFormatter DATE_TIME_FORMATTER = DateFormatters.forPattern("strictDateOptionalTime"); private static final String SNAPSHOT = "snapshot"; private static final String UUID = "uuid"; private static final String INDICES = "indices"; 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 cea83bfbccf..5203aa07d28 100644 --- a/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java +++ b/server/src/test/java/org/elasticsearch/common/joda/JavaJodaTimeDuellingTests.java @@ -19,7 +19,7 @@ package org.elasticsearch.common.joda; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.test.ESTestCase; import org.joda.time.DateTime; @@ -485,7 +485,7 @@ public class JavaJodaTimeDuellingTests extends ESTestCase { FormatDateTimeFormatter jodaFormatter = Joda.forPattern(format); DateTime jodaDateTime = jodaFormatter.parser().parseDateTime(input); - CompoundDateTimeFormatter javaTimeFormatter = DateFormatters.forPattern(format); + DateFormatter javaTimeFormatter = DateFormatters.forPattern(format); TemporalAccessor javaTimeAccessor = javaTimeFormatter.parse(input); ZonedDateTime zonedDateTime = DateFormatters.toZonedDateTime(javaTimeAccessor); @@ -507,7 +507,7 @@ public class JavaJodaTimeDuellingTests extends ESTestCase { } private void assertJavaTimeParseException(String input, String format, String expectedMessage) { - CompoundDateTimeFormatter javaTimeFormatter = DateFormatters.forPattern(format); + DateFormatter javaTimeFormatter = DateFormatters.forPattern(format); DateTimeParseException dateTimeParseException = expectThrows(DateTimeParseException.class, () -> javaTimeFormatter.parse(input)); assertThat(dateTimeParseException.getMessage(), startsWith(expectedMessage)); } diff --git a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java index f96674cf7a4..f01db140a70 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java @@ -31,17 +31,15 @@ import static org.hamcrest.Matchers.is; public class DateFormattersTests extends ESTestCase { - // the epoch milli parser is a bit special, as it does not use date formatter, see comments in DateFormatters public void testEpochMilliParser() { - CompoundDateTimeFormatter formatter = DateFormatters.forPattern("epoch_millis"); + DateFormatter formatter = DateFormatters.forPattern("epoch_millis"); DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("invalid")); assertThat(e.getMessage(), containsString("invalid number")); - // different zone, should still yield the same output, as epoch is time zoned independent + // different zone, should still yield the same output, as epoch is time zone independent ZoneId zoneId = randomZone(); - CompoundDateTimeFormatter zonedFormatter = formatter.withZone(zoneId); - assertThat(zonedFormatter.printer.getZone(), is(zoneId)); + DateFormatter zonedFormatter = formatter.withZone(zoneId); // test with negative and non negative values assertThatSameDateTime(formatter, zonedFormatter, randomNonNegativeLong() * -1); @@ -58,14 +56,21 @@ public class DateFormattersTests extends ESTestCase { assertSameFormat(formatter, 1); } - private void assertThatSameDateTime(CompoundDateTimeFormatter formatter, CompoundDateTimeFormatter zonedFormatter, long millis) { + public void testEpochMilliParsersWithDifferentFormatters() { + DateFormatter formatter = DateFormatters.forPattern("strict_date_optional_time||epoch_millis"); + TemporalAccessor accessor = formatter.parse("123"); + assertThat(DateFormatters.toZonedDateTime(accessor).toInstant().toEpochMilli(), is(123L)); + assertThat(formatter.pattern(), is("strict_date_optional_time||epoch_millis")); + } + + private void assertThatSameDateTime(DateFormatter formatter, DateFormatter zonedFormatter, long millis) { String millisAsString = String.valueOf(millis); ZonedDateTime formatterZonedDateTime = DateFormatters.toZonedDateTime(formatter.parse(millisAsString)); ZonedDateTime zonedFormatterZonedDateTime = DateFormatters.toZonedDateTime(zonedFormatter.parse(millisAsString)); assertThat(formatterZonedDateTime.toInstant().toEpochMilli(), is(zonedFormatterZonedDateTime.toInstant().toEpochMilli())); } - private void assertSameFormat(CompoundDateTimeFormatter formatter, long millis) { + private void assertSameFormat(DateFormatter formatter, long millis) { String millisAsString = String.valueOf(millis); TemporalAccessor accessor = formatter.parse(millisAsString); assertThat(millisAsString, is(formatter.format(accessor))); diff --git a/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java b/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java index d6dc7bb36f2..66e68b0aad0 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java @@ -35,7 +35,7 @@ import static org.hamcrest.Matchers.is; public class DateMathParserTests extends ESTestCase { - private final CompoundDateTimeFormatter formatter = DateFormatters.forPattern("dateOptionalTime||epoch_millis"); + private final DateFormatter formatter = DateFormatters.forPattern("dateOptionalTime||epoch_millis"); private final DateMathParser parser = new DateMathParser(formatter); public void testBasicDates() { @@ -138,7 +138,7 @@ public class DateMathParserTests extends ESTestCase { public void testRoundingPreservesEpochAsBaseDate() { // If a user only specifies times, then the date needs to always be 1970-01-01 regardless of rounding - CompoundDateTimeFormatter formatter = DateFormatters.forPattern("HH:mm:ss"); + DateFormatter formatter = DateFormatters.forPattern("HH:mm:ss"); DateMathParser parser = new DateMathParser(formatter); ZonedDateTime zonedDateTime = DateFormatters.toZonedDateTime(formatter.parse("04:52:20")); assertThat(zonedDateTime.getYear(), is(1970)); @@ -164,7 +164,7 @@ public class DateMathParserTests extends ESTestCase { assertDateMathEquals("2014-11-18T09:20", "2014-11-18T08:20:59.999Z", 0, true, ZoneId.of("CET")); // implicit rounding with explicit timezone in the date format - CompoundDateTimeFormatter formatter = DateFormatters.forPattern("yyyy-MM-ddXXX"); + DateFormatter formatter = DateFormatters.forPattern("yyyy-MM-ddXXX"); DateMathParser parser = new DateMathParser(formatter); long time = parser.parse("2011-10-09+01:00", () -> 0, false, null); assertEquals(this.parser.parse("2011-10-09T00:00:00.000+01:00", () -> 0), time); diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramTests.java index cbc3424314a..ecd8868aabd 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramTests.java @@ -26,7 +26,7 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; -import org.elasticsearch.common.time.CompoundDateTimeFormatter; +import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.aggregations.BaseAggregationTestCase; @@ -137,7 +137,7 @@ public class DateHistogramTests extends BaseAggregationTestCase