Skip to content

Commit 0ff61e1

Browse files
committed
Add time_zone setting for query_string
Query String query now supports a new `time_zone` option based on JODA time zones. When using a range on date field, the time zone is applied. ```json { "query": { "query_string": { "text": "date:[2012 TO 2014]", "timezone": "Europe/Paris" } } } ``` Closes #7880.
1 parent 65d4046 commit 0ff61e1

File tree

10 files changed

+106
-5
lines changed

10 files changed

+106
-5
lines changed

docs/reference/query-dsl/queries/query-string-query.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ providing text to a numeric field) to be ignored.
7474

7575
|`locale` | Locale that should be used for string conversions.
7676
Defaults to `ROOT`.
77+
78+
|`time_zone` | Time Zone to be applied to any range query related to dates. See also
79+
http://joda-time.sourceforge.net/api-release/org/joda/time/DateTimeZone.html[JODA timezone].
7780
|=======================================================================
7881

7982
When a multi term query is being generated, one can control how it gets

src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ public void reset(QueryParserSettings settings) {
126126
setFuzzyMinSim(settings.fuzzyMinSim());
127127
setFuzzyPrefixLength(settings.fuzzyPrefixLength());
128128
setLocale(settings.locale());
129+
if (settings.timeZone() != null) {
130+
setTimeZone(settings.timeZone().toTimeZone());
131+
}
129132
this.analyzeWildcard = settings.analyzeWildcard();
130133
}
131134

src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.lucene.analysis.Analyzer;
2424
import org.apache.lucene.search.FuzzyQuery;
2525
import org.apache.lucene.search.MultiTermQuery;
26+
import org.joda.time.DateTimeZone;
2627

2728
import java.util.Collection;
2829
import java.util.List;
@@ -61,7 +62,7 @@ public class QueryParserSettings {
6162
private String minimumShouldMatch;
6263
private boolean lenient;
6364
private Locale locale;
64-
65+
private DateTimeZone timeZone;
6566

6667
List<String> fields = null;
6768
Collection<String> queryTypes = null;
@@ -306,6 +307,14 @@ public Locale locale() {
306307
return this.locale;
307308
}
308309

310+
public void timeZone(DateTimeZone timeZone) {
311+
this.timeZone = timeZone;
312+
}
313+
314+
public DateTimeZone timeZone() {
315+
return this.timeZone;
316+
}
317+
309318
@Override
310319
public boolean equals(Object o) {
311320
if (this == o) return true;
@@ -349,6 +358,9 @@ public boolean equals(Object o) {
349358
if (locale != null ? !locale.equals(that.locale) : that.locale != null) {
350359
return false;
351360
}
361+
if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
362+
return false;
363+
}
352364

353365
if (Float.compare(that.tieBreaker, tieBreaker) != 0) return false;
354366
if (useDisMax != that.useDisMax) return false;
@@ -385,6 +397,7 @@ public int hashCode() {
385397
result = 31 * result + (tieBreaker != +0.0f ? Float.floatToIntBits(tieBreaker) : 0);
386398
result = 31 * result + (useDisMax ? 1 : 0);
387399
result = 31 * result + (locale != null ? locale.hashCode() : 0);
400+
result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
388401
return result;
389402
}
390403
}

src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public static enum Operator {
9393

9494
private String queryName;
9595

96+
private String timeZone;
97+
9698
public QueryStringQueryBuilder(String queryString) {
9799
this.queryString = queryString;
98100
}
@@ -319,6 +321,14 @@ public QueryStringQueryBuilder locale(Locale locale) {
319321
return this;
320322
}
321323

324+
/**
325+
* In case of date field, we can adjust the from/to fields using a timezone
326+
*/
327+
public QueryStringQueryBuilder timeZone(String timeZone) {
328+
this.timeZone = timeZone;
329+
return this;
330+
}
331+
322332
@Override
323333
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
324334
builder.startObject(QueryStringQueryParser.NAME);
@@ -402,6 +412,9 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
402412
if (locale != null) {
403413
builder.field("locale", locale.toString());
404414
}
415+
if (timeZone != null) {
416+
builder.field("time_zone", timeZone);
417+
}
405418
builder.endObject();
406419
}
407420
}

src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.common.ParseField;
2929
import org.elasticsearch.common.Strings;
3030
import org.elasticsearch.common.inject.Inject;
31+
import org.elasticsearch.common.joda.DateMathParser;
3132
import org.elasticsearch.common.lucene.search.Queries;
3233
import org.elasticsearch.common.regex.Regex;
3334
import org.elasticsearch.common.settings.Settings;
@@ -191,6 +192,12 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
191192
} else if ("locale".equals(currentFieldName)) {
192193
String localeStr = parser.text();
193194
qpSettings.locale(LocaleUtils.parse(localeStr));
195+
} else if ("time_zone".equals(currentFieldName)) {
196+
try {
197+
qpSettings.timeZone(DateMathParser.parseZone(parser.text()));
198+
} catch (IllegalArgumentException e) {
199+
throw new QueryParsingException(parseContext.index(), "[query_string] time_zone [" + parser.text() + "] is unknown");
200+
}
194201
} else if ("_name".equals(currentFieldName)) {
195202
queryName = parser.text();
196203
} else {

src/test/java/org/elasticsearch/index/query/SimpleIndexQueryParserTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,21 @@ public void testQueryStringFields3() throws Exception {
250250
assertThat((double) disjuncts.get(1).getBoost(), closeTo(1, 0.01));
251251
}
252252

253+
@Test
254+
public void testQueryStringTimezone() throws Exception {
255+
IndexQueryParserService queryParser = queryParser();
256+
String query = copyToStringFromClasspath("/org/elasticsearch/index/query/query-timezone.json");
257+
Query parsedQuery = queryParser.parse(query).query();
258+
assertThat(parsedQuery, instanceOf(TermRangeQuery.class));
259+
260+
try {
261+
queryParser.parse(copyToStringFromClasspath("/org/elasticsearch/index/query/query-timezone-incorrect.json"));
262+
fail("we expect a QueryParsingException as we are providing an unknown time_zome");
263+
} catch (QueryParsingException e) {
264+
// We expect this one
265+
}
266+
}
267+
253268
@Test
254269
public void testMatchAllBuilder() throws Exception {
255270
IndexQueryParserService queryParser = queryParser();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"query_string":{
3+
"time_zone":"This timezone does not exist",
4+
"query":"date:[2012 TO 2014]"
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"query_string":{
3+
"time_zone":"Europe/Paris",
4+
"query":"date:[2012 TO 2014]"
5+
}
6+
}

src/test/java/org/elasticsearch/search/query/SimpleQueryTests.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@
4747
import org.junit.Test;
4848

4949
import java.io.IOException;
50-
import java.util.HashSet;
51-
import java.util.Locale;
52-
import java.util.Random;
53-
import java.util.Set;
50+
import java.util.*;
5451
import java.util.concurrent.ExecutionException;
5552

5653
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
@@ -557,6 +554,26 @@ public void testDateRangeInQueryString() {
557554
}
558555
}
559556

557+
@Test // https://github.com/elasticsearch/elasticsearch/issues/7880
558+
public void testDateRangeInQueryStringWithTimeZone_7880() {
559+
//the mapping needs to be provided upfront otherwise we are not sure how many failures we get back
560+
//as with dynamic mappings some shards might be lacking behind and parse a different query
561+
assertAcked(prepareCreate("test").addMapping(
562+
"type", "past", "type=date"
563+
));
564+
ensureGreen();
565+
566+
DateTimeZone timeZone = randomDateTimeZone();
567+
String now = ISODateTimeFormat.dateTime().print(new DateTime(timeZone));
568+
logger.info(" --> Using time_zone [{}], now is [{}]", timeZone.getID(), now);
569+
client().prepareIndex("test", "type", "1").setSource("past", now).get();
570+
refresh();
571+
572+
SearchResponse searchResponse = client().prepareSearch().setQuery(queryString("past:[now-1m/m TO now+1m/m]")
573+
.timeZone(timeZone.getID())).get();
574+
assertHitCount(searchResponse, 1l);
575+
}
576+
560577
@Test
561578
public void typeFilterTypeIndexedTests() throws Exception {
562579
typeFilterTests("not_analyzed");

src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import org.elasticsearch.test.client.RandomizingClient;
103103
import org.elasticsearch.test.disruption.ServiceDisruptionScheme;
104104
import org.hamcrest.Matchers;
105+
import org.joda.time.DateTimeZone;
105106
import org.junit.*;
106107

107108
import java.io.IOException;
@@ -1676,6 +1677,23 @@ public static String randomBytesFieldDataFormat() {
16761677
return randomFrom(Arrays.asList("paged_bytes", "fst", "doc_values"));
16771678
}
16781679

1680+
/**
1681+
* Returns a random JODA Time Zone based on Java Time Zones
1682+
*/
1683+
public static DateTimeZone randomDateTimeZone() {
1684+
DateTimeZone timeZone;
1685+
1686+
// It sounds like some Java Time Zones are unknown by JODA. For example: Asia/Riyadh88
1687+
// We need to fallback in that case to a known time zone
1688+
try {
1689+
timeZone = DateTimeZone.forTimeZone(randomTimeZone());
1690+
} catch (IllegalArgumentException e) {
1691+
timeZone = DateTimeZone.forOffsetHours(randomIntBetween(-12, 12));
1692+
}
1693+
1694+
return timeZone;
1695+
}
1696+
16791697
protected NumShards getNumShards(String index) {
16801698
MetaData metaData = client().admin().cluster().prepareState().get().getState().metaData();
16811699
assertThat(metaData.hasIndex(index), equalTo(true));

0 commit comments

Comments
 (0)