Repository: opendatacube/datacube-core

Issue:
Issue with time based datacube query using months as a query range
### Expected behaviour
Product search using `datacube dataset search` command based on `time` range between months should yield a range between the beginning of the month to the end of the month.

Ex. query using time range `2018-02 < time < 2018-03` is expected to result in a range,
```Range(begin=datetime.datetime(2018, 2, 1, 0, 0, tzinfo=<UTC>), end=datetime.datetime(2018, 3, 31, 23, 59, 59, 999999, tzinfo=tzutc()))```

### Actual behaviour
The query range selected is as follows:
```Range(begin=datetime.datetime(2018, 2, 1, 0, 0, tzinfo=<UTC>), end=datetime.datetime(2018, 3, 1, 23, 59, 59, 999999, tzinfo=tzutc()))```

### Steps to reproduce the behaviour
1) Load latest dea module (dea/20181015)
2) Run the following command:
`datacube dataset search product=ls5_nbar_albers '2010-01 < time < 2010-02' | grep "time: {" | sort`

### Environment information
* ``datacube --version``: 1.6.1+72.gdfc6b47a
* ``dea module``: dea/20181015



Discussion:

Resolving patch:
diff --git a/datacube/ui/click.py b/datacube/ui/click.py
--- a/datacube/ui/click.py
+++ b/datacube/ui/click.py
@@ -338,9 +338,11 @@ def parsed_search_expressions(f):
     the shell will try to interpret them.
 
     \b
-    eg. '1996-01-01 < time < 1996-12-31'
-        '130<lon<140' '-30 > lat > -40'
+    eg. 'time in [1996-01-01, 1996-12-31]'
+        'lon in [130, 140]' 'lat in [-40, -30]'
         product=ls5_nbar_albers
+
+    or (deprecated) '1996-01-01 < time < 1996-12-31'
     """
 
     def my_parse(ctx, param, value):
diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py
--- a/datacube/ui/expression.py
+++ b/datacube/ui/expression.py
@@ -38,11 +38,13 @@
 import calendar
 import re
 from datetime import datetime
+import warnings
 
 from dateutil import tz
 from pypeg2 import word, attr, List, maybe_some, parse as peg_parse
 
 from datacube.model import Range
+from datacube.api.query import _time_to_search_dims
 
 FIELD_NAME = attr('field_name', word)
 
@@ -236,7 +238,7 @@ def as_query(self):
         return {self.field_name: self.value.as_value()}
 
 
-class BetweenExpression(Expr):
+class OldBetweenExpression(Expr):
     def __init__(self, field_name=None, low_value=None, high_value=None):
         self.field_name = field_name
         self.low_value = low_value
@@ -265,11 +267,39 @@ def query_repr(self, get_field):
         )
 
     def as_query(self):
+        warnings.warn("old-style between expressions are deprecated, use 'field in [start, end]' syntax instead",
+                      DeprecationWarning)
         return {self.field_name: Range(self.low_value.as_value(), self.high_value.as_value())}
 
 
+class BetweenExpression(Expr):
+    def __init__(self, field_name=None, low_value=None, high_value=None):
+        self.field_name = field_name
+        self.low_value = low_value
+        self.high_value = high_value
+
+    range_values = [DateValue, NumericValue]
+    grammar = [
+        # field in [low, high]
+        (FIELD_NAME, 'in',
+         '[',
+         attr('low_value', range_values), ',', attr('high_value', range_values),
+         ']'),
+    ]
+
+    def __str__(self):
+        return '{} in [{!r}, {!r}]'.format(self.field_name, self.low_value, self.high_value)
+
+    def query_repr(self, get_field):
+        search_range = self.as_query()[self.field_name]
+        return get_field(self.field_name).between(search_range.begin, search_range.end)
+
+    def as_query(self):
+        return {self.field_name: _time_to_search_dims([self.low_value.value, self.high_value.value])}
+
+
 class ExpressionList(List):
-    grammar = maybe_some([EqualsExpression, BetweenExpression, InExpression])
+    grammar = maybe_some([EqualsExpression, BetweenExpression, OldBetweenExpression, InExpression])
 
     def __str__(self):
         return ' and '.join(map(str, self))


Test patch: