diff --git a/modules/aggregations/src/main/java/org/elasticsearch/aggregations/bucket/histogram/AutoDateHistogramAggregationBuilder.java b/modules/aggregations/src/main/java/org/elasticsearch/aggregations/bucket/histogram/AutoDateHistogramAggregationBuilder.java index 71aee0040562..516aed6e61b0 100644 --- a/modules/aggregations/src/main/java/org/elasticsearch/aggregations/bucket/histogram/AutoDateHistogramAggregationBuilder.java +++ b/modules/aggregations/src/main/java/org/elasticsearch/aggregations/bucket/histogram/AutoDateHistogramAggregationBuilder.java @@ -64,7 +64,7 @@ public class AutoDateHistogramAggregationBuilder extends ValuesSourceAggregation entry(Rounding.DateTimeUnit.MONTH_OF_YEAR, "month"), entry(Rounding.DateTimeUnit.DAY_OF_MONTH, "day"), entry(Rounding.DateTimeUnit.HOUR_OF_DAY, "hour"), - entry(Rounding.DateTimeUnit.MINUTES_OF_HOUR, "minute"), + entry(Rounding.DateTimeUnit.MINUTE_OF_HOUR, "minute"), entry(Rounding.DateTimeUnit.SECOND_OF_MINUTE, "second") ); @@ -84,7 +84,7 @@ public class AutoDateHistogramAggregationBuilder extends ValuesSourceAggregation RoundingInfo[] roundings = new RoundingInfo[6]; roundings[0] = new RoundingInfo(Rounding.DateTimeUnit.SECOND_OF_MINUTE, timeZone, 1000L, "s", 1, 5, 10, 30); - roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTES_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30); + roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTE_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30); roundings[2] = new RoundingInfo(Rounding.DateTimeUnit.HOUR_OF_DAY, timeZone, 60 * 60 * 1000L, "h", 1, 3, 12); roundings[3] = new RoundingInfo(Rounding.DateTimeUnit.DAY_OF_MONTH, timeZone, 24 * 60 * 60 * 1000L, "d", 1, 7); roundings[4] = new RoundingInfo(Rounding.DateTimeUnit.MONTH_OF_YEAR, timeZone, 30 * 24 * 60 * 60 * 1000L, "M", 1, 3); diff --git a/modules/aggregations/src/test/java/org/elasticsearch/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java b/modules/aggregations/src/test/java/org/elasticsearch/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java index 227557590731..72afacffe554 100644 --- a/modules/aggregations/src/test/java/org/elasticsearch/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java +++ b/modules/aggregations/src/test/java/org/elasticsearch/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java @@ -108,7 +108,7 @@ public class InternalAutoDateHistogramTests extends AggregationMultiBucketAggreg // an innerInterval that is quite large, such that targetBuckets * roundings[i].getMaximumInnerInterval() // will be larger than the estimate. roundings[0] = new RoundingInfo(Rounding.DateTimeUnit.SECOND_OF_MINUTE, timeZone, 1000L, "s", 1000); - roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTES_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30); + roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTE_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30); roundings[2] = new RoundingInfo(Rounding.DateTimeUnit.HOUR_OF_DAY, timeZone, 60 * 60 * 1000L, "h", 1, 3, 12); OffsetDateTime timestamp = Instant.parse("2018-01-01T00:00:01.000Z").atOffset(ZoneOffset.UTC); diff --git a/server/src/main/java/org/elasticsearch/common/Rounding.java b/server/src/main/java/org/elasticsearch/common/Rounding.java index 9c8a2e989f8c..4759926eeeba 100644 --- a/server/src/main/java/org/elasticsearch/common/Rounding.java +++ b/server/src/main/java/org/elasticsearch/common/Rounding.java @@ -61,7 +61,8 @@ public abstract class Rounding implements Writeable { @Override long roundFloor(long utcMillis, int multiplier) { - return DateUtils.roundWeekIntervalOfWeekYear(utcMillis, multiplier); + assert multiplier == 1; + return DateUtils.roundWeekOfWeekYear(utcMillis); } @Override @@ -74,7 +75,8 @@ public abstract class Rounding implements Writeable { @Override long roundFloor(long utcMillis, int multiplier) { - return multiplier == 1 ? DateUtils.roundYear(utcMillis) : DateUtils.roundYearInterval(utcMillis, multiplier); + assert multiplier == 1; + return DateUtils.roundYear(utcMillis); } @Override @@ -87,9 +89,8 @@ public abstract class Rounding implements Writeable { @Override long roundFloor(long utcMillis, int multiplier) { - return multiplier == 1 - ? DateUtils.roundQuarterOfYear(utcMillis) - : DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier * 3); + assert multiplier == 1; + return DateUtils.roundQuarterOfYear(utcMillis); } @Override @@ -102,7 +103,8 @@ public abstract class Rounding implements Writeable { @Override long roundFloor(long utcMillis, int multiplier) { - return multiplier == 1 ? DateUtils.roundMonthOfYear(utcMillis) : DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier); + assert multiplier == 1; + return DateUtils.roundMonthOfYear(utcMillis); } @Override @@ -113,7 +115,8 @@ public abstract class Rounding implements Writeable { DAY_OF_MONTH((byte) 5, "day", ChronoField.DAY_OF_MONTH, true, ChronoField.DAY_OF_MONTH.getBaseUnit().getDuration().toMillis()) { @Override long roundFloor(long utcMillis, int multiplier) { - return DateUtils.roundFloor(utcMillis, this.ratio * multiplier); + assert multiplier == 1; + return DateUtils.roundFloor(utcMillis, this.ratio); } @Override @@ -124,7 +127,8 @@ public abstract class Rounding implements Writeable { HOUR_OF_DAY((byte) 6, "hour", ChronoField.HOUR_OF_DAY, true, ChronoField.HOUR_OF_DAY.getBaseUnit().getDuration().toMillis()) { @Override long roundFloor(long utcMillis, int multiplier) { - return DateUtils.roundFloor(utcMillis, ratio * multiplier); + assert multiplier == 1; + return DateUtils.roundFloor(utcMillis, ratio); } @Override @@ -132,7 +136,7 @@ public abstract class Rounding implements Writeable { return ratio; } }, - MINUTES_OF_HOUR( + MINUTE_OF_HOUR( (byte) 7, "minute", ChronoField.MINUTE_OF_HOUR, @@ -141,7 +145,8 @@ public abstract class Rounding implements Writeable { ) { @Override long roundFloor(long utcMillis, int multiplier) { - return DateUtils.roundFloor(utcMillis, ratio * multiplier); + assert multiplier == 1; + return DateUtils.roundFloor(utcMillis, ratio); } @Override @@ -158,13 +163,40 @@ public abstract class Rounding implements Writeable { ) { @Override long roundFloor(long utcMillis, int multiplier) { - return DateUtils.roundFloor(utcMillis, ratio * multiplier); + assert multiplier == 1; + return DateUtils.roundFloor(utcMillis, ratio); } @Override long extraLocalOffsetLookup() { return ratio; } + }, + YEARS_OF_CENTURY((byte) 9, "years", ChronoField.YEAR_OF_ERA, false, 12) { + private final long extraLocalOffsetLookup = TimeUnit.DAYS.toMillis(366); + + @Override + long roundFloor(long utcMillis, int multiplier) { + return multiplier == 1 ? DateUtils.roundYear(utcMillis) : DateUtils.roundYearInterval(utcMillis, multiplier); + } + + @Override + long extraLocalOffsetLookup() { + return extraLocalOffsetLookup; + } + }, + MONTHS_OF_YEAR((byte) 10, "months", ChronoField.MONTH_OF_YEAR, false, 1) { + private final long extraLocalOffsetLookup = TimeUnit.DAYS.toMillis(31); + + @Override + long roundFloor(long utcMillis, int multiplier) { + return multiplier == 1 ? DateUtils.roundMonthOfYear(utcMillis) : DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier); + } + + @Override + long extraLocalOffsetLookup() { + return extraLocalOffsetLookup; + } }; private final byte id; @@ -222,8 +254,10 @@ public abstract class Rounding implements Writeable { case 4 -> MONTH_OF_YEAR; case 5 -> DAY_OF_MONTH; case 6 -> HOUR_OF_DAY; - case 7 -> MINUTES_OF_HOUR; + case 7 -> MINUTE_OF_HOUR; case 8 -> SECOND_OF_MINUTE; + case 9 -> YEARS_OF_CENTURY; + case 10 -> MONTHS_OF_YEAR; default -> throw new ElasticsearchException("Unknown date time unit id [" + id + "]"); }; } @@ -480,7 +514,7 @@ public abstract class Rounding implements Writeable { case SECOND_OF_MINUTE: return localDateTime.withNano(0); - case MINUTES_OF_HOUR: + case MINUTE_OF_HOUR: return LocalDateTime.of( localDateTime.getYear(), localDateTime.getMonthValue(), @@ -517,6 +551,12 @@ public abstract class Rounding implements Writeable { case YEAR_OF_CENTURY: return LocalDateTime.of(LocalDate.of(localDateTime.getYear(), 1, 1), LocalTime.MIDNIGHT); + case YEARS_OF_CENTURY: + return LocalDateTime.of(LocalDate.of(localDateTime.getYear(), 1, 1), LocalTime.MIDNIGHT); + + case MONTHS_OF_YEAR: + return LocalDateTime.of(localDateTime.getYear(), localDateTime.getMonthValue(), 1, 0, 0); + default: throw new IllegalArgumentException("NOT YET IMPLEMENTED for unit " + unit); } @@ -879,6 +919,8 @@ public abstract class Rounding implements Writeable { case MONTH_OF_YEAR -> localMidnight.plus(1, ChronoUnit.MONTHS); case QUARTER_OF_YEAR -> localMidnight.plus(3, ChronoUnit.MONTHS); case YEAR_OF_CENTURY -> localMidnight.plus(1, ChronoUnit.YEARS); + case YEARS_OF_CENTURY -> localMidnight.plus(1, ChronoUnit.YEARS); + case MONTHS_OF_YEAR -> localMidnight.plus(1, ChronoUnit.MONTHS); default -> throw new IllegalArgumentException("Unknown round-to-midnight unit: " + unit); }; } diff --git a/server/src/main/java/org/elasticsearch/common/time/DateUtils.java b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java index c5398b371cdf..375f05a37ba3 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateUtils.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java @@ -479,24 +479,7 @@ public class DateUtils { * @return The milliseconds since the epoch rounded down to the beginning of the week based on week year */ public static long roundWeekOfWeekYear(final long utcMillis) { - return roundWeekIntervalOfWeekYear(utcMillis, 1); - } - - /** - * Round down to the beginning of the nearest multiple of the specified week interval based on week year - *

- * Consider Sun Dec 29 1969 00:00:00.000 as the start of the first week. - * @param utcMillis the milliseconds since the epoch - * @param weekInterval the interval in weeks to round down to - * - * @return The milliseconds since the epoch rounded down to the beginning of the nearest multiple of the - * specified week interval based on week year - */ - public static long roundWeekIntervalOfWeekYear(final long utcMillis, final int weekInterval) { - if (weekInterval <= 0) { - throw new IllegalArgumentException("week interval must be strictly positive, got [" + weekInterval + "]"); - } - return roundFloor(utcMillis + 3 * 86400 * 1000L, 604800000L * weekInterval) - 3 * 86400 * 1000L; + return roundFloor(utcMillis + 3 * 86400 * 1000L, 604800000L) - 3 * 86400 * 1000L; } /** 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 72d5398c4dc7..4a6b6ca1c7b8 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 @@ -67,10 +67,12 @@ public class DateHistogramAggregationBuilder extends ValuesSourceAggregationBuil entry("1d", Rounding.DateTimeUnit.DAY_OF_MONTH), entry("hour", Rounding.DateTimeUnit.HOUR_OF_DAY), entry("1h", Rounding.DateTimeUnit.HOUR_OF_DAY), - entry("minute", Rounding.DateTimeUnit.MINUTES_OF_HOUR), - entry("1m", Rounding.DateTimeUnit.MINUTES_OF_HOUR), + entry("minute", Rounding.DateTimeUnit.MINUTE_OF_HOUR), + entry("1m", Rounding.DateTimeUnit.MINUTE_OF_HOUR), entry("second", Rounding.DateTimeUnit.SECOND_OF_MINUTE), - entry("1s", Rounding.DateTimeUnit.SECOND_OF_MINUTE) + entry("1s", Rounding.DateTimeUnit.SECOND_OF_MINUTE), + entry("years", Rounding.DateTimeUnit.YEARS_OF_CENTURY), + entry("months", Rounding.DateTimeUnit.MONTHS_OF_YEAR) ); public static final ObjectParser PARSER = ObjectParser.fromBuilder( diff --git a/server/src/test/java/org/elasticsearch/common/RoundingTests.java b/server/src/test/java/org/elasticsearch/common/RoundingTests.java index 59d91b5f9fbd..4c6ecc9a488e 100644 --- a/server/src/test/java/org/elasticsearch/common/RoundingTests.java +++ b/server/src/test/java/org/elasticsearch/common/RoundingTests.java @@ -69,13 +69,22 @@ public class RoundingTests extends ESTestCase { 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(Rounding.DateTimeUnit.MINUTES_OF_HOUR).build(); + tzRounding = Rounding.builder(Rounding.DateTimeUnit.MINUTE_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(Rounding.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)); + + tzRounding = Rounding.builder(Rounding.DateTimeUnit.YEARS_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(Rounding.DateTimeUnit.MONTHS_OF_YEAR).build(); + tz = ZoneOffset.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)); } public void testUTCIntervalRounding() { @@ -135,6 +144,9 @@ public class RoundingTests extends ESTestCase { tzRounding = Rounding.builder(Rounding.DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build(); assertThat(tzRounding.round(time("2012-04-01T04:15:30Z")), equalTo(time("2012-03-01T08:00:00Z"))); + tzRounding = Rounding.builder(Rounding.DateTimeUnit.MONTHS_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 = ZoneId.of("-02:00"); tzRounding = Rounding.builder(Rounding.DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build(); @@ -645,6 +657,21 @@ public class RoundingTests extends ESTestCase { // Two timestamps in same year and different timezone offset ("Double buckets" issue - #9491) tzRounding = Rounding.builder(Rounding.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)); + + // Month interval + tzRounding = Rounding.builder(Rounding.DateTimeUnit.MONTHS_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(Rounding.DateTimeUnit.YEARS_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(Rounding.DateTimeUnit.YEARS_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)); + } /** @@ -656,7 +683,7 @@ public class RoundingTests extends ESTestCase { 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(Rounding.DateTimeUnit.MINUTES_OF_HOUR, tz); + Rounding tzRounding = new Rounding.TimeUnitRounding(Rounding.DateTimeUnit.MINUTE_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)); @@ -1045,10 +1072,7 @@ public class RoundingTests extends ESTestCase { prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.SECOND_OF_MINUTE), closeTo(36000.0, 0.000001) ); - assertThat( - prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTES_OF_HOUR), - closeTo(600.0, 0.000001) - ); + assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(600.0, 0.000001)); assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(10.0, 0.000001)); assertThat( prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.DAY_OF_MONTH), @@ -1091,6 +1115,30 @@ public class RoundingTests extends ESTestCase { + "only week, day, hour, minute and second are supported for this histogram" ) ); + + ex = expectThrows( + IllegalArgumentException.class, + () -> prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MONTHS_OF_YEAR) + ); + assertThat( + ex.getMessage(), + equalTo( + "Cannot use month-based rate unit [months] with fixed interval based histogram, " + + "only week, day, hour, minute and second are supported for this histogram" + ) + ); + + ex = expectThrows( + IllegalArgumentException.class, + () -> prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.YEARS_OF_CENTURY) + ); + assertThat( + ex.getMessage(), + equalTo( + "Cannot use month-based rate unit [years] with fixed interval based histogram, " + + "only week, day, hour, minute and second are supported for this histogram" + ) + ); } public void testMillisecondsBasedUnitCalendarRoundingSize() { @@ -1101,8 +1149,8 @@ public class RoundingTests extends ESTestCase { closeTo(3600.0, 0.000001) ); assertThat(prepared.roundingSize(Rounding.DateTimeUnit.SECOND_OF_MINUTE), closeTo(3600.0, 0.000001)); - assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(60.0, 0.000001)); - assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(60.0, 0.000001)); + assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(60.0, 0.000001)); + assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(60.0, 0.000001)); assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(1.0, 0.000001)); assertThat(prepared.roundingSize(Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(1.0, 0.000001)); assertThat( @@ -1180,14 +1228,17 @@ public class RoundingTests extends ESTestCase { long firstQuarter = prepared.round(time("2015-01-01T00:00:00.000Z")); // Ratio based assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MONTH_OF_YEAR), closeTo(3.0, 0.000001)); + assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MONTHS_OF_YEAR), closeTo(3.0, 0.000001)); assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.QUARTER_OF_YEAR), closeTo(1.0, 0.000001)); assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.YEAR_OF_CENTURY), closeTo(0.25, 0.000001)); + assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.YEARS_OF_CENTURY), closeTo(0.25, 0.000001)); assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MONTH_OF_YEAR), closeTo(3.0, 0.000001)); + assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MONTHS_OF_YEAR), closeTo(3.0, 0.000001)); assertThat(prepared.roundingSize(Rounding.DateTimeUnit.QUARTER_OF_YEAR), closeTo(1.0, 0.000001)); assertThat(prepared.roundingSize(Rounding.DateTimeUnit.YEAR_OF_CENTURY), closeTo(0.25, 0.000001)); // Real interval based assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.SECOND_OF_MINUTE), closeTo(7776000.0, 0.000001)); - assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(129600.0, 0.000001)); + assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(129600.0, 0.000001)); assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(2160.0, 0.000001)); assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.DAY_OF_MONTH), closeTo(90.0, 0.000001)); long thirdQuarter = prepared.round(time("2015-07-01T00:00:00.000Z")); diff --git a/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java b/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java index a5f7f9c18334..8932ec8a885a 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java @@ -313,15 +313,7 @@ public class DateUtilsTests extends ESTestCase { assertThat(DateUtils.roundWeekOfWeekYear(1), is(epochMilli)); assertThat(DateUtils.roundWeekOfWeekYear(-1), is(epochMilli)); - IllegalArgumentException exc = expectThrows(IllegalArgumentException.class, () -> DateUtils.roundWeekIntervalOfWeekYear(0, -1)); - assertThat(exc.getMessage(), is("week interval must be strictly positive, got [-1]")); - assertThat(DateUtils.roundWeekIntervalOfWeekYear(0, 3), is(epochMilli)); - assertThat(DateUtils.roundWeekIntervalOfWeekYear(1, 3), is(epochMilli)); - assertThat(DateUtils.roundWeekIntervalOfWeekYear(-1, 2), is(epochMilli)); - epochMilli = Year.of(2025).atMonth(1).atDay(20).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); assertThat(DateUtils.roundWeekOfWeekYear(1737378896000L), is(epochMilli)); - epochMilli = Year.of(2025).atMonth(1).atDay(13).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); - assertThat(DateUtils.roundWeekIntervalOfWeekYear(1737378896000L, 4), is(epochMilli)); } } diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/InternalResetTrackingRateTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/InternalResetTrackingRateTests.java index b34ee770fb89..d1cef32cc806 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/InternalResetTrackingRateTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/InternalResetTrackingRateTests.java @@ -61,7 +61,7 @@ public class InternalResetTrackingRateTests extends InternalAggregationTestCase< } public void testReductionMinute() { - testReduction(Rounding.DateTimeUnit.MINUTES_OF_HOUR, 0.01 * MILLIS_IN_MINUTE); + testReduction(Rounding.DateTimeUnit.MINUTE_OF_HOUR, 0.01 * MILLIS_IN_MINUTE); } public void testReductionHour() { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigUtils.java index b6c1bff95534..9ec5dbdbd67c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigUtils.java @@ -135,11 +135,10 @@ public final class DatafeedConfigUtils { case WEEK_OF_WEEKYEAR -> new TimeValue(7, TimeUnit.DAYS); case DAY_OF_MONTH -> new TimeValue(1, TimeUnit.DAYS); case HOUR_OF_DAY -> new TimeValue(1, TimeUnit.HOURS); - case MINUTES_OF_HOUR -> new TimeValue(1, TimeUnit.MINUTES); + case MINUTE_OF_HOUR -> new TimeValue(1, TimeUnit.MINUTES); case SECOND_OF_MINUTE -> new TimeValue(1, TimeUnit.SECONDS); - case MONTH_OF_YEAR, YEAR_OF_CENTURY, QUARTER_OF_YEAR -> throw ExceptionsHelper.badRequestException( - invalidDateHistogramCalendarIntervalMessage(calendarInterval) - ); + case MONTH_OF_YEAR, YEAR_OF_CENTURY, QUARTER_OF_YEAR, YEARS_OF_CENTURY, MONTHS_OF_YEAR -> throw ExceptionsHelper + .badRequestException(invalidDateHistogramCalendarIntervalMessage(calendarInterval)); }; } else { interval = TimeValue.parseTimeValue(calendarInterval, "date_histogram.calendar_interval"); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java index eda181a03abb..6981c8e3b9d8 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java @@ -194,10 +194,14 @@ public class DateTrunc extends EsqlScalarFunction { // java.time.Period does not have a QUARTERLY period, so a period of 3 months // returns a quarterly rounding rounding = new Rounding.Builder(Rounding.DateTimeUnit.QUARTER_OF_YEAR); + } else if (period.getMonths() == 1) { + rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTH_OF_YEAR); } else if (period.getMonths() > 0) { - rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTH_OF_YEAR, period.getMonths()); + rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTHS_OF_YEAR, period.getMonths()); + } else if (period.getYears() == 1) { + rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEAR_OF_CENTURY); } else if (period.getYears() > 0) { - rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEAR_OF_CENTURY, period.getYears()); + rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEARS_OF_CENTURY, period.getYears()); } else { throw new IllegalArgumentException("Time interval is not supported"); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncRoundingTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncRoundingTests.java index aed602cd8991..eab34bcfd98e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncRoundingTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncRoundingTests.java @@ -40,10 +40,10 @@ public class DateTruncRoundingTests extends ESTestCase { assertEquals(10, rounding.roundingSize(Rounding.DateTimeUnit.HOUR_OF_DAY), 0d); rounding = createRounding(Duration.ofMinutes(1)); - assertEquals(1, rounding.roundingSize(Rounding.DateTimeUnit.MINUTES_OF_HOUR), 0d); + assertEquals(1, rounding.roundingSize(Rounding.DateTimeUnit.MINUTE_OF_HOUR), 0d); rounding = createRounding(Duration.ofMinutes(100)); - assertEquals(100, rounding.roundingSize(Rounding.DateTimeUnit.MINUTES_OF_HOUR), 0d); + assertEquals(100, rounding.roundingSize(Rounding.DateTimeUnit.MINUTE_OF_HOUR), 0d); rounding = createRounding(Duration.ofSeconds(1)); assertEquals(1, rounding.roundingSize(Rounding.DateTimeUnit.SECOND_OF_MINUTE), 0d); @@ -52,7 +52,7 @@ public class DateTruncRoundingTests extends ESTestCase { assertEquals(120, rounding.roundingSize(Rounding.DateTimeUnit.SECOND_OF_MINUTE), 0d); rounding = createRounding(Duration.ofSeconds(60).plusMinutes(5).plusHours(1)); - assertEquals(1 + 5 + 60, rounding.roundingSize(Rounding.DateTimeUnit.MINUTES_OF_HOUR), 0d); + assertEquals(1 + 5 + 60, rounding.roundingSize(Rounding.DateTimeUnit.MINUTE_OF_HOUR), 0d); } public void testCreateRoundingPeriod() {