From 853e4668c83e1f44e6e57ce036bc05e657571b6f Mon Sep 17 00:00:00 2001 From: Nicholas Walter Knize Date: Thu, 26 Oct 2023 13:09:17 -0500 Subject: [PATCH] [Remove] Remaining Joda and Joda Dependency Joda was deprecated in Elasticsearch 7.x. OpenSearch should have proactively removed in 2.0. This commit removes the remaining Joda dependency and formatting logic in favor of java 8 time. It adds a new LegacyFormat class, however, to ensure camelCase DateFormat is supported for indexes created in ElasticSearch 7.x (and carried through OpenSearch 1.x). Signed-off-by: Nicholas Walter Knize --- .../time/DateFormatterBenchmark.java | 7 - .../benchmark/time/RoundingBenchmark.java | 180 -- .../opensearch-server-signatures.txt | 9 - buildSrc/version.properties | 1 - .../java/org/opensearch/client/CrudIT.java | 11 +- .../script/expression/DateObject.java | 97 +- .../expression/DateObjectValueSource.java | 16 +- plugins/repository-s3/build.gradle | 2 +- .../30_camel_case_dateformat.yml | 19 + .../test/old_cluster/20_date_range.yml | 80 - .../old_cluster/30_camel_case_dateformat.yml | 79 + .../30_camel_case_dateformat.yml | 20 + server/build.gradle | 3 - server/licenses/joda-time-2.12.2.jar.sha1 | 1 - server/licenses/joda-time-LICENSE.txt | 202 -- server/licenses/joda-time-NOTICE.txt | 5 - ...DateMathIndexExpressionsIntegrationIT.java | 104 +- .../search/fields/SearchFieldsIT.java | 19 +- .../time/format/StrictISODateTimeFormat.java | 1906 ----------------- .../org/opensearch/bootstrap/Security.java | 2 +- .../java/org/opensearch/common/joda/Joda.java | 567 ----- .../common/joda/JodaDateFormatter.java | 164 -- .../common/joda/JodaDateMathParser.java | 234 -- .../common/joda/JodaDeprecationPatterns.java | 108 - .../opensearch/common/joda/package-info.java | 10 - .../common/rounding/DateTimeUnit.java | 99 - .../opensearch/common/rounding/Rounding.java | 459 ---- .../common/rounding/package-info.java | 10 - .../opensearch/common/time/DateFormatter.java | 39 +- .../common/time/DateFormatters.java | 17 +- .../common/time/DateMathParser.java | 18 +- .../org/opensearch/common/time/DateUtils.java | 23 - .../opensearch/common/time/FormatNames.java | 197 +- .../common/time/LegacyFormatNames.java | 122 ++ .../index/mapper/DateFieldMapper.java | 11 +- .../index/mapper/ParametrizedFieldMapper.java | 3 +- .../opensearch/script/ClassPermission.java | 15 +- .../org/opensearch/search/DocValueFormat.java | 7 +- .../support/values/ScriptDoubleValues.java | 7 - .../support/values/ScriptLongValues.java | 4 - .../DateMathExpressionResolverTests.java | 93 +- .../org/opensearch/common/RoundingTests.java | 3 +- .../joda/JavaJodaTimeDuellingTests.java | 952 -------- .../common/joda/JodaDateMathParserTests.java | 364 ---- .../org/opensearch/common/joda/JodaTests.java | 84 - .../common/rounding/DateTimeUnitTests.java | 75 - .../common/rounding/RoundingDuelTests.java | 70 - .../rounding/TimeZoneRoundingTests.java | 822 ------- .../common/time/DateFormattersTests.java | 832 ++++++- .../common/time/DateUtilsTests.java | 27 - .../index/mapper/DateFieldMapperTests.java | 25 + .../index/mapper/DateFieldTypeTests.java | 34 +- .../index/mapper/RangeFieldTypeTests.java | 39 +- .../DistanceFeatureQueryBuilderTests.java | 3 +- .../index/query/RangeQueryBuilderTests.java | 30 +- .../FunctionScoreQueryBuilderTests.java | 10 +- .../bucket/DateScriptMocksPlugin.java | 20 +- .../bucket/range/InternalDateRangeTests.java | 14 +- .../test/AbstractQueryTestCase.java | 6 +- .../opensearch/test/OpenSearchTestCase.java | 136 +- 60 files changed, 1492 insertions(+), 7024 deletions(-) delete mode 100644 benchmarks/src/main/java/org/opensearch/benchmark/time/RoundingBenchmark.java create mode 100644 qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/30_camel_case_dateformat.yml create mode 100644 qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/30_camel_case_dateformat.yml create mode 100644 qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/30_camel_case_dateformat.yml delete mode 100644 server/licenses/joda-time-2.12.2.jar.sha1 delete mode 100644 server/licenses/joda-time-LICENSE.txt delete mode 100644 server/licenses/joda-time-NOTICE.txt delete mode 100644 server/src/main/java/org/joda/time/format/StrictISODateTimeFormat.java delete mode 100644 server/src/main/java/org/opensearch/common/joda/Joda.java delete mode 100644 server/src/main/java/org/opensearch/common/joda/JodaDateFormatter.java delete mode 100644 server/src/main/java/org/opensearch/common/joda/JodaDateMathParser.java delete mode 100644 server/src/main/java/org/opensearch/common/joda/JodaDeprecationPatterns.java delete mode 100644 server/src/main/java/org/opensearch/common/joda/package-info.java delete mode 100644 server/src/main/java/org/opensearch/common/rounding/DateTimeUnit.java delete mode 100644 server/src/main/java/org/opensearch/common/rounding/Rounding.java delete mode 100644 server/src/main/java/org/opensearch/common/rounding/package-info.java create mode 100644 server/src/main/java/org/opensearch/common/time/LegacyFormatNames.java delete mode 100644 server/src/test/java/org/opensearch/common/joda/JavaJodaTimeDuellingTests.java delete mode 100644 server/src/test/java/org/opensearch/common/joda/JodaDateMathParserTests.java delete mode 100644 server/src/test/java/org/opensearch/common/joda/JodaTests.java delete mode 100644 server/src/test/java/org/opensearch/common/rounding/DateTimeUnitTests.java delete mode 100644 server/src/test/java/org/opensearch/common/rounding/RoundingDuelTests.java delete mode 100644 server/src/test/java/org/opensearch/common/rounding/TimeZoneRoundingTests.java diff --git a/benchmarks/src/main/java/org/opensearch/benchmark/time/DateFormatterBenchmark.java b/benchmarks/src/main/java/org/opensearch/benchmark/time/DateFormatterBenchmark.java index 8f266f8357100..9fa124b1f3c9f 100644 --- a/benchmarks/src/main/java/org/opensearch/benchmark/time/DateFormatterBenchmark.java +++ b/benchmarks/src/main/java/org/opensearch/benchmark/time/DateFormatterBenchmark.java @@ -31,7 +31,6 @@ package org.opensearch.benchmark.time; -import org.opensearch.common.joda.Joda; import org.opensearch.common.time.DateFormatter; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -56,15 +55,9 @@ public class DateFormatterBenchmark { private final DateFormatter javaFormatter = DateFormatter.forPattern("8year_month_day||ordinal_date||epoch_millis"); - private final DateFormatter jodaFormatter = Joda.forPattern("year_month_day||ordinal_date||epoch_millis"); @Benchmark public TemporalAccessor parseJavaDate() { return javaFormatter.parse("1234567890"); } - - @Benchmark - public TemporalAccessor parseJodaDate() { - return jodaFormatter.parse("1234567890"); - } } diff --git a/benchmarks/src/main/java/org/opensearch/benchmark/time/RoundingBenchmark.java b/benchmarks/src/main/java/org/opensearch/benchmark/time/RoundingBenchmark.java deleted file mode 100644 index cdbcbfc163191..0000000000000 --- a/benchmarks/src/main/java/org/opensearch/benchmark/time/RoundingBenchmark.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.benchmark.time; - -import org.opensearch.common.Rounding; -import org.opensearch.common.rounding.DateTimeUnit; -import org.opensearch.common.time.DateUtils; -import org.opensearch.common.unit.TimeValue; -import org.joda.time.DateTimeZone; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; - -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.util.concurrent.TimeUnit; - -import static org.opensearch.common.Rounding.DateTimeUnit.DAY_OF_MONTH; -import static org.opensearch.common.Rounding.DateTimeUnit.MONTH_OF_YEAR; -import static org.opensearch.common.Rounding.DateTimeUnit.QUARTER_OF_YEAR; -import static org.opensearch.common.Rounding.DateTimeUnit.YEAR_OF_CENTURY; - -@Fork(3) -@Warmup(iterations = 10) -@Measurement(iterations = 10) -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -@State(Scope.Benchmark) -@SuppressWarnings("unused") // invoked by benchmarking framework -public class RoundingBenchmark { - - private final ZoneId zoneId = ZoneId.of("Europe/Amsterdam"); - private final DateTimeZone timeZone = DateUtils.zoneIdToDateTimeZone(zoneId); - - private long timestamp = 1548879021354L; - - private final org.opensearch.common.rounding.Rounding jodaRounding = org.opensearch.common.rounding.Rounding.builder( - DateTimeUnit.HOUR_OF_DAY - ).timeZone(timeZone).build(); - private final Rounding javaRounding = Rounding.builder(Rounding.DateTimeUnit.HOUR_OF_DAY).timeZone(zoneId).build(); - - @Benchmark - public long timeRoundingDateTimeUnitJoda() { - return jodaRounding.round(timestamp); - } - - @Benchmark - public long timeRoundingDateTimeUnitJava() { - return javaRounding.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding jodaDayOfMonthRounding = org.opensearch.common.rounding.Rounding.builder( - DateTimeUnit.DAY_OF_MONTH - ).timeZone(timeZone).build(); - private final Rounding javaDayOfMonthRounding = Rounding.builder(DAY_OF_MONTH).timeZone(zoneId).build(); - - @Benchmark - public long timeRoundingDateTimeUnitDayOfMonthJoda() { - return jodaDayOfMonthRounding.round(timestamp); - } - - @Benchmark - public long timeRoundingDateTimeUnitDayOfMonthJava() { - return javaDayOfMonthRounding.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding timeIntervalRoundingJoda = org.opensearch.common.rounding.Rounding.builder( - TimeValue.timeValueMinutes(60) - ).timeZone(timeZone).build(); - private final Rounding timeIntervalRoundingJava = Rounding.builder(TimeValue.timeValueMinutes(60)).timeZone(zoneId).build(); - - @Benchmark - public long timeIntervalRoundingJava() { - return timeIntervalRoundingJava.round(timestamp); - } - - @Benchmark - public long timeIntervalRoundingJoda() { - return timeIntervalRoundingJoda.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding timeUnitRoundingUtcDayOfMonthJoda = org.opensearch.common.rounding.Rounding - .builder(DateTimeUnit.DAY_OF_MONTH) - .timeZone(DateTimeZone.UTC) - .build(); - private final Rounding timeUnitRoundingUtcDayOfMonthJava = Rounding.builder(DAY_OF_MONTH).timeZone(ZoneOffset.UTC).build(); - - @Benchmark - public long timeUnitRoundingUtcDayOfMonthJava() { - return timeUnitRoundingUtcDayOfMonthJava.round(timestamp); - } - - @Benchmark - public long timeUnitRoundingUtcDayOfMonthJoda() { - return timeUnitRoundingUtcDayOfMonthJoda.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding timeUnitRoundingUtcQuarterOfYearJoda = org.opensearch.common.rounding.Rounding - .builder(DateTimeUnit.QUARTER) - .timeZone(DateTimeZone.UTC) - .build(); - private final Rounding timeUnitRoundingUtcQuarterOfYearJava = Rounding.builder(QUARTER_OF_YEAR).timeZone(ZoneOffset.UTC).build(); - - @Benchmark - public long timeUnitRoundingUtcQuarterOfYearJava() { - return timeUnitRoundingUtcQuarterOfYearJava.round(timestamp); - } - - @Benchmark - public long timeUnitRoundingUtcQuarterOfYearJoda() { - return timeUnitRoundingUtcQuarterOfYearJoda.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding timeUnitRoundingUtcMonthOfYearJoda = org.opensearch.common.rounding.Rounding - .builder(DateTimeUnit.MONTH_OF_YEAR) - .timeZone(DateTimeZone.UTC) - .build(); - private final Rounding timeUnitRoundingUtcMonthOfYearJava = Rounding.builder(MONTH_OF_YEAR).timeZone(ZoneOffset.UTC).build(); - - @Benchmark - public long timeUnitRoundingUtcMonthOfYearJava() { - return timeUnitRoundingUtcMonthOfYearJava.round(timestamp); - } - - @Benchmark - public long timeUnitRoundingUtcMonthOfYearJoda() { - return timeUnitRoundingUtcMonthOfYearJoda.round(timestamp); - } - - private final org.opensearch.common.rounding.Rounding timeUnitRoundingUtcYearOfCenturyJoda = org.opensearch.common.rounding.Rounding - .builder(DateTimeUnit.YEAR_OF_CENTURY) - .timeZone(DateTimeZone.UTC) - .build(); - private final Rounding timeUnitRoundingUtcYearOfCenturyJava = Rounding.builder(YEAR_OF_CENTURY).timeZone(ZoneOffset.UTC).build(); - - @Benchmark - public long timeUnitRoundingUtcYearOfCenturyJava() { - return timeUnitRoundingUtcYearOfCenturyJava.round(timestamp); - } - - @Benchmark - public long timeUnitRoundingUtcYearOfCenturyJoda() { - return timeUnitRoundingUtcYearOfCenturyJoda.round(timestamp); - } -} diff --git a/buildSrc/src/main/resources/forbidden/opensearch-server-signatures.txt b/buildSrc/src/main/resources/forbidden/opensearch-server-signatures.txt index 27fba8069125d..97e1cd35c403e 100644 --- a/buildSrc/src/main/resources/forbidden/opensearch-server-signatures.txt +++ b/buildSrc/src/main/resources/forbidden/opensearch-server-signatures.txt @@ -72,15 +72,6 @@ java.util.concurrent.Future#cancel(boolean) org.opensearch.common.io.PathUtils#get(java.lang.String, java.lang.String[]) org.opensearch.common.io.PathUtils#get(java.net.URI) -@defaultMessage Constructing a DateTime without a time zone is dangerous -org.joda.time.DateTime#() -org.joda.time.DateTime#(long) -org.joda.time.DateTime#(int, int, int, int, int) -org.joda.time.DateTime#(int, int, int, int, int, int) -org.joda.time.DateTime#(int, int, int, int, int, int, int) -org.joda.time.DateTime#now() -org.joda.time.DateTimeZone#getDefault() - @defaultMessage Local times may be ambiguous or nonexistent in a specific time zones. Use ZoneRules#getValidOffsets() instead. java.time.LocalDateTime#atZone(java.time.ZoneId) java.time.ZonedDateTime#of(int, int, int, int, int, int, int, java.time.ZoneId) diff --git a/buildSrc/version.properties b/buildSrc/version.properties index 96d398c35851d..f900e29350997 100644 --- a/buildSrc/version.properties +++ b/buildSrc/version.properties @@ -29,7 +29,6 @@ jakarta_annotation = 1.3.5 jna = 5.13.0 netty = 4.1.100.Final -joda = 2.12.2 # client dependencies httpclient5 = 5.2.1 diff --git a/client/rest-high-level/src/test/java/org/opensearch/client/CrudIT.java b/client/rest-high-level/src/test/java/org/opensearch/client/CrudIT.java index da9f790215669..dc87b303a3941 100644 --- a/client/rest-high-level/src/test/java/org/opensearch/client/CrudIT.java +++ b/client/rest-high-level/src/test/java/org/opensearch/client/CrudIT.java @@ -74,15 +74,17 @@ import org.opensearch.script.Script; import org.opensearch.script.ScriptType; import org.opensearch.search.fetch.subphase.FetchSourceContext; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; import java.io.IOException; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -1022,7 +1024,8 @@ private void validateBulkResponses(int nbItems, boolean[] errors, BulkResponse b public void testUrlEncode() throws IOException { String indexPattern = ""; String expectedIndex = "logstash-" - + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(DateTimeZone.UTC).monthOfYear().roundFloorCopy()); + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.now(ZoneOffset.UTC).withDayOfMonth(1).with(LocalTime.MIN)); { IndexRequest indexRequest = new IndexRequest(indexPattern).id("id#1"); indexRequest.source("field", "value"); diff --git a/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObject.java b/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObject.java index 502e29a5f2b58..34ebfd8e8e6a0 100644 --- a/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObject.java +++ b/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObject.java @@ -33,9 +33,12 @@ package org.opensearch.script.expression; import org.apache.lucene.search.DoubleValuesSource; +import org.opensearch.common.time.DateFormatters; import org.opensearch.index.fielddata.IndexFieldData; import org.opensearch.search.MultiValueMode; -import org.joda.time.ReadableDateTime; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; /** * Expressions API for date objects (.date) @@ -87,41 +90,51 @@ private DateObject() {} static DoubleValuesSource getVariable(IndexFieldData fieldData, String fieldName, String variable) { switch (variable) { case CENTURY_OF_ERA_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getCenturyOfEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.YEAR_OF_ERA) / 100); case DAY_OF_MONTH_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfMonth); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getDayOfMonth); case DAY_OF_WEEK_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfWeek); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.getDayOfWeek().getValue()); case DAY_OF_YEAR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getDayOfYear); case ERA_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.ERA)); case HOUR_OF_DAY_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getHourOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getHour); case MILLIS_OF_DAY_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMillisOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.MILLI_OF_DAY)); case MILLIS_OF_SECOND_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMillisOfSecond); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.MILLI_OF_SECOND)); case MINUTE_OF_DAY_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMinuteOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.MINUTE_OF_DAY)); case MINUTE_OF_HOUR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMinuteOfHour); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getMinute); case MONTH_OF_YEAR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMonthOfYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getMonthValue); case SECOND_OF_DAY_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getSecondOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.SECOND_OF_DAY)); case SECOND_OF_MINUTE_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getSecondOfMinute); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getSecond); case WEEK_OF_WEEK_YEAR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getWeekOfWeekyear); + return new DateObjectValueSource( + fieldData, + MultiValueMode.MIN, + variable, + zdt -> zdt.get(DateFormatters.WEEK_FIELDS_ROOT.weekOfWeekBasedYear()) + ); case WEEK_YEAR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getWeekyear); + return new DateObjectValueSource( + fieldData, + MultiValueMode.MIN, + variable, + zdt -> zdt.get(DateFormatters.WEEK_FIELDS_ROOT.weekBasedYear()) + ); case YEAR_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ZonedDateTime::getYear); case YEAR_OF_CENTURY_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYearOfCentury); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.YEAR_OF_ERA) % 100); case YEAR_OF_ERA_VARIABLE: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYearOfEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, zdt -> zdt.get(ChronoField.YEAR_OF_ERA)); default: throw new IllegalArgumentException( "Member variable [" + variable + "] does not exist for date object on field [" + fieldName + "]." @@ -132,41 +145,51 @@ static DoubleValuesSource getVariable(IndexFieldData fieldData, String fieldN static DoubleValuesSource getMethod(IndexFieldData fieldData, String fieldName, String method) { switch (method) { case GETCENTURY_OF_ERA_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getCenturyOfEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.YEAR_OF_ERA) / 100); case GETDAY_OF_MONTH_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfMonth); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getDayOfMonth); case GETDAY_OF_WEEK_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfWeek); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.getDayOfWeek().getValue()); case GETDAY_OF_YEAR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getDayOfYear); case GETERA_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.ERA)); case GETHOUR_OF_DAY_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getHourOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getHour); case GETMILLIS_OF_DAY_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMillisOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.MILLI_OF_DAY)); case GETMILLIS_OF_SECOND_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMillisOfSecond); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.MILLI_OF_SECOND)); case GETMINUTE_OF_DAY_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMinuteOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.MINUTE_OF_DAY)); case GETMINUTE_OF_HOUR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMinuteOfHour); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getMinute); case GETMONTH_OF_YEAR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMonthOfYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getMonthValue); case GETSECOND_OF_DAY_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getSecondOfDay); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.SECOND_OF_DAY)); case GETSECOND_OF_MINUTE_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getSecondOfMinute); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getSecond); case GETWEEK_OF_WEEK_YEAR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getWeekOfWeekyear); + return new DateObjectValueSource( + fieldData, + MultiValueMode.MIN, + method, + zdt -> zdt.get(DateFormatters.WEEK_FIELDS_ROOT.weekOfWeekBasedYear()) + ); case GETWEEK_YEAR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getWeekyear); + return new DateObjectValueSource( + fieldData, + MultiValueMode.MIN, + method, + zdt -> zdt.get(DateFormatters.WEEK_FIELDS_ROOT.weekBasedYear()) + ); case GETYEAR_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYear); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ZonedDateTime::getYear); case GETYEAR_OF_CENTURY_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYearOfCentury); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.YEAR_OF_ERA) % 100); case GETYEAR_OF_ERA_METHOD: - return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYearOfEra); + return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, zdt -> zdt.get(ChronoField.YEAR_OF_ERA)); default: throw new IllegalArgumentException( "Member method [" + method + "] does not exist for date object on field [" + fieldName + "]." diff --git a/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObjectValueSource.java b/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObjectValueSource.java index 69d7b5ad7f769..d02d2617084bb 100644 --- a/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObjectValueSource.java +++ b/modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObjectValueSource.java @@ -38,25 +38,25 @@ import org.opensearch.index.fielddata.LeafNumericFieldData; import org.opensearch.index.fielddata.NumericDoubleValues; import org.opensearch.search.MultiValueMode; -import org.joda.time.DateTimeZone; -import org.joda.time.MutableDateTime; -import org.joda.time.ReadableDateTime; import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Objects; import java.util.function.ToIntFunction; -/** Extracts a portion of a date field with joda time */ +/** Extracts a portion of a date field with java time */ class DateObjectValueSource extends FieldDataValueSource { final String methodName; - final ToIntFunction function; + final ToIntFunction function; DateObjectValueSource( IndexFieldData indexFieldData, MultiValueMode multiValueMode, String methodName, - ToIntFunction function + ToIntFunction function ) { super(indexFieldData, multiValueMode); @@ -69,13 +69,11 @@ class DateObjectValueSource extends FieldDataValueSource { @Override public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { LeafNumericFieldData leafData = (LeafNumericFieldData) fieldData.load(leaf); - MutableDateTime joda = new MutableDateTime(0, DateTimeZone.UTC); NumericDoubleValues docValues = multiValueMode.select(leafData.getDoubleValues()); return new DoubleValues() { @Override public double doubleValue() throws IOException { - joda.setMillis((long) docValues.doubleValue()); - return function.applyAsInt(joda); + return function.applyAsInt(ZonedDateTime.ofInstant(Instant.ofEpochMilli((long) docValues.doubleValue()), ZoneOffset.UTC)); } @Override diff --git a/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index 44fd45b265e82..bb98f59747557 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -80,7 +80,6 @@ dependencies { api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" api "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" api "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${versions.jackson}" - api "joda-time:joda-time:${versions.joda}" api "org.slf4j:slf4j-api:${versions.slf4j}" // network stack @@ -130,6 +129,7 @@ test { // this is tested explicitly in separate test tasks exclude '**/RepositoryCredentialsTests.class' exclude '**/S3RepositoryThirdPartyTests.class' + systemProperty 'aws.region', 'us-east-2' } boolean useFixture = false diff --git a/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/30_camel_case_dateformat.yml b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/30_camel_case_dateformat.yml new file mode 100644 index 0000000000000..61533cf8286ad --- /dev/null +++ b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/30_camel_case_dateformat.yml @@ -0,0 +1,19 @@ +--- +"Verify that we can still use index with camel case date field": + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "camel_case_date_format"}}' + - '{"date_field": "2019-02-01T00:00+01:00"}' + + - do: + search: + rest_total_hits_as_int: true + index: camel_case_date_format + body: + query: + range: + date_field: + gte: "2019-01-01T00:00+01:00" + lte: "2019-03-01T00:00+01:00" diff --git a/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/20_date_range.yml b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/20_date_range.yml index 6427a45e19f58..ed4619e060d96 100644 --- a/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/20_date_range.yml +++ b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/20_date_range.yml @@ -1,83 +1,3 @@ ---- -"Create index with joda style index that is incompatible with java.time. (6.0)": - - skip: - features: "allowed_warnings" - version: "6.8.1 -" - reason: change of warning message - - do: - allowed_warnings: - - "Use of 'Y' (year-of-era) will change to 'y' in the next major version of OpenSearch. Prefix your date format with '8' to use the new specifier." - indices.create: - index: joda_for_range - body: - settings: - index: - number_of_replicas: 2 - mappings: - "properties": - "time_frame": - "type": "date_range" - "format": "YYYY-MM-dd'T'HH:mmZZ" - - - do: - bulk: - refresh: true - body: - - '{"index": {"_index": "joda_for_range"}}' - - '{"time_frame": {"gte": "2019-01-01T00:00+01:00", "lte" : "2019-03-01T00:00+01:00"}}' - - - do: - search: - rest_total_hits_as_int: true - index: joda_for_range - body: - query: - range: - time_frame: - gte: "2019-02-01T00:00+01:00" - lte: "2019-02-01T00:00+01:00" - - match: { hits.total: 1 } - ---- -"Create index with joda style index that is incompatible with java.time (>6.1)": - - skip: - features: "allowed_warnings" - version: " - 6.8.0, 7.0.0 -" - reason: change of warning message, we skip 7 becase this format will be considered java - - do: - allowed_warnings: - - "'Y' year-of-era should be replaced with 'y'. Use 'Y' for week-based-year.; 'Z' time zone offset/id fails when parsing 'Z' for Zulu timezone. Consider using 'X'. Prefix your date format with '8' to use the new specifier." - indices.create: - index: joda_for_range - body: - settings: - index: - number_of_replicas: 2 - mappings: - "properties": - "time_frame": - "type": "date_range" - "format": "YYYY-MM-dd'T'HH:mmZZ" - - - do: - bulk: - refresh: true - body: - - '{"index": {"_index": "joda_for_range"}}' - - '{"time_frame": {"gte": "2019-01-01T00:00+01:00", "lte" : "2019-03-01T00:00+01:00"}}' - - - do: - search: - rest_total_hits_as_int: true - index: joda_for_range - body: - query: - range: - time_frame: - gte: "2019-02-01T00:00+01:00" - lte: "2019-02-01T00:00+01:00" - - match: { hits.total: 1 } - --- "Create index with java style index in 6": - do: diff --git a/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/30_camel_case_dateformat.yml b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/30_camel_case_dateformat.yml new file mode 100644 index 0000000000000..119fcf33c036f --- /dev/null +++ b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/30_camel_case_dateformat.yml @@ -0,0 +1,79 @@ +--- +"Create index with camel case on date format (allowed with warning prior to 2.12.0)": + - skip: + version: "2.12.0 - " + reason: "at version 2.12.0, camel case date format is not allowed" + features: "warnings" + - do: + warnings: + - "Camel case format name strictDateOptionalTime is deprecated and will be removed in a future version. Use snake case name strict_date_optional_time instead." + indices.create: + index: camel_case_date_format + body: + settings: + index: + number_of_replicas: 2 + mappings: + "properties": + "date_field": + "type": "date" + "format": "strictDateOptionalTime" + + + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "camel_case_date_format"}}' + - '{"date_field": "2023-02-01T00:00+01:00"}' + + - do: + search: + rest_total_hits_as_int: true + index: camel_case_date_format + body: + query: + range: + date_field: + gte: "2023-01-01T00:00+01:00" + lte: "2023-03-01T00:00+01:00" + - match: { hits.total: 1 } + +--- +"Create index with camel case date format (when bwc version is > 8.0.0)": + - skip: + version: " - 2.11.99" + reason: "at version 2.12.0, camel case date format is not allowed" + features: "warnings" + - do: + indices.create: + index: camel_case_on_format + body: + settings: + index: + number_of_replicas: 2 + mappings: + "properties": + "date_field": + "type": "date" + "format": "strict_date_optional_time" + + + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "camel_case_date_format"}}' + - '{"date_field": "2023-02-01T00:00+01:00"}' + + - do: + search: + rest_total_hits_as_int: true + index: camel_case_on_format + body: + query: + range: + date_field: + gte: "2023-01-01T00:00+01:00" + lte: "2023-03-01T00:00+01:00" + - match: { hits.total: 1 } diff --git a/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/30_camel_case_dateformat.yml b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/30_camel_case_dateformat.yml new file mode 100644 index 0000000000000..aaf9110b4bb01 --- /dev/null +++ b/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/30_camel_case_dateformat.yml @@ -0,0 +1,20 @@ +--- +"Verify that we can use index with camel case date field in upgraded cluster": + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "camel_case_date_format"}}' + - '{"date_field": "2023-02-01T00:00+01:00"}' + + - do: + search: + rest_total_hits_as_int: true + index: camel_case_date_format + body: + query: + range: + date_field: + gte: "2023-01-01T00:00+01:00" + lte: "2023-03-01T00:00+01:00" + - match: { hits.total: 4 } diff --git a/server/build.gradle b/server/build.gradle index c56f9d5aa288f..76e11d27da1c0 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -130,9 +130,6 @@ dependencies { // utilities api project(":libs:opensearch-cli") - // time handling, remove with java 8 time - api "joda-time:joda-time:${versions.joda}" - // percentiles aggregation api 'com.tdunning:t-digest:3.3' // precentil ranks aggregation diff --git a/server/licenses/joda-time-2.12.2.jar.sha1 b/server/licenses/joda-time-2.12.2.jar.sha1 deleted file mode 100644 index 6e9b28eb35597..0000000000000 --- a/server/licenses/joda-time-2.12.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -78e18a7b4180e911dafba0a412adfa82c1e3d14b \ No newline at end of file diff --git a/server/licenses/joda-time-LICENSE.txt b/server/licenses/joda-time-LICENSE.txt deleted file mode 100644 index 75b52484ea471..0000000000000 --- a/server/licenses/joda-time-LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/server/licenses/joda-time-NOTICE.txt b/server/licenses/joda-time-NOTICE.txt deleted file mode 100644 index dffbcf31cacf6..0000000000000 --- a/server/licenses/joda-time-NOTICE.txt +++ /dev/null @@ -1,5 +0,0 @@ -============================================================================= -= NOTICE file corresponding to section 4d of the Apache License Version 2.0 = -============================================================================= -This product includes software developed by -Joda.org (http://www.joda.org/). diff --git a/server/src/internalClusterTest/java/org/opensearch/indices/DateMathIndexExpressionsIntegrationIT.java b/server/src/internalClusterTest/java/org/opensearch/indices/DateMathIndexExpressionsIntegrationIT.java index 2f348e5d6218d..3c4fc59cddbc0 100644 --- a/server/src/internalClusterTest/java/org/opensearch/indices/DateMathIndexExpressionsIntegrationIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/indices/DateMathIndexExpressionsIntegrationIT.java @@ -32,6 +32,8 @@ package org.opensearch.indices; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestBuilder; import org.opensearch.action.DocWriteResponse; import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; import org.opensearch.action.admin.indices.stats.IndicesStatsResponse; @@ -41,11 +43,16 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.cluster.ClusterState; import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.core.action.ActionResponse; import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.index.IndexNotFoundException; import org.opensearch.test.OpenSearchIntegTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; +import org.junit.Before; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount; import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertSearchHits; @@ -54,15 +61,44 @@ import static org.hamcrest.Matchers.notNullValue; public class DateMathIndexExpressionsIntegrationIT extends OpenSearchIntegTestCase { + private ZonedDateTime now; + + @Before + public void setNow() { + now = ZonedDateTime.now(ZoneOffset.UTC); + } + + /** + * the internal cluster calls System.nanoTime() and System.currentTimeMillis() during evaluations of requests + * that need date-math index resolution. These are not mockable in these tests. As is, executing requests as-is + * in these test cases can potentially result in invalid responses when day-boundaries are hit mid test run. Instead + * of failing when index resolution with `now` is one day off, this method wraps calls with the assumption that + * the day did not change during the test run. + */ + public R dateSensitiveGet(ActionRequestBuilder builder) { + Runnable dayChangeAssumption = () -> assumeTrue( + "day changed between requests", + ZonedDateTime.now(ZoneOffset.UTC).getDayOfYear() == now.getDayOfYear() + ); + R response; + try { + response = builder.get(); + } catch (IndexNotFoundException e) { + // index resolver throws this if it does not find the exact index due to day changes + dayChangeAssumption.run(); + throw e; + } + dayChangeAssumption.run(); + return response; + } public void testIndexNameDateMathExpressions() { - DateTime now = new DateTime(DateTimeZone.UTC); - String index1 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now); - String index2 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(1)); - String index3 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(2)); + String index1 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now); + String index2 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(1)); + String index3 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(2)); createIndex(index1, index2, index3); - GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings(index1, index2, index3).get(); + GetSettingsResponse getSettingsResponse = dateSensitiveGet(client().admin().indices().prepareGetSettings(index1, index2, index3)); assertEquals(index1, getSettingsResponse.getSetting(index1, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); assertEquals(index2, getSettingsResponse.getSetting(index2, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); assertEquals(index3, getSettingsResponse.getSetting(index3, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); @@ -75,27 +111,25 @@ public void testIndexNameDateMathExpressions() { client().prepareIndex(dateMathExp3).setId("3").setSource("{}", MediaTypeRegistry.JSON).get(); refresh(); - SearchResponse searchResponse = client().prepareSearch(dateMathExp1, dateMathExp2, dateMathExp3).get(); + SearchResponse searchResponse = dateSensitiveGet(client().prepareSearch(dateMathExp1, dateMathExp2, dateMathExp3)); assertHitCount(searchResponse, 3); assertSearchHits(searchResponse, "1", "2", "3"); - GetResponse getResponse = client().prepareGet(dateMathExp1, "1").get(); + GetResponse getResponse = dateSensitiveGet(client().prepareGet(dateMathExp1, "1")); assertThat(getResponse.isExists(), is(true)); assertThat(getResponse.getId(), equalTo("1")); - getResponse = client().prepareGet(dateMathExp2, "2").get(); + getResponse = dateSensitiveGet(client().prepareGet(dateMathExp2, "2")); assertThat(getResponse.isExists(), is(true)); assertThat(getResponse.getId(), equalTo("2")); - getResponse = client().prepareGet(dateMathExp3, "3").get(); + getResponse = dateSensitiveGet(client().prepareGet(dateMathExp3, "3")); assertThat(getResponse.isExists(), is(true)); assertThat(getResponse.getId(), equalTo("3")); - MultiGetResponse mgetResponse = client().prepareMultiGet() - .add(dateMathExp1, "1") - .add(dateMathExp2, "2") - .add(dateMathExp3, "3") - .get(); + MultiGetResponse mgetResponse = dateSensitiveGet( + client().prepareMultiGet().add(dateMathExp1, "1").add(dateMathExp2, "2").add(dateMathExp3, "3") + ); assertThat(mgetResponse.getResponses()[0].getResponse().isExists(), is(true)); assertThat(mgetResponse.getResponses()[0].getResponse().getId(), equalTo("1")); assertThat(mgetResponse.getResponses()[1].getResponse().isExists(), is(true)); @@ -103,29 +137,30 @@ public void testIndexNameDateMathExpressions() { assertThat(mgetResponse.getResponses()[2].getResponse().isExists(), is(true)); assertThat(mgetResponse.getResponses()[2].getResponse().getId(), equalTo("3")); - IndicesStatsResponse indicesStatsResponse = client().admin().indices().prepareStats(dateMathExp1, dateMathExp2, dateMathExp3).get(); + IndicesStatsResponse indicesStatsResponse = dateSensitiveGet( + client().admin().indices().prepareStats(dateMathExp1, dateMathExp2, dateMathExp3) + ); assertThat(indicesStatsResponse.getIndex(index1), notNullValue()); assertThat(indicesStatsResponse.getIndex(index2), notNullValue()); assertThat(indicesStatsResponse.getIndex(index3), notNullValue()); - DeleteResponse deleteResponse = client().prepareDelete(dateMathExp1, "1").get(); + DeleteResponse deleteResponse = dateSensitiveGet(client().prepareDelete(dateMathExp1, "1")); assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult()); assertThat(deleteResponse.getId(), equalTo("1")); - deleteResponse = client().prepareDelete(dateMathExp2, "2").get(); + deleteResponse = dateSensitiveGet(client().prepareDelete(dateMathExp2, "2")); assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult()); assertThat(deleteResponse.getId(), equalTo("2")); - deleteResponse = client().prepareDelete(dateMathExp3, "3").get(); + deleteResponse = dateSensitiveGet(client().prepareDelete(dateMathExp3, "3")); assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult()); assertThat(deleteResponse.getId(), equalTo("3")); } - public void testAutoCreateIndexWithDateMathExpression() throws Exception { - DateTime now = new DateTime(DateTimeZone.UTC); - String index1 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now); - String index2 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(1)); - String index3 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(2)); + public void testAutoCreateIndexWithDateMathExpression() { + String index1 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now); + String index2 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(1)); + String index3 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(2)); String dateMathExp1 = "<.marvel-{now/d}>"; String dateMathExp2 = "<.marvel-{now/d-1d}>"; @@ -135,28 +170,29 @@ public void testAutoCreateIndexWithDateMathExpression() throws Exception { client().prepareIndex(dateMathExp3).setId("3").setSource("{}", MediaTypeRegistry.JSON).get(); refresh(); - SearchResponse searchResponse = client().prepareSearch(dateMathExp1, dateMathExp2, dateMathExp3).get(); + SearchResponse searchResponse = dateSensitiveGet(client().prepareSearch(dateMathExp1, dateMathExp2, dateMathExp3)); assertHitCount(searchResponse, 3); assertSearchHits(searchResponse, "1", "2", "3"); - IndicesStatsResponse indicesStatsResponse = client().admin().indices().prepareStats(dateMathExp1, dateMathExp2, dateMathExp3).get(); + IndicesStatsResponse indicesStatsResponse = dateSensitiveGet( + client().admin().indices().prepareStats(dateMathExp1, dateMathExp2, dateMathExp3) + ); assertThat(indicesStatsResponse.getIndex(index1), notNullValue()); assertThat(indicesStatsResponse.getIndex(index2), notNullValue()); assertThat(indicesStatsResponse.getIndex(index3), notNullValue()); } - public void testCreateIndexWithDateMathExpression() throws Exception { - DateTime now = new DateTime(DateTimeZone.UTC); - String index1 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now); - String index2 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(1)); - String index3 = ".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(now.minusDays(2)); + public void testCreateIndexWithDateMathExpression() { + String index1 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now); + String index2 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(1)); + String index3 = ".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.minusDays(2)); String dateMathExp1 = "<.marvel-{now/d}>"; String dateMathExp2 = "<.marvel-{now/d-1d}>"; String dateMathExp3 = "<.marvel-{now/d-2d}>"; createIndex(dateMathExp1, dateMathExp2, dateMathExp3); - GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings(index1, index2, index3).get(); + GetSettingsResponse getSettingsResponse = dateSensitiveGet(client().admin().indices().prepareGetSettings(index1, index2, index3)); assertEquals(dateMathExp1, getSettingsResponse.getSetting(index1, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); assertEquals(dateMathExp2, getSettingsResponse.getSetting(index2, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); assertEquals(dateMathExp3, getSettingsResponse.getSetting(index3, IndexMetadata.SETTING_INDEX_PROVIDED_NAME)); diff --git a/server/src/internalClusterTest/java/org/opensearch/search/fields/SearchFieldsIT.java b/server/src/internalClusterTest/java/org/opensearch/search/fields/SearchFieldsIT.java index 799bbf91a567d..f9717e3bfe937 100644 --- a/server/src/internalClusterTest/java/org/opensearch/search/fields/SearchFieldsIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/search/fields/SearchFieldsIT.java @@ -64,14 +64,12 @@ import org.opensearch.search.sort.SortOrder; import org.opensearch.test.InternalSettingsPlugin; import org.opensearch.test.ParameterizedOpenSearchIntegTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; import java.math.BigInteger; import java.time.Instant; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -80,6 +78,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -1191,7 +1190,7 @@ public void testDocValueFields() throws Exception { assertThat(searchResponse.getHits().getAt(0).getFields().get("double_field").getValue(), equalTo((Object) 6.0d)); assertThat( searchResponse.getHits().getAt(0).getFields().get("date_field").getValue(), - equalTo(DateFormatter.forPattern("dateOptionalTime").format(date)) + equalTo(DateFormatter.forPattern("date_optional_time").format(date)) ); assertThat(searchResponse.getHits().getAt(0).getFields().get("boolean_field").getValue(), equalTo((Object) true)); assertThat(searchResponse.getHits().getAt(0).getFields().get("text_field").getValue(), equalTo("foo")); @@ -1321,10 +1320,10 @@ public void testDocValueFieldsWithFieldAlias() throws Exception { assertAcked(prepareCreate("test").setMapping(mapping)); ensureGreen("test"); - DateTime date = new DateTime(1990, 12, 29, 0, 0, DateTimeZone.UTC); - org.joda.time.format.DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); + ZonedDateTime date = ZonedDateTime.of(1990, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd", Locale.ROOT); - index("test", MapperService.SINGLE_MAPPING_NAME, "1", "text_field", "foo", "date_field", formatter.print(date)); + index("test", MapperService.SINGLE_MAPPING_NAME, "1", "text_field", "foo", "date_field", formatter.format(date)); refresh("test"); SearchRequestBuilder builder = client().prepareSearch() @@ -1382,10 +1381,10 @@ public void testWildcardDocValueFieldsWithFieldAlias() throws Exception { assertAcked(prepareCreate("test").setMapping(mapping)); ensureGreen("test"); - DateTime date = new DateTime(1990, 12, 29, 0, 0, DateTimeZone.UTC); - org.joda.time.format.DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); + ZonedDateTime date = ZonedDateTime.of(1990, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd", Locale.ROOT); - index("test", MapperService.SINGLE_MAPPING_NAME, "1", "text_field", "foo", "date_field", formatter.print(date)); + index("test", MapperService.SINGLE_MAPPING_NAME, "1", "text_field", "foo", "date_field", formatter.format(date)); refresh("test"); SearchRequestBuilder builder = client().prepareSearch() diff --git a/server/src/main/java/org/joda/time/format/StrictISODateTimeFormat.java b/server/src/main/java/org/joda/time/format/StrictISODateTimeFormat.java deleted file mode 100644 index 0bc54b24d7d1a..0000000000000 --- a/server/src/main/java/org/joda/time/format/StrictISODateTimeFormat.java +++ /dev/null @@ -1,1906 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* @notice - * Copyright 2001-2009 Stephen Colebourne - * - * Licensed 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.joda.time.format; - -import org.joda.time.DateTimeFieldType; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/* - * Elasticsearch Note: This class has been copied almost identically from joda, where the - * class is named ISODatetimeFormat - * - * However there has been done one huge modification in several methods, which forces the date - * year to be exactly n digits, so that a year like "5" is invalid and must be "0005" - * - * All methods have been marked with an "// ES change" commentary - * - * In case you compare this with the original ISODateTimeFormat, make sure you use a diff - * call, that ignores whitespaces/tabs/indentations like 'diff -b' - */ - -/** - * Factory that creates instances of DateTimeFormatter based on the ISO8601 standard. - *

- * Date-time formatting is performed by the {@link DateTimeFormatter} class. - * Three classes provide factory methods to create formatters, and this is one. - * The others are {@link DateTimeFormat} and {@link DateTimeFormatterBuilder}. - *

- * ISO8601 is the international standard for data interchange. It defines a - * framework, rather than an absolute standard. As a result this provider has a - * number of methods that represent common uses of the framework. The most common - * formats are {@link #date() date}, {@link #time() time}, and {@link #dateTime() dateTime}. - *

- * For example, to format a date time in ISO format: - *

- * DateTime dt = new DateTime();
- * DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
- * String str = fmt.print(dt);
- * 
- *

- * Note that these formatters mostly follow the ISO8601 standard for printing. - * For parsing, the formatters are more lenient and allow formats that are not - * in strict compliance with the standard. - *

- * It is important to understand that these formatters are not linked to - * the ISOChronology. These formatters may be used with any - * chronology, however there may be certain side effects with more unusual - * chronologies. For example, the ISO formatters rely on dayOfWeek being - * single digit, dayOfMonth being two digit and dayOfYear being three digit. - * A chronology with a ten day week would thus cause issues. However, in - * general, it is safe to use these formatters with other chronologies. - *

- * ISODateTimeFormat is thread-safe and immutable, and the formatters it - * returns are as well. - * - * @author Brian S O'Neill - * @since 1.0 - * @see DateTimeFormat - * @see DateTimeFormatterBuilder - */ -public class StrictISODateTimeFormat { - - /** - * Constructor. - * - * @since 1.1 (previously private) - */ - protected StrictISODateTimeFormat() { - super(); - } - - // ----------------------------------------------------------------------- - /** - * Returns a formatter that outputs only those fields specified. - *

- * This method examines the fields provided and returns an ISO-style - * formatter that best fits. This can be useful for outputting - * less-common ISO styles, such as YearMonth (YYYY-MM) or MonthDay (--MM-DD). - *

- * The list provided may have overlapping fields, such as dayOfWeek and - * dayOfMonth. In this case, the style is chosen based on the following - * list, thus in the example, the calendar style is chosen as dayOfMonth - * is higher in priority than dayOfWeek: - *

    - *
  • monthOfYear - calendar date style - *
  • dayOfYear - ordinal date style - *
  • weekOfWeekYear - week date style - *
  • dayOfMonth - calendar date style - *
  • dayOfWeek - week date style - *
  • year - *
  • weekyear - *
- * The supported formats are: - *
-     * Extended      Basic       Fields
-     * 2005-03-25    20050325    year/monthOfYear/dayOfMonth
-     * 2005-03       2005-03     year/monthOfYear
-     * 2005--25      2005--25    year/dayOfMonth *
-     * 2005          2005        year
-     * --03-25       --0325      monthOfYear/dayOfMonth
-     * --03          --03        monthOfYear
-     * ---03         ---03       dayOfMonth
-     * 2005-084      2005084     year/dayOfYear
-     * -084          -084        dayOfYear
-     * 2005-W12-5    2005W125    weekyear/weekOfWeekyear/dayOfWeek
-     * 2005-W-5      2005W-5     weekyear/dayOfWeek *
-     * 2005-W12      2005W12     weekyear/weekOfWeekyear
-     * -W12-5        -W125       weekOfWeekyear/dayOfWeek
-     * -W12          -W12        weekOfWeekyear
-     * -W-5          -W-5        dayOfWeek
-     * 10:20:30.040  102030.040  hour/minute/second/milli
-     * 10:20:30      102030      hour/minute/second
-     * 10:20         1020        hour/minute
-     * 10            10          hour
-     * -20:30.040    -2030.040   minute/second/milli
-     * -20:30        -2030       minute/second
-     * -20           -20         minute
-     * --30.040      --30.040    second/milli
-     * --30          --30        second
-     * ---.040       ---.040     milli *
-     * 10-30.040     10-30.040   hour/second/milli *
-     * 10:20-.040    1020-.040   hour/minute/milli *
-     * 10-30         10-30       hour/second *
-     * 10--.040      10--.040    hour/milli *
-     * -20-.040      -20-.040    minute/milli *
-     *   plus datetime formats like {date}T{time}
-     * 
- * * indicates that this is not an official ISO format and can be excluded - * by passing in strictISO as true. - *

- * This method can side effect the input collection of fields. - * If the input collection is modifiable, then each field that was added to - * the formatter will be removed from the collection, including any duplicates. - * If the input collection is unmodifiable then no side effect occurs. - *

- * This side effect processing is useful if you need to know whether all - * the fields were converted into the formatter or not. To achieve this, - * pass in a modifiable list, and check that it is empty on exit. - * - * @param fields the fields to get a formatter for, not null, - * updated by the method call unless unmodifiable, - * removing those fields built in the formatter - * @param extended true to use the extended format (with separators) - * @param strictISO true to stick exactly to ISO8601, false to include additional formats - * @return a suitable formatter - * @throws IllegalArgumentException if there is no format for the fields - * @since 1.1 - */ - public static DateTimeFormatter forFields(Collection fields, boolean extended, boolean strictISO) { - - if (fields == null || fields.size() == 0) { - throw new IllegalArgumentException("The fields must not be null or empty"); - } - Set workingFields = new HashSet<>(fields); - int inputSize = workingFields.size(); - boolean reducedPrec = false; - DateTimeFormatterBuilder bld = new DateTimeFormatterBuilder(); - // date - if (workingFields.contains(DateTimeFieldType.monthOfYear())) { - reducedPrec = dateByMonth(bld, workingFields, extended, strictISO); - } else if (workingFields.contains(DateTimeFieldType.dayOfYear())) { - reducedPrec = dateByOrdinal(bld, workingFields, extended); - } else if (workingFields.contains(DateTimeFieldType.weekOfWeekyear())) { - reducedPrec = dateByWeek(bld, workingFields, extended, strictISO); - } else if (workingFields.contains(DateTimeFieldType.dayOfMonth())) { - reducedPrec = dateByMonth(bld, workingFields, extended, strictISO); - } else if (workingFields.contains(DateTimeFieldType.dayOfWeek())) { - reducedPrec = dateByWeek(bld, workingFields, extended, strictISO); - } else if (workingFields.remove(DateTimeFieldType.year())) { - bld.append(Constants.ye); - reducedPrec = true; - } else if (workingFields.remove(DateTimeFieldType.weekyear())) { - bld.append(Constants.we); - reducedPrec = true; - } - boolean datePresent = (workingFields.size() < inputSize); - - // time - time(bld, workingFields, extended, strictISO, reducedPrec, datePresent); - - // result - if (bld.canBuildFormatter() == false) { - throw new IllegalArgumentException("No valid format for fields: " + fields); - } - - // side effect the input collection to indicate the processed fields - // handling unmodifiable collections with no side effect - try { - fields.retainAll(workingFields); - } catch (UnsupportedOperationException ex) { - // ignore, so we can handle unmodifiable collections - } - return bld.toFormatter(); - } - - // ----------------------------------------------------------------------- - /** - * Creates a date using the calendar date format. - * Specification reference: 5.2.1. - * - * @param bld the builder - * @param fields the fields - * @param extended true to use extended format - * @param strictISO true to only allow ISO formats - * @return true if reduced precision - * @since 1.1 - */ - private static boolean dateByMonth( - DateTimeFormatterBuilder bld, - Collection fields, - boolean extended, - boolean strictISO - ) { - - boolean reducedPrec = false; - if (fields.remove(DateTimeFieldType.year())) { - bld.append(Constants.ye); - if (fields.remove(DateTimeFieldType.monthOfYear())) { - if (fields.remove(DateTimeFieldType.dayOfMonth())) { - // YYYY-MM-DD/YYYYMMDD - appendSeparator(bld, extended); - bld.appendMonthOfYear(2); - appendSeparator(bld, extended); - bld.appendDayOfMonth(2); - } else { - // YYYY-MM/YYYY-MM - bld.appendLiteral('-'); - bld.appendMonthOfYear(2); - reducedPrec = true; - } - } else { - if (fields.remove(DateTimeFieldType.dayOfMonth())) { - // YYYY--DD/YYYY--DD (non-iso) - checkNotStrictISO(fields, strictISO); - bld.appendLiteral('-'); - bld.appendLiteral('-'); - bld.appendDayOfMonth(2); - } else { - // YYYY/YYYY - reducedPrec = true; - } - } - - } else if (fields.remove(DateTimeFieldType.monthOfYear())) { - bld.appendLiteral('-'); - bld.appendLiteral('-'); - bld.appendMonthOfYear(2); - if (fields.remove(DateTimeFieldType.dayOfMonth())) { - // --MM-DD/--MMDD - appendSeparator(bld, extended); - bld.appendDayOfMonth(2); - } else { - // --MM/--MM - reducedPrec = true; - } - } else if (fields.remove(DateTimeFieldType.dayOfMonth())) { - // ---DD/---DD - bld.appendLiteral('-'); - bld.appendLiteral('-'); - bld.appendLiteral('-'); - bld.appendDayOfMonth(2); - } - return reducedPrec; - } - - // ----------------------------------------------------------------------- - /** - * Creates a date using the ordinal date format. - * Specification reference: 5.2.2. - * - * @param bld the builder - * @param fields the fields - * @param extended true to use extended format - * @since 1.1 - */ - private static boolean dateByOrdinal(DateTimeFormatterBuilder bld, Collection fields, boolean extended) { - - boolean reducedPrec = false; - if (fields.remove(DateTimeFieldType.year())) { - bld.append(Constants.ye); - if (fields.remove(DateTimeFieldType.dayOfYear())) { - // YYYY-DDD/YYYYDDD - appendSeparator(bld, extended); - bld.appendDayOfYear(3); - } else { - // YYYY/YYYY - reducedPrec = true; - } - - } else if (fields.remove(DateTimeFieldType.dayOfYear())) { - // -DDD/-DDD - bld.appendLiteral('-'); - bld.appendDayOfYear(3); - } - return reducedPrec; - } - - // ----------------------------------------------------------------------- - /** - * Creates a date using the calendar date format. - * Specification reference: 5.2.3. - * - * @param bld the builder - * @param fields the fields - * @param extended true to use extended format - * @param strictISO true to only allow ISO formats - * @since 1.1 - */ - private static boolean dateByWeek( - DateTimeFormatterBuilder bld, - Collection fields, - boolean extended, - boolean strictISO - ) { - - boolean reducedPrec = false; - if (fields.remove(DateTimeFieldType.weekyear())) { - bld.append(Constants.we); - if (fields.remove(DateTimeFieldType.weekOfWeekyear())) { - appendSeparator(bld, extended); - bld.appendLiteral('W'); - bld.appendWeekOfWeekyear(2); - if (fields.remove(DateTimeFieldType.dayOfWeek())) { - // YYYY-WWW-D/YYYYWWWD - appendSeparator(bld, extended); - bld.appendDayOfWeek(1); - } else { - // YYYY-WWW/YYYY-WWW - reducedPrec = true; - } - } else { - if (fields.remove(DateTimeFieldType.dayOfWeek())) { - // YYYY-W-D/YYYYW-D (non-iso) - checkNotStrictISO(fields, strictISO); - appendSeparator(bld, extended); - bld.appendLiteral('W'); - bld.appendLiteral('-'); - bld.appendDayOfWeek(1); - } else { - // YYYY/YYYY - reducedPrec = true; - } - } - - } else if (fields.remove(DateTimeFieldType.weekOfWeekyear())) { - bld.appendLiteral('-'); - bld.appendLiteral('W'); - bld.appendWeekOfWeekyear(2); - if (fields.remove(DateTimeFieldType.dayOfWeek())) { - // -WWW-D/-WWWD - appendSeparator(bld, extended); - bld.appendDayOfWeek(1); - } else { - // -WWW/-WWW - reducedPrec = true; - } - } else if (fields.remove(DateTimeFieldType.dayOfWeek())) { - // -W-D/-W-D - bld.appendLiteral('-'); - bld.appendLiteral('W'); - bld.appendLiteral('-'); - bld.appendDayOfWeek(1); - } - return reducedPrec; - } - - // ----------------------------------------------------------------------- - /** - * Adds the time fields to the builder. - * Specification reference: 5.3.1. - * - * @param bld the builder - * @param fields the fields - * @param extended whether to use the extended format - * @param strictISO whether to be strict - * @param reducedPrec whether the date was reduced precision - * @param datePresent whether there was a date - * @since 1.1 - */ - private static void time( - DateTimeFormatterBuilder bld, - Collection fields, - boolean extended, - boolean strictISO, - boolean reducedPrec, - boolean datePresent - ) { - - boolean hour = fields.remove(DateTimeFieldType.hourOfDay()); - boolean minute = fields.remove(DateTimeFieldType.minuteOfHour()); - boolean second = fields.remove(DateTimeFieldType.secondOfMinute()); - boolean milli = fields.remove(DateTimeFieldType.millisOfSecond()); - if (!hour && !minute && !second && !milli) { - return; - } - if (hour || minute || second || milli) { - if (strictISO && reducedPrec) { - throw new IllegalArgumentException("No valid ISO8601 format for fields because Date was reduced precision: " + fields); - } - if (datePresent) { - bld.appendLiteral('T'); - } - } - if (hour && minute && second || (hour && !second && !milli)) { - // OK - HMSm/HMS/HM/H - valid in combination with date - } else { - if (strictISO && datePresent) { - throw new IllegalArgumentException("No valid ISO8601 format for fields because Time was truncated: " + fields); - } - if (!hour && (minute && second || (minute && !milli) || second)) { - // OK - MSm/MS/M/Sm/S - valid ISO formats - } else { - if (strictISO) { - throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields); - } - } - } - if (hour) { - bld.appendHourOfDay(2); - } else if (minute || second || milli) { - bld.appendLiteral('-'); - } - if (extended && hour && minute) { - bld.appendLiteral(':'); - } - if (minute) { - bld.appendMinuteOfHour(2); - } else if (second || milli) { - bld.appendLiteral('-'); - } - if (extended && minute && second) { - bld.appendLiteral(':'); - } - if (second) { - bld.appendSecondOfMinute(2); - } else if (milli) { - bld.appendLiteral('-'); - } - if (milli) { - bld.appendLiteral('.'); - bld.appendMillisOfSecond(3); - } - } - - // ----------------------------------------------------------------------- - /** - * Checks that the iso only flag is not set, throwing an exception if it is. - * - * @param fields the fields - * @param strictISO true if only ISO formats allowed - * @since 1.1 - */ - private static void checkNotStrictISO(Collection fields, boolean strictISO) { - if (strictISO) { - throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields); - } - } - - /** - * Appends the separator if necessary. - * - * @param bld the builder - * @param extended whether to append the separator - * @since 1.1 - */ - private static void appendSeparator(DateTimeFormatterBuilder bld, boolean extended) { - if (extended) { - bld.appendLiteral('-'); - } - } - - // ----------------------------------------------------------------------- - /** - * Returns a generic ISO date parser for parsing dates with a possible zone. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * It accepts formats described by the following syntax: - *

-     * date              = date-element ['T' offset]
-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
-     * 
- */ - public static DateTimeFormatter dateParser() { - return Constants.dp; - } - - /** - * Returns a generic ISO date parser for parsing local dates. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * This parser is initialised with the local (UTC) time zone. - *

- * It accepts formats described by the following syntax: - *

-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * 
- * @since 1.3 - */ - public static DateTimeFormatter localDateParser() { - return Constants.ldp; - } - - /** - * Returns a generic ISO date parser for parsing dates. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * It accepts formats described by the following syntax: - *

-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * 
- */ - public static DateTimeFormatter dateElementParser() { - return Constants.dpe; - } - - /** - * Returns a generic ISO time parser for parsing times with a possible zone. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * time           = ['T'] time-element [offset]
-     * time-element   = HH [minute-element] | [fraction]
-     * minute-element = ':' mm [second-element] | [fraction]
-     * second-element = ':' ss [fraction]
-     * fraction       = ('.' | ',') digit+
-     * offset         = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
-     * 
- */ - public static DateTimeFormatter timeParser() { - return Constants.tp; - } - - /** - * Returns a generic ISO time parser for parsing local times. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * This parser is initialised with the local (UTC) time zone. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * time           = ['T'] time-element
-     * time-element   = HH [minute-element] | [fraction]
-     * minute-element = ':' mm [second-element] | [fraction]
-     * second-element = ':' ss [fraction]
-     * fraction       = ('.' | ',') digit+
-     * 
- * @since 1.3 - */ - public static DateTimeFormatter localTimeParser() { - return Constants.ltp; - } - - /** - * Returns a generic ISO time parser. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * time-element   = HH [minute-element] | [fraction]
-     * minute-element = ':' mm [second-element] | [fraction]
-     * second-element = ':' ss [fraction]
-     * fraction       = ('.' | ',') digit+
-     * 
- */ - public static DateTimeFormatter timeElementParser() { - return Constants.tpe; - } - - /** - * Returns a generic ISO datetime parser which parses either a date or a time or both. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * datetime          = time | date-opt-time
-     * time              = 'T' time-element [offset]
-     * date-opt-time     = date-element ['T' [time-element] [offset]]
-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * time-element      = HH [minute-element] | [fraction]
-     * minute-element    = ':' mm [second-element] | [fraction]
-     * second-element    = ':' ss [fraction]
-     * fraction          = ('.' | ',') digit+
-     * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
-     * 
- */ - public static DateTimeFormatter dateTimeParser() { - return Constants.dtp; - } - - /** - * Returns a generic ISO datetime parser where the date is mandatory and the time is optional. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * This parser can parse zoned datetimes. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * date-opt-time     = date-element ['T' [time-element] [offset]]
-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * time-element      = HH [minute-element] | [fraction]
-     * minute-element    = ':' mm [second-element] | [fraction]
-     * second-element    = ':' ss [fraction]
-     * fraction          = ('.' | ',') digit+
-     * 
- * @since 1.3 - */ - public static DateTimeFormatter dateOptionalTimeParser() { - return Constants.dotp; - } - - /** - * Returns a generic ISO datetime parser where the date is mandatory and the time is optional. - *

- * The returned formatter can only be used for parsing, printing is unsupported. - *

- * This parser only parses local datetimes. - * This parser is initialised with the local (UTC) time zone. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * It accepts formats described by the following syntax: - *

-     * datetime          = date-element ['T' time-element]
-     * date-element      = std-date-element | ord-date-element | week-date-element
-     * std-date-element  = yyyy ['-' MM ['-' dd]]
-     * ord-date-element  = yyyy ['-' DDD]
-     * week-date-element = xxxx '-W' ww ['-' e]
-     * time-element      = HH [minute-element] | [fraction]
-     * minute-element    = ':' mm [second-element] | [fraction]
-     * second-element    = ':' ss [fraction]
-     * fraction          = ('.' | ',') digit+
-     * 
- * @since 1.3 - */ - public static DateTimeFormatter localDateOptionalTimeParser() { - return Constants.ldotp; - } - - // ----------------------------------------------------------------------- - /** - * 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). - *

- * The returned formatter prints and parses only this format. - * See {@link #dateParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-MM-dd - */ - public static DateTimeFormatter date() { - return yearMonthDay(); - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, two digit second of minute, three digit fraction of second, and - * time zone offset (HH:mm:ss.SSSZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * See {@link #timeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for HH:mm:ss.SSSZZ - */ - public static DateTimeFormatter time() { - return Constants.t; - } - - /** - * 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). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * See {@link #timeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for HH:mm:ssZZ - */ - public static DateTimeFormatter timeNoMillis() { - return Constants.tx; - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, two digit second of minute, three digit fraction of second, and - * time zone offset prefixed by 'T' ('T'HH:mm:ss.SSSZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * See {@link #timeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for 'T'HH:mm:ss.SSSZZ - */ - public static DateTimeFormatter tTime() { - return Constants.tt; - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, two digit second of minute, and time zone offset prefixed - * by 'T' ('T'HH:mm:ssZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * See {@link #timeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for 'T'HH:mm:ssZZ - */ - public static DateTimeFormatter tTimeNoMillis() { - return Constants.ttx; - } - - /** - * Returns a formatter that combines a full date and time, separated by a 'T' - * (yyyy-MM-dd'T'HH:mm:ss.SSSZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSSZZ - */ - public static DateTimeFormatter dateTime() { - return Constants.dt; - } - - /** - * Returns a formatter that combines a full date and time without millis, - * separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-MM-dd'T'HH:mm:ssZZ - */ - public static DateTimeFormatter dateTimeNoMillis() { - return Constants.dtx; - } - - /** - * Returns a formatter for a full ordinal date, using a four - * digit year and three digit dayOfYear (yyyy-DDD). - *

- * The returned formatter prints and parses only this format. - * See {@link #dateParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-DDD - * @since 1.1 - */ - public static DateTimeFormatter ordinalDate() { - return Constants.od; - } - - /** - * 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). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-DDD'T'HH:mm:ss.SSSZZ - * @since 1.1 - */ - public static DateTimeFormatter ordinalDateTime() { - return Constants.odt; - } - - /** - * 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). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for yyyy-DDD'T'HH:mm:ssZZ - * @since 1.1 - */ - public static DateTimeFormatter ordinalDateTimeNoMillis() { - return Constants.odtx; - } - - /** - * 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). - *

- * The returned formatter prints and parses only this format. - * See {@link #dateParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for xxxx-'W'ww-e - */ - public static DateTimeFormatter weekDate() { - return Constants.wwd; - } - - /** - * Returns a formatter that combines a full weekyear date and time, - * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ - */ - public static DateTimeFormatter weekDateTime() { - return Constants.wdt; - } - - /** - * 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). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * See {@link #dateTimeParser()} for a more flexible parser that accepts different formats. - * - * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ssZZ - */ - public static DateTimeFormatter weekDateTimeNoMillis() { - return Constants.wdtx; - } - - // ----------------------------------------------------------------------- - /** - * Returns a basic formatter for a full date as four digit year, two digit - * month of year, and two digit day of month (yyyyMMdd). - *

- * The returned formatter prints and parses only this format. - * - * @return a formatter for yyyyMMdd - */ - public static DateTimeFormatter basicDate() { - return Constants.bd; - } - - /** - * Returns a basic formatter for a two digit hour of day, two digit minute - * of hour, two digit second of minute, three digit millis, and time zone - * offset (HHmmss.SSSZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * - * @return a formatter for HHmmss.SSSZ - */ - public static DateTimeFormatter basicTime() { - return Constants.bt; - } - - /** - * 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). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * - * @return a formatter for HHmmssZ - */ - public static DateTimeFormatter basicTimeNoMillis() { - return Constants.btx; - } - - /** - * Returns a basic formatter for a two digit hour of day, two digit minute - * of hour, two digit second of minute, three digit millis, and time zone - * offset prefixed by 'T' ('T'HHmmss.SSSZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * - * @return a formatter for 'T'HHmmss.SSSZ - */ - public static DateTimeFormatter basicTTime() { - return Constants.btt; - } - - /** - * 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 prefixed by 'T' - * ('T'HHmmssZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * - * @return a formatter for 'T'HHmmssZ - */ - public static DateTimeFormatter basicTTimeNoMillis() { - return Constants.bttx; - } - - /** - * Returns a basic formatter that combines a basic date and time, separated - * by a 'T' (yyyyMMdd'T'HHmmss.SSSZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * - * @return a formatter for yyyyMMdd'T'HHmmss.SSSZ - */ - public static DateTimeFormatter basicDateTime() { - return Constants.bdt; - } - - /** - * Returns a basic formatter that combines a basic date and time without millis, - * separated by a 'T' (yyyyMMdd'T'HHmmssZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * - * @return a formatter for yyyyMMdd'T'HHmmssZ - */ - public static DateTimeFormatter basicDateTimeNoMillis() { - return Constants.bdtx; - } - - /** - * Returns a formatter for a full ordinal date, using a four - * digit year and three digit dayOfYear (yyyyDDD). - *

- * The returned formatter prints and parses only this format. - * - * @return a formatter for yyyyDDD - * @since 1.1 - */ - public static DateTimeFormatter basicOrdinalDate() { - return Constants.bod; - } - - /** - * Returns a formatter for a full ordinal date and time, using a four - * digit year and three digit dayOfYear (yyyyDDD'T'HHmmss.SSSZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * - * @return a formatter for yyyyDDD'T'HHmmss.SSSZ - * @since 1.1 - */ - public static DateTimeFormatter basicOrdinalDateTime() { - return Constants.bodt; - } - - /** - * Returns a formatter for a full ordinal date and time without millis, - * using a four digit year and three digit dayOfYear (yyyyDDD'T'HHmmssZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * - * @return a formatter for yyyyDDD'T'HHmmssZ - * @since 1.1 - */ - public static DateTimeFormatter basicOrdinalDateTimeNoMillis() { - return Constants.bodtx; - } - - /** - * 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). - *

- * The returned formatter prints and parses only this format. - * - * @return a formatter for xxxx'W'wwe - */ - public static DateTimeFormatter basicWeekDate() { - return Constants.bwd; - } - - /** - * Returns a basic formatter that combines a basic weekyear date and time, - * separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which includes milliseconds. - * - * @return a formatter for xxxx'W'wwe'T'HHmmss.SSSZ - */ - public static DateTimeFormatter basicWeekDateTime() { - return Constants.bwdt; - } - - /** - * Returns a basic formatter that combines a basic weekyear date and time - * without millis, separated by a 'T' (xxxx'W'wwe'T'HHmmssZ). - *

- * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero. - * The parser is strict by default, thus time string {@code 24:00} cannot be parsed. - *

- * The returned formatter prints and parses only this format, which excludes milliseconds. - * - * @return a formatter for xxxx'W'wwe'T'HHmmssZ - */ - public static DateTimeFormatter basicWeekDateTimeNoMillis() { - return Constants.bwdtx; - } - - // ----------------------------------------------------------------------- - /** - * Returns a formatter for a four digit year. (yyyy) - * - * @return a formatter for yyyy - */ - public static DateTimeFormatter year() { - return Constants.ye; - } - - /** - * Returns a formatter for a four digit year and two digit month of - * year. (yyyy-MM) - * - * @return a formatter for yyyy-MM - */ - public static DateTimeFormatter yearMonth() { - return Constants.ym; - } - - /** - * Returns a formatter for a four digit year, two digit month of year, and - * two digit day of month. (yyyy-MM-dd) - * - * @return a formatter for yyyy-MM-dd - */ - public static DateTimeFormatter yearMonthDay() { - return Constants.ymd; - } - - /** - * Returns a formatter for a four digit weekyear. (xxxx) - * - * @return a formatter for xxxx - */ - public static DateTimeFormatter weekyear() { - return Constants.we; - } - - /** - * Returns a formatter for a four digit weekyear and two digit week of - * weekyear. (xxxx-'W'ww) - * - * @return a formatter for xxxx-'W'ww - */ - public static DateTimeFormatter weekyearWeek() { - return Constants.ww; - } - - /** - * Returns a formatter for a four digit weekyear, two digit week of - * weekyear, and one digit day of week. (xxxx-'W'ww-e) - * - * @return a formatter for xxxx-'W'ww-e - */ - public static DateTimeFormatter weekyearWeekDay() { - return Constants.wwd; - } - - /** - * Returns a formatter for a two digit hour of day. (HH) - * - * @return a formatter for HH - */ - public static DateTimeFormatter hour() { - return Constants.hde; - } - - /** - * Returns a formatter for a two digit hour of day and two digit minute of - * hour. (HH:mm) - * - * @return a formatter for HH:mm - */ - public static DateTimeFormatter hourMinute() { - return Constants.hm; - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, and two digit second of minute. (HH:mm:ss) - * - * @return a formatter for HH:mm:ss - */ - public static DateTimeFormatter hourMinuteSecond() { - return Constants.hms; - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, two digit second of minute, and three digit fraction of - * second (HH:mm:ss.SSS). Parsing will parse up to 3 fractional second - * digits. - * - * @return a formatter for HH:mm:ss.SSS - */ - public static DateTimeFormatter hourMinuteSecondMillis() { - return Constants.hmsl; - } - - /** - * Returns a formatter for a two digit hour of day, two digit minute of - * hour, two digit second of minute, and three digit fraction of - * second (HH:mm:ss.SSS). Parsing will parse up to 9 fractional second - * digits, throwing away all except the first three. - * - * @return a formatter for HH:mm:ss.SSS - */ - public static DateTimeFormatter hourMinuteSecondFraction() { - return Constants.hmsf; - } - - /** - * Returns a formatter that combines a full date and two digit hour of - * day. (yyyy-MM-dd'T'HH) - * - * @return a formatter for yyyy-MM-dd'T'HH - */ - public static DateTimeFormatter dateHour() { - return Constants.dh; - } - - /** - * 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) - * - * @return a formatter for yyyy-MM-dd'T'HH:mm - */ - public static DateTimeFormatter dateHourMinute() { - return Constants.dhm; - } - - /** - * Returns a formatter that combines a full date, two digit hour of day, - * two digit minute of hour, and two digit second of - * minute. (yyyy-MM-dd'T'HH:mm:ss) - * - * @return a formatter for yyyy-MM-dd'T'HH:mm:ss - */ - public static DateTimeFormatter dateHourMinuteSecond() { - return Constants.dhms; - } - - /** - * 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). Parsing will parse up - * to 3 fractional second digits. - * - * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS - */ - public static DateTimeFormatter dateHourMinuteSecondMillis() { - return Constants.dhmsl; - } - - /** - * 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). Parsing will parse up - * to 9 fractional second digits, throwing away all except the first three. - * - * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS - */ - public static DateTimeFormatter dateHourMinuteSecondFraction() { - return Constants.dhmsf; - } - - // ----------------------------------------------------------------------- - static final class Constants { - private static final DateTimeFormatter ye = yearElement(), // year element (yyyy) - mye = monthElement(), // monthOfYear element (-MM) - dme = dayOfMonthElement(), // dayOfMonth element (-dd) - we = weekyearElement(), // weekyear element (xxxx) - wwe = weekElement(), // weekOfWeekyear element (-ww) - dwe = dayOfWeekElement(), // dayOfWeek element (-ee) - dye = dayOfYearElement(), // dayOfYear element (-DDD) - hde = hourElement(), // hourOfDay element (HH) - mhe = minuteElement(), // minuteOfHour element (:mm) - sme = secondElement(), // secondOfMinute element (:ss) - fse = fractionElement(), // fractionOfSecond element (.SSSSSSSSS) - ze = offsetElement(), // zone offset element - lte = literalTElement(), // literal 'T' element - - // y, // year (same as year element) - ym = yearMonth(), // year month - ymd = yearMonthDay(), // year month day - - // w, // weekyear (same as weekyear element) - ww = weekyearWeek(), // weekyear week - wwd = weekyearWeekDay(), // weekyear week day - - // h, // hour (same as hour element) - hm = hourMinute(), // hour minute - hms = hourMinuteSecond(), // hour minute second - hmsl = hourMinuteSecondMillis(), // hour minute second millis - hmsf = hourMinuteSecondFraction(), // hour minute second fraction - - dh = dateHour(), // date hour - dhm = dateHourMinute(), // date hour minute - dhms = dateHourMinuteSecond(), // date hour minute second - dhmsl = dateHourMinuteSecondMillis(), // date hour minute second millis - dhmsf = dateHourMinuteSecondFraction(), // date hour minute second fraction - - // d, // date (same as ymd) - t = time(), // time - tx = timeNoMillis(), // time no millis - tt = tTime(), // Ttime - ttx = tTimeNoMillis(), // Ttime no millis - dt = dateTime(), // date time - dtx = dateTimeNoMillis(), // date time no millis - - // wd, // week date (same as wwd) - wdt = weekDateTime(), // week date time - wdtx = weekDateTimeNoMillis(), // week date time no millis - - od = ordinalDate(), // ordinal date (same as yd) - odt = ordinalDateTime(), // ordinal date time - odtx = ordinalDateTimeNoMillis(), // ordinal date time no millis - - bd = basicDate(), // basic date - bt = basicTime(), // basic time - btx = basicTimeNoMillis(), // basic time no millis - btt = basicTTime(), // basic Ttime - bttx = basicTTimeNoMillis(), // basic Ttime no millis - bdt = basicDateTime(), // basic date time - bdtx = basicDateTimeNoMillis(), // basic date time no millis - - bod = basicOrdinalDate(), // basic ordinal date - bodt = basicOrdinalDateTime(), // basic ordinal date time - bodtx = basicOrdinalDateTimeNoMillis(), // basic ordinal date time no millis - - bwd = basicWeekDate(), // basic week date - bwdt = basicWeekDateTime(), // basic week date time - bwdtx = basicWeekDateTimeNoMillis(), // basic week date time no millis - - dpe = dateElementParser(), // date parser element - tpe = timeElementParser(), // time parser element - dp = dateParser(), // date parser - ldp = localDateParser(), // local date parser - tp = timeParser(), // time parser - ltp = localTimeParser(), // local time parser - dtp = dateTimeParser(), // date time parser - dotp = dateOptionalTimeParser(), // date optional time parser - ldotp = localDateOptionalTimeParser(); // local date optional time parser - - // ----------------------------------------------------------------------- - private static DateTimeFormatter dateParser() { - if (dp == null) { - DateTimeParser tOffset = new DateTimeFormatterBuilder().appendLiteral('T').append(offsetElement()).toParser(); - return new DateTimeFormatterBuilder().append(dateElementParser()).appendOptional(tOffset).toFormatter(); - } - return dp; - } - - private static DateTimeFormatter localDateParser() { - if (ldp == null) { - return dateElementParser().withZoneUTC(); - } - return ldp; - } - - private static DateTimeFormatter dateElementParser() { - if (dpe == null) { - return new DateTimeFormatterBuilder().append( - null, - new DateTimeParser[] { - new DateTimeFormatterBuilder().append(yearElement()) - .appendOptional( - new DateTimeFormatterBuilder().append(monthElement()) - .appendOptional(dayOfMonthElement().getParser()) - .toParser() - ) - .toParser(), - new DateTimeFormatterBuilder().append(weekyearElement()) - .append(weekElement()) - .appendOptional(dayOfWeekElement().getParser()) - .toParser(), - new DateTimeFormatterBuilder().append(yearElement()).append(dayOfYearElement()).toParser() } - ).toFormatter(); - } - return dpe; - } - - private static DateTimeFormatter timeParser() { - if (tp == null) { - return new DateTimeFormatterBuilder().appendOptional(literalTElement().getParser()) - .append(timeElementParser()) - .appendOptional(offsetElement().getParser()) - .toFormatter(); - } - return tp; - } - - private static DateTimeFormatter localTimeParser() { - if (ltp == null) { - return new DateTimeFormatterBuilder().appendOptional(literalTElement().getParser()) - .append(timeElementParser()) - .toFormatter() - .withZoneUTC(); - } - return ltp; - } - - private static DateTimeFormatter timeElementParser() { - if (tpe == null) { - // Decimal point can be either '.' or ',' - DateTimeParser decimalPoint = new DateTimeFormatterBuilder().append( - null, - new DateTimeParser[] { - new DateTimeFormatterBuilder().appendLiteral('.').toParser(), - new DateTimeFormatterBuilder().appendLiteral(',').toParser() } - ).toParser(); - - return new DateTimeFormatterBuilder() - // time-element - .append(hourElement()) - .append( - null, - new DateTimeParser[] { - new DateTimeFormatterBuilder() - // minute-element - .append(minuteElement()) - .append( - null, - new DateTimeParser[] { - new DateTimeFormatterBuilder() - // second-element - .append(secondElement()) - // second fraction - .appendOptional( - new DateTimeFormatterBuilder().append(decimalPoint).appendFractionOfSecond(1, 9).toParser() - ) - .toParser(), - // minute fraction - new DateTimeFormatterBuilder().append(decimalPoint).appendFractionOfMinute(1, 9).toParser(), - null } - ) - .toParser(), - // hour fraction - new DateTimeFormatterBuilder().append(decimalPoint).appendFractionOfHour(1, 9).toParser(), - null } - ) - .toFormatter(); - } - return tpe; - } - - private static DateTimeFormatter dateTimeParser() { - if (dtp == null) { - // This is different from the general time parser in that the 'T' - // is required. - DateTimeParser time = new DateTimeFormatterBuilder().appendLiteral('T') - .append(timeElementParser()) - .appendOptional(offsetElement().getParser()) - .toParser(); - return new DateTimeFormatterBuilder().append(null, new DateTimeParser[] { time, dateOptionalTimeParser().getParser() }) - .toFormatter(); - } - return dtp; - } - - private static DateTimeFormatter dateOptionalTimeParser() { - if (dotp == null) { - DateTimeParser timeOrOffset = new DateTimeFormatterBuilder().appendLiteral('T') - .appendOptional(timeElementParser().getParser()) - .appendOptional(offsetElement().getParser()) - .toParser(); - return new DateTimeFormatterBuilder().append(dateElementParser()).appendOptional(timeOrOffset).toFormatter(); - } - return dotp; - } - - private static DateTimeFormatter localDateOptionalTimeParser() { - if (ldotp == null) { - DateTimeParser time = new DateTimeFormatterBuilder().appendLiteral('T').append(timeElementParser()).toParser(); - return new DateTimeFormatterBuilder().append(dateElementParser()).appendOptional(time).toFormatter().withZoneUTC(); - } - return ldotp; - } - - // ----------------------------------------------------------------------- - private static DateTimeFormatter time() { - if (t == null) { - return new DateTimeFormatterBuilder().append(hourMinuteSecondFraction()).append(offsetElement()).toFormatter(); - } - return t; - } - - private static DateTimeFormatter timeNoMillis() { - if (tx == null) { - return new DateTimeFormatterBuilder().append(hourMinuteSecond()).append(offsetElement()).toFormatter(); - } - return tx; - } - - private static DateTimeFormatter tTime() { - if (tt == null) { - return new DateTimeFormatterBuilder().append(literalTElement()).append(time()).toFormatter(); - } - return tt; - } - - private static DateTimeFormatter tTimeNoMillis() { - if (ttx == null) { - return new DateTimeFormatterBuilder().append(literalTElement()).append(timeNoMillis()).toFormatter(); - } - return ttx; - } - - private static DateTimeFormatter dateTime() { - if (dt == null) { - return new DateTimeFormatterBuilder().append(date()).append(tTime()).toFormatter(); - } - return dt; - } - - private static DateTimeFormatter dateTimeNoMillis() { - if (dtx == null) { - return new DateTimeFormatterBuilder().append(date()).append(tTimeNoMillis()).toFormatter(); - } - return dtx; - } - - private static DateTimeFormatter ordinalDate() { - if (od == null) { - return new DateTimeFormatterBuilder().append(yearElement()).append(dayOfYearElement()).toFormatter(); - } - return od; - } - - private static DateTimeFormatter ordinalDateTime() { - if (odt == null) { - return new DateTimeFormatterBuilder().append(ordinalDate()).append(tTime()).toFormatter(); - } - return odt; - } - - private static DateTimeFormatter ordinalDateTimeNoMillis() { - if (odtx == null) { - return new DateTimeFormatterBuilder().append(ordinalDate()).append(tTimeNoMillis()).toFormatter(); - } - return odtx; - } - - private static DateTimeFormatter weekDateTime() { - if (wdt == null) { - return new DateTimeFormatterBuilder().append(weekDate()).append(tTime()).toFormatter(); - } - return wdt; - } - - private static DateTimeFormatter weekDateTimeNoMillis() { - if (wdtx == null) { - return new DateTimeFormatterBuilder().append(weekDate()).append(tTimeNoMillis()).toFormatter(); - } - return wdtx; - } - - // ----------------------------------------------------------------------- - private static DateTimeFormatter basicDate() { - if (bd == null) { - return new DateTimeFormatterBuilder().appendYear(4, 4) - .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2) - .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2) - .toFormatter(); - } - return bd; - } - - private static DateTimeFormatter basicTime() { - if (bt == null) { - return new DateTimeFormatterBuilder().appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2) - .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2) - .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2) - .appendLiteral('.') - .appendFractionOfSecond(3, 9) - .appendTimeZoneOffset("Z", false, 2, 2) - .toFormatter(); - } - return bt; - } - - private static DateTimeFormatter basicTimeNoMillis() { - if (btx == null) { - return new DateTimeFormatterBuilder().appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2) - .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2) - .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2) - .appendTimeZoneOffset("Z", false, 2, 2) - .toFormatter(); - } - return btx; - } - - private static DateTimeFormatter basicTTime() { - if (btt == null) { - return new DateTimeFormatterBuilder().append(literalTElement()).append(basicTime()).toFormatter(); - } - return btt; - } - - private static DateTimeFormatter basicTTimeNoMillis() { - if (bttx == null) { - return new DateTimeFormatterBuilder().append(literalTElement()).append(basicTimeNoMillis()).toFormatter(); - } - return bttx; - } - - private static DateTimeFormatter basicDateTime() { - if (bdt == null) { - return new DateTimeFormatterBuilder().append(basicDate()).append(basicTTime()).toFormatter(); - } - return bdt; - } - - private static DateTimeFormatter basicDateTimeNoMillis() { - if (bdtx == null) { - return new DateTimeFormatterBuilder().append(basicDate()).append(basicTTimeNoMillis()).toFormatter(); - } - return bdtx; - } - - private static DateTimeFormatter basicOrdinalDate() { - if (bod == null) { - return new DateTimeFormatterBuilder().appendYear(4, 4).appendFixedDecimal(DateTimeFieldType.dayOfYear(), 3).toFormatter(); - } - return bod; - } - - private static DateTimeFormatter basicOrdinalDateTime() { - if (bodt == null) { - return new DateTimeFormatterBuilder().append(basicOrdinalDate()).append(basicTTime()).toFormatter(); - } - return bodt; - } - - private static DateTimeFormatter basicOrdinalDateTimeNoMillis() { - if (bodtx == null) { - return new DateTimeFormatterBuilder().append(basicOrdinalDate()).append(basicTTimeNoMillis()).toFormatter(); - } - return bodtx; - } - - private static DateTimeFormatter basicWeekDate() { - if (bwd == null) { - return new DateTimeFormatterBuilder() - // ES change, was .appendWeekyear(4, 4) - .appendFixedSignedDecimal(DateTimeFieldType.weekyear(), 4) - .appendLiteral('W') - .appendFixedDecimal(DateTimeFieldType.weekOfWeekyear(), 2) - .appendFixedDecimal(DateTimeFieldType.dayOfWeek(), 1) - .toFormatter(); - } - return bwd; - } - - private static DateTimeFormatter basicWeekDateTime() { - if (bwdt == null) { - return new DateTimeFormatterBuilder().append(basicWeekDate()).append(basicTTime()).toFormatter(); - } - return bwdt; - } - - private static DateTimeFormatter basicWeekDateTimeNoMillis() { - if (bwdtx == null) { - return new DateTimeFormatterBuilder().append(basicWeekDate()).append(basicTTimeNoMillis()).toFormatter(); - } - return bwdtx; - } - - // ----------------------------------------------------------------------- - private static DateTimeFormatter yearMonth() { - if (ym == null) { - return new DateTimeFormatterBuilder().append(yearElement()).append(monthElement()).toFormatter(); - } - return ym; - } - - private static DateTimeFormatter yearMonthDay() { - if (ymd == null) { - return new DateTimeFormatterBuilder().append(yearElement()) - .append(monthElement()) - .append(dayOfMonthElement()) - .toFormatter(); - } - return ymd; - } - - private static DateTimeFormatter weekyearWeek() { - if (ww == null) { - return new DateTimeFormatterBuilder().append(weekyearElement()).append(weekElement()).toFormatter(); - } - return ww; - } - - private static DateTimeFormatter weekyearWeekDay() { - if (wwd == null) { - return new DateTimeFormatterBuilder().append(weekyearElement()) - .append(weekElement()) - .append(dayOfWeekElement()) - .toFormatter(); - } - return wwd; - } - - private static DateTimeFormatter hourMinute() { - if (hm == null) { - return new DateTimeFormatterBuilder().append(hourElement()).append(minuteElement()).toFormatter(); - } - return hm; - } - - private static DateTimeFormatter hourMinuteSecond() { - if (hms == null) { - return new DateTimeFormatterBuilder().append(hourElement()).append(minuteElement()).append(secondElement()).toFormatter(); - } - return hms; - } - - private static DateTimeFormatter hourMinuteSecondMillis() { - if (hmsl == null) { - return new DateTimeFormatterBuilder().append(hourElement()) - .append(minuteElement()) - .append(secondElement()) - .appendLiteral('.') - .appendFractionOfSecond(3, 3) - .toFormatter(); - } - return hmsl; - } - - private static DateTimeFormatter hourMinuteSecondFraction() { - if (hmsf == null) { - return new DateTimeFormatterBuilder().append(hourElement()) - .append(minuteElement()) - .append(secondElement()) - .append(fractionElement()) - .toFormatter(); - } - return hmsf; - } - - private static DateTimeFormatter dateHour() { - if (dh == null) { - return new DateTimeFormatterBuilder().append(date()).append(literalTElement()).append(hour()).toFormatter(); - } - return dh; - } - - private static DateTimeFormatter dateHourMinute() { - if (dhm == null) { - return new DateTimeFormatterBuilder().append(date()).append(literalTElement()).append(hourMinute()).toFormatter(); - } - return dhm; - } - - private static DateTimeFormatter dateHourMinuteSecond() { - if (dhms == null) { - return new DateTimeFormatterBuilder().append(date()).append(literalTElement()).append(hourMinuteSecond()).toFormatter(); - } - return dhms; - } - - private static DateTimeFormatter dateHourMinuteSecondMillis() { - if (dhmsl == null) { - return new DateTimeFormatterBuilder().append(date()) - .append(literalTElement()) - .append(hourMinuteSecondMillis()) - .toFormatter(); - } - return dhmsl; - } - - private static DateTimeFormatter dateHourMinuteSecondFraction() { - if (dhmsf == null) { - return new DateTimeFormatterBuilder().append(date()) - .append(literalTElement()) - .append(hourMinuteSecondFraction()) - .toFormatter(); - } - return dhmsf; - } - - // ----------------------------------------------------------------------- - private static DateTimeFormatter yearElement() { - if (ye == null) { - return new DateTimeFormatterBuilder() - // ES change, was .appendYear(4, 9) - .appendFixedSignedDecimal(DateTimeFieldType.year(), 4) - .toFormatter(); - } - return ye; - } - - private static DateTimeFormatter monthElement() { - if (mye == null) { - return new DateTimeFormatterBuilder().appendLiteral('-') - // ES change, was .appendMonthOfYear(2) - .appendFixedSignedDecimal(DateTimeFieldType.monthOfYear(), 2) - .toFormatter(); - } - return mye; - } - - private static DateTimeFormatter dayOfMonthElement() { - if (dme == null) { - return new DateTimeFormatterBuilder().appendLiteral('-') - // ES change, was .appendDayOfMonth(2) - .appendFixedSignedDecimal(DateTimeFieldType.dayOfMonth(), 2) - .toFormatter(); - } - return dme; - } - - private static DateTimeFormatter weekyearElement() { - if (we == null) { - return new DateTimeFormatterBuilder() - // ES change, was .appendWeekyear(4, 9) - .appendFixedSignedDecimal(DateTimeFieldType.weekyear(), 4) - .toFormatter(); - } - return we; - } - - private static DateTimeFormatter weekElement() { - if (wwe == null) { - return new DateTimeFormatterBuilder().appendLiteral("-W") - // ES change, was .appendWeekOfWeekyear(2) - .appendFixedSignedDecimal(DateTimeFieldType.weekOfWeekyear(), 2) - .toFormatter(); - } - return wwe; - } - - private static DateTimeFormatter dayOfWeekElement() { - if (dwe == null) { - return new DateTimeFormatterBuilder().appendLiteral('-').appendDayOfWeek(1).toFormatter(); - } - return dwe; - } - - private static DateTimeFormatter dayOfYearElement() { - if (dye == null) { - return new DateTimeFormatterBuilder().appendLiteral('-') - // ES change, was .appendDayOfYear(3) - .appendFixedSignedDecimal(DateTimeFieldType.dayOfYear(), 3) - .toFormatter(); - } - return dye; - } - - private static DateTimeFormatter literalTElement() { - if (lte == null) { - return new DateTimeFormatterBuilder().appendLiteral('T').toFormatter(); - } - return lte; - } - - private static DateTimeFormatter hourElement() { - if (hde == null) { - return new DateTimeFormatterBuilder() - // ES change, was .appendHourOfDay(2) - .appendFixedSignedDecimal(DateTimeFieldType.hourOfDay(), 2) - .toFormatter(); - } - return hde; - } - - private static DateTimeFormatter minuteElement() { - if (mhe == null) { - return new DateTimeFormatterBuilder().appendLiteral(':') - // ES change, was .appendMinuteOfHour(2) - .appendFixedSignedDecimal(DateTimeFieldType.minuteOfHour(), 2) - .toFormatter(); - } - return mhe; - } - - private static DateTimeFormatter secondElement() { - if (sme == null) { - return new DateTimeFormatterBuilder().appendLiteral(':') - // ES change, was .appendSecondOfMinute(2) - .appendFixedSignedDecimal(DateTimeFieldType.secondOfMinute(), 2) - .toFormatter(); - } - return sme; - } - - private static DateTimeFormatter fractionElement() { - if (fse == null) { - return new DateTimeFormatterBuilder().appendLiteral('.') - // Support parsing up to nanosecond precision even though - // those extra digits will be dropped. - .appendFractionOfSecond(3, 9) - .toFormatter(); - } - return fse; - } - - private static DateTimeFormatter offsetElement() { - if (ze == null) { - return new DateTimeFormatterBuilder().appendTimeZoneOffset("Z", true, 2, 4).toFormatter(); - } - return ze; - } - - } - -} diff --git a/server/src/main/java/org/opensearch/bootstrap/Security.java b/server/src/main/java/org/opensearch/bootstrap/Security.java index 749c146de4f16..1a17ffcc449c8 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Security.java +++ b/server/src/main/java/org/opensearch/bootstrap/Security.java @@ -222,7 +222,7 @@ static Map getPluginPermissions(Environment environment) throws * Reads and returns the specified {@code policyFile}. *

* Jar files listed in {@code codebases} location will be provided to the policy file via - * a system property of the short name: e.g. ${codebase.joda-convert-1.2.jar} + * a system property of the short name: e.g. ${codebase.some-dependency-1.2.jar} * would map to full URL. */ @SuppressForbidden(reason = "accesses fully qualified URLs to configure security") diff --git a/server/src/main/java/org/opensearch/common/joda/Joda.java b/server/src/main/java/org/opensearch/common/joda/Joda.java deleted file mode 100644 index 8b466e01b15c7..0000000000000 --- a/server/src/main/java/org/opensearch/common/joda/Joda.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.common.logging.DeprecationLogger; -import org.opensearch.common.time.DateFormatter; -import org.opensearch.common.time.FormatNames; -import org.opensearch.common.util.LazyInitializable; -import org.opensearch.core.common.Strings; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.joda.time.Chronology; -import org.joda.time.DateTime; -import org.joda.time.DateTimeField; -import org.joda.time.DateTimeFieldType; -import org.joda.time.DateTimeZone; -import org.joda.time.DurationField; -import org.joda.time.DurationFieldType; -import org.joda.time.ReadablePartial; -import org.joda.time.field.DividedDateTimeField; -import org.joda.time.field.OffsetDateTimeField; -import org.joda.time.field.ScaledDurationField; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.DateTimeFormatterBuilder; -import org.joda.time.format.DateTimeParser; -import org.joda.time.format.DateTimeParserBucket; -import org.joda.time.format.DateTimePrinter; -import org.joda.time.format.ISODateTimeFormat; -import org.joda.time.format.StrictISODateTimeFormat; - -import java.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.util.Locale; -import java.util.regex.Pattern; - -/** - * Joda class. - * - * @deprecated - * - * @opensearch.internal - */ -@Deprecated -public class Joda { - // Joda.forPattern could be used even before the logging is initialized. - // If LogManager.getLogger is called before logging config is loaded - // it results in errors sent to status logger and startup to fail. - // Hence a lazy initialization. - private static final LazyInitializable deprecationLogger = new LazyInitializable( - () -> DeprecationLogger.getLogger(Joda.class) - ); - - /** - * Parses a joda based pattern, including some named ones (similar to the built in Joda ISO ones). - */ - public static JodaDateFormatter forPattern(String input) { - if (Strings.hasLength(input)) { - input = input.trim(); - } - if (input == null || input.length() == 0) { - throw new IllegalArgumentException("No date pattern provided"); - } - - FormatNames formatName = FormatNames.forName(input); - if (formatName != null && formatName.isCamelCase(input)) { - String msg = "Camel case format name {} is deprecated and will be removed in a future version. " - + "Use snake case name {} instead."; - getDeprecationLogger().deprecate( - "camelCaseDateFormat_" + formatName.getCamelCaseName(), - msg, - formatName.getCamelCaseName(), - formatName.getSnakeCaseName() - ); - } - - DateTimeFormatter formatter; - if (FormatNames.BASIC_DATE.matches(input)) { - formatter = ISODateTimeFormat.basicDate(); - } else if (FormatNames.BASIC_DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.basicDateTime(); - } else if (FormatNames.BASIC_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.basicDateTimeNoMillis(); - } else if (FormatNames.BASIC_ORDINAL_DATE.matches(input)) { - formatter = ISODateTimeFormat.basicOrdinalDate(); - } else if (FormatNames.BASIC_ORDINAL_DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.basicOrdinalDateTime(); - } else if (FormatNames.BASIC_ORDINAL_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.basicOrdinalDateTimeNoMillis(); - } else if (FormatNames.BASIC_TIME.matches(input)) { - formatter = ISODateTimeFormat.basicTime(); - } else if (FormatNames.BASIC_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.basicTimeNoMillis(); - } else if (FormatNames.BASIC_T_TIME.matches(input)) { - formatter = ISODateTimeFormat.basicTTime(); - } else if (FormatNames.BASIC_T_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.basicTTimeNoMillis(); - } else if (FormatNames.BASIC_WEEK_DATE.matches(input)) { - formatter = ISODateTimeFormat.basicWeekDate(); - } else if (FormatNames.BASIC_WEEK_DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.basicWeekDateTime(); - } else if (FormatNames.BASIC_WEEK_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.basicWeekDateTimeNoMillis(); - } else if (FormatNames.DATE.matches(input)) { - formatter = ISODateTimeFormat.date(); - } else if (FormatNames.DATE_HOUR.matches(input)) { - formatter = ISODateTimeFormat.dateHour(); - } else if (FormatNames.DATE_HOUR_MINUTE.matches(input)) { - formatter = ISODateTimeFormat.dateHourMinute(); - } else if (FormatNames.DATE_HOUR_MINUTE_SECOND.matches(input)) { - formatter = ISODateTimeFormat.dateHourMinuteSecond(); - } else if (FormatNames.DATE_HOUR_MINUTE_SECOND_FRACTION.matches(input)) { - formatter = ISODateTimeFormat.dateHourMinuteSecondFraction(); - } else if (FormatNames.DATE_HOUR_MINUTE_SECOND_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.dateHourMinuteSecondMillis(); - } else if (FormatNames.DATE_OPTIONAL_TIME.matches(input)) { - // in this case, we have a separate parser and printer since the dataOptionalTimeParser can't print - // this sucks we should use the root local by default and not be dependent on the node - return new JodaDateFormatter( - input, - ISODateTimeFormat.dateOptionalTimeParser().withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970), - ISODateTimeFormat.dateTime().withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970) - ); - } else if (FormatNames.DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.dateTime(); - } else if (FormatNames.DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.dateTimeNoMillis(); - } else if (FormatNames.HOUR.matches(input)) { - formatter = ISODateTimeFormat.hour(); - } else if (FormatNames.HOUR_MINUTE.matches(input)) { - formatter = ISODateTimeFormat.hourMinute(); - } else if (FormatNames.HOUR_MINUTE_SECOND.matches(input)) { - formatter = ISODateTimeFormat.hourMinuteSecond(); - } else if (FormatNames.HOUR_MINUTE_SECOND_FRACTION.matches(input)) { - formatter = ISODateTimeFormat.hourMinuteSecondFraction(); - } else if (FormatNames.HOUR_MINUTE_SECOND_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.hourMinuteSecondMillis(); - } else if (FormatNames.ORDINAL_DATE.matches(input)) { - formatter = ISODateTimeFormat.ordinalDate(); - } else if (FormatNames.ORDINAL_DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.ordinalDateTime(); - } else if (FormatNames.ORDINAL_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.ordinalDateTimeNoMillis(); - } else if (FormatNames.TIME.matches(input)) { - formatter = ISODateTimeFormat.time(); - } else if (FormatNames.TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.timeNoMillis(); - } else if (FormatNames.T_TIME.matches(input)) { - formatter = ISODateTimeFormat.tTime(); - } else if (FormatNames.T_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.tTimeNoMillis(); - } else if (FormatNames.WEEK_DATE.matches(input)) { - formatter = ISODateTimeFormat.weekDate(); - } else if (FormatNames.WEEK_DATE_TIME.matches(input)) { - formatter = ISODateTimeFormat.weekDateTime(); - } else if (FormatNames.WEEK_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = ISODateTimeFormat.weekDateTimeNoMillis(); - } else if (FormatNames.WEEKYEAR.matches(input)) { - getDeprecationLogger().deprecate( - "week_year_format_name", - "Format name \"week_year\" is deprecated and will be removed in a future version. " + "Use \"weekyear\" format instead" - ); - formatter = ISODateTimeFormat.weekyear(); - } else if (FormatNames.WEEK_YEAR.matches(input)) { - formatter = ISODateTimeFormat.weekyear(); - } else if (FormatNames.WEEK_YEAR_WEEK.matches(input)) { - formatter = ISODateTimeFormat.weekyearWeek(); - } else if (FormatNames.WEEKYEAR_WEEK_DAY.matches(input)) { - formatter = ISODateTimeFormat.weekyearWeekDay(); - } else if (FormatNames.YEAR.matches(input)) { - formatter = ISODateTimeFormat.year(); - } else if (FormatNames.YEAR_MONTH.matches(input)) { - formatter = ISODateTimeFormat.yearMonth(); - } else if (FormatNames.YEAR_MONTH_DAY.matches(input)) { - formatter = ISODateTimeFormat.yearMonthDay(); - } else if (FormatNames.EPOCH_SECOND.matches(input)) { - formatter = new DateTimeFormatterBuilder().append(new EpochTimePrinter(false), new EpochTimeParser(false)).toFormatter(); - } else if (FormatNames.EPOCH_MILLIS.matches(input)) { - formatter = new DateTimeFormatterBuilder().append(new EpochTimePrinter(true), new EpochTimeParser(true)).toFormatter(); - // strict date formats here, must be at least 4 digits for year and two for months and two for day - } else if (FormatNames.STRICT_BASIC_WEEK_DATE.matches(input)) { - formatter = StrictISODateTimeFormat.basicWeekDate(); - } else if (FormatNames.STRICT_BASIC_WEEK_DATE_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.basicWeekDateTime(); - } else if (FormatNames.STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.basicWeekDateTimeNoMillis(); - } else if (FormatNames.STRICT_DATE.matches(input)) { - formatter = StrictISODateTimeFormat.date(); - } else if (FormatNames.STRICT_DATE_HOUR.matches(input)) { - formatter = StrictISODateTimeFormat.dateHour(); - } else if (FormatNames.STRICT_DATE_HOUR_MINUTE.matches(input)) { - formatter = StrictISODateTimeFormat.dateHourMinute(); - } else if (FormatNames.STRICT_DATE_HOUR_MINUTE_SECOND.matches(input)) { - formatter = StrictISODateTimeFormat.dateHourMinuteSecond(); - } else if (FormatNames.STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION.matches(input)) { - formatter = StrictISODateTimeFormat.dateHourMinuteSecondFraction(); - } else if (FormatNames.STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.dateHourMinuteSecondMillis(); - } else if (FormatNames.STRICT_DATE_OPTIONAL_TIME.matches(input)) { - // in this case, we have a separate parser and printer since the dataOptionalTimeParser can't print - // this sucks we should use the root local by default and not be dependent on the node - return new JodaDateFormatter( - input, - StrictISODateTimeFormat.dateOptionalTimeParser().withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970), - StrictISODateTimeFormat.dateTime().withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970) - ); - } else if (FormatNames.STRICT_DATE_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.dateTime(); - } else if (FormatNames.STRICT_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.dateTimeNoMillis(); - } else if (FormatNames.STRICT_HOUR.matches(input)) { - formatter = StrictISODateTimeFormat.hour(); - } else if (FormatNames.STRICT_HOUR_MINUTE.matches(input)) { - formatter = StrictISODateTimeFormat.hourMinute(); - } else if (FormatNames.STRICT_HOUR_MINUTE_SECOND.matches(input)) { - formatter = StrictISODateTimeFormat.hourMinuteSecond(); - } else if (FormatNames.STRICT_HOUR_MINUTE_SECOND_FRACTION.matches(input)) { - formatter = StrictISODateTimeFormat.hourMinuteSecondFraction(); - } else if (FormatNames.STRICT_HOUR_MINUTE_SECOND_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.hourMinuteSecondMillis(); - } else if (FormatNames.STRICT_ORDINAL_DATE.matches(input)) { - formatter = StrictISODateTimeFormat.ordinalDate(); - } else if (FormatNames.STRICT_ORDINAL_DATE_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.ordinalDateTime(); - } else if (FormatNames.STRICT_ORDINAL_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.ordinalDateTimeNoMillis(); - } else if (FormatNames.STRICT_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.time(); - } else if (FormatNames.STRICT_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.timeNoMillis(); - } else if (FormatNames.STRICT_T_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.tTime(); - } else if (FormatNames.STRICT_T_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.tTimeNoMillis(); - } else if (FormatNames.STRICT_WEEK_DATE.matches(input)) { - formatter = StrictISODateTimeFormat.weekDate(); - } else if (FormatNames.STRICT_WEEK_DATE_TIME.matches(input)) { - formatter = StrictISODateTimeFormat.weekDateTime(); - } else if (FormatNames.STRICT_WEEK_DATE_TIME_NO_MILLIS.matches(input)) { - formatter = StrictISODateTimeFormat.weekDateTimeNoMillis(); - } else if (FormatNames.STRICT_WEEKYEAR.matches(input)) { - formatter = StrictISODateTimeFormat.weekyear(); - } else if (FormatNames.STRICT_WEEKYEAR_WEEK.matches(input)) { - formatter = StrictISODateTimeFormat.weekyearWeek(); - } else if (FormatNames.STRICT_WEEKYEAR_WEEK_DAY.matches(input)) { - formatter = StrictISODateTimeFormat.weekyearWeekDay(); - } else if (FormatNames.STRICT_YEAR.matches(input)) { - formatter = StrictISODateTimeFormat.year(); - } else if (FormatNames.STRICT_YEAR_MONTH.matches(input)) { - formatter = StrictISODateTimeFormat.yearMonth(); - } else if (FormatNames.STRICT_YEAR_MONTH_DAY.matches(input)) { - formatter = StrictISODateTimeFormat.yearMonthDay(); - } else if (Strings.hasLength(input) && input.contains("||")) { - String[] formats = Strings.delimitedListToStringArray(input, "||"); - DateTimeParser[] parsers = new DateTimeParser[formats.length]; - - if (formats.length == 1) { - formatter = forPattern(input).parser; - } else { - DateTimeFormatter dateTimeFormatter = null; - for (int i = 0; i < formats.length; i++) { - JodaDateFormatter currentFormatter = forPattern(formats[i]); - DateTimeFormatter currentParser = currentFormatter.parser; - if (dateTimeFormatter == null) { - dateTimeFormatter = currentFormatter.printer; - } - parsers[i] = currentParser.getParser(); - } - - DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append( - dateTimeFormatter.withZone(DateTimeZone.UTC).getPrinter(), - parsers - ); - formatter = builder.toFormatter(); - } - } else { - try { - maybeLogJodaDeprecation(input); - formatter = DateTimeFormat.forPattern(input); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid format: [" + input + "]: " + e.getMessage(), e); - } - } - - formatter = formatter.withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970); - return new JodaDateFormatter(input, formatter, formatter); - } - - public static void writeTimeZone(final StreamOutput out, final DateTimeZone timeZone) throws IOException { - out.writeString(timeZone.getID()); - } - - public static void writeOptionalTimeZone(final StreamOutput out, final DateTimeZone timeZone) throws IOException { - if (timeZone == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - writeTimeZone(out, timeZone); - } - } - - /** - * Read a {@linkplain DateTimeZone} from a {@linkplain StreamInput}. - */ - public static DateTimeZone readTimeZone(final StreamInput in) throws IOException { - return DateTimeZone.forID(in.readString()); - } - - /** - * Read an optional {@linkplain DateTimeZone}. - */ - public static DateTimeZone readOptionalTimeZone(final StreamInput in) throws IOException { - if (in.readBoolean()) { - return DateTimeZone.forID(in.readString()); - } - return null; - } - - private static void maybeLogJodaDeprecation(String format) { - if (JodaDeprecationPatterns.isDeprecatedPattern(format)) { - String suggestion = JodaDeprecationPatterns.formatSuggestion(format); - getDeprecationLogger().deprecate( - "joda-pattern-deprecation", - suggestion + " " + JodaDeprecationPatterns.USE_NEW_FORMAT_SPECIFIERS - ); - } - } - - public static DateFormatter getStrictStandardDateFormatter() { - // 2014/10/10 - DateTimeFormatter shortFormatter = new DateTimeFormatterBuilder().appendFixedDecimal(DateTimeFieldType.year(), 4) - .appendLiteral('/') - .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2) - .appendLiteral('/') - .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2) - .toFormatter() - .withZoneUTC(); - - // 2014/10/10 12:12:12 - DateTimeFormatter longFormatter = new DateTimeFormatterBuilder().appendFixedDecimal(DateTimeFieldType.year(), 4) - .appendLiteral('/') - .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2) - .appendLiteral('/') - .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2) - .appendLiteral(' ') - .appendFixedSignedDecimal(DateTimeFieldType.hourOfDay(), 2) - .appendLiteral(':') - .appendFixedSignedDecimal(DateTimeFieldType.minuteOfHour(), 2) - .appendLiteral(':') - .appendFixedSignedDecimal(DateTimeFieldType.secondOfMinute(), 2) - .toFormatter() - .withZoneUTC(); - - DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append( - longFormatter.withZone(DateTimeZone.UTC).getPrinter(), - new DateTimeParser[] { longFormatter.getParser(), shortFormatter.getParser(), new EpochTimeParser(true) } - ); - - DateTimeFormatter formatter = builder.toFormatter().withLocale(Locale.ROOT).withZone(DateTimeZone.UTC).withDefaultYear(1970); - return new JodaDateFormatter("yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis", formatter, formatter); - } - - public static final DurationFieldType Quarters = new DurationFieldType("quarters") { - @Override - public DurationField getField(Chronology chronology) { - return new ScaledDurationField(chronology.months(), Quarters, 3); - } - }; - - public static final DateTimeFieldType QuarterOfYear = new DateTimeFieldType("quarterOfYear") { - @Override - public DurationFieldType getDurationType() { - return Quarters; - } - - @Override - public DurationFieldType getRangeDurationType() { - return DurationFieldType.years(); - } - - @Override - public DateTimeField getField(Chronology chronology) { - return new OffsetDateTimeField( - new DividedDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), QuarterOfYear, 3), - 1 - ); - } - }; - - /** - * parses epcoch timers - * - * @opensearch.internal - */ - public static class EpochTimeParser implements DateTimeParser { - - private static final Pattern scientificNotation = Pattern.compile("[Ee]"); - - private final boolean hasMilliSecondPrecision; - - public EpochTimeParser(boolean hasMilliSecondPrecision) { - this.hasMilliSecondPrecision = hasMilliSecondPrecision; - } - - @Override - public int estimateParsedLength() { - return hasMilliSecondPrecision ? 19 : 16; - } - - @Override - public int parseInto(DateTimeParserBucket bucket, String text, int position) { - boolean isPositive = text.startsWith("-") == false; - int firstDotIndex = text.indexOf('.'); - boolean isTooLong = (firstDotIndex == -1 ? text.length() : firstDotIndex) > estimateParsedLength(); - - if (bucket.getZone() != DateTimeZone.UTC) { - String format = hasMilliSecondPrecision ? "epoch_millis" : "epoch_second"; - throw new IllegalArgumentException("time_zone must be UTC for format [" + format + "]"); - } else if (isPositive && isTooLong) { - return -1; - } - - int factor = hasMilliSecondPrecision ? 1 : 1000; - try { - long millis = new BigDecimal(text).longValue() * factor; - // check for deprecations, but after it has parsed correctly so invalid values aren't counted as deprecated - if (millis < 0) { - getDeprecationLogger().deprecate( - "epoch-negative", - "Use of negative values" - + " in epoch time formats is deprecated and will not be supported in the next major version of OpenSearch." - ); - } - if (scientificNotation.matcher(text).find()) { - getDeprecationLogger().deprecate( - "epoch-scientific-notation", - "Use of scientific notation" - + " in epoch time formats is deprecated and will not be supported in the next major version of OpenSearch." - ); - } - DateTime dt = new DateTime(millis, DateTimeZone.UTC); - bucket.saveField(DateTimeFieldType.year(), dt.getYear()); - bucket.saveField(DateTimeFieldType.monthOfYear(), dt.getMonthOfYear()); - bucket.saveField(DateTimeFieldType.dayOfMonth(), dt.getDayOfMonth()); - bucket.saveField(DateTimeFieldType.hourOfDay(), dt.getHourOfDay()); - bucket.saveField(DateTimeFieldType.minuteOfHour(), dt.getMinuteOfHour()); - bucket.saveField(DateTimeFieldType.secondOfMinute(), dt.getSecondOfMinute()); - bucket.saveField(DateTimeFieldType.millisOfSecond(), dt.getMillisOfSecond()); - bucket.setZone(DateTimeZone.UTC); - } catch (Exception e) { - return -1; - } - return text.length(); - } - } - - private static DeprecationLogger getDeprecationLogger() { - return deprecationLogger.getOrCompute(); - } - - /** - * Epoch timer printer - * - * @opensearch.internal - */ - public static class EpochTimePrinter implements DateTimePrinter { - - private boolean hasMilliSecondPrecision; - - public EpochTimePrinter(boolean hasMilliSecondPrecision) { - this.hasMilliSecondPrecision = hasMilliSecondPrecision; - } - - @Override - public int estimatePrintedLength() { - return hasMilliSecondPrecision ? 19 : 16; - } - - /** - * We adjust the instant by displayOffset to adjust for the offset that might have been added in - * {@link DateTimeFormatter#printTo(Appendable, long, Chronology)} when using a time zone. - */ - @Override - public void printTo(StringBuffer buf, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) { - if (hasMilliSecondPrecision) { - buf.append(instant - displayOffset); - } else { - buf.append((instant - displayOffset) / 1000); - } - } - - /** - * We adjust the instant by displayOffset to adjust for the offset that might have been added in - * {@link DateTimeFormatter#printTo(Appendable, long, Chronology)} when using a time zone. - */ - @Override - public void printTo(Writer out, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) - throws IOException { - if (hasMilliSecondPrecision) { - out.write(String.valueOf(instant - displayOffset)); - } else { - out.append(String.valueOf((instant - displayOffset) / 1000)); - } - } - - @Override - public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { - if (hasMilliSecondPrecision) { - buf.append(String.valueOf(getDateTimeMillis(partial))); - } else { - buf.append(String.valueOf(getDateTimeMillis(partial) / 1000)); - } - } - - @Override - public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { - if (hasMilliSecondPrecision) { - out.append(String.valueOf(getDateTimeMillis(partial))); - } else { - out.append(String.valueOf(getDateTimeMillis(partial) / 1000)); - } - } - - private long getDateTimeMillis(ReadablePartial partial) { - int year = partial.get(DateTimeFieldType.year()); - int monthOfYear = partial.get(DateTimeFieldType.monthOfYear()); - int dayOfMonth = partial.get(DateTimeFieldType.dayOfMonth()); - int hourOfDay = partial.get(DateTimeFieldType.hourOfDay()); - int minuteOfHour = partial.get(DateTimeFieldType.minuteOfHour()); - int secondOfMinute = partial.get(DateTimeFieldType.secondOfMinute()); - int millisOfSecond = partial.get(DateTimeFieldType.millisOfSecond()); - return partial.getChronology() - .getDateTimeMillis(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); - } - } -} diff --git a/server/src/main/java/org/opensearch/common/joda/JodaDateFormatter.java b/server/src/main/java/org/opensearch/common/joda/JodaDateFormatter.java deleted file mode 100644 index bf25e5b1b3923..0000000000000 --- a/server/src/main/java/org/opensearch/common/joda/JodaDateFormatter.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.common.time.DateFormatter; -import org.opensearch.common.time.DateMathParser; -import org.opensearch.common.time.DateUtils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormatter; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; -import java.util.Objects; - -/** - * Joda date formatter. - * - * @opensearch.internal - */ -public class JodaDateFormatter implements DateFormatter { - - final String pattern; - final DateTimeFormatter parser; - final DateTimeFormatter printer; - - JodaDateFormatter(String pattern, DateTimeFormatter parser, DateTimeFormatter printer) { - this.pattern = pattern; - this.printer = printer; - this.parser = parser; - } - - @Override - public TemporalAccessor parse(String input) { - final DateTime dt = parser.parseDateTime(input); - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dt.getMillis()), DateUtils.dateTimeZoneToZoneId(dt.getZone())); - } - - public long parseMillis(String input) { - return parser.parseMillis(input); - } - - public DateTime parseJoda(String input) { - return parser.parseDateTime(input); - } - - @Override - public DateFormatter withZone(ZoneId zoneId) { - DateTimeZone timeZone = DateUtils.zoneIdToDateTimeZone(zoneId); - if (parser.getZone().equals(timeZone)) { - return this; - } - DateTimeFormatter parser = this.parser.withZone(timeZone); - DateTimeFormatter printer = this.printer.withZone(timeZone); - return new JodaDateFormatter(pattern, parser, printer); - } - - @Override - public DateFormatter withLocale(Locale locale) { - if (parser.getLocale().equals(locale)) { - return this; - } - DateTimeFormatter parser = this.parser.withLocale(locale); - DateTimeFormatter printer = this.printer.withLocale(locale); - return new JodaDateFormatter(pattern, parser, printer); - } - - @Override - public String format(TemporalAccessor accessor) { - DateTimeZone timeZone = DateUtils.zoneIdToDateTimeZone(ZoneId.from(accessor)); - DateTime dateTime = new DateTime(Instant.from(accessor).toEpochMilli(), timeZone); - return printer.print(dateTime); - } - - public String formatJoda(DateTime dateTime) { - return printer.print(dateTime); - } - - public String formatMillis(long millis) { - return printer.print(millis); - } - - public JodaDateFormatter withYear(int year) { - if (parser.getDefaultYear() == year) { - return this; - } - return new JodaDateFormatter(pattern, parser.withDefaultYear(year), printer.withDefaultYear(year)); - } - - @Override - public String pattern() { - return pattern; - } - - @Override - public String printPattern() { - throw new UnsupportedOperationException("JodaDateFormatter does not have a print pattern"); - } - - @Override - public Locale locale() { - return printer.getLocale(); - } - - @Override - public ZoneId zone() { - return DateUtils.dateTimeZoneToZoneId(printer.getZone()); - } - - @Override - public DateMathParser toDateMathParser() { - return new JodaDateMathParser(this); - } - - @Override - public int hashCode() { - return Objects.hash(locale(), zone(), pattern()); - } - - @Override - public boolean equals(Object obj) { - if (obj.getClass().equals(this.getClass()) == false) { - return false; - } - JodaDateFormatter other = (JodaDateFormatter) obj; - - return Objects.equals(pattern(), other.pattern()) - && Objects.equals(locale(), other.locale()) - && Objects.equals(zone(), other.zone()); - } -} diff --git a/server/src/main/java/org/opensearch/common/joda/JodaDateMathParser.java b/server/src/main/java/org/opensearch/common/joda/JodaDateMathParser.java deleted file mode 100644 index ae38e9a6a8073..0000000000000 --- a/server/src/main/java/org/opensearch/common/joda/JodaDateMathParser.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.OpenSearchParseException; -import org.opensearch.common.time.DateMathParser; -import org.opensearch.common.time.DateUtils; -import org.joda.time.DateTimeZone; -import org.joda.time.MutableDateTime; -import org.joda.time.format.DateTimeFormatter; - -import java.time.Instant; -import java.time.ZoneId; -import java.util.Objects; -import java.util.function.LongSupplier; - -/** - * A parser for date/time formatted text with optional date math. - *

- * The format of the datetime is configurable, and unix timestamps can also be used. Datemath - * is appended to a datetime with the following syntax: - * ||[+-/](\d+)?[yMwdhHms]. - * - * @opensearch.internal - */ -public class JodaDateMathParser implements DateMathParser { - - private final JodaDateFormatter dateTimeFormatter; - - public JodaDateMathParser(JodaDateFormatter dateTimeFormatter) { - Objects.requireNonNull(dateTimeFormatter); - this.dateTimeFormatter = dateTimeFormatter; - } - - // Note: we take a callable here for the timestamp in order to be able to figure out - // if it has been used. For instance, the request cache does not cache requests that make - // use of `now`. - @Override - public Instant parse(String text, LongSupplier now, boolean roundUp, ZoneId tz) { - final DateTimeZone timeZone = tz == null ? null : DateUtils.zoneIdToDateTimeZone(tz); - long time; - String mathString; - if (text.startsWith("now")) { - try { - time = now.getAsLong(); - } catch (Exception e) { - throw new OpenSearchParseException("could not read the current timestamp", e); - } - mathString = text.substring("now".length()); - } else { - int index = text.indexOf("||"); - if (index == -1) { - return Instant.ofEpochMilli(parseDateTime(text, timeZone, roundUp)); - } - time = parseDateTime(text.substring(0, index), timeZone, false); - mathString = text.substring(index + 2); - } - - return Instant.ofEpochMilli(parseMath(mathString, time, roundUp, timeZone)); - } - - private long parseMath(String mathString, long time, boolean roundUp, DateTimeZone timeZone) throws OpenSearchParseException { - if (timeZone == null) { - timeZone = DateTimeZone.UTC; - } - MutableDateTime dateTime = new MutableDateTime(time, timeZone); - for (int i = 0; i < mathString.length();) { - char c = mathString.charAt(i++); - final boolean round; - final int sign; - if (c == '/') { - round = true; - sign = 1; - } else { - round = false; - if (c == '+') { - sign = 1; - } else if (c == '-') { - sign = -1; - } else { - throw new OpenSearchParseException("operator not supported for date math [{}]", mathString); - } - } - - if (i >= mathString.length()) { - throw new OpenSearchParseException("truncated date math [{}]", mathString); - } - - final int num; - if (!Character.isDigit(mathString.charAt(i))) { - num = 1; - } else { - int numFrom = i; - while (i < mathString.length() && Character.isDigit(mathString.charAt(i))) { - i++; - } - if (i >= mathString.length()) { - throw new OpenSearchParseException("truncated date math [{}]", mathString); - } - num = Integer.parseInt(mathString.substring(numFrom, i)); - } - if (round) { - if (num != 1) { - throw new OpenSearchParseException("rounding `/` can only be used on single unit types [{}]", mathString); - } - } - char unit = mathString.charAt(i++); - MutableDateTime.Property propertyToRound = null; - switch (unit) { - case 'y': - if (round) { - propertyToRound = dateTime.yearOfCentury(); - } else { - dateTime.addYears(sign * num); - } - break; - case 'M': - if (round) { - propertyToRound = dateTime.monthOfYear(); - } else { - dateTime.addMonths(sign * num); - } - break; - case 'w': - if (round) { - propertyToRound = dateTime.weekOfWeekyear(); - } else { - dateTime.addWeeks(sign * num); - } - break; - case 'd': - if (round) { - propertyToRound = dateTime.dayOfMonth(); - } else { - dateTime.addDays(sign * num); - } - break; - case 'h': - case 'H': - if (round) { - propertyToRound = dateTime.hourOfDay(); - } else { - dateTime.addHours(sign * num); - } - break; - case 'm': - if (round) { - propertyToRound = dateTime.minuteOfHour(); - } else { - dateTime.addMinutes(sign * num); - } - break; - case 's': - if (round) { - propertyToRound = dateTime.secondOfMinute(); - } else { - dateTime.addSeconds(sign * num); - } - break; - default: - throw new OpenSearchParseException("unit [{}] not supported for date math [{}]", unit, mathString); - } - if (propertyToRound != null) { - if (roundUp) { - // we want to go up to the next whole value, even if we are already on a rounded value - propertyToRound.add(1); - propertyToRound.roundFloor(); - dateTime.addMillis(-1); // subtract 1 millisecond to get the largest inclusive value - } else { - propertyToRound.roundFloor(); - } - } - } - return dateTime.getMillis(); - } - - private long parseDateTime(String value, DateTimeZone timeZone, boolean roundUpIfNoTime) { - DateTimeFormatter parser = dateTimeFormatter.parser; - if (timeZone != null) { - parser = parser.withZone(timeZone); - } - try { - MutableDateTime date; - // We use 01/01/1970 as a base date so that things keep working with date - // fields that are filled with times without dates - if (roundUpIfNoTime) { - date = new MutableDateTime(1970, 1, 1, 23, 59, 59, 999, DateTimeZone.UTC); - } else { - date = new MutableDateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC); - } - final int end = parser.parseInto(date, value, 0); - if (end < 0) { - int position = ~end; - throw new IllegalArgumentException("Parse failure at index [" + position + "] of [" + value + "]"); - } else if (end != value.length()) { - throw new IllegalArgumentException("Unrecognized chars at the end of [" + value + "]: [" + value.substring(end) + "]"); - } - return date.getMillis(); - } catch (IllegalArgumentException e) { - throw new OpenSearchParseException("failed to parse date field [{}] with format [{}]", e, value, dateTimeFormatter.pattern()); - } - } - -} diff --git a/server/src/main/java/org/opensearch/common/joda/JodaDeprecationPatterns.java b/server/src/main/java/org/opensearch/common/joda/JodaDeprecationPatterns.java deleted file mode 100644 index efb6362ab63aa..0000000000000 --- a/server/src/main/java/org/opensearch/common/joda/JodaDeprecationPatterns.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.common.time.DateFormatter; -import org.opensearch.common.time.FormatNames; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Deprecated patters for joda date/time - * - * @opensearch.internal - */ -public class JodaDeprecationPatterns { - public static final String USE_NEW_FORMAT_SPECIFIERS = "Use new java.time date format specifiers."; - private static Map JODA_PATTERNS_DEPRECATIONS = new LinkedHashMap<>(); - - static { - JODA_PATTERNS_DEPRECATIONS.put("Y", "'Y' year-of-era should be replaced with 'y'. Use 'Y' for week-based-year."); - JODA_PATTERNS_DEPRECATIONS.put("y", "'y' year should be replaced with 'u'. Use 'y' for year-of-era."); - JODA_PATTERNS_DEPRECATIONS.put("C", "'C' century of era is no longer supported."); - JODA_PATTERNS_DEPRECATIONS.put("x", "'x' weak-year should be replaced with 'Y'. Use 'x' for zone-offset."); - JODA_PATTERNS_DEPRECATIONS.put("Z", "'Z' time zone offset/id fails when parsing 'Z' for Zulu timezone. Consider using 'X'."); - JODA_PATTERNS_DEPRECATIONS.put("z", "'z' time zone text. Will print 'Z' for Zulu given UTC timezone."); - } - - /** - * Checks if date parsing pattern is deprecated. - * Deprecated here means: when it was not already prefixed with 8 (meaning already upgraded) - * and it is not a predefined pattern from FormatNames like basic_date_time_no_millis - * and it uses pattern characters which changed meaning from joda to java like Y becomes y. - * @param pattern - a format to be checked - * @return true if format is deprecated, otherwise false - */ - public static boolean isDeprecatedPattern(String pattern) { - List patterns = DateFormatter.splitCombinedPatterns(pattern); - - for (String subPattern : patterns) { - boolean isDeprecated = subPattern.startsWith("8") == false - && FormatNames.exist(subPattern) == false - && JODA_PATTERNS_DEPRECATIONS.keySet().stream().filter(s -> subPattern.contains(s)).findAny().isPresent(); - if (isDeprecated) { - return true; - } - } - return false; - } - - /** - * Formats deprecation message for suggestion field in a warning header. - * Joins all warnings in a one message. - * @param pattern - a pattern to be formatted - * @return a formatted deprecation message - */ - public static String formatSuggestion(String pattern) { - List patterns = DateFormatter.splitCombinedPatterns(pattern); - - Set warnings = new LinkedHashSet<>(); - for (String subPattern : patterns) { - if (isDeprecatedPattern(subPattern)) { - String suggestion = JODA_PATTERNS_DEPRECATIONS.entrySet() - .stream() - .filter(s -> subPattern.contains(s.getKey())) - .map(s -> s.getValue()) - .collect(Collectors.joining("; ")); - warnings.add(suggestion); - } - } - String combinedWarning = warnings.stream().collect(Collectors.joining("; ")); - return combinedWarning; - } -} diff --git a/server/src/main/java/org/opensearch/common/joda/package-info.java b/server/src/main/java/org/opensearch/common/joda/package-info.java deleted file mode 100644 index 55ed8d9592a6d..0000000000000 --- a/server/src/main/java/org/opensearch/common/joda/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/** Base Joda Time package. */ -package org.opensearch.common.joda; diff --git a/server/src/main/java/org/opensearch/common/rounding/DateTimeUnit.java b/server/src/main/java/org/opensearch/common/rounding/DateTimeUnit.java deleted file mode 100644 index 47e182b3caf84..0000000000000 --- a/server/src/main/java/org/opensearch/common/rounding/DateTimeUnit.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.rounding; - -import org.opensearch.OpenSearchException; -import org.opensearch.common.joda.Joda; -import org.joda.time.DateTimeField; -import org.joda.time.DateTimeZone; -import org.joda.time.chrono.ISOChronology; - -import java.util.function.Function; - -/** - * Main date time unit class. - * - * @opensearch.internal - */ -public enum DateTimeUnit { - - WEEK_OF_WEEKYEAR((byte) 1, tz -> ISOChronology.getInstance(tz).weekOfWeekyear()), - YEAR_OF_CENTURY((byte) 2, tz -> ISOChronology.getInstance(tz).yearOfCentury()), - QUARTER((byte) 3, tz -> Joda.QuarterOfYear.getField(ISOChronology.getInstance(tz))), - MONTH_OF_YEAR((byte) 4, tz -> ISOChronology.getInstance(tz).monthOfYear()), - DAY_OF_MONTH((byte) 5, tz -> ISOChronology.getInstance(tz).dayOfMonth()), - HOUR_OF_DAY((byte) 6, tz -> ISOChronology.getInstance(tz).hourOfDay()), - MINUTES_OF_HOUR((byte) 7, tz -> ISOChronology.getInstance(tz).minuteOfHour()), - SECOND_OF_MINUTE((byte) 8, tz -> ISOChronology.getInstance(tz).secondOfMinute()); - - private final byte id; - private final Function fieldFunction; - - DateTimeUnit(byte id, Function fieldFunction) { - this.id = id; - this.fieldFunction = fieldFunction; - } - - public byte id() { - return id; - } - - /** - * @return the {@link DateTimeField} for the provided {@link DateTimeZone} for this time unit - */ - public DateTimeField field(DateTimeZone tz) { - return fieldFunction.apply(tz); - } - - public static DateTimeUnit resolve(byte id) { - switch (id) { - case 1: - return WEEK_OF_WEEKYEAR; - case 2: - return YEAR_OF_CENTURY; - case 3: - return QUARTER; - case 4: - return MONTH_OF_YEAR; - case 5: - return DAY_OF_MONTH; - case 6: - return HOUR_OF_DAY; - case 7: - return MINUTES_OF_HOUR; - case 8: - return SECOND_OF_MINUTE; - default: - throw new OpenSearchException("Unknown date time unit id [" + id + "]"); - } - } -} diff --git a/server/src/main/java/org/opensearch/common/rounding/Rounding.java b/server/src/main/java/org/opensearch/common/rounding/Rounding.java deleted file mode 100644 index 41e808b64f7d9..0000000000000 --- a/server/src/main/java/org/opensearch/common/rounding/Rounding.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.rounding; - -import org.opensearch.OpenSearchException; -import org.opensearch.common.unit.TimeValue; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.common.io.stream.Writeable; -import org.joda.time.DateTimeField; -import org.joda.time.DateTimeZone; -import org.joda.time.IllegalInstantException; - -import java.io.IOException; -import java.util.Objects; - -/** - * A strategy for rounding long values. - *

- * Use the java based Rounding class where applicable - * - * @opensearch.internal - */ -@Deprecated -public abstract class Rounding implements Writeable { - - public abstract byte id(); - - /** - * Rounds the given value. - */ - public abstract long round(long value); - - /** - * Given the rounded value (which was potentially generated by {@link #round(long)}, returns the next rounding value. For example, with - * interval based rounding, if the interval is 3, {@code nextRoundValue(6) = 9 }. - * - * @param value The current rounding value - * @return The next rounding value; - */ - public abstract long nextRoundingValue(long value); - - @Override - public abstract boolean equals(Object obj); - - @Override - public abstract int hashCode(); - - public static Builder builder(DateTimeUnit unit) { - return new Builder(unit); - } - - public static Builder builder(TimeValue interval) { - return new Builder(interval); - } - - /** - * Builder for rounding - * - * @opensearch.internal - */ - public static class Builder { - - private final DateTimeUnit unit; - private final long interval; - - private DateTimeZone timeZone = DateTimeZone.UTC; - - public Builder(DateTimeUnit unit) { - this.unit = unit; - this.interval = -1; - } - - public Builder(TimeValue interval) { - this.unit = null; - if (interval.millis() < 1) throw new IllegalArgumentException("Zero or negative time interval not supported"); - this.interval = interval.millis(); - } - - public Builder timeZone(DateTimeZone timeZone) { - if (timeZone == null) { - throw new IllegalArgumentException("Setting null as timezone is not supported"); - } - this.timeZone = timeZone; - return this; - } - - public Rounding build() { - Rounding timeZoneRounding; - if (unit != null) { - timeZoneRounding = new TimeUnitRounding(unit, timeZone); - } else { - timeZoneRounding = new TimeIntervalRounding(interval, timeZone); - } - return timeZoneRounding; - } - } - - /** - * Rounding time units - * - * @opensearch.internal - */ - static class TimeUnitRounding extends Rounding { - - static final byte ID = 1; - - private final DateTimeUnit unit; - private final DateTimeField field; - private final DateTimeZone timeZone; - private final boolean unitRoundsToMidnight; - - TimeUnitRounding(DateTimeUnit unit, DateTimeZone timeZone) { - this.unit = unit; - this.field = unit.field(timeZone); - unitRoundsToMidnight = this.field.getDurationField().getUnitMillis() > 60L * 60L * 1000L; - this.timeZone = timeZone; - } - - TimeUnitRounding(StreamInput in) throws IOException { - unit = DateTimeUnit.resolve(in.readByte()); - timeZone = DateTimeZone.forID(in.readString()); - field = unit.field(timeZone); - unitRoundsToMidnight = field.getDurationField().getUnitMillis() > 60L * 60L * 1000L; - } - - @Override - public byte id() { - return ID; - } - - /** - * @return The latest timestamp T which is strictly before utcMillis - * and such that timeZone.getOffset(T) != timeZone.getOffset(utcMillis). - * If there is no such T, returns Long.MAX_VALUE. - */ - private long previousTransition(long utcMillis) { - final int offsetAtInputTime = timeZone.getOffset(utcMillis); - do { - // Some timezones have transitions that do not change the offset, so we have to - // repeatedly call previousTransition until a nontrivial transition is found. - - long previousTransition = timeZone.previousTransition(utcMillis); - if (previousTransition == utcMillis) { - // There are no earlier transitions - return Long.MAX_VALUE; - } - assert previousTransition < utcMillis; // Progress was made - utcMillis = previousTransition; - } while (timeZone.getOffset(utcMillis) == offsetAtInputTime); - - return utcMillis; - } - - @Override - public long round(long utcMillis) { - - // field.roundFloor() works as long as the offset doesn't change. It is worth getting this case out of the way first, as - // the calculations for fixing things near to offset changes are a little expensive and are unnecessary in the common case - // of working in UTC. - if (timeZone.isFixed()) { - return field.roundFloor(utcMillis); - } - - // When rounding to hours we consider any local time of the form 'xx:00:00' as rounded, even though this gives duplicate - // bucket names for the times when the clocks go back. Shorter units behave similarly. However, longer units round down to - // midnight, and on the days where there are two midnights we would rather pick the earlier one, so that buckets are - // uniquely identified by the date. - if (unitRoundsToMidnight) { - final long anyLocalStartOfDay = field.roundFloor(utcMillis); - // `anyLocalStartOfDay` is _supposed_ to be the Unix timestamp for the start of the day in question in the current time - // zone. Mostly this just means "midnight", which is fine, and on days with no local midnight it's the first time that - // does occur on that day which is also ok. However, on days with >1 local midnight this is _one_ of the midnights, but - // may not be the first. Check whether this is happening, and fix it if so. - - final long previousTransition = previousTransition(anyLocalStartOfDay); - - if (previousTransition == Long.MAX_VALUE) { - // No previous transitions, so there can't be another earlier local midnight. - return anyLocalStartOfDay; - } - - final long currentOffset = timeZone.getOffset(anyLocalStartOfDay); - final long previousOffset = timeZone.getOffset(previousTransition); - assert currentOffset != previousOffset; - - // NB we only assume interference from one previous transition. It's theoretically possible to have two transitions in - // quick succession, both of which have a midnight in them, but this doesn't appear to happen in the TZDB so (a) it's - // pointless to implement and (b) it won't be tested. I recognise that this comment is tempting fate and will likely - // cause this very situation to occur in the near future, and eagerly look forward to fixing this using a loop over - // previous transitions when it happens. - - final long alsoLocalStartOfDay = anyLocalStartOfDay + currentOffset - previousOffset; - // `alsoLocalStartOfDay` is the Unix timestamp for the start of the day in question if the previous offset were in - // effect. - - if (alsoLocalStartOfDay <= previousTransition) { - // Therefore the previous offset _is_ in effect at `alsoLocalStartOfDay`, and it's earlier than anyLocalStartOfDay, - // so this is the answer to use. - return alsoLocalStartOfDay; - } else { - // The previous offset is not in effect at `alsoLocalStartOfDay`, so the current offset must be. - return anyLocalStartOfDay; - } - - } else { - do { - long rounded = field.roundFloor(utcMillis); - - // field.roundFloor() mostly works as long as the offset hasn't changed in [rounded, utcMillis], so look at where - // the offset most recently changed. - - final long previousTransition = previousTransition(utcMillis); - - if (previousTransition == Long.MAX_VALUE || previousTransition < rounded) { - // The offset did not change in [rounded, utcMillis], so roundFloor() worked as expected. - return rounded; - } - - // The offset _did_ change in [rounded, utcMillis]. Put differently, this means that none of the times in - // [previousTransition+1, utcMillis] were rounded, so the rounded time must be <= previousTransition. This means - // it's sufficient to try and round previousTransition down. - assert previousTransition < utcMillis; - utcMillis = previousTransition; - } while (true); - } - } - - @Override - public long nextRoundingValue(long utcMillis) { - long floor = round(utcMillis); - // add one unit and round to get to next rounded value - long next = round(field.add(floor, 1)); - if (next == floor) { - // in rare case we need to add more than one unit - next = round(field.add(floor, 2)); - } - return next; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeByte(unit.id()); - out.writeString(timeZone.getID()); - } - - @Override - public int hashCode() { - return Objects.hash(unit, timeZone); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - TimeUnitRounding other = (TimeUnitRounding) obj; - return Objects.equals(unit, other.unit) && Objects.equals(timeZone, other.timeZone); - } - - @Override - public String toString() { - return "[" + timeZone + "][" + unit + "]"; - } - } - - /** - * Rounding time intervals - * - * @opensearch.internal - */ - static class TimeIntervalRounding extends Rounding { - - static final byte ID = 2; - - private final long interval; - private final DateTimeZone timeZone; - - TimeIntervalRounding(long interval, DateTimeZone timeZone) { - if (interval < 1) throw new IllegalArgumentException("Zero or negative time interval not supported"); - this.interval = interval; - this.timeZone = timeZone; - } - - TimeIntervalRounding(StreamInput in) throws IOException { - interval = in.readVLong(); - timeZone = DateTimeZone.forID(in.readString()); - } - - @Override - public byte id() { - return ID; - } - - @Override - public long round(long utcMillis) { - long timeLocal = timeZone.convertUTCToLocal(utcMillis); - long rounded = roundKey(timeLocal, interval) * interval; - long roundedUTC; - if (isInDSTGap(rounded) == false) { - roundedUTC = timeZone.convertLocalToUTC(rounded, true, utcMillis); - // check if we crossed DST transition, in this case we want the - // last rounded value before the transition - long transition = timeZone.previousTransition(utcMillis); - if (transition != utcMillis && transition > roundedUTC) { - roundedUTC = round(transition - 1); - } - } else { - /* - * Edge case where the rounded local time is illegal and landed - * in a DST gap. In this case, we choose 1ms tick after the - * transition date. We don't want the transition date itself - * because those dates, when rounded themselves, fall into the - * previous interval. This would violate the invariant that the - * rounding operation should be idempotent. - */ - roundedUTC = timeZone.previousTransition(utcMillis) + 1; - } - return roundedUTC; - } - - private static long roundKey(long value, long interval) { - if (value < 0) { - return (value - interval + 1) / interval; - } else { - return value / interval; - } - } - - /** - * Determine whether the local instant is a valid instant in the given - * time zone. The logic for this is taken from - * {@link DateTimeZone#convertLocalToUTC(long, boolean)} for the - * `strict` mode case, but instead of throwing an - * {@link IllegalInstantException}, which is costly, we want to return a - * flag indicating that the value is illegal in that time zone. - */ - private boolean isInDSTGap(long instantLocal) { - if (timeZone.isFixed()) { - return false; - } - // get the offset at instantLocal (first estimate) - int offsetLocal = timeZone.getOffset(instantLocal); - // adjust instantLocal using the estimate and recalc the offset - int offset = timeZone.getOffset(instantLocal - offsetLocal); - // if the offsets differ, we must be near a DST boundary - if (offsetLocal != offset) { - // determine if we are in the DST gap - long nextLocal = timeZone.nextTransition(instantLocal - offsetLocal); - if (nextLocal == (instantLocal - offsetLocal)) { - nextLocal = Long.MAX_VALUE; - } - long nextAdjusted = timeZone.nextTransition(instantLocal - offset); - if (nextAdjusted == (instantLocal - offset)) { - nextAdjusted = Long.MAX_VALUE; - } - if (nextLocal != nextAdjusted) { - // we are in the DST gap - return true; - } - } - return false; - } - - @Override - public long nextRoundingValue(long time) { - long timeLocal = time; - timeLocal = timeZone.convertUTCToLocal(time); - long next = timeLocal + interval; - return timeZone.convertLocalToUTC(next, false); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeVLong(interval); - out.writeString(timeZone.getID()); - } - - @Override - public int hashCode() { - return Objects.hash(interval, timeZone); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - TimeIntervalRounding other = (TimeIntervalRounding) obj; - return Objects.equals(interval, other.interval) && Objects.equals(timeZone, other.timeZone); - } - } - - /** - * Rounding streams - * - * @opensearch.internal - */ - public static class Streams { - - public static void write(Rounding rounding, StreamOutput out) throws IOException { - out.writeByte(rounding.id()); - rounding.writeTo(out); - } - - public static Rounding read(StreamInput in) throws IOException { - Rounding rounding; - byte id = in.readByte(); - switch (id) { - case TimeUnitRounding.ID: - rounding = new TimeUnitRounding(in); - break; - case TimeIntervalRounding.ID: - rounding = new TimeIntervalRounding(in); - break; - default: - throw new OpenSearchException("unknown rounding id [" + id + "]"); - } - return rounding; - } - - } - -} diff --git a/server/src/main/java/org/opensearch/common/rounding/package-info.java b/server/src/main/java/org/opensearch/common/rounding/package-info.java deleted file mode 100644 index 5fa3e39c6a786..0000000000000 --- a/server/src/main/java/org/opensearch/common/rounding/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/** Base DateTime rounding package. */ -package org.opensearch.common.rounding; diff --git a/server/src/main/java/org/opensearch/common/time/DateFormatter.java b/server/src/main/java/org/opensearch/common/time/DateFormatter.java index c98bd853dfced..35f38f37968db 100644 --- a/server/src/main/java/org/opensearch/common/time/DateFormatter.java +++ b/server/src/main/java/org/opensearch/common/time/DateFormatter.java @@ -32,13 +32,12 @@ package org.opensearch.common.time; +import org.opensearch.Version; import org.opensearch.core.common.Strings; -import org.joda.time.DateTime; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.ArrayList; @@ -69,14 +68,6 @@ default long parseMillis(String input) { return DateFormatters.from(parse(input)).toInstant().toEpochMilli(); } - /** - * Parse the given input into a Joda {@link DateTime}. - */ - default DateTime parseJoda(String input) { - ZonedDateTime dateTime = ZonedDateTime.from(parse(input)); - return new DateTime(dateTime.toInstant().toEpochMilli(), DateUtils.zoneIdToDateTimeZone(dateTime.getZone())); - } - /** * Create a copy of this formatter that is configured to parse dates in the specified time zone * @@ -109,15 +100,6 @@ default String formatMillis(long millis) { return format(Instant.ofEpochMilli(millis).atZone(zone)); } - /** - * Return the given Joda {@link DateTime} formatted with this format. - */ - default String formatJoda(DateTime dateTime) { - return format( - ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime.getMillis()), DateUtils.dateTimeZoneToZoneId(dateTime.getZone())) - ); - } - /** * 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 @@ -155,7 +137,7 @@ default String formatJoda(DateTime dateTime) { */ DateMathParser toDateMathParser(); - static DateFormatter forPattern(String input, String printPattern, Boolean canCacheFormatter) { + static DateFormatter forPattern(String input, String printPattern, Boolean canCacheFormatter, final Version supportedVersion) { if (Strings.hasLength(input) == false) { throw new IllegalArgumentException("No date pattern provided"); @@ -164,7 +146,12 @@ static DateFormatter forPattern(String input, String printPattern, Boolean canCa // support the 6.x BWC compatible way of parsing java 8 dates String format = strip8Prefix(input); List patterns = splitCombinedPatterns(format); - List formatters = patterns.stream().map(DateFormatters::forPattern).collect(Collectors.toList()); + List formatters = patterns.stream().map(p -> { + if (supportedVersion.before(Version.V_2_12_0)) { + return LegacyFormatNames.camelCaseToSnakeCase(p); + } + return p; + }).map(DateFormatters::forPattern).collect(Collectors.toList()); DateFormatter printFormatter = formatters.get(0); if (Strings.hasLength(printPattern)) { @@ -179,15 +166,19 @@ static DateFormatter forPattern(String input, String printPattern, Boolean canCa } static DateFormatter forPattern(String input) { - return forPattern(input, null, false); + return forPattern(input, null, false, Version.CURRENT); + } + + static DateFormatter forPattern(final String input, final Version version) { + return forPattern(input, null, false, version); } static DateFormatter forPattern(String input, String printPattern) { - return forPattern(input, printPattern, false); + return forPattern(input, printPattern, false, Version.CURRENT); } static DateFormatter forPattern(String input, Boolean canCacheFormatter) { - return forPattern(input, null, canCacheFormatter); + return forPattern(input, null, canCacheFormatter, Version.CURRENT); } static String strip8Prefix(String input) { diff --git a/server/src/main/java/org/opensearch/common/time/DateFormatters.java b/server/src/main/java/org/opensearch/common/time/DateFormatters.java index e74ab687b903b..e0373a133450e 100644 --- a/server/src/main/java/org/opensearch/common/time/DateFormatters.java +++ b/server/src/main/java/org/opensearch/common/time/DateFormatters.java @@ -242,7 +242,7 @@ public class DateFormatters { /** * Returns a ISO 8601 compatible date time formatter and parser. * This is not fully compatible to the existing spec, which would require far more edge cases, but merely compatible with the - * existing joda time ISO date formatter + * legacy joda time ISO date formatter */ private static final DateFormatter ISO_8601 = new JavaDateFormatter( "iso8601", @@ -1971,19 +1971,6 @@ static DateFormatter forPattern(String input) { throw new IllegalArgumentException("No date pattern provided"); } - FormatNames formatName = FormatNames.forName(input); - if (formatName != null && formatName.isCamelCase(input)) { - String msg = "Camel case format name {} is deprecated and will be removed in a future version. " - + "Use snake case name {} instead."; - deprecationLogger.getOrCompute() - .deprecate( - "camelCaseDateFormat_" + formatName.getCamelCaseName(), - msg, - formatName.getCamelCaseName(), - formatName.getSnakeCaseName() - ); - } - if (FormatNames.ISO8601.matches(input)) { return ISO_8601; } else if (FormatNames.BASIC_DATE.matches(input)) { @@ -2185,7 +2172,7 @@ static DateFormatter forPattern(String input) { * - If no time is given, the start of the day is used * - If no month of the year is found, the first day of the year is used * - If an iso based weekyear is found, but not week is specified, the first monday - * of the new year is chosen (reataining BWC to joda time) + * of the new year is chosen (retaing BWC w/ joda time) * - If an iso based weekyear is found and an iso based weekyear week, the start * of the day is used * diff --git a/server/src/main/java/org/opensearch/common/time/DateMathParser.java b/server/src/main/java/org/opensearch/common/time/DateMathParser.java index 7088d6cb7a498..bf2f8b5210b28 100644 --- a/server/src/main/java/org/opensearch/common/time/DateMathParser.java +++ b/server/src/main/java/org/opensearch/common/time/DateMathParser.java @@ -32,14 +32,14 @@ package org.opensearch.common.time; -import org.joda.time.DateTimeZone; - import java.time.Instant; import java.time.ZoneId; import java.util.function.LongSupplier; /** - * An abstraction over date math parsing to allow different implementation for joda and java time. + * An abstraction over date math parsing. + * + * todo: merge {@link JavaDateMathParser} into this class * * @opensearch.internal */ @@ -49,17 +49,7 @@ public interface DateMathParser { * Parse a date math expression without timezone info and rounding down. */ default Instant parse(String text, LongSupplier now) { - return parse(text, now, false, (ZoneId) null); - } - - // Note: we take a callable here for the timestamp in order to be able to figure out - // if it has been used. For instance, the request cache does not cache requests that make - // use of `now`. - - // exists for backcompat, do not use! - @Deprecated - default Instant parse(String text, LongSupplier now, boolean roundUpProperty, DateTimeZone tz) { - return parse(text, now, roundUpProperty, tz == null ? null : ZoneId.of(tz.getID())); + return parse(text, now, false, null); } /** diff --git a/server/src/main/java/org/opensearch/common/time/DateUtils.java b/server/src/main/java/org/opensearch/common/time/DateUtils.java index 7ab395a1117e7..bb469bcd73f1a 100644 --- a/server/src/main/java/org/opensearch/common/time/DateUtils.java +++ b/server/src/main/java/org/opensearch/common/time/DateUtils.java @@ -33,13 +33,11 @@ package org.opensearch.common.time; import org.opensearch.common.logging.DeprecationLogger; -import org.joda.time.DateTimeZone; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; import java.util.HashMap; @@ -57,16 +55,6 @@ * @opensearch.internal */ public class DateUtils { - public static DateTimeZone zoneIdToDateTimeZone(ZoneId zoneId) { - if (zoneId == null) { - return null; - } - if (zoneId instanceof ZoneOffset) { - // the id for zoneoffset is not ISO compatible, so cannot be read by ZoneId.of - return DateTimeZone.forOffsetMillis(((ZoneOffset) zoneId).getTotalSeconds() * 1000); - } - return DateTimeZone.forID(zoneId.getId()); - } private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(DateUtils.class); // pkg private for tests @@ -204,17 +192,6 @@ public static DateTimeZone zoneIdToDateTimeZone(ZoneId zoneId) { DEPRECATED_LONG_TIMEZONES = Collections.unmodifiableMap(tzs); } - public static ZoneId dateTimeZoneToZoneId(DateTimeZone timeZone) { - if (timeZone == null) { - return null; - } - if (DateTimeZone.UTC.equals(timeZone)) { - return ZoneOffset.UTC; - } - - return of(timeZone.getID()); - } - public static ZoneId of(String zoneId) { String deprecatedId = DEPRECATED_SHORT_TIMEZONES.get(zoneId); if (deprecatedId != null) { diff --git a/server/src/main/java/org/opensearch/common/time/FormatNames.java b/server/src/main/java/org/opensearch/common/time/FormatNames.java index ba0a8fcf4a17a..90730c587112a 100644 --- a/server/src/main/java/org/opensearch/common/time/FormatNames.java +++ b/server/src/main/java/org/opensearch/common/time/FormatNames.java @@ -32,137 +32,106 @@ package org.opensearch.common.time; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Date format names. * * @opensearch.internal */ public enum FormatNames { - ISO8601(null, "iso8601"), - BASIC_DATE("basicDate", "basic_date"), - BASIC_DATE_TIME("basicDateTime", "basic_date_time"), - BASIC_DATE_TIME_NO_MILLIS("basicDateTimeNoMillis", "basic_date_time_no_millis"), - BASIC_ORDINAL_DATE("basicOrdinalDate", "basic_ordinal_date"), - BASIC_ORDINAL_DATE_TIME("basicOrdinalDateTime", "basic_ordinal_date_time"), - BASIC_ORDINAL_DATE_TIME_NO_MILLIS("basicOrdinalDateTimeNoMillis", "basic_ordinal_date_time_no_millis"), - BASIC_TIME("basicTime", "basic_time"), - BASIC_TIME_NO_MILLIS("basicTimeNoMillis", "basic_time_no_millis"), - BASIC_T_TIME("basicTTime", "basic_t_time"), - BASIC_T_TIME_NO_MILLIS("basicTTimeNoMillis", "basic_t_time_no_millis"), - BASIC_WEEK_DATE("basicWeekDate", "basic_week_date"), - BASIC_WEEK_DATE_TIME("basicWeekDateTime", "basic_week_date_time"), - BASIC_WEEK_DATE_TIME_NO_MILLIS("basicWeekDateTimeNoMillis", "basic_week_date_time_no_millis"), - DATE(null, "date"), - DATE_HOUR("dateHour", "date_hour"), - DATE_HOUR_MINUTE("dateHourMinute", "date_hour_minute"), - DATE_HOUR_MINUTE_SECOND("dateHourMinuteSecond", "date_hour_minute_second"), - DATE_HOUR_MINUTE_SECOND_FRACTION("dateHourMinuteSecondFraction", "date_hour_minute_second_fraction"), - DATE_HOUR_MINUTE_SECOND_MILLIS("dateHourMinuteSecondMillis", "date_hour_minute_second_millis"), - DATE_OPTIONAL_TIME("dateOptionalTime", "date_optional_time"), - DATE_TIME("dateTime", "date_time"), - DATE_TIME_NO_MILLIS("dateTimeNoMillis", "date_time_no_millis"), - HOUR(null, "hour"), - HOUR_MINUTE("hourMinute", "hour_minute"), - HOUR_MINUTE_SECOND("hourMinuteSecond", "hour_minute_second"), - HOUR_MINUTE_SECOND_FRACTION("hourMinuteSecondFraction", "hour_minute_second_fraction"), - HOUR_MINUTE_SECOND_MILLIS("hourMinuteSecondMillis", "hour_minute_second_millis"), - ORDINAL_DATE("ordinalDate", "ordinal_date"), - ORDINAL_DATE_TIME("ordinalDateTime", "ordinal_date_time"), - ORDINAL_DATE_TIME_NO_MILLIS("ordinalDateTimeNoMillis", "ordinal_date_time_no_millis"), - TIME(null, "time"), - TIME_NO_MILLIS("timeNoMillis", "time_no_millis"), - T_TIME("tTime", "t_time"), - T_TIME_NO_MILLIS("tTimeNoMillis", "t_time_no_millis"), - WEEK_DATE("weekDate", "week_date"), - WEEK_DATE_TIME("weekDateTime", "week_date_time"), - WEEK_DATE_TIME_NO_MILLIS("weekDateTimeNoMillis", "week_date_time_no_millis"), - WEEK_YEAR(null, "week_year"), - WEEKYEAR(null, "weekyear"), - WEEK_YEAR_WEEK("weekyearWeek", "weekyear_week"), - WEEKYEAR_WEEK_DAY("weekyearWeekDay", "weekyear_week_day"), - YEAR(null, "year"), - YEAR_MONTH("yearMonth", "year_month"), - YEAR_MONTH_DAY("yearMonthDay", "year_month_day"), - EPOCH_SECOND(null, "epoch_second"), - EPOCH_MILLIS(null, "epoch_millis"), + ISO8601("iso8601"), + BASIC_DATE("basic_date"), + BASIC_DATE_TIME("basic_date_time"), + BASIC_DATE_TIME_NO_MILLIS("basic_date_time_no_millis"), + BASIC_ORDINAL_DATE("basic_ordinal_date"), + BASIC_ORDINAL_DATE_TIME("basic_ordinal_date_time"), + BASIC_ORDINAL_DATE_TIME_NO_MILLIS("basic_ordinal_date_time_no_millis"), + BASIC_TIME("basic_time"), + BASIC_TIME_NO_MILLIS("basic_time_no_millis"), + BASIC_T_TIME("basic_t_time"), + BASIC_T_TIME_NO_MILLIS("basic_t_time_no_millis"), + BASIC_WEEK_DATE("basic_week_date"), + BASIC_WEEK_DATE_TIME("basic_week_date_time"), + BASIC_WEEK_DATE_TIME_NO_MILLIS("basic_week_date_time_no_millis"), + DATE("date"), + DATE_HOUR("date_hour"), + DATE_HOUR_MINUTE("date_hour_minute"), + DATE_HOUR_MINUTE_SECOND("date_hour_minute_second"), + DATE_HOUR_MINUTE_SECOND_FRACTION("date_hour_minute_second_fraction"), + DATE_HOUR_MINUTE_SECOND_MILLIS("date_hour_minute_second_millis"), + DATE_OPTIONAL_TIME("date_optional_time"), + DATE_TIME("date_time"), + DATE_TIME_NO_MILLIS("date_time_no_millis"), + HOUR("hour"), + HOUR_MINUTE("hour_minute"), + HOUR_MINUTE_SECOND("hour_minute_second"), + HOUR_MINUTE_SECOND_FRACTION("hour_minute_second_fraction"), + HOUR_MINUTE_SECOND_MILLIS("hour_minute_second_millis"), + ORDINAL_DATE("ordinal_date"), + ORDINAL_DATE_TIME("ordinal_date_time"), + ORDINAL_DATE_TIME_NO_MILLIS("ordinal_date_time_no_millis"), + TIME("time"), + TIME_NO_MILLIS("time_no_millis"), + T_TIME("t_time"), + T_TIME_NO_MILLIS("t_time_no_millis"), + WEEK_DATE("week_date"), + WEEK_DATE_TIME("week_date_time"), + WEEK_DATE_TIME_NO_MILLIS("week_date_time_no_millis"), + WEEK_YEAR("week_year"), + WEEKYEAR("weekyear"), + WEEK_YEAR_WEEK("weekyear_week"), + WEEKYEAR_WEEK_DAY("weekyear_week_day"), + YEAR("year"), + YEAR_MONTH("year_month"), + YEAR_MONTH_DAY("year_month_day"), + EPOCH_SECOND("epoch_second"), + EPOCH_MILLIS("epoch_millis"), // strict date formats here, must be at least 4 digits for year and two for months and two for day" - STRICT_BASIC_WEEK_DATE("strictBasicWeekDate", "strict_basic_week_date"), - STRICT_BASIC_WEEK_DATE_TIME("strictBasicWeekDateTime", "strict_basic_week_date_time"), - STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS("strictBasicWeekDateTimeNoMillis", "strict_basic_week_date_time_no_millis"), - STRICT_DATE("strictDate", "strict_date"), - STRICT_DATE_HOUR("strictDateHour", "strict_date_hour"), - STRICT_DATE_HOUR_MINUTE("strictDateHourMinute", "strict_date_hour_minute"), - STRICT_DATE_HOUR_MINUTE_SECOND("strictDateHourMinuteSecond", "strict_date_hour_minute_second"), - STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION("strictDateHourMinuteSecondFraction", "strict_date_hour_minute_second_fraction"), - STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS("strictDateHourMinuteSecondMillis", "strict_date_hour_minute_second_millis"), - STRICT_DATE_OPTIONAL_TIME("strictDateOptionalTime", "strict_date_optional_time"), - STRICT_DATE_OPTIONAL_TIME_NANOS("strictDateOptionalTimeNanos", "strict_date_optional_time_nanos"), - STRICT_DATE_TIME("strictDateTime", "strict_date_time"), - STRICT_DATE_TIME_NO_MILLIS("strictDateTimeNoMillis", "strict_date_time_no_millis"), - STRICT_HOUR("strictHour", "strict_hour"), - STRICT_HOUR_MINUTE("strictHourMinute", "strict_hour_minute"), - STRICT_HOUR_MINUTE_SECOND("strictHourMinuteSecond", "strict_hour_minute_second"), - STRICT_HOUR_MINUTE_SECOND_FRACTION("strictHourMinuteSecondFraction", "strict_hour_minute_second_fraction"), - STRICT_HOUR_MINUTE_SECOND_MILLIS("strictHourMinuteSecondMillis", "strict_hour_minute_second_millis"), - STRICT_ORDINAL_DATE("strictOrdinalDate", "strict_ordinal_date"), - STRICT_ORDINAL_DATE_TIME("strictOrdinalDateTime", "strict_ordinal_date_time"), - STRICT_ORDINAL_DATE_TIME_NO_MILLIS("strictOrdinalDateTimeNoMillis", "strict_ordinal_date_time_no_millis"), - STRICT_TIME("strictTime", "strict_time"), - STRICT_TIME_NO_MILLIS("strictTimeNoMillis", "strict_time_no_millis"), - STRICT_T_TIME("strictTTime", "strict_t_time"), - STRICT_T_TIME_NO_MILLIS("strictTTimeNoMillis", "strict_t_time_no_millis"), - STRICT_WEEK_DATE("strictWeekDate", "strict_week_date"), - STRICT_WEEK_DATE_TIME("strictWeekDateTime", "strict_week_date_time"), - STRICT_WEEK_DATE_TIME_NO_MILLIS("strictWeekDateTimeNoMillis", "strict_week_date_time_no_millis"), - STRICT_WEEKYEAR("strictWeekyear", "strict_weekyear"), - STRICT_WEEKYEAR_WEEK("strictWeekyearWeek", "strict_weekyear_week"), - STRICT_WEEKYEAR_WEEK_DAY("strictWeekyearWeekDay", "strict_weekyear_week_day"), - STRICT_YEAR("strictYear", "strict_year"), - STRICT_YEAR_MONTH("strictYearMonth", "strict_year_month"), - STRICT_YEAR_MONTH_DAY("strictYearMonthDay", "strict_year_month_day"); + STRICT_BASIC_WEEK_DATE("strict_basic_week_date"), + STRICT_BASIC_WEEK_DATE_TIME("strict_basic_week_date_time"), + STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS("strict_basic_week_date_time_no_millis"), + STRICT_DATE("strict_date"), + STRICT_DATE_HOUR("strict_date_hour"), + STRICT_DATE_HOUR_MINUTE("strict_date_hour_minute"), + STRICT_DATE_HOUR_MINUTE_SECOND("strict_date_hour_minute_second"), + STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION("strict_date_hour_minute_second_fraction"), + STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS("strict_date_hour_minute_second_millis"), + STRICT_DATE_OPTIONAL_TIME("strict_date_optional_time"), + STRICT_DATE_OPTIONAL_TIME_NANOS("strict_date_optional_time_nanos"), + STRICT_DATE_TIME("strict_date_time"), + STRICT_DATE_TIME_NO_MILLIS("strict_date_time_no_millis"), + STRICT_HOUR("strict_hour"), + STRICT_HOUR_MINUTE("strict_hour_minute"), + STRICT_HOUR_MINUTE_SECOND("strict_hour_minute_second"), + STRICT_HOUR_MINUTE_SECOND_FRACTION("strict_hour_minute_second_fraction"), + STRICT_HOUR_MINUTE_SECOND_MILLIS("strict_hour_minute_second_millis"), + STRICT_ORDINAL_DATE("strict_ordinal_date"), + STRICT_ORDINAL_DATE_TIME("strict_ordinal_date_time"), + STRICT_ORDINAL_DATE_TIME_NO_MILLIS("strict_ordinal_date_time_no_millis"), + STRICT_TIME("strict_time"), + STRICT_TIME_NO_MILLIS("strict_time_no_millis"), + STRICT_T_TIME("strict_t_time"), + STRICT_T_TIME_NO_MILLIS("strict_t_time_no_millis"), + STRICT_WEEK_DATE("strict_week_date"), + STRICT_WEEK_DATE_TIME("strict_week_date_time"), + STRICT_WEEK_DATE_TIME_NO_MILLIS("strict_week_date_time_no_millis"), + STRICT_WEEKYEAR("strict_weekyear"), + STRICT_WEEKYEAR_WEEK("strict_weekyear_week"), + STRICT_WEEKYEAR_WEEK_DAY("strict_weekyear_week_day"), + STRICT_YEAR("strict_year"), + STRICT_YEAR_MONTH("strict_year_month"), + STRICT_YEAR_MONTH_DAY("strict_year_month_day"); - private static final Set ALL_NAMES = Arrays.stream(values()) - .flatMap(n -> Stream.of(n.snakeCaseName, n.camelCaseName)) - .collect(Collectors.toSet()); - private final String camelCaseName; private final String snakeCaseName; - FormatNames(String camelCaseName, String snakeCaseName) { - this.camelCaseName = camelCaseName; + FormatNames(String snakeCaseName) { this.snakeCaseName = snakeCaseName; } - public static boolean exist(String format) { - return ALL_NAMES.contains(format); - } - - public static FormatNames forName(String format) { - for (FormatNames name : values()) { - if (name.matches(format)) { - return name; - } - } - return null; - } - public boolean matches(String format) { - return format.equals(camelCaseName) || format.equals(snakeCaseName); - } - - public boolean isCamelCase(String format) { - return format.equals(camelCaseName); + return format.equals(snakeCaseName); } public String getSnakeCaseName() { return snakeCaseName; } - - public String getCamelCaseName() { - return camelCaseName; - } } diff --git a/server/src/main/java/org/opensearch/common/time/LegacyFormatNames.java b/server/src/main/java/org/opensearch/common/time/LegacyFormatNames.java new file mode 100644 index 0000000000000..86a318fe8ba05 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/time/LegacyFormatNames.java @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.time; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Legacy Date format names. + * + * These format names are to ensure BWC support for camel case DateFormat names on indexes + * created prior to 2.12. + * + * @opensearch.internal + */ +enum LegacyFormatNames { + ISO8601(null, "iso8601"), + BASIC_DATE("basicDate", "basic_date"), + BASIC_DATE_TIME("basicDateTime", "basic_date_time"), + BASIC_DATE_TIME_NO_MILLIS("basicDateTimeNoMillis", "basic_date_time_no_millis"), + BASIC_ORDINAL_DATE("basicOrdinalDate", "basic_ordinal_date"), + BASIC_ORDINAL_DATE_TIME("basicOrdinalDateTime", "basic_ordinal_date_time"), + BASIC_ORDINAL_DATE_TIME_NO_MILLIS("basicOrdinalDateTimeNoMillis", "basic_ordinal_date_time_no_millis"), + BASIC_TIME("basicTime", "basic_time"), + BASIC_TIME_NO_MILLIS("basicTimeNoMillis", "basic_time_no_millis"), + BASIC_T_TIME("basicTTime", "basic_t_time"), + BASIC_T_TIME_NO_MILLIS("basicTTimeNoMillis", "basic_t_time_no_millis"), + BASIC_WEEK_DATE("basicWeekDate", "basic_week_date"), + BASIC_WEEK_DATE_TIME("basicWeekDateTime", "basic_week_date_time"), + BASIC_WEEK_DATE_TIME_NO_MILLIS("basicWeekDateTimeNoMillis", "basic_week_date_time_no_millis"), + DATE(null, "date"), + DATE_HOUR("dateHour", "date_hour"), + DATE_HOUR_MINUTE("dateHourMinute", "date_hour_minute"), + DATE_HOUR_MINUTE_SECOND("dateHourMinuteSecond", "date_hour_minute_second"), + DATE_HOUR_MINUTE_SECOND_FRACTION("dateHourMinuteSecondFraction", "date_hour_minute_second_fraction"), + DATE_HOUR_MINUTE_SECOND_MILLIS("dateHourMinuteSecondMillis", "date_hour_minute_second_millis"), + DATE_OPTIONAL_TIME("dateOptionalTime", "date_optional_time"), + DATE_TIME("dateTime", "date_time"), + DATE_TIME_NO_MILLIS("dateTimeNoMillis", "date_time_no_millis"), + HOUR(null, "hour"), + HOUR_MINUTE("hourMinute", "hour_minute"), + HOUR_MINUTE_SECOND("hourMinuteSecond", "hour_minute_second"), + HOUR_MINUTE_SECOND_FRACTION("hourMinuteSecondFraction", "hour_minute_second_fraction"), + HOUR_MINUTE_SECOND_MILLIS("hourMinuteSecondMillis", "hour_minute_second_millis"), + ORDINAL_DATE("ordinalDate", "ordinal_date"), + ORDINAL_DATE_TIME("ordinalDateTime", "ordinal_date_time"), + ORDINAL_DATE_TIME_NO_MILLIS("ordinalDateTimeNoMillis", "ordinal_date_time_no_millis"), + TIME(null, "time"), + TIME_NO_MILLIS("timeNoMillis", "time_no_millis"), + T_TIME("tTime", "t_time"), + T_TIME_NO_MILLIS("tTimeNoMillis", "t_time_no_millis"), + WEEK_DATE("weekDate", "week_date"), + WEEK_DATE_TIME("weekDateTime", "week_date_time"), + WEEK_DATE_TIME_NO_MILLIS("weekDateTimeNoMillis", "week_date_time_no_millis"), + WEEK_YEAR(null, "week_year"), + WEEKYEAR(null, "weekyear"), + WEEK_YEAR_WEEK("weekyearWeek", "weekyear_week"), + WEEKYEAR_WEEK_DAY("weekyearWeekDay", "weekyear_week_day"), + YEAR(null, "year"), + YEAR_MONTH("yearMonth", "year_month"), + YEAR_MONTH_DAY("yearMonthDay", "year_month_day"), + EPOCH_SECOND(null, "epoch_second"), + EPOCH_MILLIS(null, "epoch_millis"), + // strict date formats here, must be at least 4 digits for year and two for months and two for day + STRICT_BASIC_WEEK_DATE("strictBasicWeekDate", "strict_basic_week_date"), + STRICT_BASIC_WEEK_DATE_TIME("strictBasicWeekDateTime", "strict_basic_week_date_time"), + STRICT_BASIC_WEEK_DATE_TIME_NO_MILLIS("strictBasicWeekDateTimeNoMillis", "strict_basic_week_date_time_no_millis"), + STRICT_DATE("strictDate", "strict_date"), + STRICT_DATE_HOUR("strictDateHour", "strict_date_hour"), + STRICT_DATE_HOUR_MINUTE("strictDateHourMinute", "strict_date_hour_minute"), + STRICT_DATE_HOUR_MINUTE_SECOND("strictDateHourMinuteSecond", "strict_date_hour_minute_second"), + STRICT_DATE_HOUR_MINUTE_SECOND_FRACTION("strictDateHourMinuteSecondFraction", "strict_date_hour_minute_second_fraction"), + STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS("strictDateHourMinuteSecondMillis", "strict_date_hour_minute_second_millis"), + STRICT_DATE_OPTIONAL_TIME("strictDateOptionalTime", "strict_date_optional_time"), + STRICT_DATE_OPTIONAL_TIME_NANOS("strictDateOptionalTimeNanos", "strict_date_optional_time_nanos"), + STRICT_DATE_TIME("strictDateTime", "strict_date_time"), + STRICT_DATE_TIME_NO_MILLIS("strictDateTimeNoMillis", "strict_date_time_no_millis"), + STRICT_HOUR("strictHour", "strict_hour"), + STRICT_HOUR_MINUTE("strictHourMinute", "strict_hour_minute"), + STRICT_HOUR_MINUTE_SECOND("strictHourMinuteSecond", "strict_hour_minute_second"), + STRICT_HOUR_MINUTE_SECOND_FRACTION("strictHourMinuteSecondFraction", "strict_hour_minute_second_fraction"), + STRICT_HOUR_MINUTE_SECOND_MILLIS("strictHourMinuteSecondMillis", "strict_hour_minute_second_millis"), + STRICT_ORDINAL_DATE("strictOrdinalDate", "strict_ordinal_date"), + STRICT_ORDINAL_DATE_TIME("strictOrdinalDateTime", "strict_ordinal_date_time"), + STRICT_ORDINAL_DATE_TIME_NO_MILLIS("strictOrdinalDateTimeNoMillis", "strict_ordinal_date_time_no_millis"), + STRICT_TIME("strictTime", "strict_time"), + STRICT_TIME_NO_MILLIS("strictTimeNoMillis", "strict_time_no_millis"), + STRICT_T_TIME("strictTTime", "strict_t_time"), + STRICT_T_TIME_NO_MILLIS("strictTTimeNoMillis", "strict_t_time_no_millis"), + STRICT_WEEK_DATE("strictWeekDate", "strict_week_date"), + STRICT_WEEK_DATE_TIME("strictWeekDateTime", "strict_week_date_time"), + STRICT_WEEK_DATE_TIME_NO_MILLIS("strictWeekDateTimeNoMillis", "strict_week_date_time_no_millis"), + STRICT_WEEKYEAR("strictWeekyear", "strict_weekyear"), + STRICT_WEEKYEAR_WEEK("strictWeekyearWeek", "strict_weekyear_week"), + STRICT_WEEKYEAR_WEEK_DAY("strictWeekyearWeekDay", "strict_weekyear_week_day"), + STRICT_YEAR("strictYear", "strict_year"), + STRICT_YEAR_MONTH("strictYearMonth", "strict_year_month"), + STRICT_YEAR_MONTH_DAY("strictYearMonthDay", "strict_year_month_day"); + + private static final Map ALL_NAMES = Arrays.stream(values()) + .filter(n -> n.camelCaseName != null) + .collect(Collectors.toMap(n -> n.camelCaseName, n -> n.snakeCaseName)); + + private final String camelCaseName; + private final String snakeCaseName; + + LegacyFormatNames(String camelCaseName, String snakeCaseName) { + this.camelCaseName = camelCaseName; + this.snakeCaseName = snakeCaseName; + } + + public static String camelCaseToSnakeCase(String format) { + return ALL_NAMES.getOrDefault(format, format); + } +} diff --git a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java index 3b832628695fe..a541ae00fc746 100644 --- a/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/DateFieldMapper.java @@ -282,10 +282,15 @@ public Builder( private DateFormatter buildFormatter() { try { if (format.isConfigured() && !printFormat.isConfigured()) { - return DateFormatter.forPattern(format.getValue(), null, !format.isConfigured()).withLocale(locale.getValue()); + return DateFormatter.forPattern(format.getValue(), null, format.isConfigured() == false, indexCreatedVersion) + .withLocale(locale.getValue()); } - return DateFormatter.forPattern(format.getValue(), printFormat.getValue(), !format.isConfigured()) - .withLocale(locale.getValue()); + return DateFormatter.forPattern( + format.getValue(), + printFormat.getValue(), + format.isConfigured() == false, + indexCreatedVersion + ).withLocale(locale.getValue()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Error parsing [format] on field [" + name() + "]: " + e.getMessage(), e); } diff --git a/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java index 93b929a82f095..b85b18a20e621 100644 --- a/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java @@ -325,7 +325,8 @@ private void init(FieldMapper toInit) { setValue(initializer.apply(toInit)); } - private void parse(String field, ParserContext context, Object in) { + // public for testing only + public void parse(String field, ParserContext context, Object in) { setValue(parser.apply(field, context, in)); } diff --git a/server/src/main/java/org/opensearch/script/ClassPermission.java b/server/src/main/java/org/opensearch/script/ClassPermission.java index 9be8ecbc7c33e..a6e97de1a8387 100644 --- a/server/src/main/java/org/opensearch/script/ClassPermission.java +++ b/server/src/main/java/org/opensearch/script/ClassPermission.java @@ -78,12 +78,6 @@ *

  • {@link java.util.Map}
  • *
  • {@link java.util.Set}
  • *
  • {@link java.util.UUID}
  • - *
  • {@link org.joda.time.DateTime}
  • - *
  • {@link org.joda.time.DateTimeUtils}
  • - *
  • {@link org.joda.time.DateTimeZone}
  • - *
  • {@link org.joda.time.Instant}
  • - *
  • {@link org.joda.time.ReadableDateTime}
  • - *
  • {@link org.joda.time.ReadableInstant}
  • * * * @opensearch.internal @@ -116,14 +110,7 @@ public final class ClassPermission extends BasicPermission { java.util.List.class.getName(), java.util.Map.class.getName(), java.util.Set.class.getName(), - java.util.UUID.class.getName(), - // joda-time - org.joda.time.DateTime.class.getName(), - org.joda.time.DateTimeUtils.class.getName(), - org.joda.time.DateTimeZone.class.getName(), - org.joda.time.Instant.class.getName(), - org.joda.time.ReadableDateTime.class.getName(), - org.joda.time.ReadableInstant.class.getName() + java.util.UUID.class.getName() ) ) ); diff --git a/server/src/main/java/org/opensearch/search/DocValueFormat.java b/server/src/main/java/org/opensearch/search/DocValueFormat.java index 7be51643eeb7d..00476e62bc9ae 100644 --- a/server/src/main/java/org/opensearch/search/DocValueFormat.java +++ b/server/src/main/java/org/opensearch/search/DocValueFormat.java @@ -243,14 +243,15 @@ public DateTime(DateFormatter formatter, ZoneId timeZone, DateFieldMapper.Resolu } public DateTime(StreamInput in) throws IOException { - if (in.getVersion().onOrAfter(Version.V_2_12_0)) { + final Version inVersion = in.getVersion(); + if (inVersion.onOrAfter(Version.V_2_12_0)) { this.formatter = DateFormatter.forPattern(in.readString(), in.readOptionalString()); } else { - this.formatter = DateFormatter.forPattern(in.readString()); + this.formatter = DateFormatter.forPattern(in.readString(), inVersion); } this.parser = formatter.toDateMathParser(); - String zoneId = in.readString(); + final String zoneId = in.readString(); this.timeZone = ZoneId.of(zoneId); this.resolution = DateFieldMapper.Resolution.ofOrdinal(in.readVInt()); if (in.getVersion().before(Version.V_3_0_0)) { diff --git a/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptDoubleValues.java b/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptDoubleValues.java index 9b73f1b2155fb..09a614175dbdd 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptDoubleValues.java +++ b/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptDoubleValues.java @@ -36,7 +36,6 @@ import org.opensearch.index.fielddata.SortingNumericDoubleValues; import org.opensearch.script.AggregationScript; import org.opensearch.search.aggregations.AggregationExecutionException; -import org.joda.time.ReadableInstant; import java.io.IOException; import java.lang.reflect.Array; @@ -67,9 +66,6 @@ public boolean advanceExact(int target) throws IOException { } else if (value instanceof Number) { resize(1); values[0] = ((Number) value).doubleValue(); - } else if (value instanceof ReadableInstant) { - resize(1); - values[0] = ((ReadableInstant) value).getMillis(); } else if (value instanceof ZonedDateTime) { resize(1); values[0] = ((ZonedDateTime) value).toInstant().toEpochMilli(); @@ -105,9 +101,6 @@ public boolean advanceExact(int target) throws IOException { private static double toDoubleValue(Object o) { if (o instanceof Number) { return ((Number) o).doubleValue(); - } else if (o instanceof ReadableInstant) { - // Dates are exposed in scripts as ReadableDateTimes but aggregations want them to be numeric - return ((ReadableInstant) o).getMillis(); } else if (o instanceof ZonedDateTime) { return ((ZonedDateTime) o).toInstant().toEpochMilli(); } else if (o instanceof Boolean) { diff --git a/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptLongValues.java b/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptLongValues.java index b62178f85273a..c9bb21de50468 100644 --- a/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptLongValues.java +++ b/server/src/main/java/org/opensearch/search/aggregations/support/values/ScriptLongValues.java @@ -37,7 +37,6 @@ import org.opensearch.index.fielddata.AbstractSortingNumericDocValues; import org.opensearch.script.AggregationScript; import org.opensearch.search.aggregations.AggregationExecutionException; -import org.joda.time.ReadableInstant; import java.io.IOException; import java.lang.reflect.Array; @@ -104,9 +103,6 @@ else if (value instanceof Collection) { private static long toLongValue(Object o) { if (o instanceof Number) { return ((Number) o).longValue(); - } else if (o instanceof ReadableInstant) { - // Dates are exposed in scripts as ReadableDateTimes but aggregations want them to be numeric - return ((ReadableInstant) o).getMillis(); } else if (o instanceof ZonedDateTime) { return ((ZonedDateTime) o).toInstant().toEpochMilli(); } else if (o instanceof Boolean) { diff --git a/server/src/test/java/org/opensearch/cluster/metadata/DateMathExpressionResolverTests.java b/server/src/test/java/org/opensearch/cluster/metadata/DateMathExpressionResolverTests.java index a152b4473ef1e..bdd739bf04e15 100644 --- a/server/src/test/java/org/opensearch/cluster/metadata/DateMathExpressionResolverTests.java +++ b/server/src/test/java/org/opensearch/cluster/metadata/DateMathExpressionResolverTests.java @@ -39,18 +39,20 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver.Context; import org.opensearch.cluster.metadata.IndexNameExpressionResolver.DateMathExpressionResolver; import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.joda.time.DateTimeZone.UTC; public class DateMathExpressionResolverTests extends OpenSearchTestCase { @@ -80,20 +82,32 @@ public void testExpression() throws Exception { assertThat(result.size(), equalTo(3)); assertThat( result.get(0), - equalTo(".marvel-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".marvel-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); assertThat( result.get(1), - equalTo(".watch_history-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".watch_history-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); assertThat( result.get(2), - equalTo("logstash-" + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + "logstash-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); } public void testEmpty() throws Exception { - List result = expressionResolver.resolve(context, Collections.emptyList()); + List result = expressionResolver.resolve(context, Collections.emptyList()); assertThat(result.size(), equalTo(0)); } @@ -110,9 +124,11 @@ public void testExpression_MultiParts() throws Exception { result.get(0), equalTo( ".text1-" - + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(context.getStartTime(), UTC)) + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + "-text2-" - + DateTimeFormat.forPattern("YYYY.MM.dd").print(new DateTime(context.getStartTime(), UTC).withDayOfMonth(1)) + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC).withDayOfMonth(1)) ) ); } @@ -122,7 +138,11 @@ public void testExpression_CustomFormat() throws Exception { assertThat(results.size(), equalTo(1)); assertThat( results.get(0), - equalTo(".marvel-" + DateTimeFormat.forPattern("yyyy.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".marvel-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); } @@ -131,7 +151,11 @@ public void testExpression_EscapeStatic() throws Exception { assertThat(result.size(), equalTo(1)); assertThat( result.get(0), - equalTo(".mar{v}el-" + DateTimeFormat.forPattern("yyyy.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".mar{v}el-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); } @@ -140,7 +164,11 @@ public void testExpression_EscapeDateFormat() throws Exception { assertThat(result.size(), equalTo(1)); assertThat( result.get(0), - equalTo(".marvel-" + DateTimeFormat.forPattern("'{year}'yyyy").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".marvel-" + + DateTimeFormatter.ofPattern("'{year}'yyyy", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); } @@ -153,45 +181,56 @@ public void testExpression_MixedArray() throws Exception { assertThat(result.get(0), equalTo("name1")); assertThat( result.get(1), - equalTo(".marvel-" + DateTimeFormat.forPattern("yyyy.MM.dd").print(new DateTime(context.getStartTime(), UTC))) + equalTo( + ".marvel-" + + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC)) + ) ); assertThat(result.get(2), equalTo("name2")); assertThat( result.get(3), - equalTo(".logstash-" + DateTimeFormat.forPattern("yyyy.MM").print(new DateTime(context.getStartTime(), UTC).withDayOfMonth(1))) + equalTo( + ".logstash-" + + DateTimeFormatter.ofPattern("uuuu.MM", Locale.ROOT) + .format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(context.getStartTime()), ZoneOffset.UTC).withDayOfMonth(1)) + ) ); } public void testExpression_CustomTimeZoneInIndexName() throws Exception { - DateTimeZone timeZone; + ZoneId timeZone; int hoursOffset; int minutesOffset = 0; if (randomBoolean()) { hoursOffset = randomIntBetween(-12, 14); - timeZone = DateTimeZone.forOffsetHours(hoursOffset); + timeZone = ZoneOffset.ofHours(hoursOffset); } else { hoursOffset = randomIntBetween(-11, 13); minutesOffset = randomIntBetween(0, 59); - timeZone = DateTimeZone.forOffsetHoursMinutes(hoursOffset, minutesOffset); + timeZone = ZoneOffset.ofHoursMinutes(hoursOffset, minutesOffset); } - DateTime now; + ZonedDateTime now; if (hoursOffset >= 0) { // rounding to next day 00:00 - now = DateTime.now(UTC) + now = ZonedDateTime.now(ZoneOffset.UTC) .plusHours(hoursOffset) .plusMinutes(minutesOffset) - .withHourOfDay(0) - .withMinuteOfHour(0) - .withSecondOfMinute(0); + .withHour(0) + .withMinute(0) + .withSecond(0); } else { // rounding to today 00:00 - now = DateTime.now(UTC).withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0); + now = ZonedDateTime.now(ZoneOffset.UTC).withHour(0).withMinute(0).withSecond(0); } - Context context = new Context(this.context.getState(), this.context.getOptions(), now.getMillis(), false); - List results = expressionResolver.resolve(context, Arrays.asList("<.marvel-{now/d{yyyy.MM.dd|" + timeZone.getID() + "}}>")); + Context context = new Context(this.context.getState(), this.context.getOptions(), now.toInstant().toEpochMilli(), false); + List results = expressionResolver.resolve(context, Arrays.asList("<.marvel-{now/d{yyyy.MM.dd|" + timeZone.getId() + "}}>")); assertThat(results.size(), equalTo(1)); logger.info("timezone: [{}], now [{}], name: [{}]", timeZone, now, results.get(0)); - assertThat(results.get(0), equalTo(".marvel-" + DateTimeFormat.forPattern("yyyy.MM.dd").print(now.withZone(timeZone)))); + assertThat( + results.get(0), + equalTo(".marvel-" + DateTimeFormatter.ofPattern("uuuu.MM.dd", Locale.ROOT).format(now.withZoneSameInstant(timeZone))) + ); } public void testExpressionInvalidUnescaped() throws Exception { diff --git a/server/src/test/java/org/opensearch/common/RoundingTests.java b/server/src/test/java/org/opensearch/common/RoundingTests.java index 1a499bac3e2e8..6873037070db8 100644 --- a/server/src/test/java/org/opensearch/common/RoundingTests.java +++ b/server/src/test/java/org/opensearch/common/RoundingTests.java @@ -33,7 +33,6 @@ package org.opensearch.common; import org.opensearch.common.collect.Tuple; -import org.opensearch.common.rounding.DateTimeUnit; import org.opensearch.common.time.DateFormatter; import org.opensearch.common.time.DateFormatters; import org.opensearch.common.unit.TimeValue; @@ -236,7 +235,7 @@ public void testOffsetRounding() { /** * Randomized test on TimeUnitRounding. Test uses random - * {@link DateTimeUnit} and {@link ZoneId} and often (50% of the time) + * {@link Rounding.DateTimeUnit} and {@link ZoneId} and often (50% of the time) * chooses test dates that are exactly on or close to offset changes (e.g. * DST) in the chosen time zone. *

    diff --git a/server/src/test/java/org/opensearch/common/joda/JavaJodaTimeDuellingTests.java b/server/src/test/java/org/opensearch/common/joda/JavaJodaTimeDuellingTests.java deleted file mode 100644 index e8ddfde11f4cc..0000000000000 --- a/server/src/test/java/org/opensearch/common/joda/JavaJodaTimeDuellingTests.java +++ /dev/null @@ -1,952 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.OpenSearchParseException; -import org.opensearch.common.time.DateFormatter; -import org.opensearch.common.time.DateFormatters; -import org.opensearch.common.time.DateMathParser; -import org.opensearch.common.time.FormatNames; -import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.ISODateTimeFormat; - -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; - -public class JavaJodaTimeDuellingTests extends OpenSearchTestCase { - @Override - protected boolean enableWarningsCheck() { - return false; - } - - public void testTimezoneParsing() { - /* this testcase won't work in joda. See comment in {@link #testPartialTimeParsing()} - assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time"); - */ - assertSameDateAs("2016-11-30T00+01", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T00+0100", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T00+01:00", "strict_date_optional_time", "strict_date_optional_time"); - } - - public void testPartialTimeParsing() { - /* - This does not work in Joda as it reports 2016-11-30T01:00:00Z - because StrictDateOptionalTime confuses +01 with an hour (which is a signed fixed length digit) - assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time"); - ES java.time implementation does not suffer from this, - but we intentionally not allow parsing timezone without an time part as it is not allowed in iso8601 - */ - assertJavaTimeParseException("2016-11-30T+01", "strict_date_optional_time"); - - assertSameDateAs("2016-11-30T12+01", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00+01", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00:00+01", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00:00.000+01", "strict_date_optional_time", "strict_date_optional_time"); - - // without timezone - assertSameDateAs("2016-11-30T", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00:00", "strict_date_optional_time", "strict_date_optional_time"); - assertSameDateAs("2016-11-30T12:00:00.000", "strict_date_optional_time", "strict_date_optional_time"); - } - - // date_optional part of a parser names "strict_date_optional_time" or "date_optional"time - // means that date part can be partially parsed. - public void testPartialDateParsing() { - assertSameDateAs("2001", "strict_date_optional_time_nanos", "strict_date_optional_time"); - assertSameDateAs("2001-01", "strict_date_optional_time_nanos", "strict_date_optional_time"); - assertSameDateAs("2001-01-01", "strict_date_optional_time_nanos", "strict_date_optional_time"); - - assertSameDate("2001", "strict_date_optional_time"); - assertSameDate("2001-01", "strict_date_optional_time"); - assertSameDate("2001-01-01", "strict_date_optional_time"); - - assertSameDate("2001", "date_optional_time"); - assertSameDate("2001-01", "date_optional_time"); - assertSameDate("2001-01-01", "date_optional_time"); - - assertSameDateAs("2001", "iso8601", "strict_date_optional_time"); - assertSameDateAs("2001-01", "iso8601", "strict_date_optional_time"); - assertSameDateAs("2001-01-01", "iso8601", "strict_date_optional_time"); - - assertSameDate("9999", "date_optional_time||epoch_second"); - } - - public void testCompositeDateMathParsing() { - // in all these examples the second pattern will be used - assertDateMathEquals("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS"); - assertDateMathEquals("2014-06-06T12:01:02.123", "strictDateTimeNoMillis||yyyy-MM-dd'T'HH:mm:ss.SSS"); - assertDateMathEquals("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss+HH:MM||yyyy-MM-dd'T'HH:mm:ss.SSS"); - } - - public void testExceptionWhenCompositeParsingFailsDateMath() { - // both parsing failures should contain pattern and input text in exception - // both patterns fail parsing the input text due to only 2 digits of millis. Hence full text was not parsed. - String pattern = "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SS"; - String text = "2014-06-06T12:01:02.123"; - OpenSearchParseException e1 = expectThrows( - OpenSearchParseException.class, - () -> dateMathToMillis(text, DateFormatter.forPattern(pattern)) - ); - assertThat(e1.getMessage(), containsString(pattern)); - assertThat(e1.getMessage(), containsString(text)); - - OpenSearchParseException e2 = expectThrows(OpenSearchParseException.class, () -> dateMathToMillis(text, Joda.forPattern(pattern))); - assertThat(e2.getMessage(), containsString(pattern)); - assertThat(e2.getMessage(), containsString(text)); - } - - private long dateMathToMillis(String text, DateFormatter dateFormatter) { - DateFormatter javaFormatter = dateFormatter.withLocale(randomLocale(random())); - DateMathParser javaDateMath = javaFormatter.toDateMathParser(); - return javaDateMath.parse(text, () -> 0, true, (ZoneId) null).toEpochMilli(); - } - - // these parsers should allow both ',' and '.' as a decimal point - public void testDecimalPointParsing() { - assertSameDate("2001-01-01T00:00:00.123Z", "strict_date_optional_time"); - assertSameDate("2001-01-01T00:00:00,123Z", "strict_date_optional_time"); - - assertSameDate("2001-01-01T00:00:00.123Z", "date_optional_time"); - assertSameDate("2001-01-01T00:00:00,123Z", "date_optional_time"); - - // only java.time has nanos parsing, but the results for 3digits should be the same - DateFormatter jodaFormatter = Joda.forPattern("strict_date_optional_time"); - DateFormatter javaFormatter = DateFormatter.forPattern("strict_date_optional_time_nanos"); - assertSameDate("2001-01-01T00:00:00.123Z", "strict_date_optional_time_nanos", jodaFormatter, javaFormatter); - assertSameDate("2001-01-01T00:00:00,123Z", "strict_date_optional_time_nanos", jodaFormatter, javaFormatter); - - assertParseException("2001-01-01T00:00:00.123,456Z", "strict_date_optional_time"); - assertParseException("2001-01-01T00:00:00.123,456Z", "date_optional_time"); - // This should fail, but java is ok with this because the field has the same value - // assertJavaTimeParseException("2001-01-01T00:00:00.123,123Z", "strict_date_optional_time_nanos"); - } - - public void testIncompatiblePatterns() { - // in joda 'y' means year, this is changed to 'u' in java.time. difference is in before era yeaers - assertSameMillis("-0001-01-01", "yyyy-MM-dd", "8uuuu-MM-dd"); - assertSameMillis("-1", "y", "8u"); - - // year-of-era in joda becomes 'y' in java.time - assertSameMillis("2019-01-01", "YYYY-MM-dd", "8yyyy-MM-dd"); - - // in joda 'Z' was able to parse 'Z' zulu but in java it fails. You have to use 'X' to do that. - assertSameMillis("2019-01-01T01:01:01.001Z", "YYYY-MM-dd'T'HH:mm:ss.SSSZ", "8yyyy-MM-dd'T'HH:mm:ss.SSSX"); - assertSameMillis("2019-01-01T01:01:01.001+0000", "YYYY-MM-dd'T'HH:mm:ss.SSSZ", "8yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - - // 'z' zoneId in joda prints UTC whereas joda prints 'Z' for zulu - TemporalAccessor parse = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.getDefault()) - .parse("2019-01-01T01:01:01.001+00:00"); - String javaZoneId = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.getDefault()).format(parse); - - DateTime dateTime = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ss.SSSZ") - .withOffsetParsed() - .parseDateTime("2019-01-01T01:01:01.001+0000"); - String jodaZoneId = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ss.SSSz").print(dateTime); - assertThat(javaZoneId, equalTo("2019-01-01T01:01:01.001Z")); - assertThat(jodaZoneId, equalTo("2019-01-01T01:01:01.001UTC")); - } - - private void assertSameMillis(String input, String jodaFormat, String javaFormat) { - DateFormatter jodaFormatter = Joda.forPattern(jodaFormat); - DateFormatter javaFormatter = DateFormatter.forPattern(javaFormat); - - DateTime jodaDateTime = jodaFormatter.parseJoda(input); - - TemporalAccessor javaTimeAccessor = javaFormatter.parse(input); - ZonedDateTime zonedDateTime = DateFormatters.from(javaTimeAccessor); - - String msg = String.format( - Locale.ROOT, - "Input [%s] JodaFormat [%s] JavaFormat [%s] Joda [%s], Java [%s]", - input, - jodaFormat, - javaFormat, - jodaDateTime, - DateTimeFormatter.ISO_INSTANT.format(zonedDateTime.toInstant()) - ); - - assertThat(msg, jodaDateTime.getMillis(), is(zonedDateTime.toInstant().toEpochMilli())); - } - - public void testTimeZoneFormatting() { - assertSameDate("2001-01-01T00:00:00Z", "date_time_no_millis"); - // the following fail under java 8 but work under java 10, needs investigation - assertSameDate("2001-01-01T00:00:00-0800", "date_time_no_millis"); - assertSameDate("2001-01-01T00:00:00+1030", "date_time_no_millis"); - assertSameDate("2001-01-01T00:00:00-08", "date_time_no_millis"); - assertSameDate("2001-01-01T00:00:00+10:30", "date_time_no_millis"); - - // different timezone parsing styles require a different number of letters - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSSXXX", Locale.ROOT); - formatter.parse("20181126T121212.123Z"); - formatter.parse("20181126T121212.123-08:30"); - - DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSSXXXX", Locale.ROOT); - formatter2.parse("20181126T121212.123+1030"); - formatter2.parse("20181126T121212.123-0830"); - - // ... and can be combined, note that this is not an XOR, so one could append both timezones with this example - DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSS[XXXX][XXX]", Locale.ROOT); - formatter3.parse("20181126T121212.123Z"); - formatter3.parse("20181126T121212.123-08:30"); - formatter3.parse("20181126T121212.123+1030"); - formatter3.parse("20181126T121212.123-0830"); - } - - public void testCustomTimeFormats() { - assertSameDate("2010 12 06 11:05:15", "yyyy dd MM HH:mm:ss"); - assertSameDate("12/06", "dd/MM"); - assertSameDate("Nov 24 01:29:01 -0800", "MMM dd HH:mm:ss Z"); - } - - // this test requires tests to run with -Djava.locale.providers=COMPAT in order to work - // public void testCustomLocales() { - // - // // also ensure that locale based dates are the same - // assertSameDate("Di., 05 Dez. 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de")); - // assertSameDate("Mi., 06 Dez. 2000 02:55:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de")); - // assertSameDate("Do., 07 Dez. 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de")); - // assertSameDate("Fr., 08 Dez. 2000 00:00:00 -0800", "E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de")); - // - // DateTime dateTimeNow = DateTime.now(DateTimeZone.UTC); - // ZonedDateTime javaTimeNow = Instant.ofEpochMilli(dateTimeNow.getMillis()).atZone(ZoneOffset.UTC); - // assertSamePrinterOutput("E, d MMM yyyy HH:mm:ss Z", LocaleUtils.parse("de"), javaTimeNow, dateTimeNow); - // } - - public void testDuellingFormatsValidParsing() { - assertSameDate("1522332219", "epoch_second"); - assertSameDate("0", "epoch_second"); - assertSameDate("1", "epoch_second"); - assertSameDate("1522332219321", "epoch_millis"); - assertSameDate("0", "epoch_millis"); - assertSameDate("1", "epoch_millis"); - - assertSameDate("20181126", "basic_date"); - assertSameDate("20181126T121212.123Z", "basic_date_time"); - assertSameDate("20181126T121212.123+10:00", "basic_date_time"); - assertSameDate("20181126T121212.123-0800", "basic_date_time"); - - assertSameDate("20181126T121212Z", "basic_date_time_no_millis"); - assertSameDate("20181126T121212+01:00", "basic_date_time_no_millis"); - assertSameDate("20181126T121212+0100", "basic_date_time_no_millis"); - assertSameDate("2018363", "basic_ordinal_date"); - assertSameDate("2018363T121212.1Z", "basic_ordinal_date_time"); - assertSameDate("2018363T121212.123Z", "basic_ordinal_date_time"); - assertSameDate("2018363T121212.123456789Z", "basic_ordinal_date_time"); - assertSameDate("2018363T121212.123+0100", "basic_ordinal_date_time"); - assertSameDate("2018363T121212.123+01:00", "basic_ordinal_date_time"); - assertSameDate("2018363T121212Z", "basic_ordinal_date_time_no_millis"); - assertSameDate("2018363T121212+0100", "basic_ordinal_date_time_no_millis"); - assertSameDate("2018363T121212+01:00", "basic_ordinal_date_time_no_millis"); - assertSameDate("121212.1Z", "basic_time"); - assertSameDate("121212.123Z", "basic_time"); - assertSameDate("121212.123456789Z", "basic_time"); - assertSameDate("121212.1+0100", "basic_time"); - assertSameDate("121212.123+0100", "basic_time"); - assertSameDate("121212.123+01:00", "basic_time"); - assertSameDate("121212Z", "basic_time_no_millis"); - assertSameDate("121212+0100", "basic_time_no_millis"); - assertSameDate("121212+01:00", "basic_time_no_millis"); - assertSameDate("T121212.1Z", "basic_t_time"); - assertSameDate("T121212.123Z", "basic_t_time"); - assertSameDate("T121212.123456789Z", "basic_t_time"); - assertSameDate("T121212.1+0100", "basic_t_time"); - assertSameDate("T121212.123+0100", "basic_t_time"); - assertSameDate("T121212.123+01:00", "basic_t_time"); - assertSameDate("T121212Z", "basic_t_time_no_millis"); - assertSameDate("T121212+0100", "basic_t_time_no_millis"); - assertSameDate("T121212+01:00", "basic_t_time_no_millis"); - assertSameDate("2018W313", "basic_week_date"); - assertSameDate("1W313", "basic_week_date"); - assertSameDate("18W313", "basic_week_date"); - assertSameDate("2018W313T121212.1Z", "basic_week_date_time"); - assertSameDate("2018W313T121212.123Z", "basic_week_date_time"); - assertSameDate("2018W313T121212.123456789Z", "basic_week_date_time"); - assertSameDate("2018W313T121212.123+0100", "basic_week_date_time"); - assertSameDate("2018W313T121212.123+01:00", "basic_week_date_time"); - assertSameDate("2018W313T121212Z", "basic_week_date_time_no_millis"); - assertSameDate("2018W313T121212+0100", "basic_week_date_time_no_millis"); - assertSameDate("2018W313T121212+01:00", "basic_week_date_time_no_millis"); - - assertSameDate("2018-12-31", "date"); - assertSameDate("18-5-6", "date"); - assertSameDate("10000-5-6", "date"); - - assertSameDate("2018-12-31T12", "date_hour"); - assertSameDate("2018-12-31T8", "date_hour"); - - assertSameDate("2018-12-31T12:12", "date_hour_minute"); - assertSameDate("2018-12-31T8:3", "date_hour_minute"); - - assertSameDate("2018-12-31T12:12:12", "date_hour_minute_second"); - assertSameDate("2018-12-31T12:12:1", "date_hour_minute_second"); - - assertSameDate("2018-12-31T12:12:12.1", "date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.123", "date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.123456789", "date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.1", "date_hour_minute_second_millis"); - assertSameDate("2018-12-31T12:12:12.123", "date_hour_minute_second_millis"); - assertParseException("2018-12-31T12:12:12.123456789", "date_hour_minute_second_millis"); - assertSameDate("2018-12-31T12:12:12.1", "date_hour_minute_second_millis"); - assertSameDate("2018-12-31T12:12:12.1", "date_hour_minute_second_fraction"); - - assertSameDate("2018-05", "date_optional_time"); - assertSameDate("2018-05-30", "date_optional_time"); - assertSameDate("2018-05-30T20", "date_optional_time"); - assertSameDate("2018-05-30T20:21", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.1", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123456789", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123Z", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123456789Z", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.1+0100", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123+0100", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.1+01:00", "date_optional_time"); - assertSameDate("2018-05-30T20:21:23.123+01:00", "date_optional_time"); - assertSameDate("2018-12-1", "date_optional_time"); - assertSameDate("2018-12-31T10:15:30", "date_optional_time"); - assertSameDate("2018-12-31T10:15:3", "date_optional_time"); - assertSameDate("2018-12-31T10:5:30", "date_optional_time"); - assertSameDate("2018-12-31T1:15:30", "date_optional_time"); - - assertSameDate("2018-12-31T10:15:30.1Z", "date_time"); - assertSameDate("2018-12-31T10:15:30.123Z", "date_time"); - assertSameDate("2018-12-31T10:15:30.123456789Z", "date_time"); - assertSameDate("2018-12-31T10:15:30.1+0100", "date_time"); - assertSameDate("2018-12-31T10:15:30.123+0100", "date_time"); - assertSameDate("2018-12-31T10:15:30.123+01:00", "date_time"); - assertSameDate("2018-12-31T10:15:30.1+01:00", "date_time"); - assertSameDate("2018-12-31T10:15:30.11Z", "date_time"); - assertSameDate("2018-12-31T10:15:30.11+0100", "date_time"); - assertSameDate("2018-12-31T10:15:30.11+01:00", "date_time"); - assertSameDate("2018-12-31T10:15:3.1Z", "date_time"); - assertSameDate("2018-12-31T10:15:3.123Z", "date_time"); - assertSameDate("2018-12-31T10:15:3.123456789Z", "date_time"); - assertSameDate("2018-12-31T10:15:3.1+0100", "date_time"); - assertSameDate("2018-12-31T10:15:3.123+0100", "date_time"); - assertSameDate("2018-12-31T10:15:3.123+01:00", "date_time"); - assertSameDate("2018-12-31T10:15:3.1+01:00", "date_time"); - - assertSameDate("2018-12-31T10:15:30Z", "date_time_no_millis"); - assertSameDate("2018-12-31T10:15:30+0100", "date_time_no_millis"); - assertSameDate("2018-12-31T10:15:30+01:00", "date_time_no_millis"); - assertSameDate("2018-12-31T10:5:30Z", "date_time_no_millis"); - assertSameDate("2018-12-31T10:5:30+0100", "date_time_no_millis"); - assertSameDate("2018-12-31T10:5:30+01:00", "date_time_no_millis"); - assertSameDate("2018-12-31T10:15:3Z", "date_time_no_millis"); - assertSameDate("2018-12-31T10:15:3+0100", "date_time_no_millis"); - assertSameDate("2018-12-31T10:15:3+01:00", "date_time_no_millis"); - assertSameDate("2018-12-31T1:15:30Z", "date_time_no_millis"); - assertSameDate("2018-12-31T1:15:30+0100", "date_time_no_millis"); - assertSameDate("2018-12-31T1:15:30+01:00", "date_time_no_millis"); - - assertSameDate("12", "hour"); - assertSameDate("01", "hour"); - assertSameDate("1", "hour"); - - assertSameDate("12:12", "hour_minute"); - assertSameDate("12:01", "hour_minute"); - assertSameDate("12:1", "hour_minute"); - - assertSameDate("12:12:12", "hour_minute_second"); - assertSameDate("12:12:01", "hour_minute_second"); - assertSameDate("12:12:1", "hour_minute_second"); - - assertSameDate("12:12:12.123", "hour_minute_second_fraction"); - assertSameDate("12:12:12.123456789", "hour_minute_second_fraction"); - assertSameDate("12:12:12.1", "hour_minute_second_fraction"); - assertParseException("12:12:12", "hour_minute_second_fraction"); - assertSameDate("12:12:12.123", "hour_minute_second_millis"); - assertParseException("12:12:12.123456789", "hour_minute_second_millis"); - assertSameDate("12:12:12.1", "hour_minute_second_millis"); - assertParseException("12:12:12", "hour_minute_second_millis"); - - assertSameDate("2018-128", "ordinal_date"); - assertSameDate("2018-1", "ordinal_date"); - - assertSameDate("2018-128T10:15:30.1Z", "ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123Z", "ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123456789Z", "ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123+0100", "ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123+01:00", "ordinal_date_time"); - assertSameDate("2018-1T10:15:30.1Z", "ordinal_date_time"); - assertSameDate("2018-1T10:15:30.123Z", "ordinal_date_time"); - assertSameDate("2018-1T10:15:30.123456789Z", "ordinal_date_time"); - assertSameDate("2018-1T10:15:30.123+0100", "ordinal_date_time"); - assertSameDate("2018-1T10:15:30.123+01:00", "ordinal_date_time"); - - assertSameDate("2018-128T10:15:30Z", "ordinal_date_time_no_millis"); - assertSameDate("2018-128T10:15:30+0100", "ordinal_date_time_no_millis"); - assertSameDate("2018-128T10:15:30+01:00", "ordinal_date_time_no_millis"); - assertSameDate("2018-1T10:15:30Z", "ordinal_date_time_no_millis"); - assertSameDate("2018-1T10:15:30+0100", "ordinal_date_time_no_millis"); - assertSameDate("2018-1T10:15:30+01:00", "ordinal_date_time_no_millis"); - - assertSameDate("10:15:30.1Z", "time"); - assertSameDate("10:15:30.123Z", "time"); - assertSameDate("10:15:30.123456789Z", "time"); - assertSameDate("10:15:30.123+0100", "time"); - assertSameDate("10:15:30.123+01:00", "time"); - assertSameDate("1:15:30.1Z", "time"); - assertSameDate("1:15:30.123Z", "time"); - assertSameDate("1:15:30.123+0100", "time"); - assertSameDate("1:15:30.123+01:00", "time"); - assertSameDate("10:1:30.1Z", "time"); - assertSameDate("10:1:30.123Z", "time"); - assertSameDate("10:1:30.123+0100", "time"); - assertSameDate("10:1:30.123+01:00", "time"); - assertSameDate("10:15:3.1Z", "time"); - assertSameDate("10:15:3.123Z", "time"); - assertSameDate("10:15:3.123+0100", "time"); - assertSameDate("10:15:3.123+01:00", "time"); - assertParseException("10:15:3.1", "time"); - assertParseException("10:15:3Z", "time"); - - assertSameDate("10:15:30Z", "time_no_millis"); - assertSameDate("10:15:30+0100", "time_no_millis"); - assertSameDate("10:15:30+01:00", "time_no_millis"); - assertSameDate("01:15:30Z", "time_no_millis"); - assertSameDate("01:15:30+0100", "time_no_millis"); - assertSameDate("01:15:30+01:00", "time_no_millis"); - assertSameDate("1:15:30Z", "time_no_millis"); - assertSameDate("1:15:30+0100", "time_no_millis"); - assertSameDate("1:15:30+01:00", "time_no_millis"); - assertSameDate("10:5:30Z", "time_no_millis"); - assertSameDate("10:5:30+0100", "time_no_millis"); - assertSameDate("10:5:30+01:00", "time_no_millis"); - assertSameDate("10:15:3Z", "time_no_millis"); - assertSameDate("10:15:3+0100", "time_no_millis"); - assertSameDate("10:15:3+01:00", "time_no_millis"); - assertParseException("10:15:3", "time_no_millis"); - - assertSameDate("T10:15:30.1Z", "t_time"); - assertSameDate("T10:15:30.123Z", "t_time"); - assertSameDate("T10:15:30.123456789Z", "t_time"); - assertSameDate("T10:15:30.1+0100", "t_time"); - assertSameDate("T10:15:30.123+0100", "t_time"); - assertSameDate("T10:15:30.123+01:00", "t_time"); - assertSameDate("T10:15:30.1+01:00", "t_time"); - assertSameDate("T1:15:30.123Z", "t_time"); - assertSameDate("T1:15:30.123+0100", "t_time"); - assertSameDate("T1:15:30.123+01:00", "t_time"); - assertSameDate("T10:1:30.123Z", "t_time"); - assertSameDate("T10:1:30.123+0100", "t_time"); - assertSameDate("T10:1:30.123+01:00", "t_time"); - assertSameDate("T10:15:3.123Z", "t_time"); - assertSameDate("T10:15:3.123+0100", "t_time"); - assertSameDate("T10:15:3.123+01:00", "t_time"); - assertParseException("T10:15:3.1", "t_time"); - assertParseException("T10:15:3Z", "t_time"); - - assertSameDate("T10:15:30Z", "t_time_no_millis"); - assertSameDate("T10:15:30+0100", "t_time_no_millis"); - assertSameDate("T10:15:30+01:00", "t_time_no_millis"); - assertSameDate("T1:15:30Z", "t_time_no_millis"); - assertSameDate("T1:15:30+0100", "t_time_no_millis"); - assertSameDate("T1:15:30+01:00", "t_time_no_millis"); - assertSameDate("T10:1:30Z", "t_time_no_millis"); - assertSameDate("T10:1:30+0100", "t_time_no_millis"); - assertSameDate("T10:1:30+01:00", "t_time_no_millis"); - assertSameDate("T10:15:3Z", "t_time_no_millis"); - assertSameDate("T10:15:3+0100", "t_time_no_millis"); - assertSameDate("T10:15:3+01:00", "t_time_no_millis"); - assertParseException("T10:15:3", "t_time_no_millis"); - - assertSameDate("2012-W48-6", "week_date"); - assertSameDate("2012-W01-6", "week_date"); - assertSameDate("2012-W1-6", "week_date"); - // joda comes up with a different exception message here, so we have to adapt - assertJodaParseException("2012-W1-8", "week_date", "Cannot parse \"2012-W1-8\": Value 8 for dayOfWeek must be in the range [1,7]"); - assertJavaTimeParseException("2012-W1-8", "week_date"); - - assertSameDate("2012-W48-6T10:15:30.1Z", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123Z", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123456789Z", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.1+0100", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123+0100", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.1+01:00", "week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123+01:00", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.1Z", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.123Z", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.1+0100", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.123+0100", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.1+01:00", "week_date_time"); - assertSameDate("2012-W1-6T10:15:30.123+01:00", "week_date_time"); - - assertSameDate("2012-W48-6T10:15:30Z", "week_date_time_no_millis"); - assertSameDate("2012-W48-6T10:15:30+0100", "week_date_time_no_millis"); - assertSameDate("2012-W48-6T10:15:30+01:00", "week_date_time_no_millis"); - assertSameDate("2012-W1-6T10:15:30Z", "week_date_time_no_millis"); - assertSameDate("2012-W1-6T10:15:30+0100", "week_date_time_no_millis"); - assertSameDate("2012-W1-6T10:15:30+01:00", "week_date_time_no_millis"); - - assertSameDate("2012", "year"); - assertSameDate("1", "year"); - assertSameDate("-2000", "year"); - - assertSameDate("2012-12", "yearMonth"); - assertSameDate("1-1", "yearMonth"); - - assertSameDate("2012-12-31", "yearMonthDay"); - assertSameDate("1-12-31", "yearMonthDay"); - assertSameDate("2012-1-31", "yearMonthDay"); - assertSameDate("2012-12-1", "yearMonthDay"); - - assertSameDate("2018", "weekyear"); - assertSameDate("1", "weekyear"); - assertSameDate("2017", "weekyear"); - - assertSameDate("2018-W29", "weekyear_week"); - assertSameDate("2018-W1", "weekyear_week"); - - assertSameDate("2012-W31-5", "weekyear_week_day"); - assertSameDate("2012-W1-1", "weekyear_week_day"); - } - - public void testCompositeParsing() { - // in all these examples the second pattern will be used - assertSameDate("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS"); - assertSameDate("2014-06-06T12:01:02.123", "strictDateTimeNoMillis||yyyy-MM-dd'T'HH:mm:ss.SSS"); - assertSameDate("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss+HH:MM||yyyy-MM-dd'T'HH:mm:ss.SSS"); - } - - public void testExceptionWhenCompositeParsingFails() { - assertParseException("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SS"); - } - - public void testDuelingStrictParsing() { - assertSameDate("2018W313", "strict_basic_week_date"); - assertParseException("18W313", "strict_basic_week_date"); - assertSameDate("2018W313T121212.1Z", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.123Z", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.123456789Z", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.1+0100", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.123+0100", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.1+01:00", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212.123+01:00", "strict_basic_week_date_time"); - assertParseException("2018W313T12128.123Z", "strict_basic_week_date_time"); - assertParseException("2018W313T12128.123456789Z", "strict_basic_week_date_time"); - assertParseException("2018W313T81212.123Z", "strict_basic_week_date_time"); - assertParseException("2018W313T12812.123Z", "strict_basic_week_date_time"); - assertParseException("2018W313T12812.1Z", "strict_basic_week_date_time"); - assertSameDate("2018W313T121212Z", "strict_basic_week_date_time_no_millis"); - assertSameDate("2018W313T121212+0100", "strict_basic_week_date_time_no_millis"); - assertSameDate("2018W313T121212+01:00", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12128Z", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12128+0100", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12128+01:00", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T81212Z", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T81212+0100", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T81212+01:00", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12812Z", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12812+0100", "strict_basic_week_date_time_no_millis"); - assertParseException("2018W313T12812+01:00", "strict_basic_week_date_time_no_millis"); - assertSameDate("2018-12-31", "strict_date"); - assertParseException("10000-12-31", "strict_date"); - assertParseException("2018-8-31", "strict_date"); - assertSameDate("2018-12-31T12", "strict_date_hour"); - assertParseException("2018-12-31T8", "strict_date_hour"); - assertSameDate("2018-12-31T12:12", "strict_date_hour_minute"); - assertParseException("2018-12-31T8:3", "strict_date_hour_minute"); - assertSameDate("2018-12-31T12:12:12", "strict_date_hour_minute_second"); - assertParseException("2018-12-31T12:12:1", "strict_date_hour_minute_second"); - assertSameDate("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.123", "strict_date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.123456789", "strict_date_hour_minute_second_fraction"); - assertSameDate("2018-12-31T12:12:12.123", "strict_date_hour_minute_second_millis"); - assertSameDate("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_millis"); - assertSameDate("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_fraction"); - assertParseException("2018-12-31T12:12:12", "strict_date_hour_minute_second_millis"); - assertParseException("2018-12-31T12:12:12", "strict_date_hour_minute_second_fraction"); - assertSameDate("2018-12-31", "strict_date_optional_time"); - assertParseException("2018-12-1", "strict_date_optional_time"); - assertParseException("2018-1-31", "strict_date_optional_time"); - assertParseException("10000-01-31", "strict_date_optional_time"); - assertSameDate("2010-01-05T02:00", "strict_date_optional_time"); - assertSameDate("2018-12-31T10:15:30", "strict_date_optional_time"); - assertSameDate("2018-12-31T10:15:30Z", "strict_date_optional_time"); - assertSameDate("2018-12-31T10:15:30+0100", "strict_date_optional_time"); - assertSameDate("2018-12-31T10:15:30+01:00", "strict_date_optional_time"); - assertParseException("2018-12-31T10:15:3", "strict_date_optional_time"); - assertParseException("2018-12-31T10:5:30", "strict_date_optional_time"); - assertParseException("2018-12-31T9:15:30", "strict_date_optional_time"); - assertSameDate("2015-01-04T00:00Z", "strict_date_optional_time"); - assertSameDate("2018-12-31T10:15:30.1Z", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.123Z", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.123456789Z", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.1+0100", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.123+0100", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.1+01:00", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.123+01:00", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.11Z", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.11+0100", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30.11+01:00", "strict_date_time"); - assertParseException("2018-12-31T10:15:3.123Z", "strict_date_time"); - assertParseException("2018-12-31T10:5:30.123Z", "strict_date_time"); - assertParseException("2018-12-31T1:15:30.123Z", "strict_date_time"); - assertSameDate("2018-12-31T10:15:30Z", "strict_date_time_no_millis"); - assertSameDate("2018-12-31T10:15:30+0100", "strict_date_time_no_millis"); - assertSameDate("2018-12-31T10:15:30+01:00", "strict_date_time_no_millis"); - assertParseException("2018-12-31T10:5:30Z", "strict_date_time_no_millis"); - assertParseException("2018-12-31T10:15:3Z", "strict_date_time_no_millis"); - assertParseException("2018-12-31T1:15:30Z", "strict_date_time_no_millis"); - assertSameDate("12", "strict_hour"); - assertSameDate("01", "strict_hour"); - assertParseException("1", "strict_hour"); - assertSameDate("12:12", "strict_hour_minute"); - assertSameDate("12:01", "strict_hour_minute"); - assertParseException("12:1", "strict_hour_minute"); - assertSameDate("12:12:12", "strict_hour_minute_second"); - assertSameDate("12:12:01", "strict_hour_minute_second"); - assertParseException("12:12:1", "strict_hour_minute_second"); - assertSameDate("12:12:12.123", "strict_hour_minute_second_fraction"); - assertSameDate("12:12:12.123456789", "strict_hour_minute_second_fraction"); - assertSameDate("12:12:12.1", "strict_hour_minute_second_fraction"); - assertParseException("12:12:12", "strict_hour_minute_second_fraction"); - assertSameDate("12:12:12.123", "strict_hour_minute_second_millis"); - assertSameDate("12:12:12.1", "strict_hour_minute_second_millis"); - assertParseException("12:12:12", "strict_hour_minute_second_millis"); - assertSameDate("2018-128", "strict_ordinal_date"); - assertParseException("2018-1", "strict_ordinal_date"); - - assertSameDate("2018-128T10:15:30.1Z", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123Z", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123456789Z", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.1+0100", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123+0100", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.1+01:00", "strict_ordinal_date_time"); - assertSameDate("2018-128T10:15:30.123+01:00", "strict_ordinal_date_time"); - assertParseException("2018-1T10:15:30.123Z", "strict_ordinal_date_time"); - - assertSameDate("2018-128T10:15:30Z", "strict_ordinal_date_time_no_millis"); - assertSameDate("2018-128T10:15:30+0100", "strict_ordinal_date_time_no_millis"); - assertSameDate("2018-128T10:15:30+01:00", "strict_ordinal_date_time_no_millis"); - assertParseException("2018-1T10:15:30Z", "strict_ordinal_date_time_no_millis"); - - assertSameDate("10:15:30.1Z", "strict_time"); - assertSameDate("10:15:30.123Z", "strict_time"); - assertSameDate("10:15:30.123456789Z", "strict_time"); - assertSameDate("10:15:30.123+0100", "strict_time"); - assertSameDate("10:15:30.123+01:00", "strict_time"); - assertParseException("1:15:30.123Z", "strict_time"); - assertParseException("10:1:30.123Z", "strict_time"); - assertParseException("10:15:3.123Z", "strict_time"); - assertParseException("10:15:3.1", "strict_time"); - assertParseException("10:15:3Z", "strict_time"); - - assertSameDate("10:15:30Z", "strict_time_no_millis"); - assertSameDate("10:15:30+0100", "strict_time_no_millis"); - assertSameDate("10:15:30+01:00", "strict_time_no_millis"); - assertSameDate("01:15:30Z", "strict_time_no_millis"); - assertSameDate("01:15:30+0100", "strict_time_no_millis"); - assertSameDate("01:15:30+01:00", "strict_time_no_millis"); - assertParseException("1:15:30Z", "strict_time_no_millis"); - assertParseException("10:5:30Z", "strict_time_no_millis"); - assertParseException("10:15:3Z", "strict_time_no_millis"); - assertParseException("10:15:3", "strict_time_no_millis"); - - assertSameDate("T10:15:30.1Z", "strict_t_time"); - assertSameDate("T10:15:30.123Z", "strict_t_time"); - assertSameDate("T10:15:30.123456789Z", "strict_t_time"); - assertSameDate("T10:15:30.1+0100", "strict_t_time"); - assertSameDate("T10:15:30.123+0100", "strict_t_time"); - assertSameDate("T10:15:30.1+01:00", "strict_t_time"); - assertSameDate("T10:15:30.123+01:00", "strict_t_time"); - assertParseException("T1:15:30.123Z", "strict_t_time"); - assertParseException("T10:1:30.123Z", "strict_t_time"); - assertParseException("T10:15:3.123Z", "strict_t_time"); - assertParseException("T10:15:3.1", "strict_t_time"); - assertParseException("T10:15:3Z", "strict_t_time"); - - assertSameDate("T10:15:30Z", "strict_t_time_no_millis"); - assertSameDate("T10:15:30+0100", "strict_t_time_no_millis"); - assertSameDate("T10:15:30+01:00", "strict_t_time_no_millis"); - assertParseException("T1:15:30Z", "strict_t_time_no_millis"); - assertParseException("T10:1:30Z", "strict_t_time_no_millis"); - assertParseException("T10:15:3Z", "strict_t_time_no_millis"); - assertParseException("T10:15:3", "strict_t_time_no_millis"); - - assertSameDate("2012-W48-6", "strict_week_date"); - assertSameDate("2012-W01-6", "strict_week_date"); - assertParseException("2012-W1-6", "strict_week_date"); - assertParseException("2012-W1-8", "strict_week_date"); - - assertSameDate("2012-W48-6", "strict_week_date"); - assertSameDate("2012-W01-6", "strict_week_date"); - assertParseException("2012-W1-6", "strict_week_date"); - // joda comes up with a different exception message here, so we have to adapt - assertJodaParseException( - "2012-W01-8", - "strict_week_date", - "Cannot parse \"2012-W01-8\": Value 8 for dayOfWeek must be in the range [1,7]" - ); - assertJavaTimeParseException("2012-W01-8", "strict_week_date"); - - assertSameDate("2012-W48-6T10:15:30.1Z", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123Z", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123456789Z", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.1+0100", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123+0100", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.1+01:00", "strict_week_date_time"); - assertSameDate("2012-W48-6T10:15:30.123+01:00", "strict_week_date_time"); - assertParseException("2012-W1-6T10:15:30.123Z", "strict_week_date_time"); - - assertSameDate("2012-W48-6T10:15:30Z", "strict_week_date_time_no_millis"); - assertSameDate("2012-W48-6T10:15:30+0100", "strict_week_date_time_no_millis"); - assertSameDate("2012-W48-6T10:15:30+01:00", "strict_week_date_time_no_millis"); - assertParseException("2012-W1-6T10:15:30Z", "strict_week_date_time_no_millis"); - - assertSameDate("2012", "strict_year"); - assertParseException("1", "strict_year"); - assertSameDate("-2000", "strict_year"); - - assertSameDate("2012-12", "strict_year_month"); - assertParseException("1-1", "strict_year_month"); - - assertSameDate("2012-12-31", "strict_year_month_day"); - assertParseException("1-12-31", "strict_year_month_day"); - assertParseException("2012-1-31", "strict_year_month_day"); - assertParseException("2012-12-1", "strict_year_month_day"); - - assertSameDate("2018", "strict_weekyear"); - assertParseException("1", "strict_weekyear"); - - assertSameDate("2018", "strict_weekyear"); - assertSameDate("2017", "strict_weekyear"); - assertParseException("1", "strict_weekyear"); - - assertSameDate("2018-W29", "strict_weekyear_week"); - assertSameDate("2018-W01", "strict_weekyear_week"); - assertParseException("2018-W1", "strict_weekyear_week"); - - assertSameDate("2012-W31-5", "strict_weekyear_week_day"); - assertParseException("2012-W1-1", "strict_weekyear_week_day"); - } - - public void testSamePrinterOutput() { - int year = randomIntBetween(1970, 2030); - int month = randomIntBetween(1, 12); - int day = randomIntBetween(1, 28); - int hour = randomIntBetween(0, 23); - int minute = randomIntBetween(0, 59); - int second = randomIntBetween(0, 59); - - ZonedDateTime javaDate = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC); - DateTime jodaDate = new DateTime(year, month, day, hour, minute, second, DateTimeZone.UTC); - - for (FormatNames format : FormatNames.values()) { - if (format == FormatNames.ISO8601 || format == FormatNames.STRICT_DATE_OPTIONAL_TIME_NANOS) { - // Nanos aren't supported by joda - continue; - } - assertSamePrinterOutput(format.getSnakeCaseName(), javaDate, jodaDate); - } - } - - public void testSamePrinterOutputWithTimeZone() { - String format = "strict_date_optional_time"; - String dateInput = "2017-02-01T08:02:00.000-01:00"; - DateFormatter javaFormatter = DateFormatter.forPattern(format); - TemporalAccessor javaDate = javaFormatter.parse(dateInput); - - DateFormatter jodaFormatter = Joda.forPattern(format); - DateTime dateTime = jodaFormatter.parseJoda(dateInput); - - String javaDateString = javaFormatter.withZone(ZoneOffset.ofHours(-1)).format(javaDate); - String jodaDateString = jodaFormatter.withZone(ZoneOffset.ofHours(-1)).formatJoda(dateTime); - String message = String.format( - Locale.ROOT, - "expected string representation to be equal for format [%s]: joda [%s], java [%s]", - format, - jodaDateString, - javaDateString - ); - assertThat(message, javaDateString, is(jodaDateString)); - } - - public void testDateFormatterWithLocale() { - Locale locale = randomLocale(random()); - String pattern = randomBoolean() ? "strict_date_optional_time||date_time" : "date_time||strict_date_optional_time"; - DateFormatter formatter = DateFormatter.forPattern(pattern).withLocale(locale); - assertThat(formatter.pattern(), is(pattern)); - assertThat(formatter.locale(), is(locale)); - } - - public void testSeveralTimeFormats() { - { - String format = "year_month_day||ordinal_date"; - DateFormatter jodaFormatter = Joda.forPattern(format); - DateFormatter javaFormatter = DateFormatter.forPattern(format); - assertSameDate("2018-12-12", format, jodaFormatter, javaFormatter); - assertSameDate("2018-128", format, jodaFormatter, javaFormatter); - } - { - String format = "strictDateOptionalTime||dd-MM-yyyy"; - DateFormatter jodaFormatter = Joda.forPattern(format); - DateFormatter javaFormatter = DateFormatter.forPattern(format); - assertSameDate("31-01-2014", format, jodaFormatter, javaFormatter); - } - } - - // the iso 8601 parser is available via Joda.forPattern(), so we have to test this slightly differently - public void testIso8601Parsers() { - String format = "iso8601"; - org.joda.time.format.DateTimeFormatter isoFormatter = ISODateTimeFormat.dateTimeParser().withZone(DateTimeZone.UTC); - JodaDateFormatter jodaFormatter = new JodaDateFormatter(format, isoFormatter, isoFormatter); - DateFormatter javaFormatter = DateFormatter.forPattern(format); - - assertSameDate("2018-10-10", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10+0430", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11-08:00", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11Z", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12+0100", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12.123", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12.123Z", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12.123+0000", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12,123", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12,123Z", format, jodaFormatter, javaFormatter); - assertSameDate("2018-10-10T10:11:12,123+05:30", format, jodaFormatter, javaFormatter); - } - - public void testParsingLocalDateFromYearOfEra() { - // with strict resolving, YearOfEra expect an era, otherwise it won't resolve to a date - assertSameDate("2018363", "yyyyDDD", Joda.forPattern("YYYYDDD"), DateFormatter.forPattern("uuuuDDD")); - } - - public void testParsingMissingTimezone() { - long millisJava = DateFormatter.forPattern("8yyyy-MM-dd HH:mm:ss").parseMillis("2018-02-18 17:47:17"); - long millisJoda = DateFormatter.forPattern("yyyy-MM-dd HH:mm:ss").parseMillis("2018-02-18 17:47:17"); - assertThat(millisJava, is(millisJoda)); - } - - private void assertSamePrinterOutput(String format, ZonedDateTime javaDate, DateTime jodaDate) { - DateFormatter dateFormatter = DateFormatter.forPattern(format); - JodaDateFormatter jodaDateFormatter = Joda.forPattern(format); - - assertSamePrinterOutput(format, javaDate, jodaDate, dateFormatter, jodaDateFormatter); - } - - private void assertSamePrinterOutput( - String format, - ZonedDateTime javaDate, - DateTime jodaDate, - DateFormatter dateFormatter, - DateFormatter jodaDateFormatter - ) { - String javaTimeOut = dateFormatter.format(javaDate); - String jodaTimeOut = jodaDateFormatter.formatJoda(jodaDate); - - assertThat(jodaDate.getMillis(), is(javaDate.toInstant().toEpochMilli())); - String message = String.format( - Locale.ROOT, - "expected string representation to be equal for format [%s]: joda [%s], java [%s]", - format, - jodaTimeOut, - javaTimeOut - ); - assertThat(message, javaTimeOut, is(jodaTimeOut)); - } - - private void assertSameDate(String input, String format) { - DateFormatter jodaFormatter = Joda.forPattern(format); - DateFormatter javaFormatter = DateFormatter.forPattern(format); - assertSameDate(input, format, jodaFormatter, javaFormatter); - } - - private void assertSameDate(String input, String format, DateFormatter jodaFormatter, DateFormatter javaFormatter) { - DateTime jodaDateTime = jodaFormatter.parseJoda(input); - - TemporalAccessor javaTimeAccessor = javaFormatter.parse(input); - ZonedDateTime zonedDateTime = DateFormatters.from(javaTimeAccessor); - - String msg = String.format( - Locale.ROOT, - "Input [%s] Format [%s] Joda [%s], Java [%s]", - input, - format, - jodaDateTime, - DateTimeFormatter.ISO_INSTANT.format(zonedDateTime.toInstant()) - ); - - assertThat(msg, jodaDateTime.getMillis(), is(zonedDateTime.toInstant().toEpochMilli())); - } - - private void assertParseException(String input, String format) { - assertJodaParseException(input, format, "Invalid format: \"" + input); - assertJavaTimeParseException(input, format); - } - - private void assertJodaParseException(String input, String format, String expectedMessage) { - DateFormatter jodaFormatter = Joda.forPattern(format); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> jodaFormatter.parseJoda(input)); - assertThat(e.getMessage(), containsString(expectedMessage)); - } - - private void assertJavaTimeParseException(String input, String format) { - DateFormatter javaTimeFormatter = DateFormatter.forPattern(format); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> javaTimeFormatter.parse(input)); - assertThat(e.getMessage(), containsString(input)); - assertThat(e.getMessage(), containsString(format)); - } - - private void assertDateMathEquals(String text, String pattern) { - long gotMillisJava = dateMathToMillis(text, DateFormatter.forPattern(pattern)); - long gotMillisJoda = dateMathToMillis(text, Joda.forPattern(pattern)); - - assertEquals(gotMillisJoda, gotMillisJava); - } - - private void assertSameDateAs(String input, String javaPattern, String jodaPattern) { - DateFormatter javaFormatter = DateFormatter.forPattern(javaPattern); - DateFormatter jodaFormatter = Joda.forPattern(jodaPattern); - assertSameDate(input, javaPattern, jodaFormatter, javaFormatter); - } -} diff --git a/server/src/test/java/org/opensearch/common/joda/JodaDateMathParserTests.java b/server/src/test/java/org/opensearch/common/joda/JodaDateMathParserTests.java deleted file mode 100644 index fe0f707f4ca3e..0000000000000 --- a/server/src/test/java/org/opensearch/common/joda/JodaDateMathParserTests.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.OpenSearchParseException; -import org.opensearch.common.time.DateFormatter; -import org.opensearch.common.time.DateMathParser; -import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTimeZone; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.LongSupplier; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; - -public class JodaDateMathParserTests extends OpenSearchTestCase { - - DateFormatter formatter = Joda.forPattern("date_optional_time||epoch_millis"); - DateMathParser parser = formatter.toDateMathParser(); - - void assertDateMathEquals(String toTest, String expected) { - assertDateMathEquals(toTest, expected, 0, false, null); - } - - void assertDateMathEquals(String toTest, String expected, final long now, boolean roundUp, DateTimeZone timeZone) { - long gotMillis = parser.parse(toTest, () -> now, roundUp, timeZone).toEpochMilli(); - assertDateEquals(gotMillis, toTest, expected); - } - - void assertDateEquals(long gotMillis, String original, String expected) { - long expectedMillis = parser.parse(expected, () -> 0).toEpochMilli(); - if (gotMillis != expectedMillis) { - fail( - "Date math not equal\n" - + "Original : " - + original - + "\n" - + "Parsed : " - + formatter.formatMillis(gotMillis) - + "\n" - + "Expected : " - + expected - + "\n" - + "Expected milliseconds : " - + expectedMillis - + "\n" - + "Actual milliseconds : " - + gotMillis - + "\n" - ); - } - } - - public void testOverridingLocaleOrZoneAndCompositeRoundUpParser() { - // the pattern has to be composite and the match should not be on the first one - DateFormatter formatter = Joda.forPattern("date||epoch_millis").withLocale(randomLocale(random())); - DateMathParser parser = formatter.toDateMathParser(); - long gotMillis = parser.parse("297276785531", () -> 0, true, (ZoneId) null).toEpochMilli(); - assertDateEquals(gotMillis, "297276785531", "297276785531"); - - formatter = Joda.forPattern("date||epoch_millis").withZone(ZoneOffset.UTC); - parser = formatter.toDateMathParser(); - gotMillis = parser.parse("297276785531", () -> 0, true, (ZoneId) null).toEpochMilli(); - assertDateEquals(gotMillis, "297276785531", "297276785531"); - } - - public void testBasicDates() { - assertDateMathEquals("2014", "2014-01-01T00:00:00.000"); - assertDateMathEquals("2014-05", "2014-05-01T00:00:00.000"); - assertDateMathEquals("2014-05-30", "2014-05-30T00:00:00.000"); - assertDateMathEquals("2014-05-30T20", "2014-05-30T20:00:00.000"); - assertDateMathEquals("2014-05-30T20:21", "2014-05-30T20:21:00.000"); - assertDateMathEquals("2014-05-30T20:21:35", "2014-05-30T20:21:35.000"); - assertDateMathEquals("2014-05-30T20:21:35.123", "2014-05-30T20:21:35.123"); - } - - public void testRoundingDoesNotAffectExactDate() { - assertDateMathEquals("2014-11-12T22:55:00.000Z", "2014-11-12T22:55:00.000Z", 0, true, null); - assertDateMathEquals("2014-11-12T22:55:00.000Z", "2014-11-12T22:55:00.000Z", 0, false, null); - - assertDateMathEquals("2014-11-12T22:55:00.000", "2014-11-12T21:55:00.000Z", 0, true, DateTimeZone.forID("+01:00")); - assertDateMathEquals("2014-11-12T22:55:00.000", "2014-11-12T21:55:00.000Z", 0, false, DateTimeZone.forID("+01:00")); - - assertDateMathEquals("2014-11-12T22:55:00.000+01:00", "2014-11-12T21:55:00.000Z", 0, true, null); - assertDateMathEquals("2014-11-12T22:55:00.000+01:00", "2014-11-12T21:55:00.000Z", 0, false, null); - } - - public void testTimezone() { - // timezone works within date format - assertDateMathEquals("2014-05-30T20:21+02:00", "2014-05-30T18:21:00.000"); - - // test alternative ways of writing zero offsets, according to ISO 8601 +00:00, +00, +0000 should work. - // joda also seems to allow for -00:00, -00, -0000 - assertDateMathEquals("2014-05-30T18:21+00:00", "2014-05-30T18:21:00.000"); - assertDateMathEquals("2014-05-30T18:21+00", "2014-05-30T18:21:00.000"); - assertDateMathEquals("2014-05-30T18:21+0000", "2014-05-30T18:21:00.000"); - assertDateMathEquals("2014-05-30T18:21-00:00", "2014-05-30T18:21:00.000"); - assertDateMathEquals("2014-05-30T18:21-00", "2014-05-30T18:21:00.000"); - assertDateMathEquals("2014-05-30T18:21-0000", "2014-05-30T18:21:00.000"); - - // but also externally - assertDateMathEquals("2014-05-30T20:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+02:00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+00:00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+00:00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("+0000")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("-00:00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("-00")); - assertDateMathEquals("2014-05-30T18:21", "2014-05-30T18:21:00.000", 0, false, DateTimeZone.forID("-0000")); - - // and timezone in the date has priority - assertDateMathEquals("2014-05-30T20:21+03:00", "2014-05-30T17:21:00.000", 0, false, DateTimeZone.forID("-08:00")); - assertDateMathEquals("2014-05-30T20:21Z", "2014-05-30T20:21:00.000", 0, false, DateTimeZone.forID("-08:00")); - } - - public void testBasicMath() { - assertDateMathEquals("2014-11-18||+y", "2015-11-18"); - assertDateMathEquals("2014-11-18||-2y", "2012-11-18"); - - assertDateMathEquals("2014-11-18||+3M", "2015-02-18"); - assertDateMathEquals("2014-11-18||-M", "2014-10-18"); - - assertDateMathEquals("2014-11-18||+1w", "2014-11-25"); - assertDateMathEquals("2014-11-18||-3w", "2014-10-28"); - - assertDateMathEquals("2014-11-18||+22d", "2014-12-10"); - assertDateMathEquals("2014-11-18||-423d", "2013-09-21"); - - assertDateMathEquals("2014-11-18T14||+13h", "2014-11-19T03"); - assertDateMathEquals("2014-11-18T14||-1h", "2014-11-18T13"); - assertDateMathEquals("2014-11-18T14||+13H", "2014-11-19T03"); - assertDateMathEquals("2014-11-18T14||-1H", "2014-11-18T13"); - - assertDateMathEquals("2014-11-18T14:27||+10240m", "2014-11-25T17:07"); - assertDateMathEquals("2014-11-18T14:27||-10m", "2014-11-18T14:17"); - - assertDateMathEquals("2014-11-18T14:27:32||+60s", "2014-11-18T14:28:32"); - assertDateMathEquals("2014-11-18T14:27:32||-3600s", "2014-11-18T13:27:32"); - } - - public void testLenientEmptyMath() { - assertDateMathEquals("2014-05-30T20:21||", "2014-05-30T20:21:00.000"); - } - - public void testMultipleAdjustments() { - assertDateMathEquals("2014-11-18||+1M-1M", "2014-11-18"); - assertDateMathEquals("2014-11-18||+1M-1m", "2014-12-17T23:59"); - assertDateMathEquals("2014-11-18||-1m+1M", "2014-12-17T23:59"); - assertDateMathEquals("2014-11-18||+1M/M", "2014-12-01"); - assertDateMathEquals("2014-11-18||+1M/M+1h", "2014-12-01T01"); - } - - public void testNow() { - final long now = parser.parse("2014-11-18T14:27:32", () -> 0, false, (ZoneId) null).toEpochMilli(); - - assertDateMathEquals("now", "2014-11-18T14:27:32", now, false, null); - assertDateMathEquals("now+M", "2014-12-18T14:27:32", now, false, null); - assertDateMathEquals("now+M", "2014-12-18T14:27:32", now, true, null); - assertDateMathEquals("now-2d", "2014-11-16T14:27:32", now, false, null); - assertDateMathEquals("now-2d", "2014-11-16T14:27:32", now, true, null); - assertDateMathEquals("now/m", "2014-11-18T14:27", now, false, null); - assertDateMathEquals("now/m", "2014-11-18T14:27:59.999Z", now, true, null); - assertDateMathEquals("now/M", "2014-11-01T00:00:00", now, false, null); - assertDateMathEquals("now/M", "2014-11-30T23:59:59.999Z", now, true, null); - - // timezone does not affect now - assertDateMathEquals("now/m", "2014-11-18T14:27", now, false, DateTimeZone.forID("+02:00")); - } - - public void testRoundingPreservesEpochAsBaseDate() { - // If a user only specifies times, then the date needs to always be 1970-01-01 regardless of rounding - DateFormatter formatter = DateFormatter.forPattern("HH:mm:ss"); - DateMathParser parser = formatter.toDateMathParser(); - assertEquals( - this.formatter.parseMillis("1970-01-01T04:52:20.000Z"), - parser.parse("04:52:20", () -> 0, false, (ZoneId) null).toEpochMilli() - ); - assertEquals( - this.formatter.parseMillis("1970-01-01T04:52:20.999Z"), - parser.parse("04:52:20", () -> 0, true, (ZoneId) null).toEpochMilli() - ); - } - - // Implicit rounding happening when parts of the date are not specified - public void testImplicitRounding() { - assertDateMathEquals("2014-11-18", "2014-11-18", 0, false, null); - assertDateMathEquals("2014-11-18", "2014-11-18T23:59:59.999Z", 0, true, null); - - assertDateMathEquals("2014-11-18T09:20", "2014-11-18T09:20", 0, false, null); - assertDateMathEquals("2014-11-18T09:20", "2014-11-18T09:20:59.999Z", 0, true, null); - - assertDateMathEquals("2014-11-18", "2014-11-17T23:00:00.000Z", 0, false, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-11-18", "2014-11-18T22:59:59.999Z", 0, true, DateTimeZone.forID("CET")); - - assertDateMathEquals("2014-11-18T09:20", "2014-11-18T08:20:00.000Z", 0, false, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-11-18T09:20", "2014-11-18T08:20:59.999Z", 0, true, DateTimeZone.forID("CET")); - - // implicit rounding with explicit timezone in the date format - DateFormatter formatter = Joda.forPattern("yyyy-MM-ddZ"); - DateMathParser parser = formatter.toDateMathParser(); - Instant time = parser.parse("2011-10-09+01:00", () -> 0, false, (ZoneId) null); - assertEquals(this.parser.parse("2011-10-09T00:00:00.000+01:00", () -> 0), time); - time = parser.parse("2011-10-09+01:00", () -> 0, true, (ZoneId) null); - assertEquals(this.parser.parse("2011-10-09T23:59:59.999+01:00", () -> 0), time); - } - - // Explicit rounding using the || separator - public void testExplicitRounding() { - assertDateMathEquals("2014-11-18||/y", "2014-01-01", 0, false, null); - assertDateMathEquals("2014-11-18||/y", "2014-12-31T23:59:59.999", 0, true, null); - assertDateMathEquals("2014||/y", "2014-01-01", 0, false, null); - assertDateMathEquals("2014-01-01T00:00:00.001||/y", "2014-12-31T23:59:59.999", 0, true, null); - // rounding should also take into account time zone - assertDateMathEquals("2014-11-18||/y", "2013-12-31T23:00:00.000Z", 0, false, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-11-18||/y", "2014-12-31T22:59:59.999Z", 0, true, DateTimeZone.forID("CET")); - - assertDateMathEquals("2014-11-18||/M", "2014-11-01", 0, false, null); - assertDateMathEquals("2014-11-18||/M", "2014-11-30T23:59:59.999", 0, true, null); - assertDateMathEquals("2014-11||/M", "2014-11-01", 0, false, null); - assertDateMathEquals("2014-11||/M", "2014-11-30T23:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18||/M", "2014-10-31T23:00:00.000Z", 0, false, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-11-18||/M", "2014-11-30T22:59:59.999Z", 0, true, DateTimeZone.forID("CET")); - - assertDateMathEquals("2014-11-17T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-18T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-19T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-20T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-21T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-22T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-23T14||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-18T14||/w", "2014-11-23T23:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18||/w", "2014-11-17", 0, false, null); - assertDateMathEquals("2014-11-18||/w", "2014-11-23T23:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18||/w", "2014-11-16T23:00:00.000Z", 0, false, DateTimeZone.forID("+01:00")); - assertDateMathEquals("2014-11-18||/w", "2014-11-17T01:00:00.000Z", 0, false, DateTimeZone.forID("-01:00")); - assertDateMathEquals("2014-11-18||/w", "2014-11-16T23:00:00.000Z", 0, false, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-11-18||/w", "2014-11-23T22:59:59.999Z", 0, true, DateTimeZone.forID("CET")); - assertDateMathEquals("2014-07-22||/w", "2014-07-20T22:00:00.000Z", 0, false, DateTimeZone.forID("CET")); // with DST - - assertDateMathEquals("2014-11-18T14||/d", "2014-11-18", 0, false, null); - assertDateMathEquals("2014-11-18T14||/d", "2014-11-18T23:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18||/d", "2014-11-18", 0, false, null); - assertDateMathEquals("2014-11-18||/d", "2014-11-18T23:59:59.999", 0, true, null); - - assertDateMathEquals("2014-11-18T14:27||/h", "2014-11-18T14", 0, false, null); - assertDateMathEquals("2014-11-18T14:27||/h", "2014-11-18T14:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18T14||/H", "2014-11-18T14", 0, false, null); - assertDateMathEquals("2014-11-18T14||/H", "2014-11-18T14:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18T14:27||/h", "2014-11-18T14", 0, false, null); - assertDateMathEquals("2014-11-18T14:27||/h", "2014-11-18T14:59:59.999", 0, true, null); - assertDateMathEquals("2014-11-18T14||/H", "2014-11-18T14", 0, false, null); - assertDateMathEquals("2014-11-18T14||/H", "2014-11-18T14:59:59.999", 0, true, null); - - assertDateMathEquals("2014-11-18T14:27:32||/m", "2014-11-18T14:27", 0, false, null); - assertDateMathEquals("2014-11-18T14:27:32||/m", "2014-11-18T14:27:59.999", 0, true, null); - assertDateMathEquals("2014-11-18T14:27||/m", "2014-11-18T14:27", 0, false, null); - assertDateMathEquals("2014-11-18T14:27||/m", "2014-11-18T14:27:59.999", 0, true, null); - - assertDateMathEquals("2014-11-18T14:27:32.123||/s", "2014-11-18T14:27:32", 0, false, null); - assertDateMathEquals("2014-11-18T14:27:32.123||/s", "2014-11-18T14:27:32.999", 0, true, null); - assertDateMathEquals("2014-11-18T14:27:32||/s", "2014-11-18T14:27:32", 0, false, null); - assertDateMathEquals("2014-11-18T14:27:32||/s", "2014-11-18T14:27:32.999", 0, true, null); - } - - public void testTimestamps() { - assertDateMathEquals("1418248078000", "2014-12-10T21:47:58.000"); - assertDateMathEquals("32484216259000", "2999-05-20T17:24:19.000"); - assertDateMathEquals("253382837059000", "9999-05-20T17:24:19.000"); - - // datemath still works on timestamps - assertDateMathEquals("1418248078000||/m", "2014-12-10T21:47:00.000"); - - // also check other time units - JodaDateMathParser parser = new JodaDateMathParser(Joda.forPattern("epoch_second")); - long datetime = parser.parse("1418248078", () -> 0).toEpochMilli(); - assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000"); - - // a timestamp before 10000 is a year - assertDateMathEquals("9999", "9999-01-01T00:00:00.000"); - // 10000 is also a year, breaking bwc, used to be a timestamp - assertDateMathEquals("10000", "10000-01-01T00:00:00.000"); - // but 10000 with T is still a date format - assertDateMathEquals("10000T", "10000-01-01T00:00:00.000"); - } - - void assertParseException(String msg, String date, String exc) { - try { - parser.parse(date, () -> 0); - fail("Date: " + date + "\n" + msg); - } catch (OpenSearchParseException e) { - assertThat(e.getMessage().contains(exc), equalTo(true)); - } - } - - public void testIllegalMathFormat() { - assertParseException("Expected date math unsupported operator exception", "2014-11-18||*5", "operator not supported"); - assertParseException("Expected date math incompatible rounding exception", "2014-11-18||/2m", "rounding"); - assertParseException("Expected date math illegal unit type exception", "2014-11-18||+2a", "unit [a] not supported"); - assertParseException("Expected date math truncation exception", "2014-11-18||+12", "truncated"); - assertParseException("Expected date math truncation exception", "2014-11-18||-", "truncated"); - } - - public void testIllegalDateFormat() { - assertParseException("Expected bad timestamp exception", Long.toString(Long.MAX_VALUE) + "0", "failed to parse date field"); - assertParseException("Expected bad date format exception", "123bogus", "with format"); - } - - public void testOnlyCallsNowIfNecessary() { - final AtomicBoolean called = new AtomicBoolean(); - final LongSupplier now = () -> { - called.set(true); - return 42L; - }; - parser.parse("2014-11-18T14:27:32", now, false, (ZoneId) null); - assertFalse(called.get()); - parser.parse("now/d", now, false, (ZoneId) null); - assertTrue(called.get()); - } - - public void testThatUnixTimestampMayNotHaveTimeZone() { - JodaDateMathParser parser = new JodaDateMathParser(Joda.forPattern("epoch_millis")); - try { - parser.parse("1234567890123", () -> 42, false, ZoneId.of("CET")); - fail("Expected OpenSearchParseException"); - } catch (OpenSearchParseException e) { - assertThat(e.getMessage(), containsString("failed to parse date field")); - assertThat(e.getMessage(), containsString("with format [epoch_millis]")); - } - } -} diff --git a/server/src/test/java/org/opensearch/common/joda/JodaTests.java b/server/src/test/java/org/opensearch/common/joda/JodaTests.java deleted file mode 100644 index 3f6e2b2ca918a..0000000000000 --- a/server/src/test/java/org/opensearch/common/joda/JodaTests.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.joda; - -import org.opensearch.common.time.DateFormatter; -import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -import java.time.ZoneOffset; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class JodaTests extends OpenSearchTestCase { - - public void testBasicTTimePattern() { - DateFormatter formatter1 = Joda.forPattern("basic_t_time"); - assertEquals(formatter1.pattern(), "basic_t_time"); - assertEquals(formatter1.zone(), ZoneOffset.UTC); - - DateFormatter formatter2 = Joda.forPattern("basicTTime"); - assertEquals(formatter2.pattern(), "basicTTime"); - assertEquals(formatter2.zone(), ZoneOffset.UTC); - assertWarnings( - "Camel case format name basicTTime is deprecated and will be removed in a future version. " - + "Use snake case name basic_t_time instead." - ); - DateTime dt = new DateTime(2004, 6, 9, 10, 20, 30, 40, DateTimeZone.UTC); - assertEquals("T102030.040Z", formatter1.formatJoda(dt)); - assertEquals("T102030.040Z", formatter1.formatJoda(dt)); - - expectThrows(IllegalArgumentException.class, () -> Joda.forPattern("basic_t_Time")); - expectThrows(IllegalArgumentException.class, () -> Joda.forPattern("basic_T_Time")); - expectThrows(IllegalArgumentException.class, () -> Joda.forPattern("basic_T_time")); - } - - public void testEqualsAndHashcode() { - String format = randomFrom("yyyy/MM/dd HH:mm:ss", "basic_t_time"); - JodaDateFormatter first = Joda.forPattern(format); - JodaDateFormatter second = Joda.forPattern(format); - JodaDateFormatter third = Joda.forPattern(" HH:mm:ss, yyyy/MM/dd"); - - assertThat(first, is(second)); - assertThat(second, is(first)); - assertThat(first, is(not(third))); - assertThat(second, is(not(third))); - - assertThat(first.hashCode(), is(second.hashCode())); - assertThat(second.hashCode(), is(first.hashCode())); - assertThat(first.hashCode(), is(not(third.hashCode()))); - assertThat(second.hashCode(), is(not(third.hashCode()))); - } -} diff --git a/server/src/test/java/org/opensearch/common/rounding/DateTimeUnitTests.java b/server/src/test/java/org/opensearch/common/rounding/DateTimeUnitTests.java deleted file mode 100644 index 7b87e136c5f38..0000000000000 --- a/server/src/test/java/org/opensearch/common/rounding/DateTimeUnitTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.rounding; - -import org.opensearch.test.OpenSearchTestCase; - -import static org.opensearch.common.rounding.DateTimeUnit.DAY_OF_MONTH; -import static org.opensearch.common.rounding.DateTimeUnit.HOUR_OF_DAY; -import static org.opensearch.common.rounding.DateTimeUnit.MINUTES_OF_HOUR; -import static org.opensearch.common.rounding.DateTimeUnit.MONTH_OF_YEAR; -import static org.opensearch.common.rounding.DateTimeUnit.QUARTER; -import static org.opensearch.common.rounding.DateTimeUnit.SECOND_OF_MINUTE; -import static org.opensearch.common.rounding.DateTimeUnit.WEEK_OF_WEEKYEAR; -import static org.opensearch.common.rounding.DateTimeUnit.YEAR_OF_CENTURY; - -public class DateTimeUnitTests extends OpenSearchTestCase { - - /** - * test that we don't accidentally change enum ids - */ - public void testEnumIds() { - assertEquals(1, WEEK_OF_WEEKYEAR.id()); - assertEquals(WEEK_OF_WEEKYEAR, DateTimeUnit.resolve((byte) 1)); - - assertEquals(2, YEAR_OF_CENTURY.id()); - assertEquals(YEAR_OF_CENTURY, DateTimeUnit.resolve((byte) 2)); - - assertEquals(3, QUARTER.id()); - assertEquals(QUARTER, DateTimeUnit.resolve((byte) 3)); - - assertEquals(4, MONTH_OF_YEAR.id()); - assertEquals(MONTH_OF_YEAR, DateTimeUnit.resolve((byte) 4)); - - assertEquals(5, DAY_OF_MONTH.id()); - assertEquals(DAY_OF_MONTH, DateTimeUnit.resolve((byte) 5)); - - assertEquals(6, HOUR_OF_DAY.id()); - assertEquals(HOUR_OF_DAY, DateTimeUnit.resolve((byte) 6)); - - assertEquals(7, MINUTES_OF_HOUR.id()); - assertEquals(MINUTES_OF_HOUR, DateTimeUnit.resolve((byte) 7)); - - assertEquals(8, SECOND_OF_MINUTE.id()); - assertEquals(SECOND_OF_MINUTE, DateTimeUnit.resolve((byte) 8)); - } -} diff --git a/server/src/test/java/org/opensearch/common/rounding/RoundingDuelTests.java b/server/src/test/java/org/opensearch/common/rounding/RoundingDuelTests.java deleted file mode 100644 index 3088067cd1f84..0000000000000 --- a/server/src/test/java/org/opensearch/common/rounding/RoundingDuelTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.rounding; - -import org.opensearch.common.unit.TimeValue; -import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTimeZone; - -import java.time.ZoneOffset; - -import static org.hamcrest.Matchers.is; - -public class RoundingDuelTests extends OpenSearchTestCase { - - // dont include nano/micro seconds as rounding would become zero then and throw an exception - private static final String[] ALLOWED_TIME_SUFFIXES = new String[] { "d", "h", "ms", "s", "m" }; - - public void testDuellingImplementations() { - org.opensearch.common.Rounding.DateTimeUnit randomDateTimeUnit = randomFrom(org.opensearch.common.Rounding.DateTimeUnit.values()); - org.opensearch.common.Rounding.Prepared rounding; - Rounding roundingJoda; - - if (randomBoolean()) { - rounding = org.opensearch.common.Rounding.builder(randomDateTimeUnit).timeZone(ZoneOffset.UTC).build().prepareForUnknown(); - DateTimeUnit dateTimeUnit = DateTimeUnit.resolve(randomDateTimeUnit.getId()); - roundingJoda = Rounding.builder(dateTimeUnit).timeZone(DateTimeZone.UTC).build(); - } else { - TimeValue interval = timeValue(); - rounding = org.opensearch.common.Rounding.builder(interval).timeZone(ZoneOffset.UTC).build().prepareForUnknown(); - roundingJoda = Rounding.builder(interval).timeZone(DateTimeZone.UTC).build(); - } - - long roundValue = randomLong(); - assertThat(roundingJoda.round(roundValue), is(rounding.round(roundValue))); - } - - static TimeValue timeValue() { - return TimeValue.parseTimeValue(randomIntBetween(1, 1000) + randomFrom(ALLOWED_TIME_SUFFIXES), "settingName"); - } -} diff --git a/server/src/test/java/org/opensearch/common/rounding/TimeZoneRoundingTests.java b/server/src/test/java/org/opensearch/common/rounding/TimeZoneRoundingTests.java deleted file mode 100644 index d1b3adcd55f0c..0000000000000 --- a/server/src/test/java/org/opensearch/common/rounding/TimeZoneRoundingTests.java +++ /dev/null @@ -1,822 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * 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. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.common.rounding; - -import org.opensearch.common.collect.Tuple; -import org.opensearch.common.rounding.Rounding.TimeIntervalRounding; -import org.opensearch.common.rounding.Rounding.TimeUnitRounding; -import org.opensearch.common.unit.TimeValue; -import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.hamcrest.Matchers.startsWith; - -public class TimeZoneRoundingTests extends OpenSearchTestCase { - - public void testUTCTimeUnitRounding() { - Rounding tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).build(); - DateTimeZone tz = DateTimeZone.UTC; - assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-01T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-01T00:00:00.000Z")), isDate(time("2009-03-01T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.WEEK_OF_WEEKYEAR).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-09T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-16T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.QUARTER).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-01T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-04-01T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-10T01:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-09T01:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-10T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-10T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.YEAR_OF_CENTURY).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-01T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2013-01-01T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.MINUTES_OF_HOUR).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-10T01:01:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-09T00:01:00.000Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.SECOND_OF_MINUTE).build(); - assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-10T01:01:01.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-09T00:00:01.000Z"), tz)); - } - - public void testUTCIntervalRounding() { - Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(12)).build(); - DateTimeZone tz = DateTimeZone.UTC; - assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T00:00:00.000Z")), isDate(time("2009-02-03T12:00:00.000Z"), tz)); - assertThat(tzRounding.round(time("2009-02-03T13:01:01")), isDate(time("2009-02-03T12:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T12:00:00.000Z")), isDate(time("2009-02-04T00:00:00.000Z"), tz)); - - tzRounding = Rounding.builder(TimeValue.timeValueHours(48)).build(); - assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T00:00:00.000Z")), isDate(time("2009-02-05T00:00:00.000Z"), tz)); - assertThat(tzRounding.round(time("2009-02-05T13:01:01")), isDate(time("2009-02-05T00:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-05T00:00:00.000Z")), isDate(time("2009-02-07T00:00:00.000Z"), tz)); - } - - /** - * test TimeIntervalRounding, (interval < 12h) with time zone shift - */ - public void testTimeIntervalRounding() { - DateTimeZone tz = DateTimeZone.forOffsetHours(-1); - Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(6)).timeZone(tz).build(); - assertThat(tzRounding.round(time("2009-02-03T00:01:01")), isDate(time("2009-02-02T19:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-02T19:00:00.000Z")), isDate(time("2009-02-03T01:00:00.000Z"), tz)); - - assertThat(tzRounding.round(time("2009-02-03T13:01:01")), isDate(time("2009-02-03T13:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T13:00:00.000Z")), isDate(time("2009-02-03T19:00:00.000Z"), tz)); - } - - /** - * test DayIntervalRounding, (interval >= 12h) with time zone shift - */ - public void testDayIntervalRounding() { - DateTimeZone tz = DateTimeZone.forOffsetHours(-8); - Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(12)).timeZone(tz).build(); - assertThat(tzRounding.round(time("2009-02-03T00:01:01")), isDate(time("2009-02-02T20:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-02T20:00:00.000Z")), isDate(time("2009-02-03T08:00:00.000Z"), tz)); - - assertThat(tzRounding.round(time("2009-02-03T13:01:01")), isDate(time("2009-02-03T08:00:00.000Z"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T08:00:00.000Z")), isDate(time("2009-02-03T20:00:00.000Z"), tz)); - } - - public void testDayRounding() { - int timezoneOffset = -2; - Rounding tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(DateTimeZone.forOffsetHours(timezoneOffset)).build(); - assertThat(tzRounding.round(0), equalTo(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis())); - assertThat( - tzRounding.nextRoundingValue(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()), - equalTo(TimeValue.timeValueHours(-timezoneOffset).millis()) - ); - - DateTimeZone tz = DateTimeZone.forID("-08:00"); - tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build(); - assertThat(tzRounding.round(time("2012-04-01T04:15:30Z")), isDate(time("2012-03-31T08:00:00Z"), tz)); - - tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build(); - assertThat(tzRounding.round(time("2012-04-01T04:15:30Z")), equalTo(time("2012-03-01T08:00:00Z"))); - - // date in Feb-3rd, but still in Feb-2nd in -02:00 timezone - tz = DateTimeZone.forID("-02:00"); - tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build(); - assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-02T02:00:00"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-02T02:00:00")), isDate(time("2009-02-03T02:00:00"), tz)); - - // date in Feb-3rd, also in -02:00 timezone - tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build(); - assertThat(tzRounding.round(time("2009-02-03T02:01:01")), isDate(time("2009-02-03T02:00:00"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T02:00:00")), isDate(time("2009-02-04T02:00:00"), tz)); - } - - public void testTimeRounding() { - // hour unit - DateTimeZone tz = DateTimeZone.forOffsetHours(-2); - Rounding tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build(); - assertThat(tzRounding.round(0), equalTo(0L)); - assertThat(tzRounding.nextRoundingValue(0L), equalTo(TimeValue.timeValueHours(1L).getMillis())); - - assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T01:00:00"), tz)); - assertThat(tzRounding.nextRoundingValue(time("2009-02-03T01:00:00")), isDate(time("2009-02-03T02:00:00"), tz)); - } - - public void testTimeUnitRoundingDST() { - Rounding tzRounding; - // testing savings to non savings switch - DateTimeZone cet = DateTimeZone.forID("CET"); - tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build(); - assertThat(tzRounding.round(time("2014-10-26T01:01:01", cet)), isDate(time("2014-10-26T01:00:00+02:00"), cet)); - assertThat(tzRounding.nextRoundingValue(time("2014-10-26T01:00:00", cet)), isDate(time("2014-10-26T02:00:00+02:00"), cet)); - assertThat(tzRounding.nextRoundingValue(time("2014-10-26T02:00:00", cet)), isDate(time("2014-10-26T02:00:00+01:00"), cet)); - - // testing non savings to savings switch - tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build(); - assertThat(tzRounding.round(time("2014-03-30T01:01:01", cet)), isDate(time("2014-03-30T01:00:00+01:00"), cet)); - assertThat(tzRounding.nextRoundingValue(time("2014-03-30T01:00:00", cet)), isDate(time("2014-03-30T03:00:00", cet), cet)); - assertThat(tzRounding.nextRoundingValue(time("2014-03-30T03:00:00", cet)), isDate(time("2014-03-30T04:00:00", cet), cet)); - - // testing non savings to savings switch (America/Chicago) - DateTimeZone chg = DateTimeZone.forID("America/Chicago"); - Rounding tzRounding_utc = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(DateTimeZone.UTC).build(); - assertThat(tzRounding.round(time("2014-03-09T03:01:01", chg)), isDate(time("2014-03-09T03:00:00", chg), chg)); - - Rounding tzRounding_chg = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(chg).build(); - assertThat(tzRounding_chg.round(time("2014-03-09T03:01:01", chg)), isDate(time("2014-03-09T03:00:00", chg), chg)); - - // testing savings to non savings switch 2013 (America/Chicago) - assertThat(tzRounding_utc.round(time("2013-11-03T06:01:01", chg)), isDate(time("2013-11-03T06:00:00", chg), chg)); - assertThat(tzRounding_chg.round(time("2013-11-03T06:01:01", chg)), isDate(time("2013-11-03T06:00:00", chg), chg)); - - // testing savings to non savings switch 2014 (America/Chicago) - assertThat(tzRounding_utc.round(time("2014-11-02T06:01:01", chg)), isDate(time("2014-11-02T06:00:00", chg), chg)); - assertThat(tzRounding_chg.round(time("2014-11-02T06:01:01", chg)), isDate(time("2014-11-02T06:00:00", chg), chg)); - } - - /** - * Randomized test on TimeUnitRounding. Test uses random - * {@link DateTimeUnit} and {@link DateTimeZone} and often (50% of the time) - * chooses test dates that are exactly on or close to offset changes (e.g. - * DST) in the chosen time zone. - *

    - * It rounds the test date down and up and performs various checks on the - * rounding unit interval that is defined by this. Assumptions tested are - * described in - * {@link #assertInterval(long, long, long, Rounding, DateTimeZone)} - */ - public void testRoundingRandom() { - for (int i = 0; i < 1000; ++i) { - DateTimeUnit timeUnit = randomTimeUnit(); - DateTimeZone tz = randomDateTimeZone(); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - long date = Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00 - long unitMillis = timeUnit.field(tz).getDurationField().getUnitMillis(); - if (randomBoolean()) { - nastyDate(date, tz, unitMillis); - } - final long roundedDate = rounding.round(date); - final long nextRoundingValue = rounding.nextRoundingValue(roundedDate); - - assertInterval(roundedDate, date, nextRoundingValue, rounding, tz); - - // check correct unit interval width for units smaller than a day, they should be fixed size except for transitions - if (unitMillis <= DateTimeConstants.MILLIS_PER_DAY) { - // if the interval defined didn't cross timezone offset transition, it should cover unitMillis width - if (tz.getOffset(roundedDate - 1) == tz.getOffset(nextRoundingValue + 1)) { - assertThat( - "unit interval width not as expected for [" + timeUnit + "], [" + tz + "] at " + new DateTime(roundedDate), - nextRoundingValue - roundedDate, - equalTo(unitMillis) - ); - } - } - } - } - - /** - * To be even more nasty, go to a transition in the selected time zone. - * In one third of the cases stay there, otherwise go half a unit back or forth - */ - private static long nastyDate(long initialDate, DateTimeZone timezone, long unitMillis) { - long date = timezone.nextTransition(initialDate); - if (randomBoolean()) { - return date + (randomLong() % unitMillis); // positive and negative offset possible - } else { - return date; - } - } - - /** - * test DST end with interval rounding - * CET: 25 October 2015, 03:00:00 clocks were turned backward 1 hour to 25 October 2015, 02:00:00 local standard time - */ - public void testTimeIntervalCET_DST_End() { - long interval = TimeUnit.MINUTES.toMillis(20); - DateTimeZone tz = DateTimeZone.forID("CET"); - Rounding rounding = new TimeIntervalRounding(interval, tz); - - assertThat(rounding.round(time("2015-10-25T01:55:00+02:00")), isDate(time("2015-10-25T01:40:00+02:00"), tz)); - assertThat(rounding.round(time("2015-10-25T02:15:00+02:00")), isDate(time("2015-10-25T02:00:00+02:00"), tz)); - assertThat(rounding.round(time("2015-10-25T02:35:00+02:00")), isDate(time("2015-10-25T02:20:00+02:00"), tz)); - assertThat(rounding.round(time("2015-10-25T02:55:00+02:00")), isDate(time("2015-10-25T02:40:00+02:00"), tz)); - // after DST shift - assertThat(rounding.round(time("2015-10-25T02:15:00+01:00")), isDate(time("2015-10-25T02:00:00+01:00"), tz)); - assertThat(rounding.round(time("2015-10-25T02:35:00+01:00")), isDate(time("2015-10-25T02:20:00+01:00"), tz)); - assertThat(rounding.round(time("2015-10-25T02:55:00+01:00")), isDate(time("2015-10-25T02:40:00+01:00"), tz)); - assertThat(rounding.round(time("2015-10-25T03:15:00+01:00")), isDate(time("2015-10-25T03:00:00+01:00"), tz)); - } - - /** - * test DST start with interval rounding - * CET: 27 March 2016, 02:00:00 clocks were turned forward 1 hour to 27 March 2016, 03:00:00 local daylight time - */ - public void testTimeIntervalCET_DST_Start() { - long interval = TimeUnit.MINUTES.toMillis(20); - DateTimeZone tz = DateTimeZone.forID("CET"); - Rounding rounding = new TimeIntervalRounding(interval, tz); - // test DST start - assertThat(rounding.round(time("2016-03-27T01:55:00+01:00")), isDate(time("2016-03-27T01:40:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T02:00:00+01:00")), isDate(time("2016-03-27T03:00:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:15:00+02:00")), isDate(time("2016-03-27T03:00:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:35:00+02:00")), isDate(time("2016-03-27T03:20:00+02:00"), tz)); - } - - /** - * test DST start with offset not fitting interval, e.g. Asia/Kathmandu - * adding 15min on 1986-01-01T00:00:00 the interval from - * 1986-01-01T00:15:00+05:45 to 1986-01-01T00:20:00+05:45 to only be 5min - * long - */ - public void testTimeInterval_Kathmandu_DST_Start() { - long interval = TimeUnit.MINUTES.toMillis(20); - DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu"); - Rounding rounding = new TimeIntervalRounding(interval, tz); - assertThat(rounding.round(time("1985-12-31T23:55:00+05:30")), isDate(time("1985-12-31T23:40:00+05:30"), tz)); - assertThat(rounding.round(time("1986-01-01T00:16:00+05:45")), isDate(time("1986-01-01T00:15:00+05:45"), tz)); - assertThat(time("1986-01-01T00:15:00+05:45") - time("1985-12-31T23:40:00+05:30"), equalTo(TimeUnit.MINUTES.toMillis(20))); - assertThat(rounding.round(time("1986-01-01T00:26:00+05:45")), isDate(time("1986-01-01T00:20:00+05:45"), tz)); - assertThat(time("1986-01-01T00:20:00+05:45") - time("1986-01-01T00:15:00+05:45"), equalTo(TimeUnit.MINUTES.toMillis(5))); - assertThat(rounding.round(time("1986-01-01T00:46:00+05:45")), isDate(time("1986-01-01T00:40:00+05:45"), tz)); - assertThat(time("1986-01-01T00:40:00+05:45") - time("1986-01-01T00:20:00+05:45"), equalTo(TimeUnit.MINUTES.toMillis(20))); - } - - /** - * Special test for intervals that don't fit evenly into rounding interval. - * In this case, when interval crosses DST transition point, rounding in local - * time can land in a DST gap which results in wrong UTC rounding values. - */ - public void testIntervalRounding_NotDivisibleInteval() { - DateTimeZone tz = DateTimeZone.forID("CET"); - long interval = TimeUnit.MINUTES.toMillis(14); - Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz); - - assertThat(rounding.round(time("2016-03-27T01:41:00+01:00")), isDate(time("2016-03-27T01:30:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T01:51:00+01:00")), isDate(time("2016-03-27T01:44:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T01:59:00+01:00")), isDate(time("2016-03-27T01:58:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:05:00+02:00")), isDate(time("2016-03-27T03:00:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:12:00+02:00")), isDate(time("2016-03-27T03:08:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:25:00+02:00")), isDate(time("2016-03-27T03:22:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-27T03:39:00+02:00")), isDate(time("2016-03-27T03:36:00+02:00"), tz)); - } - - /** - * Test for half day rounding intervals scrossing DST. - */ - public void testIntervalRounding_HalfDay_DST() { - DateTimeZone tz = DateTimeZone.forID("CET"); - long interval = TimeUnit.HOURS.toMillis(12); - Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz); - - assertThat(rounding.round(time("2016-03-26T01:00:00+01:00")), isDate(time("2016-03-26T00:00:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-26T13:00:00+01:00")), isDate(time("2016-03-26T12:00:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T01:00:00+01:00")), isDate(time("2016-03-27T00:00:00+01:00"), tz)); - assertThat(rounding.round(time("2016-03-27T13:00:00+02:00")), isDate(time("2016-03-27T12:00:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-28T01:00:00+02:00")), isDate(time("2016-03-28T00:00:00+02:00"), tz)); - assertThat(rounding.round(time("2016-03-28T13:00:00+02:00")), isDate(time("2016-03-28T12:00:00+02:00"), tz)); - } - - /** - * randomized test on {@link TimeIntervalRounding} with random interval and time zone offsets - */ - public void testIntervalRoundingRandom() { - for (int i = 0; i < 1000; i++) { - TimeUnit unit = randomFrom(new TimeUnit[] { TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS }); - long interval = unit.toMillis(randomIntBetween(1, 365)); - DateTimeZone tz = randomDateTimeZone(); - Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz); - long mainDate = Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00 - if (randomBoolean()) { - mainDate = nastyDate(mainDate, tz, interval); - } - // check two intervals around date - long previousRoundedValue = Long.MIN_VALUE; - for (long date = mainDate - 2 * interval; date < mainDate + 2 * interval; date += interval / 2) { - try { - final long roundedDate = rounding.round(date); - final long nextRoundingValue = rounding.nextRoundingValue(roundedDate); - assertThat("Rounding should be idempotent", roundedDate, equalTo(rounding.round(roundedDate))); - assertThat("Rounded value smaller or equal than unrounded", roundedDate, lessThanOrEqualTo(date)); - assertThat( - "Values smaller than rounded value should round further down", - rounding.round(roundedDate - 1), - lessThan(roundedDate) - ); - assertThat("Rounding should be >= previous rounding value", roundedDate, greaterThanOrEqualTo(previousRoundedValue)); - - if (tz.isFixed()) { - assertThat("NextRounding value should be greater than date", nextRoundingValue, greaterThan(roundedDate)); - assertThat( - "NextRounding value should be interval from rounded value", - nextRoundingValue - roundedDate, - equalTo(interval) - ); - assertThat( - "NextRounding value should be a rounded date", - nextRoundingValue, - equalTo(rounding.round(nextRoundingValue)) - ); - } - previousRoundedValue = roundedDate; - } catch (AssertionError e) { - logger.error("Rounding error at {}, timezone {}, interval: {},", new DateTime(date, tz), tz, interval); - throw e; - } - } - } - } - - /** - * Test that rounded values are always greater or equal to last rounded value if date is increasing. - * The example covers an interval around 2011-10-30T02:10:00+01:00, time zone CET, interval: 2700000ms - */ - public void testIntervalRoundingMonotonic_CET() { - long interval = TimeUnit.MINUTES.toMillis(45); - DateTimeZone tz = DateTimeZone.forID("CET"); - Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz); - List> expectedDates = new ArrayList<>(); - // first date is the date to be rounded, second the expected result - expectedDates.add(new Tuple<>("2011-10-30T01:40:00.000+02:00", "2011-10-30T01:30:00.000+02:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:02:30.000+02:00", "2011-10-30T01:30:00.000+02:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:25:00.000+02:00", "2011-10-30T02:15:00.000+02:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:47:30.000+02:00", "2011-10-30T02:15:00.000+02:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:10:00.000+01:00", "2011-10-30T02:15:00.000+02:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:32:30.000+01:00", "2011-10-30T02:15:00.000+01:00")); - expectedDates.add(new Tuple<>("2011-10-30T02:55:00.000+01:00", "2011-10-30T02:15:00.000+01:00")); - expectedDates.add(new Tuple<>("2011-10-30T03:17:30.000+01:00", "2011-10-30T03:00:00.000+01:00")); - - long previousDate = Long.MIN_VALUE; - for (Tuple dates : expectedDates) { - final long roundedDate = rounding.round(time(dates.v1())); - assertThat(roundedDate, isDate(time(dates.v2()), tz)); - assertThat(roundedDate, greaterThanOrEqualTo(previousDate)); - previousDate = roundedDate; - } - // here's what this means for interval widths - assertEquals(TimeUnit.MINUTES.toMillis(45), time("2011-10-30T02:15:00.000+02:00") - time("2011-10-30T01:30:00.000+02:00")); - assertEquals(TimeUnit.MINUTES.toMillis(60), time("2011-10-30T02:15:00.000+01:00") - time("2011-10-30T02:15:00.000+02:00")); - assertEquals(TimeUnit.MINUTES.toMillis(45), time("2011-10-30T03:00:00.000+01:00") - time("2011-10-30T02:15:00.000+01:00")); - } - - /** - * special test for DST switch from #9491 - */ - public void testAmbiguousHoursAfterDSTSwitch() { - Rounding tzRounding; - final DateTimeZone tz = DateTimeZone.forID("Asia/Jerusalem"); - tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build(); - assertThat(tzRounding.round(time("2014-10-26T00:30:00+03:00")), isDate(time("2014-10-26T00:00:00+03:00"), tz)); - assertThat(tzRounding.round(time("2014-10-26T01:30:00+03:00")), isDate(time("2014-10-26T01:00:00+03:00"), tz)); - // the utc date for "2014-10-25T03:00:00+03:00" and "2014-10-25T03:00:00+02:00" is the same, local time turns back 1h here - assertThat(time("2014-10-26T03:00:00+03:00"), isDate(time("2014-10-26T02:00:00+02:00"), tz)); - assertThat(tzRounding.round(time("2014-10-26T01:30:00+02:00")), isDate(time("2014-10-26T01:00:00+02:00"), tz)); - assertThat(tzRounding.round(time("2014-10-26T02:30:00+02:00")), isDate(time("2014-10-26T02:00:00+02:00"), tz)); - - // Day interval - tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build(); - assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-11T00:00:00", tz), tz)); - // DST on - assertThat(tzRounding.round(time("2014-08-11T17:00:00", tz)), isDate(time("2014-08-11T00:00:00", tz), tz)); - // Day of switching DST on -> off - assertThat(tzRounding.round(time("2014-10-26T17:00:00", tz)), isDate(time("2014-10-26T00:00:00", tz), tz)); - // Day of switching DST off -> on - assertThat(tzRounding.round(time("2015-03-27T17:00:00", tz)), isDate(time("2015-03-27T00:00:00", tz), tz)); - - // Month interval - tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build(); - assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-01T00:00:00", tz), tz)); - // DST on - assertThat(tzRounding.round(time("2014-10-10T17:00:00", tz)), isDate(time("2014-10-01T00:00:00", tz), tz)); - - // Year interval - tzRounding = Rounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build(); - assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-01-01T00:00:00", tz), tz)); - - // Two timestamps in same year and different timezone offset ("Double buckets" issue - #9491) - tzRounding = Rounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build(); - assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(tzRounding.round(time("2014-08-11T17:00:00", tz)), tz)); - } - - /** - * test for #10025, strict local to UTC conversion can cause joda exceptions - * on DST start - */ - public void testLenientConversionDST() { - DateTimeZone tz = DateTimeZone.forID("America/Sao_Paulo"); - long start = time("2014-10-18T20:50:00.000", tz); - long end = time("2014-10-19T01:00:00.000", tz); - Rounding tzRounding = new Rounding.TimeUnitRounding(DateTimeUnit.MINUTES_OF_HOUR, tz); - Rounding dayTzRounding = new Rounding.TimeIntervalRounding(60000, tz); - for (long time = start; time < end; time = time + 60000) { - assertThat(tzRounding.nextRoundingValue(time), greaterThan(time)); - assertThat(dayTzRounding.nextRoundingValue(time), greaterThan(time)); - } - } - - public void testEdgeCasesTransition() { - { - // standard +/-1 hour DST transition, CET - DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; - DateTimeZone tz = DateTimeZone.forID("CET"); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - // 29 Mar 2015 - Daylight Saving Time Started - // at 02:00:00 clocks were turned forward 1 hour to 03:00:00 - assertInterval(time("2015-03-29T00:00:00.000+01:00"), time("2015-03-29T01:00:00.000+01:00"), rounding, 60, tz); - assertInterval(time("2015-03-29T01:00:00.000+01:00"), time("2015-03-29T03:00:00.000+02:00"), rounding, 60, tz); - assertInterval(time("2015-03-29T03:00:00.000+02:00"), time("2015-03-29T04:00:00.000+02:00"), rounding, 60, tz); - - // 25 Oct 2015 - Daylight Saving Time Ended - // at 03:00:00 clocks were turned backward 1 hour to 02:00:00 - assertInterval(time("2015-10-25T01:00:00.000+02:00"), time("2015-10-25T02:00:00.000+02:00"), rounding, 60, tz); - assertInterval(time("2015-10-25T02:00:00.000+02:00"), time("2015-10-25T02:00:00.000+01:00"), rounding, 60, tz); - assertInterval(time("2015-10-25T02:00:00.000+01:00"), time("2015-10-25T03:00:00.000+01:00"), rounding, 60, tz); - } - - { - // time zone "Asia/Kathmandu" - // 1 Jan 1986 - Time Zone Change (IST → NPT), at 00:00:00 clocks were turned forward 00:15 minutes - // - // hour rounding is stable before 1985-12-31T23:00:00.000 and after 1986-01-01T01:00:00.000+05:45 - // the interval between is 105 minutes long because the hour after transition starts at 00:15 - // which is not a round value for hourly rounding - DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; - DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu"); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - assertInterval(time("1985-12-31T22:00:00.000+05:30"), time("1985-12-31T23:00:00.000+05:30"), rounding, 60, tz); - assertInterval(time("1985-12-31T23:00:00.000+05:30"), time("1986-01-01T01:00:00.000+05:45"), rounding, 105, tz); - assertInterval(time("1986-01-01T01:00:00.000+05:45"), time("1986-01-01T02:00:00.000+05:45"), rounding, 60, tz); - } - - { - // time zone "Australia/Lord_Howe" - // 3 Mar 1991 - Daylight Saving Time Ended - // at 02:00:00 clocks were turned backward 0:30 hours to Sunday, 3 March 1991, 01:30:00 - DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; - DateTimeZone tz = DateTimeZone.forID("Australia/Lord_Howe"); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - assertInterval(time("1991-03-03T00:00:00.000+11:00"), time("1991-03-03T01:00:00.000+11:00"), rounding, 60, tz); - assertInterval(time("1991-03-03T01:00:00.000+11:00"), time("1991-03-03T02:00:00.000+10:30"), rounding, 90, tz); - assertInterval(time("1991-03-03T02:00:00.000+10:30"), time("1991-03-03T03:00:00.000+10:30"), rounding, 60, tz); - - // 27 Oct 1991 - Daylight Saving Time Started - // at 02:00:00 clocks were turned forward 0:30 hours to 02:30:00 - assertInterval(time("1991-10-27T00:00:00.000+10:30"), time("1991-10-27T01:00:00.000+10:30"), rounding, 60, tz); - // the interval containing the switch time is 90 minutes long - assertInterval(time("1991-10-27T01:00:00.000+10:30"), time("1991-10-27T03:00:00.000+11:00"), rounding, 90, tz); - assertInterval(time("1991-10-27T03:00:00.000+11:00"), time("1991-10-27T04:00:00.000+11:00"), rounding, 60, tz); - } - - { - // time zone "Pacific/Chatham" - // 5 Apr 2015 - Daylight Saving Time Ended - // at 03:45:00 clocks were turned backward 1 hour to 02:45:00 - DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; - DateTimeZone tz = DateTimeZone.forID("Pacific/Chatham"); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - assertInterval(time("2015-04-05T02:00:00.000+13:45"), time("2015-04-05T03:00:00.000+13:45"), rounding, 60, tz); - assertInterval(time("2015-04-05T03:00:00.000+13:45"), time("2015-04-05T03:00:00.000+12:45"), rounding, 60, tz); - assertInterval(time("2015-04-05T03:00:00.000+12:45"), time("2015-04-05T04:00:00.000+12:45"), rounding, 60, tz); - - // 27 Sep 2015 - Daylight Saving Time Started - // at 02:45:00 clocks were turned forward 1 hour to 03:45:00 - - assertInterval(time("2015-09-27T01:00:00.000+12:45"), time("2015-09-27T02:00:00.000+12:45"), rounding, 60, tz); - assertInterval(time("2015-09-27T02:00:00.000+12:45"), time("2015-09-27T04:00:00.000+13:45"), rounding, 60, tz); - assertInterval(time("2015-09-27T04:00:00.000+13:45"), time("2015-09-27T05:00:00.000+13:45"), rounding, 60, tz); - } - } - - public void testDST_Europe_Rome() { - // time zone "Europe/Rome", rounding to days. Rome had two midnights on the day the clocks went back in 1978, and - // timeZone.convertLocalToUTC() gives the later of the two because Rome is east of UTC, whereas we want the earlier. - - DateTimeUnit timeUnit = DateTimeUnit.DAY_OF_MONTH; - DateTimeZone tz = DateTimeZone.forID("Europe/Rome"); - Rounding rounding = new TimeUnitRounding(timeUnit, tz); - - { - long timeBeforeFirstMidnight = time("1978-09-30T23:59:00+02:00"); - long floor = rounding.round(timeBeforeFirstMidnight); - assertThat(floor, isDate(time("1978-09-30T00:00:00+02:00"), tz)); - } - - { - long timeBetweenMidnights = time("1978-10-01T00:30:00+02:00"); - long floor = rounding.round(timeBetweenMidnights); - assertThat(floor, isDate(time("1978-10-01T00:00:00+02:00"), tz)); - } - - { - long timeAfterSecondMidnight = time("1978-10-01T00:30:00+01:00"); - long floor = rounding.round(timeAfterSecondMidnight); - assertThat(floor, isDate(time("1978-10-01T00:00:00+02:00"), tz)); - - long prevFloor = rounding.round(floor - 1); - assertThat(prevFloor, lessThan(floor)); - assertThat(prevFloor, isDate(time("1978-09-30T00:00:00+02:00"), tz)); - } - } - - /** - * Test for a time zone whose days overlap because the clocks are set back across midnight at the end of DST. - */ - public void testDST_America_St_Johns() { - // time zone "America/St_Johns", rounding to days. - DateTimeUnit timeUnit = DateTimeUnit.DAY_OF_MONTH; - DateTimeZone tz = DateTimeZone.forID("America/St_Johns"); - Rounding rounding = new TimeUnitRounding(timeUnit, tz); - - // 29 October 2006 - Daylight Saving Time ended, changing the UTC offset from -02:30 to -03:30. - // This happened at 02:31 UTC, 00:01 local time, so the clocks were set back 1 hour to 23:01 on the 28th. - // This means that 2006-10-29 has _two_ midnights, one in the -02:30 offset and one in the -03:30 offset. - // Only the first of these is considered "rounded". Moreover, the extra time between 23:01 and 23:59 - // should be considered as part of the 28th even though it comes after midnight on the 29th. - - { - // Times before the first midnight should be rounded up to the first midnight. - long timeBeforeFirstMidnight = time("2006-10-28T23:30:00.000-02:30"); - long floor = rounding.round(timeBeforeFirstMidnight); - assertThat(floor, isDate(time("2006-10-28T00:00:00.000-02:30"), tz)); - long ceiling = rounding.nextRoundingValue(timeBeforeFirstMidnight); - assertThat(ceiling, isDate(time("2006-10-29T00:00:00.000-02:30"), tz)); - assertInterval(floor, timeBeforeFirstMidnight, ceiling, rounding, tz); - } - - { - // Times between the two midnights which are on the later day should be rounded down to the later day's midnight. - long timeBetweenMidnights = time("2006-10-29T00:00:30.000-02:30"); - // (this is halfway through the last minute before the clocks changed, in which local time was ambiguous) - - long floor = rounding.round(timeBetweenMidnights); - assertThat(floor, isDate(time("2006-10-29T00:00:00.000-02:30"), tz)); - - long ceiling = rounding.nextRoundingValue(timeBetweenMidnights); - assertThat(ceiling, isDate(time("2006-10-30T00:00:00.000-03:30"), tz)); - - assertInterval(floor, timeBetweenMidnights, ceiling, rounding, tz); - } - - { - // Times between the two midnights which are on the earlier day should be rounded down to the earlier day's midnight. - long timeBetweenMidnights = time("2006-10-28T23:30:00.000-03:30"); - // (this is halfway through the hour after the clocks changed, in which local time was ambiguous) - - long floor = rounding.round(timeBetweenMidnights); - assertThat(floor, isDate(time("2006-10-28T00:00:00.000-02:30"), tz)); - - long ceiling = rounding.nextRoundingValue(timeBetweenMidnights); - assertThat(ceiling, isDate(time("2006-10-29T00:00:00.000-02:30"), tz)); - - assertInterval(floor, timeBetweenMidnights, ceiling, rounding, tz); - } - - { - // Times after the second midnight should be rounded down to the first midnight. - long timeAfterSecondMidnight = time("2006-10-29T06:00:00.000-03:30"); - long floor = rounding.round(timeAfterSecondMidnight); - assertThat(floor, isDate(time("2006-10-29T00:00:00.000-02:30"), tz)); - long ceiling = rounding.nextRoundingValue(timeAfterSecondMidnight); - assertThat(ceiling, isDate(time("2006-10-30T00:00:00.000-03:30"), tz)); - assertInterval(floor, timeAfterSecondMidnight, ceiling, rounding, tz); - } - } - - /** - * tests for dst transition with overlaps and day roundings. - */ - public void testDST_END_Edgecases() { - // First case, dst happens at 1am local time, switching back one hour. - // We want the overlapping hour to count for the next day, making it a 25h interval - - DateTimeUnit timeUnit = DateTimeUnit.DAY_OF_MONTH; - DateTimeZone tz = DateTimeZone.forID("Atlantic/Azores"); - Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - // Sunday, 29 October 2000, 01:00:00 clocks were turned backward 1 hour - // to Sunday, 29 October 2000, 00:00:00 local standard time instead - // which means there were two midnights that day. - - long midnightBeforeTransition = time("2000-10-29T00:00:00", tz); - long midnightOfTransition = time("2000-10-29T00:00:00-01:00"); - assertEquals(60L * 60L * 1000L, midnightOfTransition - midnightBeforeTransition); - long nextMidnight = time("2000-10-30T00:00:00", tz); - - assertInterval(midnightBeforeTransition, nextMidnight, rounding, 25 * 60, tz); - - assertThat(rounding.round(time("2000-10-29T06:00:00-01:00")), isDate(time("2000-10-29T00:00:00Z"), tz)); - - // Second case, dst happens at 0am local time, switching back one hour to 23pm local time. - // We want the overlapping hour to count for the previous day here - - tz = DateTimeZone.forID("America/Lima"); - rounding = new Rounding.TimeUnitRounding(timeUnit, tz); - - // Sunday, 1 April 1990, 00:00:00 clocks were turned backward 1 hour to - // Saturday, 31 March 1990, 23:00:00 local standard time instead - - midnightBeforeTransition = time("1990-03-31T00:00:00.000-04:00"); - nextMidnight = time("1990-04-01T00:00:00.000-05:00"); - assertInterval(midnightBeforeTransition, nextMidnight, rounding, 25 * 60, tz); - - // make sure the next interval is 24h long again - long midnightAfterTransition = time("1990-04-01T00:00:00.000-05:00"); - nextMidnight = time("1990-04-02T00:00:00.000-05:00"); - assertInterval(midnightAfterTransition, nextMidnight, rounding, 24 * 60, tz); - } - - /** - * Test that time zones are correctly parsed. There is a bug with - * Joda 2.9.4 (see https://github.com/JodaOrg/joda-time/issues/373) - */ - public void testsTimeZoneParsing() { - final DateTime expected = new DateTime(2016, 11, 10, 5, 37, 59, randomDateTimeZone()); - - // Formatter used to print and parse the sample date. - // Printing the date works but parsing it back fails - // with Joda 2.9.4 - DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ss " + randomFrom("ZZZ", "[ZZZ]", "'['ZZZ']'")); - - String dateTimeAsString = formatter.print(expected); - assertThat(dateTimeAsString, startsWith("2016-11-10T05:37:59 ")); - - DateTime parsedDateTime = formatter.parseDateTime(dateTimeAsString); - assertThat(parsedDateTime.getZone(), equalTo(expected.getZone())); - } - - private static void assertInterval(long rounded, long nextRoundingValue, Rounding rounding, int minutes, DateTimeZone tz) { - assertInterval(rounded, dateBetween(rounded, nextRoundingValue), nextRoundingValue, rounding, tz); - assertEquals(DateTimeConstants.MILLIS_PER_MINUTE * minutes, nextRoundingValue - rounded); - } - - /** - * perform a number on assertions and checks on {@link TimeUnitRounding} intervals - * @param rounded the expected low end of the rounding interval - * @param unrounded a date in the interval to be checked for rounding - * @param nextRoundingValue the expected upper end of the rounding interval - * @param rounding the rounding instance - */ - private static void assertInterval(long rounded, long unrounded, long nextRoundingValue, Rounding rounding, DateTimeZone tz) { - assertThat("rounding should be idempotent ", rounding.round(rounded), isDate(rounded, tz)); - assertThat("rounded value smaller or equal than unrounded" + rounding, rounded, lessThanOrEqualTo(unrounded)); - assertThat("values less than rounded should round further down" + rounding, rounding.round(rounded - 1), lessThan(rounded)); - assertThat("nextRounding value should be a rounded date", rounding.round(nextRoundingValue), isDate(nextRoundingValue, tz)); - assertThat( - "values above nextRounding should round down there", - rounding.round(nextRoundingValue + 1), - isDate(nextRoundingValue, tz) - ); - - if (isTimeWithWellDefinedRounding(tz, unrounded)) { - assertThat("nextRounding value should be greater than date" + rounding, nextRoundingValue, greaterThan(unrounded)); - - long dateBetween = dateBetween(rounded, nextRoundingValue); - assertThat( - "dateBetween [" + new DateTime(dateBetween, tz) + "] should round down to roundedDate", - rounding.round(dateBetween), - isDate(rounded, tz) - ); - assertThat( - "dateBetween [" + new DateTime(dateBetween, tz) + "] should round up to nextRoundingValue", - rounding.nextRoundingValue(dateBetween), - isDate(nextRoundingValue, tz) - ); - } - } - - private static boolean isTimeWithWellDefinedRounding(DateTimeZone tz, long t) { - if (tz.getID().equals("America/St_Johns") - || tz.getID().equals("America/Goose_Bay") - || tz.getID().equals("America/Moncton") - || tz.getID().equals("Canada/Newfoundland")) { - - // Clocks went back at 00:01 between 1987 and 2010, causing overlapping days. - // These timezones are otherwise uninteresting, so just skip this period. - - return t <= time("1987-10-01T00:00:00Z") || t >= time("2010-12-01T00:00:00Z"); - } - - if (tz.getID().equals("Antarctica/Casey")) { - - // Clocks went back 3 hours at 02:00 on 2010-03-05, causing overlapping days. - - return t <= time("2010-03-03T00:00:00Z") || t >= time("2010-03-07T00:00:00Z"); - } - - return true; - } - - private static long dateBetween(long lower, long upper) { - long dateBetween = randomLongBetween(lower, upper - 1); - assert lower <= dateBetween && dateBetween < upper; - return dateBetween; - } - - private static DateTimeUnit randomTimeUnit() { - byte id = (byte) randomIntBetween(1, 8); - return DateTimeUnit.resolve(id); - } - - private static long time(String time) { - return time(time, DateTimeZone.UTC); - } - - private static long time(String time, DateTimeZone zone) { - return ISODateTimeFormat.dateOptionalTimeParser().withZone(zone).parseMillis(time); - } - - private static Matcher isDate(final long expected, DateTimeZone tz) { - return new TypeSafeMatcher() { - @Override - public boolean matchesSafely(final Long item) { - return expected == item.longValue(); - } - - @Override - public void describeTo(Description description) { - description.appendText(new DateTime(expected, tz) + " [" + expected + "] "); - } - - @Override - protected void describeMismatchSafely(final Long actual, final Description mismatchDescription) { - mismatchDescription.appendText(" was ").appendValue(new DateTime(actual, tz) + " [" + actual + "]"); - } - }; - } -} diff --git a/server/src/test/java/org/opensearch/common/time/DateFormattersTests.java b/server/src/test/java/org/opensearch/common/time/DateFormattersTests.java index 681daf1755890..c8560134dc208 100644 --- a/server/src/test/java/org/opensearch/common/time/DateFormattersTests.java +++ b/server/src/test/java/org/opensearch/common/time/DateFormattersTests.java @@ -32,7 +32,7 @@ package org.opensearch.common.time; -import org.opensearch.common.joda.Joda; +import org.opensearch.OpenSearchParseException; import org.opensearch.test.OpenSearchTestCase; import java.time.Clock; @@ -40,6 +40,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.util.Locale; @@ -580,107 +581,734 @@ public void testWeek_yearDeprecation() { ); } - public void testCamelCaseDeprecation() { - String[] deprecatedNames = new String[] { - "basicDate", - "basicDateTime", - "basicDateTimeNoMillis", - "basicOrdinalDate", - "basicOrdinalDateTime", - "basicOrdinalDateTimeNoMillis", - "basicTime", - "basicTimeNoMillis", - "basicTTime", - "basicTTimeNoMillis", - "basicWeekDate", - "basicWeekDateTime", - "basicWeekDateTimeNoMillis", - "dateHour", - "dateHourMinute", - "dateHourMinuteSecond", - "dateHourMinuteSecondFraction", - "dateHourMinuteSecondMillis", - "dateOptionalTime", - "dateTime", - "dateTimeNoMillis", - "hourMinute", - "hourMinuteSecond", - "hourMinuteSecondFraction", - "hourMinuteSecondMillis", - "ordinalDate", - "ordinalDateTime", - "ordinalDateTimeNoMillis", - "timeNoMillis", - "tTime", - "tTimeNoMillis", - "weekDate", - "weekDateTime", - "weekDateTimeNoMillis", - "weekyearWeek", - "weekyearWeekDay", - "yearMonth", - "yearMonthDay", - "strictBasicWeekDate", - "strictBasicWeekDateTime", - "strictBasicWeekDateTimeNoMillis", - "strictDate", - "strictDateHour", - "strictDateHourMinute", - "strictDateHourMinuteSecond", - "strictDateHourMinuteSecondFraction", - "strictDateHourMinuteSecondMillis", - "strictDateOptionalTime", - "strictDateOptionalTimeNanos", - "strictDateTime", - "strictDateTimeNoMillis", - "strictHour", - "strictHourMinute", - "strictHourMinuteSecond", - "strictHourMinuteSecondFraction", - "strictHourMinuteSecondMillis", - "strictOrdinalDate", - "strictOrdinalDateTime", - "strictOrdinalDateTimeNoMillis", - "strictTime", - "strictTimeNoMillis", - "strictTTime", - "strictTTimeNoMillis", - "strictWeekDate", - "strictWeekDateTime", - "strictWeekDateTimeNoMillis", - "strictWeekyear", - "strictWeekyearWeek", - "strictWeekyearWeekDay", - "strictYear", - "strictYearMonth", - "strictYearMonthDay" }; - for (String name : deprecatedNames) { - String snakeCaseName = FormatNames.forName(name).getSnakeCaseName(); - - DateFormatter dateFormatter = DateFormatter.forPattern(name); - assertThat(dateFormatter.pattern(), equalTo(name)); - assertWarnings( - "Camel case format name " - + name - + " is deprecated and will be removed in a future version. " - + "Use snake case name " - + snakeCaseName - + " instead." - ); - - dateFormatter = DateFormatter.forPattern(snakeCaseName); - assertThat(dateFormatter.pattern(), equalTo(snakeCaseName)); - } - - for (String name : deprecatedNames) { - if (name.equals("strictDateOptionalTimeNanos") == false) { - DateFormatter dateFormatter = Joda.forPattern(name); - assertThat(dateFormatter.pattern(), equalTo(name)); - - String snakeCaseName = FormatNames.forName(name).getSnakeCaseName(); - dateFormatter = Joda.forPattern(snakeCaseName); - assertThat(dateFormatter.pattern(), equalTo(snakeCaseName)); - } + public void testTimezoneParsing() { + /** this testcase won't work in joda. See comment in {@link #testPartialTimeParsing()} + * assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time"); + */ + assertSameDateAs("2016-11-30T00+01", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T00+0100", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T00+01:00", "strict_date_optional_time"); + } + + public void testPartialTimeParsing() { + /* + This does not work in Joda as it reports 2016-11-30T01:00:00Z + because strict_date_optional_time confuses +01 with an hour (which is a signed fixed length digit) + assertSameDateAs("2016-11-30T+01", "strict_date_optional_time", "strict_date_optional_time"); + ES java.time implementation does not suffer from this, + but we intentionally not allow parsing timezone without an time part as it is not allowed in iso8601 + */ + assertParseException("2016-11-30T+01", "strict_date_optional_time"); + + assertSameDateAs("2016-11-30T12+01", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00+01", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00:00+01", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00:00.000+01", "strict_date_optional_time"); + + // without timezone + assertSameDateAs("2016-11-30T", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00:00", "strict_date_optional_time"); + assertSameDateAs("2016-11-30T12:00:00.000", "strict_date_optional_time"); + } + + // date_optional part of a parser names "strict_date_optional_time" or "date_optional"time + // means that date part can be partially parsed. + public void testPartialDateParsing() { + assertSameDateAs("2001", "strict_date_optional_time_nanos"); + assertSameDateAs("2001-01", "strict_date_optional_time_nanos"); + assertSameDateAs("2001-01-01", "strict_date_optional_time_nanos"); + + assertSameDateAs("2001", "strict_date_optional_time"); + assertSameDateAs("2001-01", "strict_date_optional_time"); + assertSameDateAs("2001-01-01", "strict_date_optional_time"); + + assertSameDateAs("2001", "date_optional_time"); + assertSameDateAs("2001-01", "date_optional_time"); + assertSameDateAs("2001-01-01", "date_optional_time"); + + assertSameDateAs("2001", "iso8601"); + assertSameDateAs("2001-01", "iso8601"); + assertSameDateAs("2001-01-01", "iso8601"); + + assertSameDateAs("9999", "date_optional_time||epoch_second"); + } + + public void testCompositeDateMathParsing() { + // in all these examples the second pattern will be used + assertDateMathEquals("2014-06-06T12:01:02.123", "2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS"); + assertDateMathEquals("2014-06-06T12:01:02.123", "2014-06-06T12:01:02.123", "strict_date_time_no_millis||yyyy-MM-dd'T'HH:mm:ss.SSS"); + assertDateMathEquals( + "2014-06-06T12:01:02.123", + "2014-06-06T12:01:02.123", + "yyyy-MM-dd'T'HH:mm:ss+HH:MM||yyyy-MM-dd'T'HH:mm:ss.SSS" + ); + } + + public void testExceptionWhenCompositeParsingFailsDateMath() { + // both parsing failures should contain pattern and input text in exception + // both patterns fail parsing the input text due to only 2 digits of millis. Hence full text was not parsed. + String pattern = "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SS"; + String text = "2014-06-06T12:01:02.123"; + OpenSearchParseException e1 = expectThrows( + OpenSearchParseException.class, + () -> dateMathToMillis(text, DateFormatter.forPattern(pattern)) + ); + assertThat(e1.getMessage(), containsString(pattern)); + assertThat(e1.getMessage(), containsString(text)); + } + + // these parsers should allow both ',' and '.' as a decimal point + public void testDecimalPointParsing() { + assertSameDateAs("2001-01-01T00:00:00.123Z", "strict_date_optional_time"); + assertSameDateAs("2001-01-01T00:00:00,123Z", "strict_date_optional_time"); + + assertSameDateAs("2001-01-01T00:00:00.123Z", "date_optional_time"); + assertSameDateAs("2001-01-01T00:00:00,123Z", "date_optional_time"); + + // only java.time has nanos parsing, but the results for 3digits should be the same + DateFormatter javaFormatter = DateFormatter.forPattern("strict_date_optional_time_nanos"); + assertSameDate("2001-01-01T00:00:00.123Z", javaFormatter); + assertSameDate("2001-01-01T00:00:00,123Z", javaFormatter); + + assertParseException("2001-01-01T00:00:00.123,456Z", "strict_date_optional_time"); + assertParseException("2001-01-01T00:00:00.123,456Z", "date_optional_time"); + // This should fail, but java is ok with this because the field has the same value + // assertJavaTimeParseException("2001-01-01T00:00:00.123,123Z", "strict_date_optional_time_nanos"); + } + + public void testTimeZoneFormatting() { + assertSameDateAs("2001-01-01T00:00:00Z", "date_time_no_millis"); + // the following fail under java 8 but work under java 10, needs investigation + assertSameDateAs("2001-01-01T00:00:00-0800", "date_time_no_millis"); + assertSameDateAs("2001-01-01T00:00:00+1030", "date_time_no_millis"); + assertSameDateAs("2001-01-01T00:00:00-08", "date_time_no_millis"); + assertSameDateAs("2001-01-01T00:00:00+10:30", "date_time_no_millis"); + + // different timezone parsing styles require a different number of letters + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSSXXX", Locale.ROOT); + formatter.parse("20181126T121212.123Z"); + formatter.parse("20181126T121212.123-08:30"); + + DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSSXXXX", Locale.ROOT); + formatter2.parse("20181126T121212.123+1030"); + formatter2.parse("20181126T121212.123-0830"); + + // ... and can be combined, note that this is not an XOR, so one could append both timezones with this example + DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSS[XXXX][XXX]", Locale.ROOT); + formatter3.parse("20181126T121212.123Z"); + formatter3.parse("20181126T121212.123-08:30"); + formatter3.parse("20181126T121212.123+1030"); + formatter3.parse("20181126T121212.123-0830"); + } + + public void testCustomTimeFormats() { + assertSameDateAs("2010 12 06 11:05:15", "yyyy dd MM HH:mm:ss"); + assertSameDateAs("12/06", "dd/MM"); + assertSameDateAs("Nov 24 01:29:01 -0800", "MMM dd HH:mm:ss Z"); + } + + public void testFormatsValidParsing() { + assertSameDateAs("1522332219", "epoch_second"); + assertSameDateAs("0", "epoch_second"); + assertSameDateAs("1", "epoch_second"); + assertSameDateAs("1522332219321", "epoch_millis"); + assertSameDateAs("0", "epoch_millis"); + assertSameDateAs("1", "epoch_millis"); + + assertSameDateAs("20181126", "basic_date"); + assertSameDateAs("20181126T121212.123Z", "basic_date_time"); + assertSameDateAs("20181126T121212.123+10:00", "basic_date_time"); + assertSameDateAs("20181126T121212.123-0800", "basic_date_time"); + + assertSameDateAs("20181126T121212Z", "basic_date_time_no_millis"); + assertSameDateAs("20181126T121212+01:00", "basic_date_time_no_millis"); + assertSameDateAs("20181126T121212+0100", "basic_date_time_no_millis"); + assertSameDateAs("2018363", "basic_ordinal_date"); + assertSameDateAs("2018363T121212.1Z", "basic_ordinal_date_time"); + assertSameDateAs("2018363T121212.123Z", "basic_ordinal_date_time"); + assertSameDateAs("2018363T121212.123456789Z", "basic_ordinal_date_time"); + assertSameDateAs("2018363T121212.123+0100", "basic_ordinal_date_time"); + assertSameDateAs("2018363T121212.123+01:00", "basic_ordinal_date_time"); + assertSameDateAs("2018363T121212Z", "basic_ordinal_date_time_no_millis"); + assertSameDateAs("2018363T121212+0100", "basic_ordinal_date_time_no_millis"); + assertSameDateAs("2018363T121212+01:00", "basic_ordinal_date_time_no_millis"); + assertSameDateAs("121212.1Z", "basic_time"); + assertSameDateAs("121212.123Z", "basic_time"); + assertSameDateAs("121212.123456789Z", "basic_time"); + assertSameDateAs("121212.1+0100", "basic_time"); + assertSameDateAs("121212.123+0100", "basic_time"); + assertSameDateAs("121212.123+01:00", "basic_time"); + assertSameDateAs("121212Z", "basic_time_no_millis"); + assertSameDateAs("121212+0100", "basic_time_no_millis"); + assertSameDateAs("121212+01:00", "basic_time_no_millis"); + assertSameDateAs("T121212.1Z", "basic_t_time"); + assertSameDateAs("T121212.123Z", "basic_t_time"); + assertSameDateAs("T121212.123456789Z", "basic_t_time"); + assertSameDateAs("T121212.1+0100", "basic_t_time"); + assertSameDateAs("T121212.123+0100", "basic_t_time"); + assertSameDateAs("T121212.123+01:00", "basic_t_time"); + assertSameDateAs("T121212Z", "basic_t_time_no_millis"); + assertSameDateAs("T121212+0100", "basic_t_time_no_millis"); + assertSameDateAs("T121212+01:00", "basic_t_time_no_millis"); + assertSameDateAs("2018W313", "basic_week_date"); + assertSameDateAs("1W313", "basic_week_date"); + assertSameDateAs("18W313", "basic_week_date"); + assertSameDateAs("2018W313T121212.1Z", "basic_week_date_time"); + assertSameDateAs("2018W313T121212.123Z", "basic_week_date_time"); + assertSameDateAs("2018W313T121212.123456789Z", "basic_week_date_time"); + assertSameDateAs("2018W313T121212.123+0100", "basic_week_date_time"); + assertSameDateAs("2018W313T121212.123+01:00", "basic_week_date_time"); + assertSameDateAs("2018W313T121212Z", "basic_week_date_time_no_millis"); + assertSameDateAs("2018W313T121212+0100", "basic_week_date_time_no_millis"); + assertSameDateAs("2018W313T121212+01:00", "basic_week_date_time_no_millis"); + + assertSameDateAs("2018-12-31", "date"); + assertSameDateAs("18-5-6", "date"); + assertSameDateAs("10000-5-6", "date"); + + assertSameDateAs("2018-12-31T12", "date_hour"); + assertSameDateAs("2018-12-31T8", "date_hour"); + + assertSameDateAs("2018-12-31T12:12", "date_hour_minute"); + assertSameDateAs("2018-12-31T8:3", "date_hour_minute"); + + assertSameDateAs("2018-12-31T12:12:12", "date_hour_minute_second"); + assertSameDateAs("2018-12-31T12:12:1", "date_hour_minute_second"); + + assertSameDateAs("2018-12-31T12:12:12.1", "date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.123", "date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.123456789", "date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.1", "date_hour_minute_second_millis"); + assertSameDateAs("2018-12-31T12:12:12.123", "date_hour_minute_second_millis"); + assertParseException("2018-12-31T12:12:12.123456789", "date_hour_minute_second_millis"); + assertSameDateAs("2018-12-31T12:12:12.1", "date_hour_minute_second_millis"); + assertSameDateAs("2018-12-31T12:12:12.1", "date_hour_minute_second_fraction"); + + assertSameDateAs("2018-05", "date_optional_time"); + assertSameDateAs("2018-05-30", "date_optional_time"); + assertSameDateAs("2018-05-30T20", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.1", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123456789", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123Z", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123456789Z", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.1+0100", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123+0100", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.1+01:00", "date_optional_time"); + assertSameDateAs("2018-05-30T20:21:23.123+01:00", "date_optional_time"); + assertSameDateAs("2018-12-1", "date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30", "date_optional_time"); + assertSameDateAs("2018-12-31T10:15:3", "date_optional_time"); + assertSameDateAs("2018-12-31T10:5:30", "date_optional_time"); + assertSameDateAs("2018-12-31T1:15:30", "date_optional_time"); + + assertSameDateAs("2018-12-31T10:15:30.1Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.123Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.123456789Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.1+0100", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.123+0100", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.123+01:00", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.1+01:00", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.11Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.11+0100", "date_time"); + assertSameDateAs("2018-12-31T10:15:30.11+01:00", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.1Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.123Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.123456789Z", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.1+0100", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.123+0100", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.123+01:00", "date_time"); + assertSameDateAs("2018-12-31T10:15:3.1+01:00", "date_time"); + + assertSameDateAs("2018-12-31T10:15:30Z", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:30+0100", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:30+01:00", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:5:30Z", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:5:30+0100", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:5:30+01:00", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:3Z", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:3+0100", "date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:3+01:00", "date_time_no_millis"); + assertSameDateAs("2018-12-31T1:15:30Z", "date_time_no_millis"); + assertSameDateAs("2018-12-31T1:15:30+0100", "date_time_no_millis"); + assertSameDateAs("2018-12-31T1:15:30+01:00", "date_time_no_millis"); + + assertSameDateAs("12", "hour"); + assertSameDateAs("01", "hour"); + assertSameDateAs("1", "hour"); + + assertSameDateAs("12:12", "hour_minute"); + assertSameDateAs("12:01", "hour_minute"); + assertSameDateAs("12:1", "hour_minute"); + + assertSameDateAs("12:12:12", "hour_minute_second"); + assertSameDateAs("12:12:01", "hour_minute_second"); + assertSameDateAs("12:12:1", "hour_minute_second"); + + assertSameDateAs("12:12:12.123", "hour_minute_second_fraction"); + assertSameDateAs("12:12:12.123456789", "hour_minute_second_fraction"); + assertSameDateAs("12:12:12.1", "hour_minute_second_fraction"); + assertParseException("12:12:12", "hour_minute_second_fraction"); + assertSameDateAs("12:12:12.123", "hour_minute_second_millis"); + assertParseException("12:12:12.123456789", "hour_minute_second_millis"); + assertSameDateAs("12:12:12.1", "hour_minute_second_millis"); + assertParseException("12:12:12", "hour_minute_second_millis"); + + assertSameDateAs("2018-128", "ordinal_date"); + assertSameDateAs("2018-1", "ordinal_date"); + + assertSameDateAs("2018-128T10:15:30.1Z", "ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123Z", "ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123456789Z", "ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123+0100", "ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123+01:00", "ordinal_date_time"); + assertSameDateAs("2018-1T10:15:30.1Z", "ordinal_date_time"); + assertSameDateAs("2018-1T10:15:30.123Z", "ordinal_date_time"); + assertSameDateAs("2018-1T10:15:30.123456789Z", "ordinal_date_time"); + assertSameDateAs("2018-1T10:15:30.123+0100", "ordinal_date_time"); + assertSameDateAs("2018-1T10:15:30.123+01:00", "ordinal_date_time"); + + assertSameDateAs("2018-128T10:15:30Z", "ordinal_date_time_no_millis"); + assertSameDateAs("2018-128T10:15:30+0100", "ordinal_date_time_no_millis"); + assertSameDateAs("2018-128T10:15:30+01:00", "ordinal_date_time_no_millis"); + assertSameDateAs("2018-1T10:15:30Z", "ordinal_date_time_no_millis"); + assertSameDateAs("2018-1T10:15:30+0100", "ordinal_date_time_no_millis"); + assertSameDateAs("2018-1T10:15:30+01:00", "ordinal_date_time_no_millis"); + + assertSameDateAs("10:15:30.1Z", "time"); + assertSameDateAs("10:15:30.123Z", "time"); + assertSameDateAs("10:15:30.123456789Z", "time"); + assertSameDateAs("10:15:30.123+0100", "time"); + assertSameDateAs("10:15:30.123+01:00", "time"); + assertSameDateAs("1:15:30.1Z", "time"); + assertSameDateAs("1:15:30.123Z", "time"); + assertSameDateAs("1:15:30.123+0100", "time"); + assertSameDateAs("1:15:30.123+01:00", "time"); + assertSameDateAs("10:1:30.1Z", "time"); + assertSameDateAs("10:1:30.123Z", "time"); + assertSameDateAs("10:1:30.123+0100", "time"); + assertSameDateAs("10:1:30.123+01:00", "time"); + assertSameDateAs("10:15:3.1Z", "time"); + assertSameDateAs("10:15:3.123Z", "time"); + assertSameDateAs("10:15:3.123+0100", "time"); + assertSameDateAs("10:15:3.123+01:00", "time"); + assertParseException("10:15:3.1", "time"); + assertParseException("10:15:3Z", "time"); + + assertSameDateAs("10:15:30Z", "time_no_millis"); + assertSameDateAs("10:15:30+0100", "time_no_millis"); + assertSameDateAs("10:15:30+01:00", "time_no_millis"); + assertSameDateAs("01:15:30Z", "time_no_millis"); + assertSameDateAs("01:15:30+0100", "time_no_millis"); + assertSameDateAs("01:15:30+01:00", "time_no_millis"); + assertSameDateAs("1:15:30Z", "time_no_millis"); + assertSameDateAs("1:15:30+0100", "time_no_millis"); + assertSameDateAs("1:15:30+01:00", "time_no_millis"); + assertSameDateAs("10:5:30Z", "time_no_millis"); + assertSameDateAs("10:5:30+0100", "time_no_millis"); + assertSameDateAs("10:5:30+01:00", "time_no_millis"); + assertSameDateAs("10:15:3Z", "time_no_millis"); + assertSameDateAs("10:15:3+0100", "time_no_millis"); + assertSameDateAs("10:15:3+01:00", "time_no_millis"); + assertParseException("10:15:3", "time_no_millis"); + + assertSameDateAs("T10:15:30.1Z", "t_time"); + assertSameDateAs("T10:15:30.123Z", "t_time"); + assertSameDateAs("T10:15:30.123456789Z", "t_time"); + assertSameDateAs("T10:15:30.1+0100", "t_time"); + assertSameDateAs("T10:15:30.123+0100", "t_time"); + assertSameDateAs("T10:15:30.123+01:00", "t_time"); + assertSameDateAs("T10:15:30.1+01:00", "t_time"); + assertSameDateAs("T1:15:30.123Z", "t_time"); + assertSameDateAs("T1:15:30.123+0100", "t_time"); + assertSameDateAs("T1:15:30.123+01:00", "t_time"); + assertSameDateAs("T10:1:30.123Z", "t_time"); + assertSameDateAs("T10:1:30.123+0100", "t_time"); + assertSameDateAs("T10:1:30.123+01:00", "t_time"); + assertSameDateAs("T10:15:3.123Z", "t_time"); + assertSameDateAs("T10:15:3.123+0100", "t_time"); + assertSameDateAs("T10:15:3.123+01:00", "t_time"); + assertParseException("T10:15:3.1", "t_time"); + assertParseException("T10:15:3Z", "t_time"); + + assertSameDateAs("T10:15:30Z", "t_time_no_millis"); + assertSameDateAs("T10:15:30+0100", "t_time_no_millis"); + assertSameDateAs("T10:15:30+01:00", "t_time_no_millis"); + assertSameDateAs("T1:15:30Z", "t_time_no_millis"); + assertSameDateAs("T1:15:30+0100", "t_time_no_millis"); + assertSameDateAs("T1:15:30+01:00", "t_time_no_millis"); + assertSameDateAs("T10:1:30Z", "t_time_no_millis"); + assertSameDateAs("T10:1:30+0100", "t_time_no_millis"); + assertSameDateAs("T10:1:30+01:00", "t_time_no_millis"); + assertSameDateAs("T10:15:3Z", "t_time_no_millis"); + assertSameDateAs("T10:15:3+0100", "t_time_no_millis"); + assertSameDateAs("T10:15:3+01:00", "t_time_no_millis"); + assertParseException("T10:15:3", "t_time_no_millis"); + + assertSameDateAs("2012-W48-6", "week_date"); + assertSameDateAs("2012-W01-6", "week_date"); + assertSameDateAs("2012-W1-6", "week_date"); + assertParseException("2012-W1-8", "week_date"); + + assertSameDateAs("2012-W48-6T10:15:30.1Z", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123Z", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123456789Z", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.1+0100", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123+0100", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.1+01:00", "week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123+01:00", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.1Z", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.123Z", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.1+0100", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.123+0100", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.1+01:00", "week_date_time"); + assertSameDateAs("2012-W1-6T10:15:30.123+01:00", "week_date_time"); + + assertSameDateAs("2012-W48-6T10:15:30Z", "week_date_time_no_millis"); + assertSameDateAs("2012-W48-6T10:15:30+0100", "week_date_time_no_millis"); + assertSameDateAs("2012-W48-6T10:15:30+01:00", "week_date_time_no_millis"); + assertSameDateAs("2012-W1-6T10:15:30Z", "week_date_time_no_millis"); + assertSameDateAs("2012-W1-6T10:15:30+0100", "week_date_time_no_millis"); + assertSameDateAs("2012-W1-6T10:15:30+01:00", "week_date_time_no_millis"); + + assertSameDateAs("2012", "year"); + assertSameDateAs("1", "year"); + assertSameDateAs("-2000", "year"); + + assertSameDateAs("2012-12", "year_month"); + assertSameDateAs("1-1", "year_month"); + + assertSameDateAs("2012-12-31", "year_month_day"); + assertSameDateAs("1-12-31", "year_month_day"); + assertSameDateAs("2012-1-31", "year_month_day"); + assertSameDateAs("2012-12-1", "year_month_day"); + + assertSameDateAs("2018", "weekyear"); + assertSameDateAs("1", "weekyear"); + assertSameDateAs("2017", "weekyear"); + + assertSameDateAs("2018-W29", "weekyear_week"); + assertSameDateAs("2018-W1", "weekyear_week"); + + assertSameDateAs("2012-W31-5", "weekyear_week_day"); + assertSameDateAs("2012-W1-1", "weekyear_week_day"); + } + + public void testCompositeParsing() { + // in all these examples the second pattern will be used + assertSameDateAs("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSS"); + assertSameDateAs("2014-06-06T12:01:02.123", "strict_date_time_no_millis||yyyy-MM-dd'T'HH:mm:ss.SSS"); + assertSameDateAs("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss+HH:MM||yyyy-MM-dd'T'HH:mm:ss.SSS"); + } + + public void testStrictParsing() { + assertSameDateAs("2018W313", "strict_basic_week_date"); + assertParseException("18W313", "strict_basic_week_date"); + assertSameDateAs("2018W313T121212.1Z", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.123Z", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.123456789Z", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.1+0100", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.123+0100", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.1+01:00", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212.123+01:00", "strict_basic_week_date_time"); + assertParseException("2018W313T12128.123Z", "strict_basic_week_date_time"); + assertParseException("2018W313T12128.123456789Z", "strict_basic_week_date_time"); + assertParseException("2018W313T81212.123Z", "strict_basic_week_date_time"); + assertParseException("2018W313T12812.123Z", "strict_basic_week_date_time"); + assertParseException("2018W313T12812.1Z", "strict_basic_week_date_time"); + assertSameDateAs("2018W313T121212Z", "strict_basic_week_date_time_no_millis"); + assertSameDateAs("2018W313T121212+0100", "strict_basic_week_date_time_no_millis"); + assertSameDateAs("2018W313T121212+01:00", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12128Z", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12128+0100", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12128+01:00", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T81212Z", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T81212+0100", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T81212+01:00", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12812Z", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12812+0100", "strict_basic_week_date_time_no_millis"); + assertParseException("2018W313T12812+01:00", "strict_basic_week_date_time_no_millis"); + assertSameDateAs("2018-12-31", "strict_date"); + assertParseException("10000-12-31", "strict_date"); + assertParseException("2018-8-31", "strict_date"); + assertSameDateAs("2018-12-31T12", "strict_date_hour"); + assertParseException("2018-12-31T8", "strict_date_hour"); + assertSameDateAs("2018-12-31T12:12", "strict_date_hour_minute"); + assertParseException("2018-12-31T8:3", "strict_date_hour_minute"); + assertSameDateAs("2018-12-31T12:12:12", "strict_date_hour_minute_second"); + assertParseException("2018-12-31T12:12:1", "strict_date_hour_minute_second"); + assertSameDateAs("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.123", "strict_date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.123456789", "strict_date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31T12:12:12.123", "strict_date_hour_minute_second_millis"); + assertSameDateAs("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_millis"); + assertSameDateAs("2018-12-31T12:12:12.1", "strict_date_hour_minute_second_fraction"); + assertParseException("2018-12-31T12:12:12", "strict_date_hour_minute_second_millis"); + assertParseException("2018-12-31T12:12:12", "strict_date_hour_minute_second_fraction"); + assertSameDateAs("2018-12-31", "strict_date_optional_time"); + assertParseException("2018-12-1", "strict_date_optional_time"); + assertParseException("2018-1-31", "strict_date_optional_time"); + assertParseException("10000-01-31", "strict_date_optional_time"); + assertSameDateAs("2010-01-05T02:00", "strict_date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30", "strict_date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30Z", "strict_date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30+0100", "strict_date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30+01:00", "strict_date_optional_time"); + assertParseException("2018-12-31T10:15:3", "strict_date_optional_time"); + assertParseException("2018-12-31T10:5:30", "strict_date_optional_time"); + assertParseException("2018-12-31T9:15:30", "strict_date_optional_time"); + assertSameDateAs("2015-01-04T00:00Z", "strict_date_optional_time"); + assertSameDateAs("2018-12-31T10:15:30.1Z", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.123Z", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.123456789Z", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.1+0100", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.123+0100", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.1+01:00", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.123+01:00", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.11Z", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.11+0100", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30.11+01:00", "strict_date_time"); + assertParseException("2018-12-31T10:15:3.123Z", "strict_date_time"); + assertParseException("2018-12-31T10:5:30.123Z", "strict_date_time"); + assertParseException("2018-12-31T1:15:30.123Z", "strict_date_time"); + assertSameDateAs("2018-12-31T10:15:30Z", "strict_date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:30+0100", "strict_date_time_no_millis"); + assertSameDateAs("2018-12-31T10:15:30+01:00", "strict_date_time_no_millis"); + assertParseException("2018-12-31T10:5:30Z", "strict_date_time_no_millis"); + assertParseException("2018-12-31T10:15:3Z", "strict_date_time_no_millis"); + assertParseException("2018-12-31T1:15:30Z", "strict_date_time_no_millis"); + assertSameDateAs("12", "strict_hour"); + assertSameDateAs("01", "strict_hour"); + assertParseException("1", "strict_hour"); + assertSameDateAs("12:12", "strict_hour_minute"); + assertSameDateAs("12:01", "strict_hour_minute"); + assertParseException("12:1", "strict_hour_minute"); + assertSameDateAs("12:12:12", "strict_hour_minute_second"); + assertSameDateAs("12:12:01", "strict_hour_minute_second"); + assertParseException("12:12:1", "strict_hour_minute_second"); + assertSameDateAs("12:12:12.123", "strict_hour_minute_second_fraction"); + assertSameDateAs("12:12:12.123456789", "strict_hour_minute_second_fraction"); + assertSameDateAs("12:12:12.1", "strict_hour_minute_second_fraction"); + assertParseException("12:12:12", "strict_hour_minute_second_fraction"); + assertSameDateAs("12:12:12.123", "strict_hour_minute_second_millis"); + assertSameDateAs("12:12:12.1", "strict_hour_minute_second_millis"); + assertParseException("12:12:12", "strict_hour_minute_second_millis"); + assertSameDateAs("2018-128", "strict_ordinal_date"); + assertParseException("2018-1", "strict_ordinal_date"); + + assertSameDateAs("2018-128T10:15:30.1Z", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123Z", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123456789Z", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.1+0100", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123+0100", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.1+01:00", "strict_ordinal_date_time"); + assertSameDateAs("2018-128T10:15:30.123+01:00", "strict_ordinal_date_time"); + assertParseException("2018-1T10:15:30.123Z", "strict_ordinal_date_time"); + + assertSameDateAs("2018-128T10:15:30Z", "strict_ordinal_date_time_no_millis"); + assertSameDateAs("2018-128T10:15:30+0100", "strict_ordinal_date_time_no_millis"); + assertSameDateAs("2018-128T10:15:30+01:00", "strict_ordinal_date_time_no_millis"); + assertParseException("2018-1T10:15:30Z", "strict_ordinal_date_time_no_millis"); + + assertSameDateAs("10:15:30.1Z", "strict_time"); + assertSameDateAs("10:15:30.123Z", "strict_time"); + assertSameDateAs("10:15:30.123456789Z", "strict_time"); + assertSameDateAs("10:15:30.123+0100", "strict_time"); + assertSameDateAs("10:15:30.123+01:00", "strict_time"); + assertParseException("1:15:30.123Z", "strict_time"); + assertParseException("10:1:30.123Z", "strict_time"); + assertParseException("10:15:3.123Z", "strict_time"); + assertParseException("10:15:3.1", "strict_time"); + assertParseException("10:15:3Z", "strict_time"); + + assertSameDateAs("10:15:30Z", "strict_time_no_millis"); + assertSameDateAs("10:15:30+0100", "strict_time_no_millis"); + assertSameDateAs("10:15:30+01:00", "strict_time_no_millis"); + assertSameDateAs("01:15:30Z", "strict_time_no_millis"); + assertSameDateAs("01:15:30+0100", "strict_time_no_millis"); + assertSameDateAs("01:15:30+01:00", "strict_time_no_millis"); + assertParseException("1:15:30Z", "strict_time_no_millis"); + assertParseException("10:5:30Z", "strict_time_no_millis"); + assertParseException("10:15:3Z", "strict_time_no_millis"); + assertParseException("10:15:3", "strict_time_no_millis"); + + assertSameDateAs("T10:15:30.1Z", "strict_t_time"); + assertSameDateAs("T10:15:30.123Z", "strict_t_time"); + assertSameDateAs("T10:15:30.123456789Z", "strict_t_time"); + assertSameDateAs("T10:15:30.1+0100", "strict_t_time"); + assertSameDateAs("T10:15:30.123+0100", "strict_t_time"); + assertSameDateAs("T10:15:30.1+01:00", "strict_t_time"); + assertSameDateAs("T10:15:30.123+01:00", "strict_t_time"); + assertParseException("T1:15:30.123Z", "strict_t_time"); + assertParseException("T10:1:30.123Z", "strict_t_time"); + assertParseException("T10:15:3.123Z", "strict_t_time"); + assertParseException("T10:15:3.1", "strict_t_time"); + assertParseException("T10:15:3Z", "strict_t_time"); + + assertSameDateAs("T10:15:30Z", "strict_t_time_no_millis"); + assertSameDateAs("T10:15:30+0100", "strict_t_time_no_millis"); + assertSameDateAs("T10:15:30+01:00", "strict_t_time_no_millis"); + assertParseException("T1:15:30Z", "strict_t_time_no_millis"); + assertParseException("T10:1:30Z", "strict_t_time_no_millis"); + assertParseException("T10:15:3Z", "strict_t_time_no_millis"); + assertParseException("T10:15:3", "strict_t_time_no_millis"); + + assertSameDateAs("2012-W48-6", "strict_week_date"); + assertSameDateAs("2012-W01-6", "strict_week_date"); + assertParseException("2012-W1-6", "strict_week_date"); + assertParseException("2012-W1-8", "strict_week_date"); + + assertSameDateAs("2012-W48-6", "strict_week_date"); + assertSameDateAs("2012-W01-6", "strict_week_date"); + assertParseException("2012-W1-6", "strict_week_date"); + assertParseException("2012-W01-8", "strict_week_date"); + + assertSameDateAs("2012-W48-6T10:15:30.1Z", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123Z", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123456789Z", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.1+0100", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123+0100", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.1+01:00", "strict_week_date_time"); + assertSameDateAs("2012-W48-6T10:15:30.123+01:00", "strict_week_date_time"); + assertParseException("2012-W1-6T10:15:30.123Z", "strict_week_date_time"); + + assertSameDateAs("2012-W48-6T10:15:30Z", "strict_week_date_time_no_millis"); + assertSameDateAs("2012-W48-6T10:15:30+0100", "strict_week_date_time_no_millis"); + assertSameDateAs("2012-W48-6T10:15:30+01:00", "strict_week_date_time_no_millis"); + assertParseException("2012-W1-6T10:15:30Z", "strict_week_date_time_no_millis"); + + assertSameDateAs("2012", "strict_year"); + assertParseException("1", "strict_year"); + assertSameDateAs("-2000", "strict_year"); + + assertSameDateAs("2012-12", "strict_year_month"); + assertParseException("1-1", "strict_year_month"); + + assertSameDateAs("2012-12-31", "strict_year_month_day"); + assertParseException("1-12-31", "strict_year_month_day"); + assertParseException("2012-1-31", "strict_year_month_day"); + assertParseException("2012-12-1", "strict_year_month_day"); + + assertSameDateAs("2018", "strict_weekyear"); + assertParseException("1", "strict_weekyear"); + + assertSameDateAs("2018", "strict_weekyear"); + assertSameDateAs("2017", "strict_weekyear"); + assertParseException("1", "strict_weekyear"); + + assertSameDateAs("2018-W29", "strict_weekyear_week"); + assertSameDateAs("2018-W01", "strict_weekyear_week"); + assertParseException("2018-W1", "strict_weekyear_week"); + + assertSameDateAs("2012-W31-5", "strict_weekyear_week_day"); + assertParseException("2012-W1-1", "strict_weekyear_week_day"); + } + + public void testSeveralTimeFormats() { + { + String format = "year_month_day||ordinal_date"; + DateFormatter javaFormatter = DateFormatter.forPattern(format); + assertSameDate("2018-12-12", javaFormatter); + assertSameDate("2018-128", javaFormatter); } + { + String format = "strict_date_optional_time||dd-MM-yyyy"; + DateFormatter javaFormatter = DateFormatter.forPattern(format); + assertSameDate("31-01-2014", javaFormatter); + } + } + + public void testParsingLocalDateFromYearOfEra() { + // with strict resolving, YearOfEra expect an era, otherwise it won't resolve to a date + assertSameDate("2018363", DateFormatter.forPattern("uuuuDDD")); + } + + public void testDateFormatterWithLocale() { + Locale locale = randomLocale(random()); + String pattern = randomBoolean() ? "strict_date_optional_time||date_time" : "date_time||strict_date_optional_time"; + DateFormatter formatter = DateFormatter.forPattern(pattern).withLocale(locale); + assertThat(formatter.pattern(), is(pattern)); + assertThat(formatter.locale(), is(locale)); + } + + public void testParsingMissingTimezone() { + long millisJava = DateFormatter.forPattern("8yyyy-MM-dd HH:mm:ss").parseMillis("2018-02-18 17:47:17"); + long millisJoda = DateFormatter.forPattern("yyyy-MM-dd HH:mm:ss").parseMillis("2018-02-18 17:47:17"); + assertThat(millisJava, is(millisJoda)); + } + + // the iso 8601 parser is available via Joda.forPattern(), so we have to test this slightly differently + public void testIso8601Parsers() { + String format = "iso8601"; + DateFormatter javaFormatter = DateFormatter.forPattern(format); + + assertSameDate("2018-10-10", javaFormatter); + assertSameDate("2018-10-10T", javaFormatter); + assertSameDate("2018-10-10T10", javaFormatter); + assertSameDate("2018-10-10T10+0430", javaFormatter); + assertSameDate("2018-10-10T10:11", javaFormatter); + assertSameDate("2018-10-10T10:11-08:00", javaFormatter); + assertSameDate("2018-10-10T10:11Z", javaFormatter); + assertSameDate("2018-10-10T10:11:12", javaFormatter); + assertSameDate("2018-10-10T10:11:12+0100", javaFormatter); + assertSameDate("2018-10-10T10:11:12.123", javaFormatter); + assertSameDate("2018-10-10T10:11:12.123Z", javaFormatter); + assertSameDate("2018-10-10T10:11:12.123+0000", javaFormatter); + assertSameDate("2018-10-10T10:11:12,123", javaFormatter); + assertSameDate("2018-10-10T10:11:12,123Z", javaFormatter); + assertSameDate("2018-10-10T10:11:12,123+05:30", javaFormatter); + } + + public void testExceptionWhenCompositeParsingFails() { + assertParseException("2014-06-06T12:01:02.123", "yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SS"); + } + + private void assertSameDateAs(String input, String javaPattern) { + DateFormatter javaFormatter = DateFormatter.forPattern(javaPattern); + assertSameDate(input, javaFormatter); } + + private void assertSameDate(String input, DateFormatter javaFormatter) { + TemporalAccessor javaTimeAccessor = javaFormatter.parse(input); + ZonedDateTime zonedDateTime = DateFormatters.from(javaTimeAccessor); + assertNotNull(zonedDateTime); + } + + private void assertDateMathEquals(String text, String expected, String pattern) { + Locale locale = randomLocale(random()); + assertDateMathEquals(text, expected, pattern, locale); + } + + private void assertDateMathEquals(String text, String expected, String pattern, Locale locale) { + long gotMillisJava = dateMathToMillis(text, DateFormatter.forPattern(pattern), locale); + long expectedMillis = DateFormatters.from(DateFormatter.forPattern("strict_date_optional_time").withLocale(locale).parse(expected)) + .toInstant() + .toEpochMilli(); + + assertThat(gotMillisJava, equalTo(expectedMillis)); + } + + private long dateMathToMillis(final String text, final DateFormatter dateFormatter) { + DateFormatter javaFormatter = dateFormatter.withLocale(randomLocale(random())); + DateMathParser javaDateMath = javaFormatter.toDateMathParser(); + return javaDateMath.parse(text, () -> 0, true, null).toEpochMilli(); + } + + private long dateMathToMillis(final String text, final DateFormatter dateFormatter, final Locale locale) { + DateFormatter javaFormatter = dateFormatter.withLocale(locale); + DateMathParser javaDateMath = javaFormatter.toDateMathParser(); + return javaDateMath.parse(text, () -> 0, true, null).toEpochMilli(); + } + + private void assertParseException(String input, String format) { + DateFormatter javaTimeFormatter = DateFormatter.forPattern(format); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> javaTimeFormatter.parse(input)); + assertThat(e.getMessage(), containsString(input)); + assertThat(e.getMessage(), containsString(format)); + } + } diff --git a/server/src/test/java/org/opensearch/common/time/DateUtilsTests.java b/server/src/test/java/org/opensearch/common/time/DateUtilsTests.java index d9662d1de9e0c..283bea1d1c2e9 100644 --- a/server/src/test/java/org/opensearch/common/time/DateUtilsTests.java +++ b/server/src/test/java/org/opensearch/common/time/DateUtilsTests.java @@ -33,20 +33,15 @@ package org.opensearch.common.time; import org.opensearch.test.OpenSearchTestCase; -import org.joda.time.DateTimeZone; import java.time.Instant; import java.time.LocalDate; import java.time.Month; import java.time.Year; import java.time.YearMonth; -import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoField; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import static org.opensearch.common.time.DateUtils.clampToNanosRange; import static org.opensearch.common.time.DateUtils.toInstant; @@ -58,28 +53,6 @@ import static org.hamcrest.Matchers.is; public class DateUtilsTests extends OpenSearchTestCase { - private static final Set IGNORE = new HashSet<>(Arrays.asList("America/Ciudad_Juarez")); - - public void testTimezoneIds() { - assertNull(DateUtils.dateTimeZoneToZoneId(null)); - assertNull(DateUtils.zoneIdToDateTimeZone(null)); - for (String jodaId : DateTimeZone.getAvailableIDs()) { - if (IGNORE.contains(jodaId)) continue; - DateTimeZone jodaTz = DateTimeZone.forID(jodaId); - ZoneId zoneId = DateUtils.dateTimeZoneToZoneId(jodaTz); // does not throw - long now = 0; - assertThat( - jodaId, - zoneId.getRules().getOffset(Instant.ofEpochMilli(now)).getTotalSeconds() * 1000, - equalTo(jodaTz.getOffset(now)) - ); - if (DateUtils.DEPRECATED_SHORT_TIMEZONES.containsKey(jodaTz.getID())) { - assertWarnings("Use of short timezone id " + jodaId + " is deprecated. Use " + zoneId.getId() + " instead"); - } - // roundtrip does not throw either - assertNotNull(DateUtils.zoneIdToDateTimeZone(zoneId)); - } - } public void testInstantToLong() { assertThat(toLong(Instant.EPOCH), is(0L)); diff --git a/server/src/test/java/org/opensearch/index/mapper/DateFieldMapperTests.java b/server/src/test/java/org/opensearch/index/mapper/DateFieldMapperTests.java index 054d3956596af..74a4cbfa99787 100644 --- a/server/src/test/java/org/opensearch/index/mapper/DateFieldMapperTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/DateFieldMapperTests.java @@ -34,10 +34,12 @@ import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.IndexableField; +import org.opensearch.Version; import org.opensearch.common.time.DateFormatter; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.termvectors.TermVectorsService; import org.opensearch.search.DocValueFormat; +import org.opensearch.test.VersionUtils; import java.io.IOException; import java.time.ZoneId; @@ -216,6 +218,29 @@ public void testNullValue() throws IOException { assertFalse(dvField.fieldType().stored()); } + public void testLegacyDateFormatName() throws IOException { + // check that indexes created prior to 2.12.0 support camel case + final DocumentMapper legacyMapper = createDocumentMapper( + VersionUtils.randomVersionBetween(random(), Version.V_2_0_0, VersionUtils.getPreviousVersion(Version.V_2_12_0)), // BWC + // compatible + fieldMapping(b -> { + b.field("type", "date"); + b.field("format", "strictDateOptionalTime||strictDateOptionalTimeNanos"); + }) + ); + + // check that indexes created on or after 2.12 + MapperParsingException e = expectThrows( + MapperParsingException.class, + () -> createDocumentMapper(VersionUtils.randomVersionBetween(random(), Version.V_2_12_0, Version.CURRENT), fieldMapping(b -> { + b.field("type", "date"); + b.field("format", "strictDateOptionalTime||strictDateOptionalTimeNanos"); + })) + ); + assertThat(e.getMessage(), containsString("Invalid format: [strictDateOptionalTime]: Unknown pattern letter: t")); + assertThat(e.getMessage(), containsString("Error parsing [format] on field [field]: Invalid")); + } + public void testNanosNullValue() throws IOException { DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); diff --git a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java index ab53ae81ab0ce..098a196f697ed 100644 --- a/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/DateFieldTypeTests.java @@ -65,9 +65,9 @@ import org.opensearch.index.query.DateRangeIncludingNowQuery; import org.opensearch.index.query.QueryRewriteContext; import org.opensearch.index.query.QueryShardContext; -import org.joda.time.DateTimeZone; import java.io.IOException; +import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Collections; @@ -110,8 +110,8 @@ public void isFieldWithinRangeTestCase(DateFieldType ft) throws IOException { DateMathParser alternateFormat = DateFieldMapper.getDefaultDateTimeFormatter().toDateMathParser(); doTestIsFieldWithinQuery(ft, reader, null, null); doTestIsFieldWithinQuery(ft, reader, null, alternateFormat); - doTestIsFieldWithinQuery(ft, reader, DateTimeZone.UTC, null); - doTestIsFieldWithinQuery(ft, reader, DateTimeZone.UTC, alternateFormat); + doTestIsFieldWithinQuery(ft, reader, ZoneOffset.UTC, null); + doTestIsFieldWithinQuery(ft, reader, ZoneOffset.UTC, alternateFormat); QueryRewriteContext context = new QueryRewriteContext(xContentRegistry(), writableRegistry(), null, () -> nowInMillis); @@ -123,37 +123,41 @@ public void isFieldWithinRangeTestCase(DateFieldType ft) throws IOException { IOUtils.close(reader, w, dir); } - private void doTestIsFieldWithinQuery(DateFieldType ft, DirectoryReader reader, DateTimeZone zone, DateMathParser alternateFormat) - throws IOException { + private void doTestIsFieldWithinQuery( + final DateFieldType ft, + final DirectoryReader reader, + final ZoneId zone, + final DateMathParser alternateFormat + ) throws IOException { QueryRewriteContext context = new QueryRewriteContext(xContentRegistry(), writableRegistry(), null, () -> nowInMillis); assertEquals( Relation.INTERSECTS, - ft.isFieldWithinQuery(reader, "2015-10-09", "2016-01-02", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2015-10-09", "2016-01-02", randomBoolean(), randomBoolean(), zone, null, context) ); assertEquals( Relation.INTERSECTS, - ft.isFieldWithinQuery(reader, "2016-01-02", "2016-06-20", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2016-01-02", "2016-06-20", randomBoolean(), randomBoolean(), zone, null, context) ); assertEquals( Relation.INTERSECTS, - ft.isFieldWithinQuery(reader, "2016-01-02", "2016-02-12", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2016-01-02", "2016-02-12", randomBoolean(), randomBoolean(), zone, null, context) ); assertEquals( Relation.DISJOINT, - ft.isFieldWithinQuery(reader, "2014-01-02", "2015-02-12", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2014-01-02", "2015-02-12", randomBoolean(), randomBoolean(), zone, null, context) ); assertEquals( Relation.DISJOINT, - ft.isFieldWithinQuery(reader, "2016-05-11", "2016-08-30", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2016-05-11", "2016-08-30", randomBoolean(), randomBoolean(), zone, null, context) ); assertEquals( Relation.WITHIN, - ft.isFieldWithinQuery(reader, "2015-09-25", "2016-05-29", randomBoolean(), randomBoolean(), null, null, context) + ft.isFieldWithinQuery(reader, "2015-09-25", "2016-05-29", randomBoolean(), randomBoolean(), zone, null, context) ); - assertEquals(Relation.WITHIN, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", true, true, null, null, context)); - assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", false, false, null, null, context)); - assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", false, true, null, null, context)); - assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", true, false, null, null, context)); + assertEquals(Relation.WITHIN, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", true, true, zone, null, context)); + assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", false, false, zone, null, context)); + assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", false, true, zone, null, context)); + assertEquals(Relation.INTERSECTS, ft.isFieldWithinQuery(reader, "2015-10-12", "2016-04-03", true, false, zone, null, context)); } public void testValueFormat() { diff --git a/server/src/test/java/org/opensearch/index/mapper/RangeFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/RangeFieldTypeTests.java index 755d77c6ae392..81d38bb0e5028 100644 --- a/server/src/test/java/org/opensearch/index/mapper/RangeFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/RangeFieldTypeTests.java @@ -57,11 +57,13 @@ import org.opensearch.index.query.QueryShardContext; import org.opensearch.index.query.QueryShardException; import org.opensearch.test.IndexSettingsModule; -import org.joda.time.DateTime; import org.junit.Before; import java.io.IOException; import java.net.InetAddress; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Collections; import java.util.Map; @@ -125,8 +127,8 @@ public void testRangeQueryIntersectsAdjacentValues() throws Exception { } case DATE: { long fromValue = randomInt(); - from = new DateTime(fromValue); - to = new DateTime(fromValue + 1); + from = ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromValue), ZoneOffset.UTC); + to = ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromValue + 1), ZoneOffset.UTC); break; } case INTEGER: { @@ -182,8 +184,8 @@ public void testFromLargerToErrors() throws Exception { } case DATE: { long fromValue = randomInt(); - from = new DateTime(fromValue); - to = new DateTime(fromValue - 1); + from = ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromValue), ZoneOffset.UTC); + to = ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromValue - 1), ZoneOffset.UTC); break; } case INTEGER: { @@ -339,7 +341,7 @@ public void testDateVsDateRangeBounds() { private Query getExpectedRangeQuery(ShapeRelation relation, Object from, Object to, boolean includeLower, boolean includeUpper) { switch (type) { case DATE: - return getDateRangeQuery(relation, (DateTime) from, (DateTime) to, includeLower, includeUpper); + return getDateRangeQuery(relation, (ZonedDateTime) from, (ZonedDateTime) to, includeLower, includeUpper); case INTEGER: return getIntRangeQuery(relation, (int) from, (int) to, includeLower, includeUpper); case LONG: @@ -353,9 +355,15 @@ private Query getExpectedRangeQuery(ShapeRelation relation, Object from, Object } } - private Query getDateRangeQuery(ShapeRelation relation, DateTime from, DateTime to, boolean includeLower, boolean includeUpper) { - long[] lower = new long[] { from.getMillis() + (includeLower ? 0 : 1) }; - long[] upper = new long[] { to.getMillis() - (includeUpper ? 0 : 1) }; + private Query getDateRangeQuery( + final ShapeRelation relation, + final ZonedDateTime from, + final ZonedDateTime to, + final boolean includeLower, + final boolean includeUpper + ) { + long[] lower = new long[] { from.toInstant().toEpochMilli() + (includeLower ? 0 : 1) }; + long[] upper = new long[] { to.toInstant().toEpochMilli() - (includeUpper ? 0 : 1) }; Query indexQuery; BinaryDocValuesRangeQuery.QueryType queryType; if (relation == ShapeRelation.WITHIN) { @@ -368,7 +376,14 @@ private Query getDateRangeQuery(ShapeRelation relation, DateTime from, DateTime indexQuery = LongRange.newIntersectsQuery("field", lower, upper); queryType = BinaryDocValuesRangeQuery.QueryType.INTERSECTS; } - Query dvQuery = RangeType.DATE.dvRangeQuery("field", queryType, from.getMillis(), to.getMillis(), includeLower, includeUpper); + Query dvQuery = RangeType.DATE.dvRangeQuery( + "field", + queryType, + from.toInstant().toEpochMilli(), + to.toInstant().toEpochMilli(), + includeLower, + includeUpper + ); return new IndexOrDocValuesQuery(indexQuery, dvQuery); } @@ -478,7 +493,7 @@ private Object nextFrom() throws Exception { case INTEGER: return (int) (random().nextInt() * 0.5 - DISTANCE); case DATE: - return DateTime.now(); + return ZonedDateTime.now(ZoneOffset.UTC); case LONG: return (long) (random().nextLong() * 0.5 - DISTANCE); case FLOAT: @@ -495,7 +510,7 @@ private Object nextTo(Object from) throws Exception { case INTEGER: return (Integer) from + DISTANCE; case DATE: - return DateTime.now().plusDays(DISTANCE); + return ZonedDateTime.now(ZoneOffset.UTC).plusDays(DISTANCE); case LONG: return (Long) from + DISTANCE; case DOUBLE: diff --git a/server/src/test/java/org/opensearch/index/query/DistanceFeatureQueryBuilderTests.java b/server/src/test/java/org/opensearch/index/query/DistanceFeatureQueryBuilderTests.java index 8489a65d26c91..af010747b4567 100644 --- a/server/src/test/java/org/opensearch/index/query/DistanceFeatureQueryBuilderTests.java +++ b/server/src/test/java/org/opensearch/index/query/DistanceFeatureQueryBuilderTests.java @@ -45,7 +45,6 @@ import org.opensearch.index.mapper.MapperService; import org.opensearch.index.query.DistanceFeatureQueryBuilder.Origin; import org.opensearch.test.AbstractQueryTestCase; -import org.joda.time.DateTime; import java.io.IOException; import java.time.Instant; @@ -66,7 +65,7 @@ protected DistanceFeatureQueryBuilder doCreateTestQueryBuilder() { break; case DATE_FIELD_NAME: long randomDateMills = randomLongBetween(0, 2_000_000_000_000L); - origin = randomBoolean() ? new Origin(randomDateMills) : new Origin(new DateTime(randomDateMills).toString()); + origin = randomBoolean() ? new Origin(randomDateMills) : new Origin(Instant.ofEpochMilli(randomDateMills).toString()); pivot = randomTimeValue(1, 1000, "d", "h", "ms", "s", "m"); break; default: // DATE_NANOS_FIELD_NAME diff --git a/server/src/test/java/org/opensearch/index/query/RangeQueryBuilderTests.java b/server/src/test/java/org/opensearch/index/query/RangeQueryBuilderTests.java index e72be29b85b63..4d0dc884b3d5a 100644 --- a/server/src/test/java/org/opensearch/index/query/RangeQueryBuilderTests.java +++ b/server/src/test/java/org/opensearch/index/query/RangeQueryBuilderTests.java @@ -54,8 +54,6 @@ import org.opensearch.index.mapper.MappedFieldType.Relation; import org.opensearch.index.mapper.MapperService; import org.opensearch.test.AbstractQueryTestCase; -import org.joda.time.DateTime; -import org.joda.time.chrono.ISOChronology; import java.io.IOException; import java.time.Instant; @@ -306,8 +304,8 @@ public void testDateRangeQueryFormat() throws IOException { assertEquals( LongPoint.newRangeQuery( DATE_FIELD_NAME, - DateTime.parse("2012-01-01T00:00:00.000+00").getMillis(), - DateTime.parse("2030-01-01T00:00:00.000+00").getMillis() - 1 + ZonedDateTime.parse("2012-01-01T00:00:00.000+00").toInstant().toEpochMilli(), + ZonedDateTime.parse("2030-01-01T00:00:00.000+00").toInstant().toEpochMilli() - 1 ), parsedQuery ); @@ -345,8 +343,8 @@ public void testDateRangeBoundaries() throws IOException { assertEquals( LongPoint.newRangeQuery( DATE_FIELD_NAME, - DateTime.parse("2014-11-01T00:00:00.000+00").getMillis(), - DateTime.parse("2014-12-08T23:59:59.999+00").getMillis() + ZonedDateTime.parse("2014-11-01T00:00:00.000+00").toInstant().toEpochMilli(), + ZonedDateTime.parse("2014-12-08T23:59:59.999+00").toInstant().toEpochMilli() ), parsedQuery ); @@ -368,8 +366,8 @@ public void testDateRangeBoundaries() throws IOException { assertEquals( LongPoint.newRangeQuery( DATE_FIELD_NAME, - DateTime.parse("2014-11-30T23:59:59.999+00").getMillis() + 1, - DateTime.parse("2014-12-08T00:00:00.000+00").getMillis() - 1 + ZonedDateTime.parse("2014-11-30T23:59:59.999+00").toInstant().toEpochMilli() + 1, + ZonedDateTime.parse("2014-12-08T00:00:00.000+00").toInstant().toEpochMilli() - 1 ), parsedQuery ); @@ -454,8 +452,8 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC return Relation.WITHIN; } }; - DateTime queryFromValue = new DateTime(2015, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); - DateTime queryToValue = new DateTime(2016, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); + ZonedDateTime queryFromValue = ZonedDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime queryToValue = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); query.from(queryFromValue); query.to(queryToValue); QueryShardContext queryShardContext = createShardContext(); @@ -489,8 +487,8 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC return Relation.WITHIN; } }; - DateTime queryFromValue = new DateTime(2015, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); - DateTime queryToValue = new DateTime(2016, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); + ZonedDateTime queryFromValue = ZonedDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime queryToValue = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); query.from(queryFromValue); query.to(queryToValue); query.timeZone(randomZone().getId()); @@ -514,8 +512,8 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC return Relation.DISJOINT; } }; - DateTime queryFromValue = new DateTime(2015, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); - DateTime queryToValue = new DateTime(2016, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); + ZonedDateTime queryFromValue = ZonedDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime queryToValue = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); query.from(queryFromValue); query.to(queryToValue); QueryShardContext queryShardContext = createShardContext(); @@ -531,8 +529,8 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC return Relation.INTERSECTS; } }; - DateTime queryFromValue = new DateTime(2015, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); - DateTime queryToValue = new DateTime(2016, 1, 1, 0, 0, 0, ISOChronology.getInstanceUTC()); + ZonedDateTime queryFromValue = ZonedDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime queryToValue = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); query.from(queryFromValue); query.to(queryToValue); QueryShardContext queryShardContext = createShardContext(); diff --git a/server/src/test/java/org/opensearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java b/server/src/test/java/org/opensearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java index 4e64a1ec03688..b66bd7afd48e5 100644 --- a/server/src/test/java/org/opensearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java +++ b/server/src/test/java/org/opensearch/index/query/functionscore/FunctionScoreQueryBuilderTests.java @@ -69,12 +69,13 @@ import org.opensearch.search.MultiValueMode; import org.opensearch.test.AbstractQueryTestCase; import org.opensearch.test.TestGeoShapeFieldMapperPlugin; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -253,7 +254,10 @@ private static DecayFunctionBuilder createRandomDecayFunction() { offset = randomFrom(DistanceUnit.values()).toString(randomDouble()); break; case DATE_FIELD_NAME: - origin = new DateTime(System.currentTimeMillis() - randomIntBetween(0, 1000000), DateTimeZone.UTC).toString(); + origin = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(System.currentTimeMillis() - randomIntBetween(0, 1000000)), + ZoneOffset.UTC + ).toString(); scale = randomTimeValue(1, 1000, "d", "h", "ms", "s", "m"); offset = randomPositiveTimeValue(); break; diff --git a/server/src/test/java/org/opensearch/search/aggregations/bucket/DateScriptMocksPlugin.java b/server/src/test/java/org/opensearch/search/aggregations/bucket/DateScriptMocksPlugin.java index d6ba4eedd3a19..d6b5d47a3bb24 100644 --- a/server/src/test/java/org/opensearch/search/aggregations/bucket/DateScriptMocksPlugin.java +++ b/server/src/test/java/org/opensearch/search/aggregations/bucket/DateScriptMocksPlugin.java @@ -34,9 +34,10 @@ import org.opensearch.script.MockScriptPlugin; import org.opensearch.search.lookup.LeafDocLookup; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -63,16 +64,25 @@ public Map, Object>> pluginScripts() { }); scripts.put( DOUBLE_PLUS_ONE_MONTH, - params -> new DateTime(Double.valueOf((double) params.get("_value")).longValue(), DateTimeZone.UTC).plusMonths(1).getMillis() + params -> ZonedDateTime.ofInstant( + Instant.ofEpochMilli(Double.valueOf((double) params.get("_value")).longValue()), + ZoneOffset.UTC + ).plusMonths(1).toInstant().toEpochMilli() + ); + scripts.put( + LONG_PLUS_ONE_MONTH, + params -> ZonedDateTime.ofInstant(Instant.ofEpochMilli((long) params.get("_value")), ZoneOffset.UTC) + .plusMonths(1) + .toInstant() + .toEpochMilli() ); - scripts.put(LONG_PLUS_ONE_MONTH, params -> new DateTime((long) params.get("_value"), DateTimeZone.UTC).plusMonths(1).getMillis()); return scripts; } @Override protected Map, Object>> nonDeterministicPluginScripts() { Map, Object>> scripts = new HashMap<>(); - scripts.put(CURRENT_DATE, params -> new DateTime().getMillis()); + scripts.put(CURRENT_DATE, params -> ZonedDateTime.now().toInstant().toEpochMilli()); return scripts; } } diff --git a/server/src/test/java/org/opensearch/search/aggregations/bucket/range/InternalDateRangeTests.java b/server/src/test/java/org/opensearch/search/aggregations/bucket/range/InternalDateRangeTests.java index 9a126abea3135..701473175be7f 100644 --- a/server/src/test/java/org/opensearch/search/aggregations/bucket/range/InternalDateRangeTests.java +++ b/server/src/test/java/org/opensearch/search/aggregations/bucket/range/InternalDateRangeTests.java @@ -37,9 +37,9 @@ import org.opensearch.search.aggregations.InternalAggregations; import org.opensearch.search.aggregations.InternalMultiBucketAggregation; import org.opensearch.search.aggregations.ParsedMultiBucketAggregation; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -57,7 +57,7 @@ public void setUp() throws Exception { super.setUp(); format = randomNumericDocValueFormat(); - Function interval = randomFrom( + Function interval = randomFrom( dateTime -> dateTime.plusSeconds(1), dateTime -> dateTime.plusMinutes(1), dateTime -> dateTime.plusHours(1), @@ -69,13 +69,13 @@ public void setUp() throws Exception { final int numRanges = randomNumberOfBuckets(); final List> listOfRanges = new ArrayList<>(numRanges); - DateTime date = new DateTime(DateTimeZone.UTC); - double start = date.getMillis(); + ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC); + double start = date.toInstant().toEpochMilli(); double end = 0; for (int i = 0; i < numRanges; i++) { - double from = date.getMillis(); + double from = date.toInstant().toEpochMilli(); date = interval.apply(date); - double to = date.getMillis(); + double to = date.toInstant().toEpochMilli(); if (to > end) { end = to; } diff --git a/test/framework/src/main/java/org/opensearch/test/AbstractQueryTestCase.java b/test/framework/src/main/java/org/opensearch/test/AbstractQueryTestCase.java index afd93e1b72fbb..437a9ac2db60d 100644 --- a/test/framework/src/main/java/org/opensearch/test/AbstractQueryTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/AbstractQueryTestCase.java @@ -68,11 +68,11 @@ import org.opensearch.index.query.QueryShardContext; import org.opensearch.index.query.Rewriteable; import org.opensearch.index.query.support.QueryParsers; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import java.io.IOException; import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; @@ -691,7 +691,7 @@ protected static Object getRandomValueForFieldName(String fieldName) { value = randomBoolean(); break; case DATE_FIELD_NAME: - value = new DateTime(System.currentTimeMillis(), DateTimeZone.UTC).toString(); + value = ZonedDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneOffset.UTC).toString(); break; case DATE_NANOS_FIELD_NAME: value = Instant.now().toString(); diff --git a/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java b/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java index 8490ee4fc39bc..cd3bf8e52d2af 100644 --- a/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java @@ -74,7 +74,6 @@ import org.opensearch.common.io.PathUtils; import org.opensearch.common.io.PathUtilsForTesting; import org.opensearch.common.io.stream.BytesStreamOutput; -import org.opensearch.common.joda.JodaDeprecationPatterns; import org.opensearch.common.logging.DeprecatedMessage; import org.opensearch.common.logging.HeaderWarning; import org.opensearch.common.logging.HeaderWarningAppender; @@ -133,7 +132,6 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; import org.opensearch.transport.nio.MockNioTransportPlugin; -import org.joda.time.DateTimeZone; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -209,7 +207,6 @@ @LuceneTestCase.SuppressReproduceLine public abstract class OpenSearchTestCase extends LuceneTestCase { - protected static final List JODA_TIMEZONE_IDS; protected static final List JAVA_TIMEZONE_IDS; protected static final List JAVA_ZONE_IDS; @@ -269,20 +266,52 @@ public void append(LogEvent event) { TransportService.ensureClassloaded(); // ensure server streamables are registered // filter out joda timezones that are deprecated for the java time migration - List jodaTZIds = DateTimeZone.getAvailableIDs() - .stream() - .filter(s -> DateUtils.DEPRECATED_SHORT_TZ_IDS.contains(s) == false) + Set removedJodaTZIds = Set.of( + "ACT", + "AET", + "AGT", + "ART", + "AST", + "BET", + "BST", + "CAT", + "CNT", + "CST", + "CTT", + "EAT", + "ECT", + "EST", + "HST", + "IET", + "IST", + "JST", + "MIT", + "MST", + "NET", + "NST", + "PLT", + "PNT", + "PRT", + "PST", + "SST", + "VST" + ); + final Predicate removedJodaTZIdsFilter = removedJodaTZIds::contains; + final Predicate removedZoneIdsFilter = tz -> tz.startsWith("System/") || DateUtils.DEPRECATED_SHORT_TZ_IDS.contains(tz); + + // filter time zones that aren't supported by joda since there is no java date equivalent. + // this is only needed until 3.0 (when wire compatibility no longer communicates w/ joda compatible nodes) + JAVA_TIMEZONE_IDS = Arrays.stream(TimeZone.getAvailableIDs()) + .filter(removedJodaTZIdsFilter.negate()) + .filter(removedZoneIdsFilter.negate()) .sorted() .collect(Collectors.toList()); - JODA_TIMEZONE_IDS = Collections.unmodifiableList(jodaTZIds); - List javaTZIds = Arrays.asList(TimeZone.getAvailableIDs()); - Collections.sort(javaTZIds); - JAVA_TIMEZONE_IDS = Collections.unmodifiableList(javaTZIds); - - List javaZoneIds = new ArrayList<>(ZoneId.getAvailableZoneIds()); - Collections.sort(javaZoneIds); - JAVA_ZONE_IDS = Collections.unmodifiableList(javaZoneIds); + JAVA_ZONE_IDS = ZoneId.getAvailableZoneIds() + .stream() + .filter(removedZoneIdsFilter.negate()) + .sorted() + .collect(Collectors.toUnmodifiableList()); } @SuppressForbidden(reason = "force log4j and netty sysprops") @@ -419,10 +448,6 @@ protected boolean enableWarningsCheck() { return true; } - protected boolean enableJodaDeprecationWarningsCheck() { - return false; - } - @After public final void after() throws Exception { checkStaticState(false); @@ -458,9 +483,6 @@ private void ensureNoWarnings() { final List warnings = threadContext.getResponseHeaders().get("Warning"); if (warnings != null) { List filteredWarnings = new ArrayList<>(warnings); - if (enableJodaDeprecationWarningsCheck() == false) { - filteredWarnings = filterJodaDeprecationWarnings(filteredWarnings); - } if (JvmInfo.jvmInfo().getBundledJdk() == false) { // unit tests do not run with the bundled JDK, if there are warnings we need to filter the no-jdk deprecation warning filteredWarnings = filteredWarnings.stream() @@ -557,45 +579,30 @@ protected final void assertWarnings(boolean stripXContentPosition, String... exp } try { final List actualWarnings = threadContext.getResponseHeaders().get("Warning"); - if (actualWarnings != null && enableJodaDeprecationWarningsCheck() == false) { - List filteredWarnings = filterJodaDeprecationWarnings(actualWarnings); - assertWarnings(stripXContentPosition, filteredWarnings, expectedWarnings); - } else { - assertWarnings(stripXContentPosition, actualWarnings, expectedWarnings); + assertNotNull("no warnings, expected: " + Arrays.asList(expectedWarnings), actualWarnings); + final Set actualWarningValues = actualWarnings.stream() + .map(s -> HeaderWarning.extractWarningValueFromWarningHeader(s, stripXContentPosition)) + .collect(Collectors.toSet()); + for (String msg : expectedWarnings) { + assertThat(actualWarningValues, hasItem(HeaderWarning.escapeAndEncode(msg))); } + assertEquals( + "Expected " + + expectedWarnings.length + + " warnings but found " + + actualWarnings.size() + + "\nExpected: " + + Arrays.asList(expectedWarnings) + + "\nActual: " + + actualWarnings, + expectedWarnings.length, + actualWarnings.size() + ); } finally { resetDeprecationLogger(); } } - private List filterJodaDeprecationWarnings(List actualWarnings) { - return actualWarnings.stream() - .filter(m -> m.contains(JodaDeprecationPatterns.USE_NEW_FORMAT_SPECIFIERS) == false) - .collect(Collectors.toList()); - } - - private void assertWarnings(boolean stripXContentPosition, List actualWarnings, String[] expectedWarnings) { - assertNotNull("no warnings, expected: " + Arrays.asList(expectedWarnings), actualWarnings); - final Set actualWarningValues = actualWarnings.stream() - .map(s -> HeaderWarning.extractWarningValueFromWarningHeader(s, stripXContentPosition)) - .collect(Collectors.toSet()); - for (String msg : expectedWarnings) { - assertThat(actualWarningValues, hasItem(HeaderWarning.escapeAndEncode(msg))); - } - assertEquals( - "Expected " - + expectedWarnings.length - + " warnings but found " - + actualWarnings.size() - + "\nExpected: " - + Arrays.asList(expectedWarnings) - + "\nActual: " - + actualWarnings, - expectedWarnings.length, - actualWarnings.size() - ); - } - /** * Reset the deprecation logger by clearing the current thread context. */ @@ -985,35 +992,18 @@ public static String randomPositiveTimeValue() { return randomTimeValue(1, 1000); } - /** - * generate a random DateTimeZone from the ones available in joda library - */ - public static DateTimeZone randomDateTimeZone() { - return DateTimeZone.forID(randomFrom(JODA_TIMEZONE_IDS)); - } - /** * generate a random TimeZone from the ones available in java.util */ public static TimeZone randomTimeZone() { - return TimeZone.getTimeZone(randomJodaAndJavaSupportedTimezone(JAVA_TIMEZONE_IDS)); + return TimeZone.getTimeZone(randomFrom(JAVA_TIMEZONE_IDS)); } /** * generate a random TimeZone from the ones available in java.time */ public static ZoneId randomZone() { - return ZoneId.of(randomJodaAndJavaSupportedTimezone(JAVA_ZONE_IDS)); - } - - /** - * We need to exclude time zones not supported by joda (like SystemV* timezones) - * because they cannot be converted back to DateTimeZone which we currently - * still need to do internally e.g. in bwc serialization and in the extract() method - * //TODO remove once joda is not supported - */ - private static String randomJodaAndJavaSupportedTimezone(List zoneIds) { - return randomValueOtherThanMany(id -> JODA_TIMEZONE_IDS.contains(id) == false, () -> randomFrom(zoneIds)); + return ZoneId.of(randomFrom(JAVA_ZONE_IDS)); } /**