Skip to content

Commit

Permalink
Search: add time zone setting for relative date math in range filter/…
Browse files Browse the repository at this point in the history
…query

Filters and Queries now supports `time_zone` parameter which defines which time zone should be applied to the query or filter to convert it to UTC time based value.

Closes elastic#3729.
  • Loading branch information
dadoonet committed Jul 31, 2014
1 parent 6b39aa6 commit 42d43ff
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 46 deletions.
23 changes: 23 additions & 0 deletions docs/reference/query-dsl/filters/range-filter.asciidoc
Expand Up @@ -30,6 +30,29 @@ The `range` filter accepts the following parameters:
`lte`:: Less-than or equal to
`lt`:: Less-than

When applied on `date` fields the `range` filter accepts also a `time_zone` parameter.
The `time_zone` parameter will be applied to your input lower and upper bounds and will
move them to UTC time based date:

[source,js]
--------------------------------------------------
{
"constant_score": {
"filter": {
"range" : {
"born" : {
"gte": "2012-01-01",
"lte": "now",
"time_zone": "+1:00"
}
}
}
}
}
--------------------------------------------------

In the above example, `gte` will be actually moved to `2011-12-31T23:00:00` UTC date.

[float]
==== Execution

Expand Down
18 changes: 18 additions & 0 deletions docs/reference/query-dsl/queries/range-query.asciidoc
Expand Up @@ -29,3 +29,21 @@ The `range` query accepts the following parameters:
`lt`:: Less-than
`boost`:: Sets the boost value of the query, defaults to `1.0`

When applied on `date` fields the `range` filter accepts also a `time_zone` parameter.
The `time_zone` parameter will be applied to your input lower and upper bounds and will
move them to UTC time based date:

[source,js]
--------------------------------------------------
{
"range" : {
"born" : {
"gte": "2012-01-01",
"lte": "now",
"time_zone": "+1:00"
}
}
}
--------------------------------------------------

In the above example, `gte` will be actually moved to `2011-12-31T23:00:00` UTC date.
17 changes: 17 additions & 0 deletions src/main/java/org/elasticsearch/common/joda/DateMathParser.java
Expand Up @@ -23,6 +23,7 @@
import org.joda.time.DateTimeZone;
import org.joda.time.MutableDateTime;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -260,4 +261,20 @@ private long parseRoundCeilStringValue(String value) {
}
}
}

public static DateTimeZone parseZone(String text) throws IOException {
int index = text.indexOf(':');
if (index != -1) {
int beginIndex = text.charAt(0) == '+' ? 1 : 0;
// format like -02:30
return DateTimeZone.forOffsetHoursMinutes(
Integer.parseInt(text.substring(beginIndex, index)),
Integer.parseInt(text.substring(index + 1))
);
} else {
// id, listed here: http://joda-time.sourceforge.net/timezones.html
return DateTimeZone.forID(text);
}
}

}
Expand Up @@ -51,6 +51,7 @@
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.search.NumericRangeFieldDataFilter;
import org.elasticsearch.index.similarity.SimilarityProvider;
import org.joda.time.DateTimeZone;

import java.io.IOException;
import java.util.List;
Expand Down Expand Up @@ -298,16 +299,28 @@ public long parseToMilliseconds(Object value, @Nullable QueryParseContext contex
}

public long parseToMilliseconds(Object value, @Nullable QueryParseContext context, boolean includeUpper) {
return parseToMilliseconds(value, context, includeUpper, DateTimeZone.UTC);
}

public long parseToMilliseconds(Object value, @Nullable QueryParseContext context, boolean includeUpper, DateTimeZone zone) {
if (value instanceof Number) {
return ((Number) value).longValue();
return adjustToUTCTime(((Number) value).longValue(), zone);
}
long now = context == null ? System.currentTimeMillis() : context.nowInMillis();
return includeUpper && roundCeil ? dateMathParser.parseRoundCeil(convertToString(value), now) : dateMathParser.parse(convertToString(value), now);
return parseToMilliseconds(convertToString(value), context, includeUpper, zone);
}

public long parseToMilliseconds(String value, @Nullable QueryParseContext context, boolean includeUpper) {
public long parseToMilliseconds(String value, @Nullable QueryParseContext context, boolean includeUpper, DateTimeZone zone) {
long now = context == null ? System.currentTimeMillis() : context.nowInMillis();
return includeUpper && roundCeil ? dateMathParser.parseRoundCeil(value, now) : dateMathParser.parse(value, now);
long time = includeUpper && roundCeil ? dateMathParser.parseRoundCeil(value, now) : dateMathParser.parse(value, now);
return adjustToUTCTime(time, zone);
}

public long adjustToUTCTime(long value, DateTimeZone zone) {
// Small optimization: we don't do math when not needed :)
if (DateTimeZone.UTC.equals(zone)) {
return value;
}
return value - zone.getOffset(value);
}

@Override
Expand All @@ -319,37 +332,41 @@ public Filter termFilter(Object value, @Nullable QueryParseContext context) {

@Override
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context) {
return rangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, DateTimeZone.UTC, context);
}

public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable QueryParseContext context) {
return NumericRangeQuery.newLongRange(names.indexName(), precisionStep,
lowerTerm == null ? null : parseToMilliseconds(lowerTerm, context),
upperTerm == null ? null : parseToMilliseconds(upperTerm, context, includeUpper),
lowerTerm == null ? null : parseToMilliseconds(lowerTerm, context, false, timeZone == null ? DateTimeZone.UTC : timeZone),
upperTerm == null ? null : parseToMilliseconds(upperTerm, context, includeUpper, timeZone == null ? DateTimeZone.UTC : timeZone),
includeLower, includeUpper);
}

@Override
public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context) {
return rangeFilter(lowerTerm, upperTerm, includeLower, includeUpper, context, false);
return rangeFilter(lowerTerm, upperTerm, includeLower, includeUpper, DateTimeZone.UTC, context, false);
}

public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context, boolean explicitCaching) {
public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable QueryParseContext context, boolean explicitCaching) {
boolean cache = explicitCaching;
Long lowerVal = null;
Long upperVal = null;
if (lowerTerm != null) {
if (lowerTerm instanceof Number) {
lowerVal = ((Number) lowerTerm).longValue();
lowerVal = adjustToUTCTime(((Number) lowerTerm).longValue(), timeZone == null ? DateTimeZone.UTC : timeZone);
} else {
String value = convertToString(lowerTerm);
cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
lowerVal = parseToMilliseconds(value, context, false);
lowerVal = parseToMilliseconds(value, context, false, timeZone == null ? DateTimeZone.UTC : timeZone);
}
}
if (upperTerm != null) {
if (upperTerm instanceof Number) {
upperVal = ((Number) upperTerm).longValue();
upperVal = adjustToUTCTime(((Number) upperTerm).longValue(), timeZone == null ? DateTimeZone.UTC : timeZone);
} else {
String value = convertToString(upperTerm);
cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
upperVal = parseToMilliseconds(value, context, includeUpper);
upperVal = parseToMilliseconds(value, context, includeUpper, timeZone == null ? DateTimeZone.UTC : timeZone);
}
}

Expand All @@ -367,29 +384,29 @@ public Filter rangeFilter(Object lowerTerm, Object upperTerm, boolean includeLow

@Override
public Filter rangeFilter(QueryParseContext parseContext, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context) {
return rangeFilter(parseContext, lowerTerm, upperTerm, includeLower, includeUpper, context, false);
return rangeFilter(parseContext, lowerTerm, upperTerm, includeLower, includeUpper, DateTimeZone.UTC, context, false);
}

public Filter rangeFilter(QueryParseContext parseContext, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable QueryParseContext context, boolean explicitCaching) {
public Filter rangeFilter(QueryParseContext parseContext, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @Nullable DateTimeZone timeZone, @Nullable QueryParseContext context, boolean explicitCaching) {
boolean cache = explicitCaching;
Long lowerVal = null;
Long upperVal = null;
if (lowerTerm != null) {
if (lowerTerm instanceof Number) {
lowerVal = ((Number) lowerTerm).longValue();
lowerVal = adjustToUTCTime(((Number) lowerTerm).longValue(), timeZone == null ? DateTimeZone.UTC : timeZone);
} else {
String value = convertToString(lowerTerm);
cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
lowerVal = parseToMilliseconds(value, context, false);
lowerVal = parseToMilliseconds(value, context, false, timeZone == null ? DateTimeZone.UTC : timeZone);
}
}
if (upperTerm != null) {
if (upperTerm instanceof Number) {
upperVal = ((Number) upperTerm).longValue();
upperVal = adjustToUTCTime(((Number) upperTerm).longValue(), timeZone == null ? DateTimeZone.UTC : timeZone);
} else {
String value = convertToString(upperTerm);
cache = explicitCaching || !hasNowExpressionWithNoRounding(value);
upperVal = parseToMilliseconds(value, context, includeUpper);
upperVal = parseToMilliseconds(value, context, includeUpper, timeZone == null ? DateTimeZone.UTC : timeZone);
}
}

Expand Down
Expand Up @@ -35,6 +35,7 @@ public class RangeFilterBuilder extends BaseFilterBuilder {
private Object from;

private Object to;
private String timeZone;

private boolean includeLower = true;

Expand Down Expand Up @@ -371,13 +372,24 @@ public RangeFilterBuilder setExecution(String execution) {
return this;
}

/**
* In case of date field, we can adjust the from/to fields using a timezone
*/
public RangeFilterBuilder timeZone(String preZone) {
this.timeZone = preZone;
return this;
}

@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(RangeFilterParser.NAME);

builder.startObject(name);
builder.field("from", from);
builder.field("to", to);
if (timeZone != null) {
builder.field("time_zone", timeZone);
}
builder.field("include_lower", includeLower);
builder.field("include_upper", includeUpper);
builder.endObject();
Expand All @@ -397,4 +409,4 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep

builder.endObject();
}
}
}
14 changes: 11 additions & 3 deletions src/main/java/org/elasticsearch/index/query/RangeFilterParser.java
Expand Up @@ -22,13 +22,15 @@
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.TermRangeFilter;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.cache.filter.support.CacheKeyFilter;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import org.joda.time.DateTimeZone;

import java.io.IOException;

Expand Down Expand Up @@ -61,6 +63,7 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
Object to = null;
boolean includeLower = true;
boolean includeUpper = true;
DateTimeZone timeZone = null;
String execution = "index";

String filterName = null;
Expand Down Expand Up @@ -95,6 +98,8 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
} else if ("lte".equals(currentFieldName) || "le".equals(currentFieldName)) {
to = parser.objectBytes();
includeUpper = true;
} else if ("time_zone".equals(currentFieldName) || "timeZone".equals(currentFieldName)) {
timeZone = DateMathParser.parseZone(parser.text());
} else {
throw new QueryParsingException(parseContext.index(), "[range] filter does not support [" + currentFieldName + "]");
}
Expand Down Expand Up @@ -130,8 +135,11 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
}
FieldMapper mapper = smartNameFieldMappers.mapper();
if (mapper instanceof DateFieldMapper) {
filter = ((DateFieldMapper) mapper).rangeFilter(from, to, includeLower, includeUpper, parseContext, explicitlyCached);
filter = ((DateFieldMapper) mapper).rangeFilter(from, to, includeLower, includeUpper, timeZone, parseContext, explicitlyCached);
} else {
if (timeZone != null) {
throw new QueryParsingException(parseContext.index(), "[range] time_zone can not be applied to non date field [" + fieldName + "]");
}
filter = mapper.rangeFilter(from, to, includeLower, includeUpper, parseContext);
}
} else if ("fielddata".equals(execution)) {
Expand All @@ -143,7 +151,7 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
throw new QueryParsingException(parseContext.index(), "[range] filter field [" + fieldName + "] is not a numeric type");
}
if (mapper instanceof DateFieldMapper) {
filter = ((DateFieldMapper) mapper).rangeFilter(parseContext, from, to, includeLower, includeUpper, parseContext, explicitlyCached);
filter = ((DateFieldMapper) mapper).rangeFilter(parseContext, from, to, includeLower, includeUpper, timeZone, parseContext, explicitlyCached);
} else {
filter = ((NumberFieldMapper) mapper).rangeFilter(parseContext, from, to, includeLower, includeUpper, parseContext);
}
Expand All @@ -170,4 +178,4 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
}
return filter;
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java
Expand Up @@ -35,6 +35,7 @@ public class RangeQueryBuilder extends BaseQueryBuilder implements MultiTermQuer
private Object from;

private Object to;
private String timeZone;

private boolean includeLower = true;

Expand Down Expand Up @@ -398,12 +399,23 @@ public RangeQueryBuilder queryName(String queryName) {
return this;
}

/**
* In case of date field, we can adjust the from/to fields using a timezone
*/
public RangeQueryBuilder timeZone(String preZone) {
this.timeZone = preZone;
return this;
}

@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(RangeQueryParser.NAME);
builder.startObject(name);
builder.field("from", from);
builder.field("to", to);
if (timeZone != null) {
builder.field("time_zone", timeZone);
}
builder.field("include_lower", includeLower);
builder.field("include_upper", includeUpper);
if (boost != -1) {
Expand Down

0 comments on commit 42d43ff

Please sign in to comment.