@@ -259,121 +259,97 @@ def apply_collections_filter(search: Search, collection_ids: List[str]):
259259 @staticmethod
260260 def apply_datetime_filter (
261261 search : Search , interval : Optional [Union [DateTimeType , str ]]
262- ):
262+ ) -> Search :
263263 """Apply a filter to search on datetime, start_datetime, and end_datetime fields.
264264
265265 Args:
266- search (Search): The search object to filter.
267- interval: Optional[Union[DateTimeType, str]]
266+ search: The search object to filter.
267+ interval: Optional datetime interval to filter by. Can be:
268+ - A single datetime string (e.g., "2023-01-01T12:00:00")
269+ - A datetime range string (e.g., "2023-01-01/2023-12-31")
270+ - A datetime object
271+ - A tuple of (start_datetime, end_datetime)
268272
269273 Returns:
270- Search: The filtered search object.
274+ The filtered search object.
271275 """
276+ if not interval :
277+ return search
278+
272279 should = []
273- datetime_search = return_date (interval )
280+ try :
281+ datetime_search = return_date (interval )
282+ except (ValueError , TypeError ) as e :
283+ # Handle invalid interval formats if return_date fails
284+ logger .error (f"Invalid interval format: { interval } , error: { e } " )
285+ return search
274286
275- # If the request is a single datetime return
276- # items with datetimes equal to the requested datetime OR
277- # the requested datetime is between their start and end datetimes
278287 if "eq" in datetime_search :
279- should .extend (
280- [
281- Q (
282- "bool" ,
283- filter = [
284- Q (
285- "term" ,
286- properties__datetime = datetime_search ["eq" ],
287- ),
288- ],
289- ),
290- Q (
291- "bool" ,
292- filter = [
293- Q (
294- "range" ,
295- properties__start_datetime = {
296- "lte" : datetime_search ["eq" ],
297- },
298- ),
299- Q (
300- "range" ,
301- properties__end_datetime = {
302- "gte" : datetime_search ["eq" ],
303- },
304- ),
305- ],
306- ),
307- ]
308- )
309-
310- # If the request is a date range return
311- # items with datetimes within the requested date range OR
312- # their startdatetime ithin the requested date range OR
313- # their enddatetime ithin the requested date range OR
314- # the requested daterange within their start and end datetimes
288+ # For exact matches, include:
289+ # 1. Items with matching exact datetime
290+ # 2. Items with datetime:null where the time falls within their range
291+ should = [
292+ Q (
293+ "bool" ,
294+ filter = [
295+ Q ("exists" , field = "properties.datetime" ),
296+ Q ("term" , ** {"properties__datetime" : datetime_search ["eq" ]}),
297+ ],
298+ ),
299+ Q (
300+ "bool" ,
301+ must_not = [Q ("exists" , field = "properties.datetime" )],
302+ filter = [
303+ Q ("exists" , field = "properties.start_datetime" ),
304+ Q ("exists" , field = "properties.end_datetime" ),
305+ Q (
306+ "range" ,
307+ properties__start_datetime = {"lte" : datetime_search ["eq" ]},
308+ ),
309+ Q (
310+ "range" ,
311+ properties__end_datetime = {"gte" : datetime_search ["eq" ]},
312+ ),
313+ ],
314+ ),
315+ ]
315316 else :
316- should .extend (
317- [
318- Q (
319- "bool" ,
320- filter = [
321- Q (
322- "range" ,
323- properties__datetime = {
324- "gte" : datetime_search ["gte" ],
325- "lte" : datetime_search ["lte" ],
326- },
327- ),
328- ],
329- ),
330- Q (
331- "bool" ,
332- filter = [
333- Q (
334- "range" ,
335- properties__start_datetime = {
336- "gte" : datetime_search ["gte" ],
337- "lte" : datetime_search ["lte" ],
338- },
339- ),
340- ],
341- ),
342- Q (
343- "bool" ,
344- filter = [
345- Q (
346- "range" ,
347- properties__end_datetime = {
348- "gte" : datetime_search ["gte" ],
349- "lte" : datetime_search ["lte" ],
350- },
351- ),
352- ],
353- ),
354- Q (
355- "bool" ,
356- filter = [
357- Q (
358- "range" ,
359- properties__start_datetime = {
360- "lte" : datetime_search ["gte" ]
361- },
362- ),
363- Q (
364- "range" ,
365- properties__end_datetime = {
366- "gte" : datetime_search ["lte" ]
367- },
368- ),
369- ],
370- ),
371- ]
372- )
373-
374- search = search .query (Q ("bool" , filter = [Q ("bool" , should = should )]))
317+ # For date ranges, include:
318+ # 1. Items with datetime in the range
319+ # 2. Items with datetime:null that overlap the search range
320+ should = [
321+ Q (
322+ "bool" ,
323+ filter = [
324+ Q ("exists" , field = "properties.datetime" ),
325+ Q (
326+ "range" ,
327+ properties__datetime = {
328+ "gte" : datetime_search ["gte" ],
329+ "lte" : datetime_search ["lte" ],
330+ },
331+ ),
332+ ],
333+ ),
334+ Q (
335+ "bool" ,
336+ must_not = [Q ("exists" , field = "properties.datetime" )],
337+ filter = [
338+ Q ("exists" , field = "properties.start_datetime" ),
339+ Q ("exists" , field = "properties.end_datetime" ),
340+ Q (
341+ "range" ,
342+ properties__start_datetime = {"lte" : datetime_search ["lte" ]},
343+ ),
344+ Q (
345+ "range" ,
346+ properties__end_datetime = {"gte" : datetime_search ["gte" ]},
347+ ),
348+ ],
349+ ),
350+ ]
375351
376- return search
352+ return search . query ( Q ( "bool" , should = should , minimum_should_match = 1 ))
377353
378354 @staticmethod
379355 def apply_bbox_filter (search : Search , bbox : List ):
0 commit comments