Skip to content

Commit 75aee6e

Browse files
committed
improve logic, test
1 parent 5b7dd4e commit 75aee6e

File tree

4 files changed

+266
-227
lines changed

4 files changed

+266
-227
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88

99
## [Unreleased]
1010

11+
### Changed
12+
13+
- Improved datetime query handling to only check start and end datetime values when datetime is None
14+
1115
## [v5.0.0a1] - 2025-05-30
1216

1317
### Changed

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ def apply_datetime_filter(
251251
Args:
252252
search: The search object to filter.
253253
interval: Optional datetime interval to filter by. Can be:
254-
- A single datetime string
255-
- A datetime range string (e.g., "2020-01-01/2020-12-31")
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")
256256
- A datetime object
257257
- A tuple of (start_datetime, end_datetime)
258258
@@ -263,18 +263,31 @@ def apply_datetime_filter(
263263
return search
264264

265265
should = []
266-
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
267272

268273
if "eq" in datetime_search:
269274
# For exact matches, include:
270275
# 1. Items with matching exact datetime
271276
# 2. Items with datetime:null where the time falls within their range
272277
should = [
273-
Q("term", **{"properties.datetime": datetime_search["eq"]}),
278+
Q(
279+
"bool",
280+
filter=[
281+
Q("exists", field="properties.datetime"),
282+
Q("term", **{"properties__datetime": datetime_search["eq"]}),
283+
],
284+
),
274285
Q(
275286
"bool",
276287
must_not=[Q("exists", field="properties.datetime")],
277288
filter=[
289+
Q("exists", field="properties.start_datetime"),
290+
Q("exists", field="properties.end_datetime"),
278291
Q(
279292
"range",
280293
properties__start_datetime={"lte": datetime_search["eq"]},
@@ -286,23 +299,30 @@ def apply_datetime_filter(
286299
],
287300
),
288301
]
289-
return search.query(Q("bool", should=should, minimum_should_match=1))
290302
else:
291-
# For date ranges, include both:
303+
# For date ranges, include:
292304
# 1. Items with datetime in the range
305+
# 2. Items with datetime:null that overlap the search range
293306
should = [
294307
Q(
295-
"range",
296-
properties__datetime={
297-
"gte": datetime_search["gte"],
298-
"lte": datetime_search["lte"],
299-
},
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+
],
300319
),
301-
# 2. Items with datetime:null that overlap the search range
302320
Q(
303321
"bool",
304322
must_not=[Q("exists", field="properties.datetime")],
305323
filter=[
324+
Q("exists", field="properties.start_datetime"),
325+
Q("exists", field="properties.end_datetime"),
306326
Q(
307327
"range",
308328
properties__start_datetime={"lte": datetime_search["lte"]},
@@ -314,7 +334,8 @@ def apply_datetime_filter(
314334
],
315335
),
316336
]
317-
return search.query(Q("bool", should=should, minimum_should_match=1))
337+
338+
return search.query(Q("bool", should=should, minimum_should_match=1))
318339

319340
@staticmethod
320341
def apply_bbox_filter(search: Search, bbox: List):

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 82 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -282,121 +282,97 @@ def apply_free_text_filter(search: Search, free_text_queries: Optional[List[str]
282282
@staticmethod
283283
def apply_datetime_filter(
284284
search: Search, interval: Optional[Union[DateTimeType, str]]
285-
):
286-
"""Apply a filter to search based on datetime field, start_datetime, and end_datetime fields.
285+
) -> Search:
286+
"""Apply a filter to search on datetime, start_datetime, and end_datetime fields.
287287
288288
Args:
289-
search (Search): The search object to filter.
290-
interval: Optional[Union[DateTimeType, str]]
289+
search: The search object to filter.
290+
interval: Optional datetime interval to filter by. Can be:
291+
- A single datetime string (e.g., "2023-01-01T12:00:00")
292+
- A datetime range string (e.g., "2023-01-01/2023-12-31")
293+
- A datetime object
294+
- A tuple of (start_datetime, end_datetime)
291295
292296
Returns:
293-
Search: The filtered search object.
297+
The filtered search object.
294298
"""
299+
if not interval:
300+
return search
301+
295302
should = []
296-
datetime_search = return_date(interval)
303+
try:
304+
datetime_search = return_date(interval)
305+
except (ValueError, TypeError) as e:
306+
# Handle invalid interval formats if return_date fails
307+
logger.error(f"Invalid interval format: {interval}, error: {e}")
308+
return search
297309

298-
# If the request is a single datetime return
299-
# items with datetimes equal to the requested datetime OR
300-
# the requested datetime is between their start and end datetimes
301310
if "eq" in datetime_search:
302-
should.extend(
303-
[
304-
Q(
305-
"bool",
306-
filter=[
307-
Q(
308-
"term",
309-
properties__datetime=datetime_search["eq"],
310-
),
311-
],
312-
),
313-
Q(
314-
"bool",
315-
filter=[
316-
Q(
317-
"range",
318-
properties__start_datetime={
319-
"lte": datetime_search["eq"],
320-
},
321-
),
322-
Q(
323-
"range",
324-
properties__end_datetime={
325-
"gte": datetime_search["eq"],
326-
},
327-
),
328-
],
329-
),
330-
]
331-
)
332-
333-
# If the request is a date range return
334-
# items with datetimes within the requested date range OR
335-
# their startdatetime ithin the requested date range OR
336-
# their enddatetime ithin the requested date range OR
337-
# the requested daterange within their start and end datetimes
311+
# For exact matches, include:
312+
# 1. Items with matching exact datetime
313+
# 2. Items with datetime:null where the time falls within their range
314+
should = [
315+
Q(
316+
"bool",
317+
filter=[
318+
Q("exists", field="properties.datetime"),
319+
Q("term", **{"properties__datetime": datetime_search["eq"]}),
320+
],
321+
),
322+
Q(
323+
"bool",
324+
must_not=[Q("exists", field="properties.datetime")],
325+
filter=[
326+
Q("exists", field="properties.start_datetime"),
327+
Q("exists", field="properties.end_datetime"),
328+
Q(
329+
"range",
330+
properties__start_datetime={"lte": datetime_search["eq"]},
331+
),
332+
Q(
333+
"range",
334+
properties__end_datetime={"gte": datetime_search["eq"]},
335+
),
336+
],
337+
),
338+
]
338339
else:
339-
should.extend(
340-
[
341-
Q(
342-
"bool",
343-
filter=[
344-
Q(
345-
"range",
346-
properties__datetime={
347-
"gte": datetime_search["gte"],
348-
"lte": datetime_search["lte"],
349-
},
350-
),
351-
],
352-
),
353-
Q(
354-
"bool",
355-
filter=[
356-
Q(
357-
"range",
358-
properties__start_datetime={
359-
"gte": datetime_search["gte"],
360-
"lte": datetime_search["lte"],
361-
},
362-
),
363-
],
364-
),
365-
Q(
366-
"bool",
367-
filter=[
368-
Q(
369-
"range",
370-
properties__end_datetime={
371-
"gte": datetime_search["gte"],
372-
"lte": datetime_search["lte"],
373-
},
374-
),
375-
],
376-
),
377-
Q(
378-
"bool",
379-
filter=[
380-
Q(
381-
"range",
382-
properties__start_datetime={
383-
"lte": datetime_search["gte"]
384-
},
385-
),
386-
Q(
387-
"range",
388-
properties__end_datetime={
389-
"gte": datetime_search["lte"]
390-
},
391-
),
392-
],
393-
),
394-
]
395-
)
396-
397-
search = search.query(Q("bool", filter=[Q("bool", should=should)]))
398-
399-
return search
340+
# For date ranges, include:
341+
# 1. Items with datetime in the range
342+
# 2. Items with datetime:null that overlap the search range
343+
should = [
344+
Q(
345+
"bool",
346+
filter=[
347+
Q("exists", field="properties.datetime"),
348+
Q(
349+
"range",
350+
properties__datetime={
351+
"gte": datetime_search["gte"],
352+
"lte": datetime_search["lte"],
353+
},
354+
),
355+
],
356+
),
357+
Q(
358+
"bool",
359+
must_not=[Q("exists", field="properties.datetime")],
360+
filter=[
361+
Q("exists", field="properties.start_datetime"),
362+
Q("exists", field="properties.end_datetime"),
363+
Q(
364+
"range",
365+
properties__start_datetime={"lte": datetime_search["lte"]},
366+
),
367+
Q(
368+
"range",
369+
properties__end_datetime={"gte": datetime_search["gte"]},
370+
),
371+
],
372+
),
373+
]
374+
375+
return search.query(Q("bool", should=should, minimum_should_match=1))
400376

401377
@staticmethod
402378
def apply_bbox_filter(search: Search, bbox: List):

0 commit comments

Comments
 (0)