Repository: opendatacube/datacube-core

Issue:
Support open-ended ranges in dataset search
### Expected behaviour

`datacube dataset search 'time > 2021-01-01'` should product datasets more recent than 2021-01-01

### Actual behaviour

This isn't supported and an error is shown:

```
time > 2021-01-01
        ^
Expected one of:
  * EQUAL
  * IN
```



Discussion:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This seems like a nice usability addition.

Eventually people learn to work around it by specifying a really high or really low date as one end of the range, which works, but isn't particularly nice.

The fix implementing this could potentially even work in the same way.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Resolving patch:
diff --git a/datacube/api/query.py b/datacube/api/query.py
--- a/datacube/api/query.py
+++ b/datacube/api/query.py
@@ -354,6 +354,19 @@ def _time_to_search_dims(time_range):
         return tr
 
 
+def _time_to_open_range(time, lower_bound: bool):
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", UserWarning)
+
+        if lower_bound:  # from date provided (not inclusive) to latest available
+            start = _to_datetime(pandas.Period(time).end_time.to_pydatetime())
+            end = _to_datetime(datetime.datetime.now())
+        else:  # from earliest available to date provided (not inclusive)
+            start = _to_datetime(datetime.datetime.fromtimestamp(0))
+            end = _to_datetime(pandas.Period(time).start_time.to_pydatetime())
+        return Range(start, end)
+
+
 def _convert_to_solar_time(utc, longitude):
     seconds_per_degree = 240
     offset_seconds = int(longitude * seconds_per_degree)
diff --git a/datacube/ui/click.py b/datacube/ui/click.py
--- a/datacube/ui/click.py
+++ b/datacube/ui/click.py
@@ -340,16 +340,19 @@ def parsed_search_expressions(f):
         FIELD = VALUE
         FIELD in DATE-RANGE
         FIELD in [START, END]
+        TIME < DATE
+        TIME > DATE
 
     \b
-    DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD
     START and END can be either numbers or dates
+    Dates follow YYYY, YYYY-MM, or YYYY-MM-DD format
 
     FIELD: x, y, lat, lon, time, product, ...
 
     \b
     eg. 'time in [1996-01-01, 1996-12-31]'
         'time in 1996'
+        'time > 2020-01'
         'lon in [130, 140]' 'lat in [-40, -30]'
         product=ls5_nbar_albers
 
diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py
--- a/datacube/ui/expression.py
+++ b/datacube/ui/expression.py
@@ -5,20 +5,22 @@
 """
 Search expression parsing for command line applications.
 
-Three types of expressions are available:
+Four types of expressions are available:
 
     FIELD = VALUE
     FIELD in DATE-RANGE
     FIELD in [START, END]
+    TIME > DATE
+    TIME < DATE
 
-Where DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD
+Where DATE or DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD
 and START, END are either numbers or dates.
 """
 # flake8: noqa
 
 from lark import Lark, v_args, Transformer
 
-from datacube.api.query import _time_to_search_dims
+from datacube.api.query import _time_to_search_dims, _time_to_open_range
 from datacube.model import Range
 
 
@@ -27,10 +29,14 @@
     ?expression: equals_expr
                | time_in_expr
                | field_in_expr
+               | time_gt_expr
+               | time_lt_expr
 
     equals_expr: field "=" value
     time_in_expr: time "in" date_range
     field_in_expr: field "in" "[" orderable "," orderable "]"
+    time_gt_expr: time ">" date_gt
+    time_lt_expr: time "<" date_lt
 
     field: FIELD
     time: TIME
@@ -48,6 +54,10 @@
     ?date_range: date -> single_date
                | "[" date "," date "]" -> date_pair
 
+    date_gt: date -> range_lower_bound
+
+    date_lt: date -> range_upper_bound
+
     date: YEAR ["-" MONTH ["-" DAY ]]
 
     TIME: "time"
@@ -86,6 +96,12 @@ def field_in_expr(self, field, lower, upper):
 
     def time_in_expr(self, time_field, date_range):
         return {str(time_field): date_range}
+    
+    def time_gt_expr(self, time_field, date_gt):
+        return {str(time_field): date_gt}
+    
+    def time_lt_expr(self, time_field, date_lt):
+        return {str(time_field): date_lt}
 
     # Convert the literals
     def string(self, val):
@@ -101,6 +117,13 @@ def single_date(self, date):
 
     def date_pair(self, start, end):
         return _time_to_search_dims((start, end))
+    
+    def range_lower_bound(self, date):
+        return _time_to_open_range(date, lower_bound=True)
+
+    def range_upper_bound(self, date):
+        return _time_to_open_range(date, lower_bound=False)
+
 
     def date(self, y, m=None, d=None):
         return "-".join(x for x in [y, m, d] if x is not None)


Test patch: