88import re
99from datetime import date
1010from datetime import datetime as datetime_type
11+ from datetime import timezone
1112from typing import Dict , Optional , Union
1213
13- from stac_fastapi .types .rfc3339 import DateTimeType
14+ from stac_fastapi .types .rfc3339 import DateTimeType , rfc3339_str_to_datetime
1415
1516logger = logging .getLogger (__name__ )
1617
@@ -36,6 +37,16 @@ def return_date(
3637 dict: A dictionary representing the date interval for use in filtering search results,
3738 always containing 'gte' and 'lte' keys.
3839 """
40+
41+ def normalize_datetime (dt_str ):
42+ """Normalize datetime string and preserve millisecond precision."""
43+ if not dt_str or dt_str == ".." :
44+ return dt_str
45+ dt_obj = rfc3339_str_to_datetime (dt_str )
46+ dt_utc = dt_obj .astimezone (timezone .utc )
47+ rounded_dt = dt_utc .replace (microsecond = round (dt_utc .microsecond / 1000 ) * 1000 )
48+ return rounded_dt .isoformat (timespec = "milliseconds" ).replace ("+00:00" , "Z" )
49+
3950 result : Dict [str , Optional [str ]] = {"gte" : None , "lte" : None }
4051
4152 if interval is None :
@@ -44,29 +55,53 @@ def return_date(
4455 if isinstance (interval , str ):
4556 if "/" in interval :
4657 parts = interval .split ("/" )
47- result [ "gte" ] = (
58+ gte_value = (
4859 parts [0 ] if parts [0 ] != ".." else datetime_type .min .isoformat () + "Z"
4960 )
50- result [ "lte" ] = (
61+ lte_value = (
5162 parts [1 ]
5263 if len (parts ) > 1 and parts [1 ] != ".."
5364 else datetime_type .max .isoformat () + "Z"
5465 )
66+
67+ result ["gte" ] = (
68+ normalize_datetime (gte_value )
69+ if gte_value != datetime_type .min .isoformat () + "Z"
70+ else gte_value
71+ )
72+ result ["lte" ] = (
73+ normalize_datetime (lte_value )
74+ if lte_value != datetime_type .max .isoformat () + "Z"
75+ else lte_value
76+ )
5577 else :
56- converted_time = interval if interval != ".." else None
78+ converted_time = normalize_datetime ( interval ) if interval != ".." else ".."
5779 result ["gte" ] = result ["lte" ] = converted_time
5880 return result
5981
6082 if isinstance (interval , datetime_type ):
61- datetime_iso = interval .isoformat ()
62- result ["gte" ] = result ["lte" ] = datetime_iso
83+ datetime_str = interval .isoformat ()
84+ normalized_datetime = normalize_datetime (datetime_str )
85+ result ["gte" ] = result ["lte" ] = normalized_datetime
6386 elif isinstance (interval , tuple ):
6487 start , end = interval
6588 # Ensure datetimes are converted to UTC and formatted with 'Z'
6689 if start :
67- result ["gte" ] = start .strftime ("%Y-%m-%dT%H:%M:%S.%f" )[:- 3 ] + "Z"
90+ dt_utc = start .astimezone (timezone .utc )
91+ rounded_dt = dt_utc .replace (
92+ microsecond = round (dt_utc .microsecond / 1000 ) * 1000
93+ )
94+ result ["gte" ] = rounded_dt .isoformat (timespec = "milliseconds" ).replace (
95+ "+00:00" , "Z"
96+ )
6897 if end :
69- result ["lte" ] = end .strftime ("%Y-%m-%dT%H:%M:%S.%f" )[:- 3 ] + "Z"
98+ dt_utc = end .astimezone (timezone .utc )
99+ rounded_dt = dt_utc .replace (
100+ microsecond = round (dt_utc .microsecond / 1000 ) * 1000
101+ )
102+ result ["lte" ] = rounded_dt .isoformat (timespec = "milliseconds" ).replace (
103+ "+00:00" , "Z"
104+ )
70105
71106 return result
72107
0 commit comments