Skip to content

Commit ae81078

Browse files
authored
Update datetime filter (#396)
**Related Issue(s):** - #391 - #403 **Description:** - Improved datetime query handling to only check start and end datetime values when datetime is None **PR Checklist:** - [x] Code is formatted and linted (run `pre-commit run --all-files`) - [x] Tests pass (run `make test`) - [x] Documentation has been updated to reflect changes, if applicable - [x] Changes are added to the changelog
1 parent 8a6a3e6 commit ae81078

File tree

4 files changed

+351
-247
lines changed

4 files changed

+351
-247
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1414

1515
### Changed
1616

17+
- Improved datetime query handling to only check start and end datetime values when datetime is None [#396](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/396)
1718
- Optimize data_loader.py script [#395](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/395)
1819
- Refactored test configuration to use shared app config pattern [#399](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/399)
1920

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 81 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -245,121 +245,97 @@ def apply_collections_filter(search: Search, collection_ids: List[str]):
245245
@staticmethod
246246
def apply_datetime_filter(
247247
search: Search, interval: Optional[Union[DateTimeType, str]]
248-
):
248+
) -> Search:
249249
"""Apply a filter to search on datetime, start_datetime, and end_datetime fields.
250250
251251
Args:
252-
search (Search): The search object to filter.
253-
interval: Optional[Union[DateTimeType, str]]
252+
search: The search object to filter.
253+
interval: Optional datetime interval to filter by. Can be:
254+
- A single datetime string (e.g., "2023-01-01T12:00:00")
255+
- A datetime range string (e.g., "2023-01-01/2023-12-31")
256+
- A datetime object
257+
- A tuple of (start_datetime, end_datetime)
254258
255259
Returns:
256-
Search: The filtered search object.
260+
The filtered search object.
257261
"""
262+
if not interval:
263+
return search
264+
258265
should = []
259-
datetime_search = return_date(interval)
266+
try:
267+
datetime_search = return_date(interval)
268+
except (ValueError, TypeError) as e:
269+
# Handle invalid interval formats if return_date fails
270+
logger.error(f"Invalid interval format: {interval}, error: {e}")
271+
return search
260272

261-
# If the request is a single datetime return
262-
# items with datetimes equal to the requested datetime OR
263-
# the requested datetime is between their start and end datetimes
264273
if "eq" in datetime_search:
265-
should.extend(
266-
[
267-
Q(
268-
"bool",
269-
filter=[
270-
Q(
271-
"term",
272-
properties__datetime=datetime_search["eq"],
273-
),
274-
],
275-
),
276-
Q(
277-
"bool",
278-
filter=[
279-
Q(
280-
"range",
281-
properties__start_datetime={
282-
"lte": datetime_search["eq"],
283-
},
284-
),
285-
Q(
286-
"range",
287-
properties__end_datetime={
288-
"gte": datetime_search["eq"],
289-
},
290-
),
291-
],
292-
),
293-
]
294-
)
295-
296-
# If the request is a date range return
297-
# items with datetimes within the requested date range OR
298-
# their startdatetime ithin the requested date range OR
299-
# their enddatetime ithin the requested date range OR
300-
# the requested daterange within their start and end datetimes
274+
# For exact matches, include:
275+
# 1. Items with matching exact datetime
276+
# 2. Items with datetime:null where the time falls within their range
277+
should = [
278+
Q(
279+
"bool",
280+
filter=[
281+
Q("exists", field="properties.datetime"),
282+
Q("term", **{"properties__datetime": datetime_search["eq"]}),
283+
],
284+
),
285+
Q(
286+
"bool",
287+
must_not=[Q("exists", field="properties.datetime")],
288+
filter=[
289+
Q("exists", field="properties.start_datetime"),
290+
Q("exists", field="properties.end_datetime"),
291+
Q(
292+
"range",
293+
properties__start_datetime={"lte": datetime_search["eq"]},
294+
),
295+
Q(
296+
"range",
297+
properties__end_datetime={"gte": datetime_search["eq"]},
298+
),
299+
],
300+
),
301+
]
301302
else:
302-
should.extend(
303-
[
304-
Q(
305-
"bool",
306-
filter=[
307-
Q(
308-
"range",
309-
properties__datetime={
310-
"gte": datetime_search["gte"],
311-
"lte": datetime_search["lte"],
312-
},
313-
),
314-
],
315-
),
316-
Q(
317-
"bool",
318-
filter=[
319-
Q(
320-
"range",
321-
properties__start_datetime={
322-
"gte": datetime_search["gte"],
323-
"lte": datetime_search["lte"],
324-
},
325-
),
326-
],
327-
),
328-
Q(
329-
"bool",
330-
filter=[
331-
Q(
332-
"range",
333-
properties__end_datetime={
334-
"gte": datetime_search["gte"],
335-
"lte": datetime_search["lte"],
336-
},
337-
),
338-
],
339-
),
340-
Q(
341-
"bool",
342-
filter=[
343-
Q(
344-
"range",
345-
properties__start_datetime={
346-
"lte": datetime_search["gte"]
347-
},
348-
),
349-
Q(
350-
"range",
351-
properties__end_datetime={
352-
"gte": datetime_search["lte"]
353-
},
354-
),
355-
],
356-
),
357-
]
358-
)
359-
360-
search = search.query(Q("bool", filter=[Q("bool", should=should)]))
361-
362-
return search
303+
# For date ranges, include:
304+
# 1. Items with datetime in the range
305+
# 2. Items with datetime:null that overlap the search range
306+
should = [
307+
Q(
308+
"bool",
309+
filter=[
310+
Q("exists", field="properties.datetime"),
311+
Q(
312+
"range",
313+
properties__datetime={
314+
"gte": datetime_search["gte"],
315+
"lte": datetime_search["lte"],
316+
},
317+
),
318+
],
319+
),
320+
Q(
321+
"bool",
322+
must_not=[Q("exists", field="properties.datetime")],
323+
filter=[
324+
Q("exists", field="properties.start_datetime"),
325+
Q("exists", field="properties.end_datetime"),
326+
Q(
327+
"range",
328+
properties__start_datetime={"lte": datetime_search["lte"]},
329+
),
330+
Q(
331+
"range",
332+
properties__end_datetime={"gte": datetime_search["gte"]},
333+
),
334+
],
335+
),
336+
]
337+
338+
return search.query(Q("bool", should=should, minimum_should_match=1))
363339

364340
@staticmethod
365341
def apply_bbox_filter(search: Search, bbox: List):

0 commit comments

Comments
 (0)