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 8d0ad8efb7f5b..7a43ce31d33df 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.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 static final class Tombstone implements ToXContentObject, Writeable { 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 ad715500a9ecd..21885d1788c7e 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.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 @@ */ 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 13d13066e16d6..a41fd267329ff 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 Table endHeaders() { 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 0000000000000..d16662b23b930 --- /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 69b8cb0c85bfa..5f68765134498 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.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.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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { .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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { /* * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { /* * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { * 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 String format(TemporalAccessor accessor) { // ///////////////////////////////////////// - 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 static CompoundDateTimeFormatter forPattern(String input, Locale locale) 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 39f6dabbdb2e6..5e5ecc5bafd9a 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 @@ private long parseMath(final String mathString, final long time, final boolean r } 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 0000000000000..d50cc0cf466a9 --- /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 0332c03814de7..f68215fde492a 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.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 TemporalAccessor parse(String input) { 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 CompoundDateTimeFormatter withZone(ZoneId zoneId) { 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 5793bcf8a0e49..684e96f678cee 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 @@ 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 @@ 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 10a3e81163ab6..7e00aaa7cd99c 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 @@ 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 2da5e432ca32a..fb302b1b3b3a4 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.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 @@ protected Table getTableWithHeader(RestRequest request) { .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 7d14422b37c2b..39b3f08dcdc5f 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.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 @@ protected Table getTableWithHeader(final RestRequest request) { 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 fdbe74d8d4dd9..38e31519ea138 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.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 cea83bfbccfdc..5203aa07d286e 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 @@ private void assertSameDate(String input, String format) { 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 @@ private void assertJodaParseException(String input, String format, String expect } 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 f96674cf7a4a9..f01db140a7057 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 @@ 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 void testEpochMilliParser() { 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 d6dc7bb36f2a6..66e68b0aad049 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 @@ 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 void testNow() { 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 void testImplicitRounding() { 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 cbc3424314ab3..ecd8868aabd02 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.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 @@ private static Document documentForDate(String field, long millis) { } public void testRewriteTimeZone() throws IOException { - CompoundDateTimeFormatter format = DateFormatters.forPattern("strict_date_optional_time"); + DateFormatter format = DateFormatters.forPattern("strict_date_optional_time"); try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) {