diff --git a/datacube/api/core.py b/datacube/api/core.py index 47a8c1798..a07d45008 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -232,12 +232,17 @@ def load(self, product=None, measurements=None, output_crs=None, resolution=None x=(1516200, 1541300), y=(-3867375, -3867350), crs='EPSG:3577' - The ``time`` dimension can be specified using a tuple of datetime objects or strings with - ``YYYY-MM-DD hh:mm:ss`` format. Data will be loaded inclusive of the start and finish times. E.g:: + The ``time`` dimension can be specified using a single or tuple of datetime objects or strings with + ``YYYY-MM-DD hh:mm:ss`` format. Data will be loaded inclusive of the start and finish times. + A ``None`` value in the range indicates an open range, with the provided date serving as either the + upper or lower bound. E.g:: time=('2000-01-01', '2001-12-31') time=('2000-01', '2001-12') time=('2000', '2001') + time=('2000') + time=('2000', None) # all data from 2000 onward + time=(None, '2000') # all data up to and including 2000 For 3D datasets, where the product definition contains an ``extra_dimension`` specification, these dimensions can be queried using that dimension's name. E.g.:: diff --git a/datacube/api/query.py b/datacube/api/query.py index f72a41479..fe77c9f12 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -128,8 +128,8 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc if time_coord is not None: self.search['time'] = _time_to_search_dims( (pandas_to_datetime(time_coord.values[0]).to_pydatetime(), - pandas_to_datetime(time_coord.values[-1]).to_pydatetime() - + datetime.timedelta(milliseconds=1)) # TODO: inclusive time searches + pandas_to_datetime(time_coord.values[-1]).to_pydatetime() + + datetime.timedelta(milliseconds=1)) # TODO: inclusive time searches ) @property @@ -342,7 +342,11 @@ def _time_to_search_dims(time_range): if hasattr(tr_end, 'isoformat'): tr_end = tr_end.isoformat() + if tr_start is None: + tr_start = datetime.datetime.fromtimestamp(0) start = _to_datetime(tr_start) + if tr_end is None: + tr_end = datetime.datetime.now().strftime("%Y-%m-%d") end = _to_datetime(pandas.Period(tr_end) .end_time .to_pydatetime()) @@ -354,19 +358,6 @@ 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/expression.py b/datacube/ui/expression.py index 4e9c44d56..8ef1fdf73 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -20,7 +20,7 @@ from lark import Lark, v_args, Transformer -from datacube.api.query import _time_to_search_dims, _time_to_open_range +from datacube.api.query import _time_to_search_dims from datacube.model import Range @@ -119,11 +119,10 @@ 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) + return _time_to_search_dims((date, None)) def range_upper_bound(self, date): - return _time_to_open_range(date, lower_bound=False) - + return _time_to_search_dims((None, date)) def date(self, y, m=None, d=None): return "-".join(x for x in [y, m, d] if x is not None) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index d897720a9..fe863fe8d 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -14,7 +14,7 @@ v1.8.next - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) - ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) - Add pre-commit hook to verify license headers (:pull:`1438`) -- Support open-ended date ranges in `datacube dataset search` (:pull:`1439`) +- Support open-ended date ranges in `datacube dataset search`, `dc.load`, and `dc.find_datasets` (:pull:`1439`, :pull:`1443`) v1.8.12 (7th March 2023) diff --git a/tests/api/test_query.py b/tests/api/test_query.py index 78b138908..d7d8e0b54 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -140,7 +140,11 @@ def format_test(start_out, end_out): ((datetime.datetime(2008, 1, 1), datetime.datetime(2008, 1, 10, 23, 59, 40)), format_test('2008-01-01T00:00:00', '2008-01-10T23:59:40.999999')), ((datetime.date(2008, 1, 1)), - format_test('2008-01-01T00:00:00', '2008-01-01T23:59:59.999999')) + format_test('2008-01-01T00:00:00', '2008-01-01T23:59:59.999999')), + ((datetime.date(2008, 1, 1), None), + format_test('2008-01-01T00:00:00', datetime.datetime.now().strftime("%Y-%m-%dT23:59:59.999999"))), + ((None, '2008'), + format_test(datetime.datetime.fromtimestamp(0).strftime("%Y-%m-%dT%H:%M:%S"), '2008-12-31T23:59:59.999999')) ]