Skip to content

Commit e644c09

Browse files
author
Christoph Büscher
committed
Fix use of time zone in date_histogram rewrite (#31407)
Currently, DateHistogramAggregationBuilder#rewriteTimeZone uses the aggregation date math parser and time zone to check whether all values in a read have the same timezone to speed up computation. However, the upper and lower bounds to check are retrieved as longs in epoch_millis, so they don't need to get parsed using a time zone or a parser other than "epoch_millis". This changes this behaviour that was causing problems when the field type mapping was specifying only "epoch_millis" as a format but a different timezone than UTC was used. Closes #31392
1 parent faa78e8 commit e644c09

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.apache.lucene.search.DocIdSetIterator;
2626
import org.elasticsearch.common.io.stream.StreamInput;
2727
import org.elasticsearch.common.io.stream.StreamOutput;
28+
import org.elasticsearch.common.joda.DateMathParser;
29+
import org.elasticsearch.common.joda.Joda;
2830
import org.elasticsearch.common.rounding.DateTimeUnit;
2931
import org.elasticsearch.common.rounding.Rounding;
3032
import org.elasticsearch.common.unit.TimeValue;
@@ -36,7 +38,6 @@
3638
import org.elasticsearch.index.mapper.MappedFieldType;
3739
import org.elasticsearch.index.mapper.MappedFieldType.Relation;
3840
import org.elasticsearch.index.query.QueryShardContext;
39-
import org.elasticsearch.search.DocValueFormat;
4041
import org.elasticsearch.search.aggregations.AggregationBuilder;
4142
import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
4243
import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -59,6 +60,7 @@
5960
import java.io.IOException;
6061
import java.util.HashMap;
6162
import java.util.List;
63+
import java.util.Locale;
6264
import java.util.Map;
6365
import java.util.Objects;
6466

@@ -70,6 +72,7 @@
7072
public class DateHistogramAggregationBuilder extends ValuesSourceAggregationBuilder<ValuesSource.Numeric, DateHistogramAggregationBuilder>
7173
implements MultiBucketAggregationBuilder {
7274
public static final String NAME = "date_histogram";
75+
private static DateMathParser EPOCH_MILLIS_PARSER = new DateMathParser(Joda.forPattern("epoch_millis", Locale.ROOT));
7376

7477
public static final Map<String, DateTimeUnit> DATE_FIELD_UNITS;
7578

@@ -380,7 +383,7 @@ DateTimeZone rewriteTimeZone(QueryShardContext context) throws IOException {
380383
Long anyInstant = null;
381384
final IndexNumericFieldData fieldData = context.getForField(ft);
382385
for (LeafReaderContext ctx : reader.leaves()) {
383-
AtomicNumericFieldData leafFD = ((IndexNumericFieldData) fieldData).load(ctx);
386+
AtomicNumericFieldData leafFD = fieldData.load(ctx);
384387
SortedNumericDocValues values = leafFD.getLongValues();
385388
if (values.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
386389
anyInstant = values.nextValue();
@@ -406,11 +409,8 @@ DateTimeZone rewriteTimeZone(QueryShardContext context) throws IOException {
406409
// rounding rounds down, so 'nextTransition' is a good upper bound
407410
final long high = nextTransition;
408411

409-
final DocValueFormat format = ft.docValueFormat(null, null);
410-
final Object formattedLow = format.format(low);
411-
final Object formattedHigh = format.format(high);
412-
if (ft.isFieldWithinQuery(reader, formattedLow, formattedHigh,
413-
true, false, tz, null, context) == Relation.WITHIN) {
412+
if (ft.isFieldWithinQuery(reader, low, high, true, false, DateTimeZone.UTC, EPOCH_MILLIS_PARSER,
413+
context) == Relation.WITHIN) {
414414
// All values in this reader have the same offset despite daylight saving times.
415415
// This is very common for location-based timezones such as Europe/Paris in
416416
// combination with time-based indices.

server/src/test/java/org/elasticsearch/search/aggregations/bucket/DateHistogramIT.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
import org.elasticsearch.script.Script;
3535
import org.elasticsearch.script.ScriptType;
3636
import org.elasticsearch.search.aggregations.AggregationExecutionException;
37+
import org.elasticsearch.search.aggregations.BucketOrder;
3738
import org.elasticsearch.search.aggregations.InternalAggregation;
3839
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
3940
import org.elasticsearch.search.aggregations.bucket.histogram.ExtendedBounds;
4041
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
4142
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram.Bucket;
4243
import org.elasticsearch.search.aggregations.metrics.avg.Avg;
4344
import org.elasticsearch.search.aggregations.metrics.sum.Sum;
44-
import org.elasticsearch.search.aggregations.BucketOrder;
4545
import org.elasticsearch.test.ESIntegTestCase;
4646
import org.hamcrest.Matchers;
4747
import org.joda.time.DateTime;
@@ -1341,6 +1341,38 @@ public void testExceptionOnNegativeInterval() {
13411341
}
13421342
}
13431343

1344+
/**
1345+
* https://github.com/elastic/elasticsearch/issues/31392 demonstrates an edge case where a date field mapping with
1346+
* "format" = "epoch_millis" can lead for the date histogram aggregation to throw an error if a non-UTC time zone
1347+
* with daylight savings time is used. This test was added to check this is working now
1348+
* @throws ExecutionException
1349+
* @throws InterruptedException
1350+
*/
1351+
public void testRewriteTimeZone_EpochMillisFormat() throws InterruptedException, ExecutionException {
1352+
String index = "test31392";
1353+
assertAcked(client().admin().indices().prepareCreate(index).addMapping("type", "d", "type=date,format=epoch_millis").get());
1354+
indexRandom(true, client().prepareIndex(index, "type").setSource("d", "1477954800000"));
1355+
ensureSearchable(index);
1356+
SearchResponse response = client().prepareSearch(index).addAggregation(dateHistogram("histo").field("d")
1357+
.dateHistogramInterval(DateHistogramInterval.MONTH).timeZone(DateTimeZone.forID("Europe/Berlin"))).execute().actionGet();
1358+
assertSearchResponse(response);
1359+
Histogram histo = response.getAggregations().get("histo");
1360+
assertThat(histo.getBuckets().size(), equalTo(1));
1361+
assertThat(histo.getBuckets().get(0).getKeyAsString(), equalTo("1477954800000"));
1362+
assertThat(histo.getBuckets().get(0).getDocCount(), equalTo(1L));
1363+
1364+
response = client().prepareSearch(index).addAggregation(dateHistogram("histo").field("d")
1365+
.dateHistogramInterval(DateHistogramInterval.MONTH).timeZone(DateTimeZone.forID("Europe/Berlin")).format("yyyy-MM-dd"))
1366+
.execute().actionGet();
1367+
assertSearchResponse(response);
1368+
histo = response.getAggregations().get("histo");
1369+
assertThat(histo.getBuckets().size(), equalTo(1));
1370+
assertThat(histo.getBuckets().get(0).getKeyAsString(), equalTo("2016-11-01"));
1371+
assertThat(histo.getBuckets().get(0).getDocCount(), equalTo(1L));
1372+
1373+
internalCluster().wipeIndices(index);
1374+
}
1375+
13441376
/**
13451377
* When DST ends, local time turns back one hour, so between 2am and 4am wall time we should have four buckets:
13461378
* "2015-10-25T02:00:00.000+02:00",

0 commit comments

Comments
 (0)