From 7800b4fa9188255baa114496e2941c6c627ccad9 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Wed, 26 Sep 2018 07:56:25 -0700 Subject: [PATCH] Core: Abstract DateMathParser in an interface (#33905) This commits creates a DateMathParser interface, which is already implemented for both joda and java time. While currently the java time DateMathParser is not used, this change will allow a followup which will create a DateMathParser from a DateFormatter, so the caller does not need to know the internals of the DateFormatter they have. --- .../metadata/IndexNameExpressionResolver.java | 8 +- .../common/joda/FormatDateTimeFormatter.java | 5 + ...athParser.java => JodaDateMathParser.java} | 15 +- .../common/time/DateMathParser.java | 227 ++--------------- .../elasticsearch/common/time/DateUtils.java | 70 +++++ .../common/time/JavaDateMathParser.java | 240 ++++++++++++++++++ .../index/mapper/DateFieldMapper.java | 19 +- .../index/mapper/MappedFieldType.java | 2 +- .../index/mapper/RangeFieldMapper.java | 15 +- .../index/mapper/SimpleMappedFieldType.java | 4 +- .../index/query/RangeQueryBuilder.java | 4 +- .../elasticsearch/search/DocValueFormat.java | 8 +- .../DateHistogramAggregationBuilder.java | 4 +- ...ests.java => JodaDateMathParserTests.java} | 30 +-- .../common/time/DateUtilsTests.java | 54 ++++ ...ests.java => JavaDateMathParserTests.java} | 24 +- .../index/mapper/DateFieldTypeTests.java | 6 +- ...angeFieldQueryStringQueryBuilderTests.java | 2 +- .../aggregations/bucket/DateHistogramIT.java | 4 +- .../org/elasticsearch/test/ESTestCase.java | 2 +- .../license/licensor/TestUtils.java | 5 +- .../ml/action/GetOverallBucketsAction.java | 4 +- .../core/ml/action/StartDatafeedAction.java | 4 +- .../watcher/support/WatcherDateTimeUtils.java | 4 +- .../org/elasticsearch/license/TestUtils.java | 4 +- .../job/RollupIndexerIndexingTests.java | 5 +- 26 files changed, 476 insertions(+), 293 deletions(-) rename server/src/main/java/org/elasticsearch/common/joda/{DateMathParser.java => JodaDateMathParser.java} (95%) create mode 100644 server/src/main/java/org/elasticsearch/common/time/DateUtils.java create mode 100644 server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java rename server/src/test/java/org/elasticsearch/common/joda/{DateMathParserTests.java => JodaDateMathParserTests.java} (94%) create mode 100644 server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java rename server/src/test/java/org/elasticsearch/common/time/{DateMathParserTests.java => JavaDateMathParserTests.java} (95%) diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index 1f6a9fe027d..09fde36e1f9 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -27,10 +27,11 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.common.time.DateUtils; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; @@ -923,8 +924,9 @@ public class IndexNameExpressionResolver extends AbstractComponent { } DateTimeFormatter parser = dateFormatter.withZone(timeZone); FormatDateTimeFormatter formatter = new FormatDateTimeFormatter(dateFormatterPattern, parser, Locale.ROOT); - DateMathParser dateMathParser = new DateMathParser(formatter); - long millis = dateMathParser.parse(mathExpression, context::getStartTime, false, timeZone); + DateMathParser dateMathParser = formatter.toDateMathParser(); + long millis = dateMathParser.parse(mathExpression, context::getStartTime, false, + DateUtils.dateTimeZoneToZoneId(timeZone)); String time = formatter.printer().print(millis); beforePlaceHolderSb.append(time); diff --git a/server/src/main/java/org/elasticsearch/common/joda/FormatDateTimeFormatter.java b/server/src/main/java/org/elasticsearch/common/joda/FormatDateTimeFormatter.java index 72a60e8678c..e953e9563c6 100644 --- a/server/src/main/java/org/elasticsearch/common/joda/FormatDateTimeFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/joda/FormatDateTimeFormatter.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.joda; +import org.elasticsearch.common.time.DateMathParser; import org.joda.time.format.DateTimeFormatter; import java.util.Locale; @@ -64,4 +65,8 @@ public class FormatDateTimeFormatter { public Locale locale() { return locale; } + + public DateMathParser toDateMathParser() { + return new JodaDateMathParser(this); + } } diff --git a/server/src/main/java/org/elasticsearch/common/joda/DateMathParser.java b/server/src/main/java/org/elasticsearch/common/joda/JodaDateMathParser.java similarity index 95% rename from server/src/main/java/org/elasticsearch/common/joda/DateMathParser.java rename to server/src/main/java/org/elasticsearch/common/joda/JodaDateMathParser.java index ba5531c813c..0cef1d3e09b 100644 --- a/server/src/main/java/org/elasticsearch/common/joda/DateMathParser.java +++ b/server/src/main/java/org/elasticsearch/common/joda/JodaDateMathParser.java @@ -20,10 +20,13 @@ package org.elasticsearch.common.joda; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.common.time.DateUtils; import org.joda.time.DateTimeZone; import org.joda.time.MutableDateTime; import org.joda.time.format.DateTimeFormatter; +import java.time.ZoneId; import java.util.Objects; import java.util.function.LongSupplier; @@ -34,23 +37,21 @@ import java.util.function.LongSupplier; * is appended to a datetime with the following syntax: * ||[+-/](\d+)?[yMwdhHms]. */ -public class DateMathParser { +public class JodaDateMathParser implements DateMathParser { private final FormatDateTimeFormatter dateTimeFormatter; - public DateMathParser(FormatDateTimeFormatter dateTimeFormatter) { + public JodaDateMathParser(FormatDateTimeFormatter dateTimeFormatter) { Objects.requireNonNull(dateTimeFormatter); this.dateTimeFormatter = dateTimeFormatter; } - public long parse(String text, LongSupplier now) { - return parse(text, now, false, 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`. - public long parse(String text, LongSupplier now, boolean roundUp, DateTimeZone timeZone) { + @Override + public long 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")) { diff --git a/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java b/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java index 5e5ecc5bafd..b2cb319071f 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateMathParser.java @@ -19,56 +19,31 @@ package org.elasticsearch.common.time; -import org.elasticsearch.ElasticsearchParseException; +import org.joda.time.DateTimeZone; -import java.time.DateTimeException; -import java.time.DayOfWeek; -import java.time.Instant; -import java.time.LocalTime; import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalAdjusters; -import java.time.temporal.TemporalField; -import java.time.temporal.TemporalQueries; -import java.util.HashMap; -import java.util.Map; -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]. + * An abstraction over date math parsing to allow different implementation for joda and java time. */ -public class DateMathParser { +public interface DateMathParser { - // base fields which should be used for default parsing, when we round up - private static final Map ROUND_UP_BASE_FIELDS = new HashMap<>(6); - { - ROUND_UP_BASE_FIELDS.put(ChronoField.MONTH_OF_YEAR, 1L); - ROUND_UP_BASE_FIELDS.put(ChronoField.DAY_OF_MONTH, 1L); - ROUND_UP_BASE_FIELDS.put(ChronoField.HOUR_OF_DAY, 23L); - ROUND_UP_BASE_FIELDS.put(ChronoField.MINUTE_OF_HOUR, 59L); - ROUND_UP_BASE_FIELDS.put(ChronoField.SECOND_OF_MINUTE, 59L); - ROUND_UP_BASE_FIELDS.put(ChronoField.MILLI_OF_SECOND, 999L); + /** + * Parse a date math expression without timzeone info and rounding down. + */ + default long parse(String text, LongSupplier now) { + return parse(text, now, false, (ZoneId) null); } - private final DateFormatter formatter; - private final DateFormatter roundUpFormatter; + // 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`. - public DateMathParser(DateFormatter formatter) { - Objects.requireNonNull(formatter); - this.formatter = formatter; - this.roundUpFormatter = formatter.parseDefaulting(ROUND_UP_BASE_FIELDS); - } - - public long parse(String text, LongSupplier now) { - return parse(text, now, false, null); + // exists for backcompat, do not use! + @Deprecated + default long parse(String text, LongSupplier now, boolean roundUp, DateTimeZone tz) { + return parse(text, now, roundUp, tz == null ? null : ZoneId.of(tz.getID())); } /** @@ -92,176 +67,8 @@ public class DateMathParser { * @param text the input * @param now a supplier to retrieve the current date in milliseconds, if needed for additions * @param roundUp should the result be rounded up - * @param timeZone an optional timezone that should be applied before returning the milliseconds since the epoch + * @param tz an optional timezone that should be applied before returning the milliseconds since the epoch * @return the parsed date in milliseconds since the epoch */ - public long parse(String text, LongSupplier now, boolean roundUp, ZoneId timeZone) { - long time; - String mathString; - if (text.startsWith("now")) { - try { - time = now.getAsLong(); - } catch (Exception e) { - throw new ElasticsearchParseException("could not read the current timestamp", e); - } - mathString = text.substring("now".length()); - } else { - int index = text.indexOf("||"); - if (index == -1) { - return parseDateTime(text, timeZone, roundUp); - } - time = parseDateTime(text.substring(0, index), timeZone, false); - mathString = text.substring(index + 2); - } - - return parseMath(mathString, time, roundUp, timeZone); - } - - private long parseMath(final String mathString, final long time, final boolean roundUp, - ZoneId timeZone) throws ElasticsearchParseException { - if (timeZone == null) { - timeZone = ZoneOffset.UTC; - } - ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(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 ElasticsearchParseException("operator not supported for date math [{}]", mathString); - } - } - - if (i >= mathString.length()) { - throw new ElasticsearchParseException("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 ElasticsearchParseException("truncated date math [{}]", mathString); - } - num = Integer.parseInt(mathString.substring(numFrom, i)); - } - if (round) { - if (num != 1) { - throw new ElasticsearchParseException("rounding `/` can only be used on single unit types [{}]", mathString); - } - } - char unit = mathString.charAt(i++); - switch (unit) { - case 'y': - if (round) { - dateTime = dateTime.withDayOfYear(1).with(LocalTime.MIN); - } else { - dateTime = dateTime.plusYears(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusYears(1); - } - break; - case 'M': - if (round) { - dateTime = dateTime.withDayOfMonth(1).with(LocalTime.MIN); - } else { - dateTime = dateTime.plusMonths(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusMonths(1); - } - break; - case 'w': - if (round) { - dateTime = dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(LocalTime.MIN); - } else { - dateTime = dateTime.plusWeeks(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusWeeks(1); - } - break; - case 'd': - if (round) { - dateTime = dateTime.with(LocalTime.MIN); - } else { - dateTime = dateTime.plusDays(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusDays(1); - } - break; - case 'h': - case 'H': - if (round) { - dateTime = dateTime.withMinute(0).withSecond(0).withNano(0); - } else { - dateTime = dateTime.plusHours(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusHours(1); - } - break; - case 'm': - if (round) { - dateTime = dateTime.withSecond(0).withNano(0); - } else { - dateTime = dateTime.plusMinutes(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusMinutes(1); - } - break; - case 's': - if (round) { - dateTime = dateTime.withNano(0); - } else { - dateTime = dateTime.plusSeconds(sign * num); - } - if (roundUp) { - dateTime = dateTime.plusSeconds(1); - } - break; - default: - throw new ElasticsearchParseException("unit [{}] not supported for date math [{}]", unit, mathString); - } - if (roundUp) { - dateTime = dateTime.minus(1, ChronoField.MILLI_OF_SECOND.getBaseUnit()); - } - } - return dateTime.toInstant().toEpochMilli(); - } - - private long parseDateTime(String value, ZoneId timeZone, boolean roundUpIfNoTime) { - DateFormatter formatter = roundUpIfNoTime ? this.roundUpFormatter : this.formatter; - try { - if (timeZone == null) { - return DateFormatters.toZonedDateTime(formatter.parse(value)).toInstant().toEpochMilli(); - } else { - TemporalAccessor accessor = formatter.parse(value); - ZoneId zoneId = TemporalQueries.zone().queryFrom(accessor); - if (zoneId != null) { - timeZone = zoneId; - } - - return DateFormatters.toZonedDateTime(accessor).withZoneSameLocal(timeZone).toInstant().toEpochMilli(); - } - } catch (IllegalArgumentException | DateTimeException e) { - throw new ElasticsearchParseException("failed to parse date field [{}]: [{}]", e, value, e.getMessage()); - } - } + long parse(String text, LongSupplier now, boolean roundUp, ZoneId tz); } diff --git a/server/src/main/java/org/elasticsearch/common/time/DateUtils.java b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java new file mode 100644 index 00000000000..ed04321ee83 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.time; + +import org.apache.logging.log4j.LogManager; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.joda.time.DateTimeZone; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +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 DEPRECATION_LOGGER = new DeprecationLogger(LogManager.getLogger(DateFormatters.class)); + // pkg private for tests + static final Map DEPRECATED_SHORT_TIMEZONES; + static { + Map tzs = new HashMap<>(); + tzs.put("EST", "-05:00"); // eastern time without daylight savings + tzs.put("HST", "-10:00"); + tzs.put("MST", "-07:00"); + tzs.put("ROC", "Asia/Taipei"); + tzs.put("Eire", "Europe/London"); + DEPRECATED_SHORT_TIMEZONES = Collections.unmodifiableMap(tzs); + } + + public static ZoneId dateTimeZoneToZoneId(DateTimeZone timeZone) { + if (timeZone == null) { + return null; + } + + String deprecatedId = DEPRECATED_SHORT_TIMEZONES.get(timeZone.getID()); + if (deprecatedId != null) { + DEPRECATION_LOGGER.deprecatedAndMaybeLog("timezone", + "Use of short timezone id " + timeZone.getID() + " is deprecated. Use " + deprecatedId + " instead"); + return ZoneId.of(deprecatedId); + } + return ZoneId.of(timeZone.getID()); + } +} diff --git a/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java b/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java new file mode 100644 index 00000000000..c3a59f52190 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java @@ -0,0 +1,240 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.time; + +import org.elasticsearch.ElasticsearchParseException; + +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjusters; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQueries; +import java.util.HashMap; +import java.util.Map; +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]. + */ +public class JavaDateMathParser implements DateMathParser { + + // base fields which should be used for default parsing, when we round up + private static final Map ROUND_UP_BASE_FIELDS = new HashMap<>(6); + { + ROUND_UP_BASE_FIELDS.put(ChronoField.MONTH_OF_YEAR, 1L); + ROUND_UP_BASE_FIELDS.put(ChronoField.DAY_OF_MONTH, 1L); + ROUND_UP_BASE_FIELDS.put(ChronoField.HOUR_OF_DAY, 23L); + ROUND_UP_BASE_FIELDS.put(ChronoField.MINUTE_OF_HOUR, 59L); + ROUND_UP_BASE_FIELDS.put(ChronoField.SECOND_OF_MINUTE, 59L); + ROUND_UP_BASE_FIELDS.put(ChronoField.MILLI_OF_SECOND, 999L); + } + + private final DateFormatter formatter; + private final DateFormatter roundUpFormatter; + + public JavaDateMathParser(DateFormatter formatter) { + Objects.requireNonNull(formatter); + this.formatter = formatter; + this.roundUpFormatter = formatter.parseDefaulting(ROUND_UP_BASE_FIELDS); + } + + @Override + public long parse(String text, LongSupplier now, boolean roundUp, ZoneId timeZone) { + long time; + String mathString; + if (text.startsWith("now")) { + try { + time = now.getAsLong(); + } catch (Exception e) { + throw new ElasticsearchParseException("could not read the current timestamp", e); + } + mathString = text.substring("now".length()); + } else { + int index = text.indexOf("||"); + if (index == -1) { + return parseDateTime(text, timeZone, roundUp); + } + time = parseDateTime(text.substring(0, index), timeZone, false); + mathString = text.substring(index + 2); + } + + return parseMath(mathString, time, roundUp, timeZone); + } + + private long parseMath(final String mathString, final long time, final boolean roundUp, + ZoneId timeZone) throws ElasticsearchParseException { + if (timeZone == null) { + timeZone = ZoneOffset.UTC; + } + ZonedDateTime dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(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 ElasticsearchParseException("operator not supported for date math [{}]", mathString); + } + } + + if (i >= mathString.length()) { + throw new ElasticsearchParseException("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 ElasticsearchParseException("truncated date math [{}]", mathString); + } + num = Integer.parseInt(mathString.substring(numFrom, i)); + } + if (round) { + if (num != 1) { + throw new ElasticsearchParseException("rounding `/` can only be used on single unit types [{}]", mathString); + } + } + char unit = mathString.charAt(i++); + switch (unit) { + case 'y': + if (round) { + dateTime = dateTime.withDayOfYear(1).with(LocalTime.MIN); + } else { + dateTime = dateTime.plusYears(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusYears(1); + } + break; + case 'M': + if (round) { + dateTime = dateTime.withDayOfMonth(1).with(LocalTime.MIN); + } else { + dateTime = dateTime.plusMonths(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusMonths(1); + } + break; + case 'w': + if (round) { + dateTime = dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(LocalTime.MIN); + } else { + dateTime = dateTime.plusWeeks(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusWeeks(1); + } + break; + case 'd': + if (round) { + dateTime = dateTime.with(LocalTime.MIN); + } else { + dateTime = dateTime.plusDays(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusDays(1); + } + break; + case 'h': + case 'H': + if (round) { + dateTime = dateTime.withMinute(0).withSecond(0).withNano(0); + } else { + dateTime = dateTime.plusHours(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusHours(1); + } + break; + case 'm': + if (round) { + dateTime = dateTime.withSecond(0).withNano(0); + } else { + dateTime = dateTime.plusMinutes(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusMinutes(1); + } + break; + case 's': + if (round) { + dateTime = dateTime.withNano(0); + } else { + dateTime = dateTime.plusSeconds(sign * num); + } + if (roundUp) { + dateTime = dateTime.plusSeconds(1); + } + break; + default: + throw new ElasticsearchParseException("unit [{}] not supported for date math [{}]", unit, mathString); + } + if (roundUp) { + dateTime = dateTime.minus(1, ChronoField.MILLI_OF_SECOND.getBaseUnit()); + } + } + return dateTime.toInstant().toEpochMilli(); + } + + private long parseDateTime(String value, ZoneId timeZone, boolean roundUpIfNoTime) { + DateFormatter formatter = roundUpIfNoTime ? this.roundUpFormatter : this.formatter; + try { + if (timeZone == null) { + return DateFormatters.toZonedDateTime(formatter.parse(value)).toInstant().toEpochMilli(); + } else { + TemporalAccessor accessor = formatter.parse(value); + ZoneId zoneId = TemporalQueries.zone().queryFrom(accessor); + if (zoneId != null) { + timeZone = zoneId; + } + + return DateFormatters.toZonedDateTime(accessor).withZoneSameLocal(timeZone).toInstant().toEpochMilli(); + } + } catch (IllegalArgumentException | DateTimeException e) { + throw new ElasticsearchParseException("failed to parse date field [{}]: [{}]", e, value, e.getMessage()); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index c8360e468d7..0de2731ffd1 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -36,10 +36,11 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.common.time.DateUtils; import org.elasticsearch.common.util.LocaleUtils; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.support.XContentMapValues; @@ -231,7 +232,7 @@ public class DateFieldMapper extends FieldMapper { public void setDateTimeFormatter(FormatDateTimeFormatter dateTimeFormatter) { checkIfFrozen(); this.dateTimeFormatter = dateTimeFormatter; - this.dateMathParser = new DateMathParser(dateTimeFormatter); + this.dateMathParser = dateTimeFormatter.toDateMathParser(); } protected DateMathParser dateMathParser() { @@ -262,7 +263,7 @@ public class DateFieldMapper extends FieldMapper { @Override public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, ShapeRelation relation, - @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, QueryShardContext context) { + @Nullable DateTimeZone timeZone, @Nullable DateMathParser forcedDateParser, QueryShardContext context) { failIfNotIndexed(); if (relation == ShapeRelation.DISJOINT) { throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + @@ -296,8 +297,8 @@ public class DateFieldMapper extends FieldMapper { return query; } - public long parseToMilliseconds(Object value, boolean roundUp, - @Nullable DateTimeZone zone, @Nullable DateMathParser forcedDateParser, QueryRewriteContext context) { + public long parseToMilliseconds(Object value, boolean roundUp, @Nullable DateTimeZone zone, + @Nullable DateMathParser forcedDateParser, QueryRewriteContext context) { DateMathParser dateParser = dateMathParser(); if (forcedDateParser != null) { dateParser = forcedDateParser; @@ -309,13 +310,13 @@ public class DateFieldMapper extends FieldMapper { } else { strValue = value.toString(); } - return dateParser.parse(strValue, context::nowInMillis, roundUp, zone); + return dateParser.parse(strValue, context::nowInMillis, roundUp, DateUtils.dateTimeZoneToZoneId(zone)); } @Override - public Relation isFieldWithinQuery(IndexReader reader, - Object from, Object to, boolean includeLower, boolean includeUpper, - DateTimeZone timeZone, DateMathParser dateParser, QueryRewriteContext context) throws IOException { + public Relation isFieldWithinQuery(IndexReader reader, Object from, Object to, boolean includeLower, boolean includeUpper, + DateTimeZone timeZone, DateMathParser dateParser, + QueryRewriteContext context) throws IOException { if (dateParser == null) { dateParser = this.dateMathParser; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java index 82a601de05e..4ce7af9f09f 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java @@ -38,7 +38,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.joda.DateMathParser; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.fielddata.IndexFieldData; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java index 4c356c3a559..0deb6e8afa0 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java @@ -44,11 +44,12 @@ import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.common.time.DateUtils; import org.elasticsearch.common.util.LocaleUtils; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; @@ -60,6 +61,7 @@ import org.joda.time.DateTimeZone; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.ZoneId; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -257,7 +259,7 @@ public class RangeFieldMapper extends FieldMapper { public void setDateTimeFormatter(FormatDateTimeFormatter dateTimeFormatter) { checkIfFrozen(); this.dateTimeFormatter = dateTimeFormatter; - this.dateMathParser = new DateMathParser(dateTimeFormatter); + this.dateMathParser = dateTimeFormatter.toDateMathParser(); } protected DateMathParser dateMathParser() { @@ -587,15 +589,16 @@ public class RangeFieldMapper extends FieldMapper { public Query rangeQuery(String field, boolean hasDocValues, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, ShapeRelation relation, @Nullable DateTimeZone timeZone, @Nullable DateMathParser parser, QueryShardContext context) { - DateTimeZone zone = (timeZone == null) ? DateTimeZone.UTC : timeZone; + DateTimeZone zone = (timeZone == null) ? DateTimeZone.UTC : timeZone; + ZoneId zoneId = DateUtils.dateTimeZoneToZoneId(zone); DateMathParser dateMathParser = (parser == null) ? - new DateMathParser(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER) : parser; + DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.toDateMathParser() : parser; Long low = lowerTerm == null ? Long.MIN_VALUE : dateMathParser.parse(lowerTerm instanceof BytesRef ? ((BytesRef) lowerTerm).utf8ToString() : lowerTerm.toString(), - context::nowInMillis, false, zone); + context::nowInMillis, false, zoneId); Long high = upperTerm == null ? Long.MAX_VALUE : dateMathParser.parse(upperTerm instanceof BytesRef ? ((BytesRef) upperTerm).utf8ToString() : upperTerm.toString(), - context::nowInMillis, false, zone); + context::nowInMillis, false, zoneId); return super.rangeQuery(field, hasDocValues, low, high, includeLower, includeUpper, relation, zone, dateMathParser, context); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/SimpleMappedFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/SimpleMappedFieldType.java index b91be82cd6b..3d3b1607870 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/SimpleMappedFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/SimpleMappedFieldType.java @@ -21,7 +21,7 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.search.Query; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.joda.DateMathParser; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.index.query.QueryShardContext; import org.joda.time.DateTimeZone; @@ -40,7 +40,7 @@ public abstract class SimpleMappedFieldType extends MappedFieldType { @Override public final Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, - ShapeRelation relation, DateTimeZone timeZone, DateMathParser parser, QueryShardContext context) { + ShapeRelation relation, DateTimeZone timeZone, DateMathParser parser, QueryShardContext context) { if (relation == ShapeRelation.DISJOINT) { throw new IllegalArgumentException("Field [" + name() + "] of type [" + typeName() + "] does not support DISJOINT ranges"); diff --git a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java index b297036f2f3..d57e7cbaff4 100644 --- a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java @@ -29,10 +29,10 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.lucene.BytesRefs; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.FieldNamesFieldMapper; @@ -302,7 +302,7 @@ public class RangeQueryBuilder extends AbstractQueryBuilder i DateMathParser getForceDateParser() { // pkg private for testing if (this.format != null) { - return new DateMathParser(this.format); + return this.format.toDateMathParser(); } return null; } diff --git a/server/src/main/java/org/elasticsearch/search/DocValueFormat.java b/server/src/main/java/org/elasticsearch/search/DocValueFormat.java index 3a3b1c680ab..e5ece1afa33 100644 --- a/server/src/main/java/org/elasticsearch/search/DocValueFormat.java +++ b/server/src/main/java/org/elasticsearch/search/DocValueFormat.java @@ -25,11 +25,12 @@ import org.elasticsearch.common.geo.GeoHashUtils; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.network.NetworkAddress; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.common.time.DateUtils; import org.joda.time.DateTimeZone; import java.io.IOException; @@ -171,13 +172,14 @@ public interface DocValueFormat extends NamedWriteable { public static final String NAME = "date_time"; final FormatDateTimeFormatter formatter; + // TODO: change this to ZoneId, but will require careful change to serialization final DateTimeZone timeZone; private final DateMathParser parser; public DateTime(FormatDateTimeFormatter formatter, DateTimeZone timeZone) { this.formatter = Objects.requireNonNull(formatter); this.timeZone = Objects.requireNonNull(timeZone); - this.parser = new DateMathParser(formatter); + this.parser = formatter.toDateMathParser(); } public DateTime(StreamInput in) throws IOException { @@ -212,7 +214,7 @@ public interface DocValueFormat extends NamedWriteable { @Override public long parseLong(String value, boolean roundUp, LongSupplier now) { - return parser.parse(value, now, roundUp, timeZone); + return parser.parse(value, now, roundUp, DateUtils.dateTimeZoneToZoneId(timeZone)); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregationBuilder.java index bb785efde48..dba7fbb34fb 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregationBuilder.java @@ -25,10 +25,10 @@ import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.search.DocIdSetIterator; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.rounding.DateTimeUnit; import org.elasticsearch.common.rounding.Rounding; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -72,7 +72,7 @@ import static java.util.Collections.unmodifiableMap; public class DateHistogramAggregationBuilder extends ValuesSourceAggregationBuilder implements MultiBucketAggregationBuilder { public static final String NAME = "date_histogram"; - private static DateMathParser EPOCH_MILLIS_PARSER = new DateMathParser(Joda.forPattern("epoch_millis", Locale.ROOT)); + private static DateMathParser EPOCH_MILLIS_PARSER = Joda.forPattern("epoch_millis", Locale.ROOT).toDateMathParser(); public static final Map DATE_FIELD_UNITS; diff --git a/server/src/test/java/org/elasticsearch/common/joda/DateMathParserTests.java b/server/src/test/java/org/elasticsearch/common/joda/JodaDateMathParserTests.java similarity index 94% rename from server/src/test/java/org/elasticsearch/common/joda/DateMathParserTests.java rename to server/src/test/java/org/elasticsearch/common/joda/JodaDateMathParserTests.java index 2fad9738cb5..61448ce15ea 100644 --- a/server/src/test/java/org/elasticsearch/common/joda/DateMathParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/joda/JodaDateMathParserTests.java @@ -24,17 +24,17 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.test.ESTestCase; import org.joda.time.DateTimeZone; -import java.util.TimeZone; +import java.time.ZoneId; 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 DateMathParserTests extends ESTestCase { +public class JodaDateMathParserTests extends ESTestCase { FormatDateTimeFormatter formatter = Joda.forPattern("dateOptionalTime||epoch_millis"); - DateMathParser parser = new DateMathParser(formatter); + JodaDateMathParser parser = new JodaDateMathParser(formatter); void assertDateMathEquals(String toTest, String expected) { assertDateMathEquals(toTest, expected, 0, false, null); @@ -145,7 +145,7 @@ public class DateMathParserTests extends ESTestCase { public void testNow() { - final long now = parser.parse("2014-11-18T14:27:32", () -> 0, false, null); + final long now = parser.parse("2014-11-18T14:27:32", () -> 0, false, (ZoneId) null); assertDateMathEquals("now", "2014-11-18T14:27:32", now, false, null); assertDateMathEquals("now+M", "2014-12-18T14:27:32", now, false, null); @@ -159,13 +159,13 @@ public class DateMathParserTests extends ESTestCase { public void testRoundingPreservesEpochAsBaseDate() { // If a user only specifies times, then the date needs to always be 1970-01-01 regardless of rounding FormatDateTimeFormatter formatter = Joda.forPattern("HH:mm:ss"); - DateMathParser parser = new DateMathParser(formatter); + JodaDateMathParser parser = new JodaDateMathParser(formatter); assertEquals( this.formatter.parser().parseMillis("1970-01-01T04:52:20.000Z"), - parser.parse("04:52:20", () -> 0, false, null)); + parser.parse("04:52:20", () -> 0, false, (ZoneId) null)); assertEquals( this.formatter.parser().parseMillis("1970-01-01T04:52:20.999Z"), - parser.parse("04:52:20", () -> 0, true, null)); + parser.parse("04:52:20", () -> 0, true, (ZoneId) null)); } // Implicit rounding happening when parts of the date are not specified @@ -184,10 +184,10 @@ public class DateMathParserTests extends ESTestCase { // implicit rounding with explicit timezone in the date format FormatDateTimeFormatter formatter = Joda.forPattern("YYYY-MM-ddZ"); - DateMathParser parser = new DateMathParser(formatter); - long time = parser.parse("2011-10-09+01:00", () -> 0, false, null); + JodaDateMathParser parser = new JodaDateMathParser(formatter); + long 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, null); + 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); } @@ -258,7 +258,7 @@ public class DateMathParserTests extends ESTestCase { assertDateMathEquals("1418248078000||/m", "2014-12-10T21:47:00.000"); // also check other time units - DateMathParser parser = new DateMathParser(Joda.forPattern("epoch_second||dateOptionalTime")); + JodaDateMathParser parser = new JodaDateMathParser(Joda.forPattern("epoch_second||dateOptionalTime")); long datetime = parser.parse("1418248078", () -> 0); assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000"); @@ -298,16 +298,16 @@ public class DateMathParserTests extends ESTestCase { called.set(true); return 42L; }; - parser.parse("2014-11-18T14:27:32", now, false, null); + parser.parse("2014-11-18T14:27:32", now, false, (ZoneId) null); assertFalse(called.get()); - parser.parse("now/d", now, false, null); + parser.parse("now/d", now, false, (ZoneId) null); assertTrue(called.get()); } public void testThatUnixTimestampMayNotHaveTimeZone() { - DateMathParser parser = new DateMathParser(Joda.forPattern("epoch_millis")); + JodaDateMathParser parser = new JodaDateMathParser(Joda.forPattern("epoch_millis")); try { - parser.parse("1234567890123", () -> 42, false, DateTimeZone.forTimeZone(TimeZone.getTimeZone("CET"))); + parser.parse("1234567890123", () -> 42, false, ZoneId.of("CET")); fail("Expected ElasticsearchParseException"); } catch(ElasticsearchParseException e) { assertThat(e.getMessage(), containsString("failed to parse date field")); diff --git a/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java b/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java new file mode 100644 index 00000000000..8f36258c5fe --- /dev/null +++ b/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.common.time; + +import org.elasticsearch.test.ESTestCase; +import org.joda.time.DateTimeZone; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class DateUtilsTests extends ESTestCase { + private static final Set IGNORE = new HashSet<>(Arrays.asList( + "Eire", "Europe/Dublin" // dublin timezone in joda does not account for DST + )); + 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)); + } + } +} diff --git a/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java b/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java similarity index 95% rename from server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java rename to server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java index 66e68b0aad0..a543af0445d 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateMathParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java @@ -33,10 +33,10 @@ import java.util.function.LongSupplier; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -public class DateMathParserTests extends ESTestCase { +public class JavaDateMathParserTests extends ESTestCase { private final DateFormatter formatter = DateFormatters.forPattern("dateOptionalTime||epoch_millis"); - private final DateMathParser parser = new DateMathParser(formatter); + private final JavaDateMathParser parser = new JavaDateMathParser(formatter); public void testBasicDates() { assertDateMathEquals("2014", "2014-01-01T00:00:00.000"); @@ -125,7 +125,7 @@ public class DateMathParserTests extends ESTestCase { } public void testNow() { - final long now = parser.parse("2014-11-18T14:27:32", () -> 0, false, null); + final long now = parser.parse("2014-11-18T14:27:32", () -> 0, false, (ZoneId) null); assertDateMathEquals("now", "2014-11-18T14:27:32", now, false, null); assertDateMathEquals("now+M", "2014-12-18T14:27:32", now, false, null); @@ -139,14 +139,14 @@ public class DateMathParserTests extends ESTestCase { public void testRoundingPreservesEpochAsBaseDate() { // If a user only specifies times, then the date needs to always be 1970-01-01 regardless of rounding DateFormatter formatter = DateFormatters.forPattern("HH:mm:ss"); - DateMathParser parser = new DateMathParser(formatter); + JavaDateMathParser parser = new JavaDateMathParser(formatter); ZonedDateTime zonedDateTime = DateFormatters.toZonedDateTime(formatter.parse("04:52:20")); assertThat(zonedDateTime.getYear(), is(1970)); long millisStart = zonedDateTime.toInstant().toEpochMilli(); - assertEquals(millisStart, parser.parse("04:52:20", () -> 0, false, null)); + assertEquals(millisStart, parser.parse("04:52:20", () -> 0, false, (ZoneId) null)); // due to rounding up, we have to add the number of milliseconds here manually long millisEnd = DateFormatters.toZonedDateTime(formatter.parse("04:52:20")).toInstant().toEpochMilli() + 999; - assertEquals(millisEnd, parser.parse("04:52:20", () -> 0, true, null)); + assertEquals(millisEnd, parser.parse("04:52:20", () -> 0, true, (ZoneId) null)); } // Implicit rounding happening when parts of the date are not specified @@ -165,10 +165,10 @@ public class DateMathParserTests extends ESTestCase { // implicit rounding with explicit timezone in the date format DateFormatter formatter = DateFormatters.forPattern("yyyy-MM-ddXXX"); - DateMathParser parser = new DateMathParser(formatter); - long time = parser.parse("2011-10-09+01:00", () -> 0, false, null); + JavaDateMathParser parser = new JavaDateMathParser(formatter); + long 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, null); + 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); } @@ -239,7 +239,7 @@ public class DateMathParserTests extends ESTestCase { assertDateMathEquals("1418248078000||/m", "2014-12-10T21:47:00.000"); // also check other time units - DateMathParser parser = new DateMathParser(DateFormatters.forPattern("epoch_second||dateOptionalTime")); + JavaDateMathParser parser = new JavaDateMathParser(DateFormatters.forPattern("epoch_second||dateOptionalTime")); long datetime = parser.parse("1418248078", () -> 0); assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000"); @@ -279,9 +279,9 @@ public class DateMathParserTests extends ESTestCase { called.set(true); return 42L; }; - parser.parse("2014-11-18T14:27:32", now, false, null); + parser.parse("2014-11-18T14:27:32", now, false, (ZoneId) null); assertFalse(called.get()); - parser.parse("now/d", now, false, null); + parser.parse("now/d", now, false, (ZoneId) null); assertTrue(called.get()); } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java index ad9d0c41494..3a185620f7b 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java @@ -29,12 +29,12 @@ import org.apache.lucene.index.MultiReader; import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.Query; import org.apache.lucene.store.Directory; -import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; +import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType; import org.elasticsearch.index.mapper.MappedFieldType.Relation; @@ -121,7 +121,7 @@ public class DateFieldTypeTests extends FieldTypeTestCase { DirectoryReader reader = DirectoryReader.open(w); DateFieldType ft = new DateFieldType(); ft.setName("my_date"); - DateMathParser alternateFormat = new DateMathParser(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER); + DateMathParser alternateFormat = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.toDateMathParser(); doTestIsFieldWithinQuery(ft, reader, null, null); doTestIsFieldWithinQuery(ft, reader, null, alternateFormat); doTestIsFieldWithinQuery(ft, reader, DateTimeZone.UTC, null); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldQueryStringQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldQueryStringQueryBuilderTests.java index 0aa8565ea57..34e7081d51d 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldQueryStringQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/RangeFieldQueryStringQueryBuilderTests.java @@ -31,8 +31,8 @@ import org.apache.lucene.search.Query; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.common.Strings; import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.search.internal.SearchContext; diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramIT.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramIT.java index 58d0ca09ff2..885e4680d9c 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramIT.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramIT.java @@ -23,9 +23,9 @@ import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.index.query.MatchNoneQueryBuilder; @@ -1120,7 +1120,7 @@ public class DateHistogramIT extends ESIntegTestCase { .setSettings(Settings.builder().put(indexSettings()).put("index.number_of_shards", 1).put("index.number_of_replicas", 0)) .execute().actionGet(); - DateMathParser parser = new DateMathParser(Joda.getStrictStandardDateFormatter()); + DateMathParser parser = Joda.getStrictStandardDateFormatter().toDateMathParser(); // we pick a random timezone offset of +12/-12 hours and insert two documents // one at 00:00 in that time zone and one at 12:00 diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 922a6e0d276..ffa7c601184 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -408,7 +408,7 @@ public abstract class ESTestCase extends LuceneTestCase { } try { final List actualWarnings = threadContext.getResponseHeaders().get("Warning"); - assertNotNull(actualWarnings); + assertNotNull("no warnings, expected: " + Arrays.asList(expectedWarnings), actualWarnings); final Set actualWarningValues = actualWarnings.stream().map(DeprecationLogger::extractWarningValueFromWarningHeader).collect(Collectors.toSet()); for (String msg : expectedWarnings) { diff --git a/x-pack/license-tools/src/test/java/org/elasticsearch/license/licensor/TestUtils.java b/x-pack/license-tools/src/test/java/org/elasticsearch/license/licensor/TestUtils.java index 31b458489d4..8743bc708f4 100644 --- a/x-pack/license-tools/src/test/java/org/elasticsearch/license/licensor/TestUtils.java +++ b/x-pack/license-tools/src/test/java/org/elasticsearch/license/licensor/TestUtils.java @@ -6,9 +6,9 @@ package org.elasticsearch.license.licensor; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -38,8 +38,7 @@ public class TestUtils { private static final FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd"); - private static final DateMathParser dateMathParser = - new DateMathParser(formatDateTimeFormatter); + private static final DateMathParser dateMathParser = formatDateTimeFormatter.toDateMathParser(); private static final DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer(); public static String dumpLicense(License license) throws Exception { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetOverallBucketsAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetOverallBucketsAction.java index e6ace63f44a..ef284e13942 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetOverallBucketsAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetOverallBucketsAction.java @@ -16,7 +16,7 @@ import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.joda.DateMathParser; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; @@ -88,7 +88,7 @@ public class GetOverallBucketsAction extends Action { } static long parseDateOrThrow(String date, ParseField paramName, LongSupplier now) { - DateMathParser dateMathParser = new DateMathParser(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER); + DateMathParser dateMathParser = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.toDateMathParser(); try { return dateMathParser.parse(date, now); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/WatcherDateTimeUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/WatcherDateTimeUtils.java index 097d136c629..991f9ba3323 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/WatcherDateTimeUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/WatcherDateTimeUtils.java @@ -9,8 +9,8 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit; public class WatcherDateTimeUtils { public static final FormatDateTimeFormatter dateTimeFormatter = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER; - public static final DateMathParser dateMathParser = new DateMathParser(dateTimeFormatter); + public static final DateMathParser dateMathParser = dateTimeFormatter.toDateMathParser(); private WatcherDateTimeUtils() { } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java index afa1a8d6796..0113634a882 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java @@ -10,10 +10,10 @@ import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -50,7 +50,7 @@ import static org.junit.Assert.assertThat; public class TestUtils { private static final FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd"); - private static final DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter); + private static final DateMathParser dateMathParser = formatDateTimeFormatter.toDateMathParser(); private static final DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer(); public static String dateMathString(String time, final long now) { diff --git a/x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/job/RollupIndexerIndexingTests.java b/x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/job/RollupIndexerIndexingTests.java index 55f1cfbdbb2..2e52160a6fa 100644 --- a/x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/job/RollupIndexerIndexingTests.java +++ b/x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/job/RollupIndexerIndexingTests.java @@ -29,7 +29,6 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponseSections; import org.elasticsearch.action.search.ShardSearchFailure; -import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.rounding.Rounding; import org.elasticsearch.common.unit.TimeValue; @@ -47,13 +46,13 @@ import org.elasticsearch.search.aggregations.AggregatorTestCase; import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregation; import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; +import org.elasticsearch.xpack.core.indexing.IndexerState; import org.elasticsearch.xpack.core.rollup.ConfigTestHelpers; import org.elasticsearch.xpack.core.rollup.job.DateHistogramGroupConfig; import org.elasticsearch.xpack.core.rollup.job.GroupConfig; import org.elasticsearch.xpack.core.rollup.job.MetricConfig; import org.elasticsearch.xpack.core.rollup.job.RollupJob; import org.elasticsearch.xpack.core.rollup.job.RollupJobConfig; -import org.elasticsearch.xpack.core.indexing.IndexerState; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.Before; @@ -601,7 +600,7 @@ public class RollupIndexerIndexingTests extends AggregatorTestCase { RangeQueryBuilder range = (RangeQueryBuilder) request.source().query(); final DateTimeZone timeZone = range.timeZone() != null ? DateTimeZone.forID(range.timeZone()) : null; Query query = timestampField.rangeQuery(range.from(), range.to(), range.includeLower(), range.includeUpper(), - null, timeZone, new DateMathParser(Joda.forPattern(range.format())), queryShardContext); + null, timeZone, Joda.forPattern(range.format()).toDateMathParser(), queryShardContext); // extract composite agg assertThat(request.source().aggregations().getAggregatorFactories().size(), equalTo(1));