@@ -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