Skip to content

Commit f253a14

Browse files
Optimize date rounding
1 parent c14e602 commit f253a14

File tree

11 files changed

+72
-63
lines changed

11 files changed

+72
-63
lines changed

modules/aggregations/src/main/java/org/elasticsearch/aggregations/bucket/histogram/AutoDateHistogramAggregationBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public class AutoDateHistogramAggregationBuilder extends ValuesSourceAggregation
6464
entry(Rounding.DateTimeUnit.MONTH_OF_YEAR, "month"),
6565
entry(Rounding.DateTimeUnit.DAY_OF_MONTH, "day"),
6666
entry(Rounding.DateTimeUnit.HOUR_OF_DAY, "hour"),
67-
entry(Rounding.DateTimeUnit.MINUTES_OF_HOUR, "minute"),
67+
entry(Rounding.DateTimeUnit.MINUTE_OF_HOUR, "minute"),
6868
entry(Rounding.DateTimeUnit.SECOND_OF_MINUTE, "second")
6969
);
7070

@@ -84,7 +84,7 @@ static RoundingInfo[] buildRoundings(ZoneId timeZone, String minimumInterval) {
8484

8585
RoundingInfo[] roundings = new RoundingInfo[6];
8686
roundings[0] = new RoundingInfo(Rounding.DateTimeUnit.SECOND_OF_MINUTE, timeZone, 1000L, "s", 1, 5, 10, 30);
87-
roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTES_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30);
87+
roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTE_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30);
8888
roundings[2] = new RoundingInfo(Rounding.DateTimeUnit.HOUR_OF_DAY, timeZone, 60 * 60 * 1000L, "h", 1, 3, 12);
8989
roundings[3] = new RoundingInfo(Rounding.DateTimeUnit.DAY_OF_MONTH, timeZone, 24 * 60 * 60 * 1000L, "d", 1, 7);
9090
roundings[4] = new RoundingInfo(Rounding.DateTimeUnit.MONTH_OF_YEAR, timeZone, 30 * 24 * 60 * 60 * 1000L, "M", 1, 3);

modules/aggregations/src/test/java/org/elasticsearch/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void testGetAppropriateRoundingUsesCorrectIntervals() {
108108
// an innerInterval that is quite large, such that targetBuckets * roundings[i].getMaximumInnerInterval()
109109
// will be larger than the estimate.
110110
roundings[0] = new RoundingInfo(Rounding.DateTimeUnit.SECOND_OF_MINUTE, timeZone, 1000L, "s", 1000);
111-
roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTES_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30);
111+
roundings[1] = new RoundingInfo(Rounding.DateTimeUnit.MINUTE_OF_HOUR, timeZone, 60 * 1000L, "m", 1, 5, 10, 30);
112112
roundings[2] = new RoundingInfo(Rounding.DateTimeUnit.HOUR_OF_DAY, timeZone, 60 * 60 * 1000L, "h", 1, 3, 12);
113113

114114
OffsetDateTime timestamp = Instant.parse("2018-01-01T00:00:01.000Z").atOffset(ZoneOffset.UTC);

server/src/main/java/org/elasticsearch/common/Rounding.java

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public enum DateTimeUnit {
6161

6262
@Override
6363
long roundFloor(long utcMillis, int multiplier) {
64-
return DateUtils.roundWeekIntervalOfWeekYear(utcMillis, multiplier);
64+
assert multiplier == 1;
65+
return DateUtils.roundWeekOfWeekYear(utcMillis);
6566
}
6667

6768
@Override
@@ -74,7 +75,8 @@ long extraLocalOffsetLookup() {
7475

7576
@Override
7677
long roundFloor(long utcMillis, int multiplier) {
77-
return multiplier == 1 ? DateUtils.roundYear(utcMillis) : DateUtils.roundYearInterval(utcMillis, multiplier);
78+
assert multiplier == 1;
79+
return DateUtils.roundYear(utcMillis);
7880
}
7981

8082
@Override
@@ -87,9 +89,8 @@ long extraLocalOffsetLookup() {
8789

8890
@Override
8991
long roundFloor(long utcMillis, int multiplier) {
90-
return multiplier == 1
91-
? DateUtils.roundQuarterOfYear(utcMillis)
92-
: DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier * 3);
92+
assert multiplier == 1;
93+
return DateUtils.roundQuarterOfYear(utcMillis);
9394
}
9495

9596
@Override
@@ -102,7 +103,8 @@ long extraLocalOffsetLookup() {
102103

103104
@Override
104105
long roundFloor(long utcMillis, int multiplier) {
105-
return multiplier == 1 ? DateUtils.roundMonthOfYear(utcMillis) : DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier);
106+
assert multiplier == 1;
107+
return DateUtils.roundMonthOfYear(utcMillis);
106108
}
107109

108110
@Override
@@ -113,7 +115,8 @@ long extraLocalOffsetLookup() {
113115
DAY_OF_MONTH((byte) 5, "day", ChronoField.DAY_OF_MONTH, true, ChronoField.DAY_OF_MONTH.getBaseUnit().getDuration().toMillis()) {
114116
@Override
115117
long roundFloor(long utcMillis, int multiplier) {
116-
return DateUtils.roundFloor(utcMillis, this.ratio * multiplier);
118+
assert multiplier == 1;
119+
return DateUtils.roundFloor(utcMillis, this.ratio);
117120
}
118121

119122
@Override
@@ -124,15 +127,16 @@ long extraLocalOffsetLookup() {
124127
HOUR_OF_DAY((byte) 6, "hour", ChronoField.HOUR_OF_DAY, true, ChronoField.HOUR_OF_DAY.getBaseUnit().getDuration().toMillis()) {
125128
@Override
126129
long roundFloor(long utcMillis, int multiplier) {
127-
return DateUtils.roundFloor(utcMillis, ratio * multiplier);
130+
assert multiplier == 1;
131+
return DateUtils.roundFloor(utcMillis, ratio);
128132
}
129133

130134
@Override
131135
long extraLocalOffsetLookup() {
132136
return ratio;
133137
}
134138
},
135-
MINUTES_OF_HOUR(
139+
MINUTE_OF_HOUR(
136140
(byte) 7,
137141
"minute",
138142
ChronoField.MINUTE_OF_HOUR,
@@ -141,7 +145,8 @@ long extraLocalOffsetLookup() {
141145
) {
142146
@Override
143147
long roundFloor(long utcMillis, int multiplier) {
144-
return DateUtils.roundFloor(utcMillis, ratio * multiplier);
148+
assert multiplier == 1;
149+
return DateUtils.roundFloor(utcMillis, ratio);
145150
}
146151

147152
@Override
@@ -158,13 +163,40 @@ long extraLocalOffsetLookup() {
158163
) {
159164
@Override
160165
long roundFloor(long utcMillis, int multiplier) {
161-
return DateUtils.roundFloor(utcMillis, ratio * multiplier);
166+
assert multiplier == 1;
167+
return DateUtils.roundFloor(utcMillis, ratio);
162168
}
163169

164170
@Override
165171
long extraLocalOffsetLookup() {
166172
return ratio;
167173
}
174+
},
175+
YEARS_OF_CENTURY((byte) 9, "year", ChronoField.YEAR_OF_ERA, false, 12) {
176+
private final long extraLocalOffsetLookup = TimeUnit.DAYS.toMillis(366);
177+
178+
@Override
179+
long roundFloor(long utcMillis, int multiplier) {
180+
return multiplier == 1 ? DateUtils.roundYear(utcMillis) : DateUtils.roundYearInterval(utcMillis, multiplier);
181+
}
182+
183+
@Override
184+
long extraLocalOffsetLookup() {
185+
return extraLocalOffsetLookup;
186+
}
187+
},
188+
MONTHS_OF_YEAR((byte) 10, "month", ChronoField.MONTH_OF_YEAR, false, 1) {
189+
private final long extraLocalOffsetLookup = TimeUnit.DAYS.toMillis(31);
190+
191+
@Override
192+
long roundFloor(long utcMillis, int multiplier) {
193+
return multiplier == 1 ? DateUtils.roundMonthOfYear(utcMillis) : DateUtils.roundIntervalMonthOfYear(utcMillis, multiplier);
194+
}
195+
196+
@Override
197+
long extraLocalOffsetLookup() {
198+
return extraLocalOffsetLookup;
199+
}
168200
};
169201

170202
private final byte id;
@@ -222,8 +254,10 @@ public static DateTimeUnit resolve(byte id) {
222254
case 4 -> MONTH_OF_YEAR;
223255
case 5 -> DAY_OF_MONTH;
224256
case 6 -> HOUR_OF_DAY;
225-
case 7 -> MINUTES_OF_HOUR;
257+
case 7 -> MINUTE_OF_HOUR;
226258
case 8 -> SECOND_OF_MINUTE;
259+
case 9 -> YEARS_OF_CENTURY;
260+
case 10 -> MONTHS_OF_YEAR;
227261
default -> throw new ElasticsearchException("Unknown date time unit id [" + id + "]");
228262
};
229263
}
@@ -480,7 +514,7 @@ private LocalDateTime truncateLocalDateTime(LocalDateTime localDateTime) {
480514
case SECOND_OF_MINUTE:
481515
return localDateTime.withNano(0);
482516

483-
case MINUTES_OF_HOUR:
517+
case MINUTE_OF_HOUR:
484518
return LocalDateTime.of(
485519
localDateTime.getYear(),
486520
localDateTime.getMonthValue(),

server/src/main/java/org/elasticsearch/common/time/DateUtils.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -479,24 +479,7 @@ public static long roundYearInterval(final long utcMillis, final int yearInterva
479479
* @return The milliseconds since the epoch rounded down to the beginning of the week based on week year
480480
*/
481481
public static long roundWeekOfWeekYear(final long utcMillis) {
482-
return roundWeekIntervalOfWeekYear(utcMillis, 1);
483-
}
484-
485-
/**
486-
* Round down to the beginning of the nearest multiple of the specified week interval based on week year
487-
* <p>
488-
* Consider Sun Dec 29 1969 00:00:00.000 as the start of the first week.
489-
* @param utcMillis the milliseconds since the epoch
490-
* @param weekInterval the interval in weeks to round down to
491-
*
492-
* @return The milliseconds since the epoch rounded down to the beginning of the nearest multiple of the
493-
* specified week interval based on week year
494-
*/
495-
public static long roundWeekIntervalOfWeekYear(final long utcMillis, final int weekInterval) {
496-
if (weekInterval <= 0) {
497-
throw new IllegalArgumentException("week interval must be strictly positive, got [" + weekInterval + "]");
498-
}
499-
return roundFloor(utcMillis + 3 * 86400 * 1000L, 604800000L * weekInterval) - 3 * 86400 * 1000L;
482+
return roundFloor(utcMillis + 3 * 86400 * 1000L, 604800000L) - 3 * 86400 * 1000L;
500483
}
501484

502485
/**

server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregationBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public class DateHistogramAggregationBuilder extends ValuesSourceAggregationBuil
6767
entry("1d", Rounding.DateTimeUnit.DAY_OF_MONTH),
6868
entry("hour", Rounding.DateTimeUnit.HOUR_OF_DAY),
6969
entry("1h", Rounding.DateTimeUnit.HOUR_OF_DAY),
70-
entry("minute", Rounding.DateTimeUnit.MINUTES_OF_HOUR),
71-
entry("1m", Rounding.DateTimeUnit.MINUTES_OF_HOUR),
70+
entry("minute", Rounding.DateTimeUnit.MINUTE_OF_HOUR),
71+
entry("1m", Rounding.DateTimeUnit.MINUTE_OF_HOUR),
7272
entry("second", Rounding.DateTimeUnit.SECOND_OF_MINUTE),
7373
entry("1s", Rounding.DateTimeUnit.SECOND_OF_MINUTE)
7474
);

server/src/test/java/org/elasticsearch/common/RoundingTests.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void testUTCTimeUnitRounding() {
6969
assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-01T00:00:00.000Z"), tz));
7070
assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2013-01-01T00:00:00.000Z"), tz));
7171

72-
tzRounding = Rounding.builder(Rounding.DateTimeUnit.MINUTES_OF_HOUR).build();
72+
tzRounding = Rounding.builder(Rounding.DateTimeUnit.MINUTE_OF_HOUR).build();
7373
assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-10T01:01:00.000Z"), tz));
7474
assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-09T00:01:00.000Z"), tz));
7575

@@ -656,7 +656,7 @@ public void testLenientConversionDST() {
656656

657657
long start = time("2014-10-18T20:50:00.000", tz);
658658
long end = time("2014-10-19T01:00:00.000", tz);
659-
Rounding tzRounding = new Rounding.TimeUnitRounding(Rounding.DateTimeUnit.MINUTES_OF_HOUR, tz);
659+
Rounding tzRounding = new Rounding.TimeUnitRounding(Rounding.DateTimeUnit.MINUTE_OF_HOUR, tz);
660660
Rounding dayTzRounding = new Rounding.TimeIntervalRounding(60000, tz);
661661
for (long time = start; time < end; time = time + 60000) {
662662
assertThat(tzRounding.nextRoundingValue(time), greaterThan(time));
@@ -1045,10 +1045,7 @@ public void testFixedIntervalRoundingSize() {
10451045
prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.SECOND_OF_MINUTE),
10461046
closeTo(36000.0, 0.000001)
10471047
);
1048-
assertThat(
1049-
prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTES_OF_HOUR),
1050-
closeTo(600.0, 0.000001)
1051-
);
1048+
assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(600.0, 0.000001));
10521049
assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(10.0, 0.000001));
10531050
assertThat(
10541051
prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.DAY_OF_MONTH),
@@ -1101,8 +1098,8 @@ public void testMillisecondsBasedUnitCalendarRoundingSize() {
11011098
closeTo(3600.0, 0.000001)
11021099
);
11031100
assertThat(prepared.roundingSize(Rounding.DateTimeUnit.SECOND_OF_MINUTE), closeTo(3600.0, 0.000001));
1104-
assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(60.0, 0.000001));
1105-
assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(60.0, 0.000001));
1101+
assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(60.0, 0.000001));
1102+
assertThat(prepared.roundingSize(Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(60.0, 0.000001));
11061103
assertThat(prepared.roundingSize(time("2015-01-01T00:00:00.000Z"), Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(1.0, 0.000001));
11071104
assertThat(prepared.roundingSize(Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(1.0, 0.000001));
11081105
assertThat(
@@ -1187,7 +1184,7 @@ public void testNonMillisecondsBasedUnitCalendarRoundingSize() {
11871184
assertThat(prepared.roundingSize(Rounding.DateTimeUnit.YEAR_OF_CENTURY), closeTo(0.25, 0.000001));
11881185
// Real interval based
11891186
assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.SECOND_OF_MINUTE), closeTo(7776000.0, 0.000001));
1190-
assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MINUTES_OF_HOUR), closeTo(129600.0, 0.000001));
1187+
assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.MINUTE_OF_HOUR), closeTo(129600.0, 0.000001));
11911188
assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.HOUR_OF_DAY), closeTo(2160.0, 0.000001));
11921189
assertThat(prepared.roundingSize(firstQuarter, Rounding.DateTimeUnit.DAY_OF_MONTH), closeTo(90.0, 0.000001));
11931190
long thirdQuarter = prepared.round(time("2015-07-01T00:00:00.000Z"));

server/src/test/java/org/elasticsearch/common/time/DateUtilsTests.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,7 @@ public void testRoundWeek() {
313313
assertThat(DateUtils.roundWeekOfWeekYear(1), is(epochMilli));
314314
assertThat(DateUtils.roundWeekOfWeekYear(-1), is(epochMilli));
315315

316-
IllegalArgumentException exc = expectThrows(IllegalArgumentException.class, () -> DateUtils.roundWeekIntervalOfWeekYear(0, -1));
317-
assertThat(exc.getMessage(), is("week interval must be strictly positive, got [-1]"));
318-
assertThat(DateUtils.roundWeekIntervalOfWeekYear(0, 3), is(epochMilli));
319-
assertThat(DateUtils.roundWeekIntervalOfWeekYear(1, 3), is(epochMilli));
320-
assertThat(DateUtils.roundWeekIntervalOfWeekYear(-1, 2), is(epochMilli));
321-
322316
epochMilli = Year.of(2025).atMonth(1).atDay(20).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
323317
assertThat(DateUtils.roundWeekOfWeekYear(1737378896000L), is(epochMilli));
324-
epochMilli = Year.of(2025).atMonth(1).atDay(13).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
325-
assertThat(DateUtils.roundWeekIntervalOfWeekYear(1737378896000L, 4), is(epochMilli));
326318
}
327319
}

x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/InternalResetTrackingRateTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void testReductionSecond() {
6161
}
6262

6363
public void testReductionMinute() {
64-
testReduction(Rounding.DateTimeUnit.MINUTES_OF_HOUR, 0.01 * MILLIS_IN_MINUTE);
64+
testReduction(Rounding.DateTimeUnit.MINUTE_OF_HOUR, 0.01 * MILLIS_IN_MINUTE);
6565
}
6666

6767
public void testReductionHour() {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigUtils.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,10 @@ public static long validateAndGetCalendarInterval(String calendarInterval) {
135135
case WEEK_OF_WEEKYEAR -> new TimeValue(7, TimeUnit.DAYS);
136136
case DAY_OF_MONTH -> new TimeValue(1, TimeUnit.DAYS);
137137
case HOUR_OF_DAY -> new TimeValue(1, TimeUnit.HOURS);
138-
case MINUTES_OF_HOUR -> new TimeValue(1, TimeUnit.MINUTES);
138+
case MINUTE_OF_HOUR -> new TimeValue(1, TimeUnit.MINUTES);
139139
case SECOND_OF_MINUTE -> new TimeValue(1, TimeUnit.SECONDS);
140-
case MONTH_OF_YEAR, YEAR_OF_CENTURY, QUARTER_OF_YEAR -> throw ExceptionsHelper.badRequestException(
141-
invalidDateHistogramCalendarIntervalMessage(calendarInterval)
142-
);
140+
case MONTH_OF_YEAR, YEAR_OF_CENTURY, QUARTER_OF_YEAR, YEARS_OF_CENTURY, MONTHS_OF_YEAR -> throw ExceptionsHelper
141+
.badRequestException(invalidDateHistogramCalendarIntervalMessage(calendarInterval));
143142
};
144143
} else {
145144
interval = TimeValue.parseTimeValue(calendarInterval, "date_histogram.calendar_interval");

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTrunc.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,14 @@ private static Rounding.Prepared createRounding(final Period period, final ZoneI
194194
// java.time.Period does not have a QUARTERLY period, so a period of 3 months
195195
// returns a quarterly rounding
196196
rounding = new Rounding.Builder(Rounding.DateTimeUnit.QUARTER_OF_YEAR);
197+
} else if (period.getMonths() == 1) {
198+
rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTH_OF_YEAR);
197199
} else if (period.getMonths() > 0) {
198-
rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTH_OF_YEAR, period.getMonths());
200+
rounding = new Rounding.Builder(Rounding.DateTimeUnit.MONTHS_OF_YEAR, period.getMonths());
201+
} else if (period.getYears() == 1) {
202+
rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEAR_OF_CENTURY);
199203
} else if (period.getYears() > 0) {
200-
rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEAR_OF_CENTURY, period.getYears());
204+
rounding = new Rounding.Builder(Rounding.DateTimeUnit.YEARS_OF_CENTURY, period.getYears());
201205
} else {
202206
throw new IllegalArgumentException("Time interval is not supported");
203207
}

0 commit comments

Comments
 (0)