@@ -109,6 +109,11 @@ def constraint(start: datetime, end: datetime) -> DateRange:
109109 end .replace (hour = 23 , minute = 59 , second = 59 , microsecond = 999999 ),
110110 )
111111
112+ def safe_constraint (start : datetime | None , end : datetime | None ) -> DateRange | NoTemporalConstraintSentinel :
113+ if start is None or end is None :
114+ return NO_TEMPORAL_CONSTRAINT
115+ return constraint (start , end )
116+
112117 def subtract_months (months : int ) -> datetime :
113118 month_index = reference_date .month - months - 1
114119 year = reference_date .year + month_index // 12
@@ -126,11 +131,18 @@ def add_months(base_date: datetime, months: int) -> datetime:
126131 day = min (base_date .day , calendar .monthrange (year , month )[1 ])
127132 return base_date .replace (year = year , month = month , day = day )
128133
129- def add_years (base_date : datetime , years : int ) -> datetime :
134+ def add_years (base_date : datetime , years : int ) -> datetime | None :
130135 year = base_date .year + years
136+ if year < datetime .min .year or year > datetime .max .year :
137+ return None
131138 day = min (base_date .day , calendar .monthrange (year , base_date .month )[1 ])
132139 return base_date .replace (year = year , day = day )
133140
141+ def add_days (base_date : datetime | None , days : int ) -> datetime | None :
142+ if base_date is None :
143+ return None
144+ return base_date + timedelta (days = days )
145+
134146 def has_chinese_temporal_context (match : re .Match [str ]) -> bool :
135147 if match .end () >= len (query ):
136148 return True
@@ -438,6 +450,11 @@ def since_constraint(start: datetime) -> DateRange | NoTemporalConstraintSentine
438450 return NO_TEMPORAL_CONSTRAINT
439451 return constraint (start , reference_date )
440452
453+ def safe_since_constraint (start : datetime | None ) -> DateRange | NoTemporalConstraintSentinel :
454+ if start is None :
455+ return NO_TEMPORAL_CONSTRAINT
456+ return since_constraint (start )
457+
441458 def since_from_period (
442459 period : DateRange | None ,
443460 ) -> DateRange | NoTemporalConstraintSentinel | None :
@@ -450,7 +467,7 @@ def since_from_day(day: datetime | None) -> DateRange | NoTemporalConstraintSent
450467 return None
451468 return since_constraint (day )
452469
453- def relative_offset_datetime (amount : int , unit : str , direction : int ) -> datetime :
470+ def relative_offset_datetime (amount : int , unit : str , direction : int ) -> datetime | None :
454471 if unit in ("天" , "日" ):
455472 return reference_date + timedelta (days = direction * amount )
456473 if unit in ("周" , "星期" , "礼拜" ):
@@ -459,15 +476,15 @@ def relative_offset_datetime(amount: int, unit: str, direction: int) -> datetime
459476 return add_months (reference_date , direction * amount )
460477 return add_years (reference_date , direction * amount )
461478
462- def point_constraint_at_offset (amount : int , unit : str , direction : int ) -> DateRange :
479+ def point_constraint_at_offset (amount : int , unit : str , direction : int ) -> DateRange | NoTemporalConstraintSentinel :
463480 d = relative_offset_datetime (amount , unit , direction )
464- return constraint (d , d )
481+ return safe_constraint (d , d )
465482
466- def window_to_reference (amount : int , unit : str ) -> DateRange :
467- return constraint (relative_offset_datetime (amount , unit , - 1 ), reference_date )
483+ def window_to_reference (amount : int , unit : str ) -> DateRange | NoTemporalConstraintSentinel :
484+ return safe_constraint (relative_offset_datetime (amount , unit , - 1 ), reference_date )
468485
469- def window_from_reference (amount : int , unit : str ) -> DateRange :
470- return constraint (reference_date , relative_offset_datetime (amount , unit , 1 ))
486+ def window_from_reference (amount : int , unit : str ) -> DateRange | NoTemporalConstraintSentinel :
487+ return safe_constraint (reference_date , relative_offset_datetime (amount , unit , 1 ))
471488
472489 # Chinese rule guide
473490 #
@@ -781,8 +798,8 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
781798 if relative_year_fixed_day_since_match :
782799 year = relative_year_number (relative_year_fixed_day_since_match .group (1 ))
783800 base = add_years (reference_date , year - reference_date .year )
784- d = base + timedelta ( days = fixed_day_offset (relative_year_fixed_day_since_match .group (2 )))
785- return since_constraint (d )
801+ d = add_days ( base , fixed_day_offset (relative_year_fixed_day_since_match .group (2 )))
802+ return safe_since_constraint (d )
786803
787804 fixed_day_since_match = chinese_search (
788805 rf"(大大后天|大后天|后天|明天|明日|今天|今日|本日|当日|当天|昨天|昨日|大大前天|大前天|前天)"
@@ -799,7 +816,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
799816 amount = parse_chinese_number (exact_relative_since_match .group (1 ))
800817 unit = exact_relative_since_match .group (2 )
801818 if amount is not None :
802- return since_constraint (relative_offset_datetime (amount , unit , - 1 ))
819+ return safe_since_constraint (relative_offset_datetime (amount , unit , - 1 ))
803820
804821 weekend_since_match = chinese_search (
805822 rf"(?<![上下大小每个各隔])"
@@ -899,8 +916,8 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
899916 if relative_year_daypart_since_match :
900917 year = relative_year_number (relative_year_daypart_since_match .group (1 ))
901918 base = add_years (reference_date , year - reference_date .year )
902- d = base + timedelta ( days = daypart_day_offset (relative_year_daypart_since_match .group (2 )))
903- return since_constraint (d )
919+ d = add_days ( base , daypart_day_offset (relative_year_daypart_since_match .group (2 )))
920+ return safe_since_constraint (d )
904921
905922 daypart_since_match = chinese_search (
906923 rf"(昨晚|昨夜|前晚|前夜|今晚|今早|今晨|明早|明晚|明夜){ chinese_since_suffix_pattern } "
@@ -915,13 +932,13 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
915932 if relative_year_daypart_match :
916933 year = relative_year_number (relative_year_daypart_match .group (1 ))
917934 base = add_years (reference_date , year - reference_date .year )
918- d = base + timedelta ( days = daypart_day_offset (relative_year_daypart_match .group (2 )))
919- return constraint (d , d )
935+ d = add_days ( base , daypart_day_offset (relative_year_daypart_match .group (2 )))
936+ return safe_constraint (d , d )
920937
921938 # Day-part abbreviations still resolve only to date granularity.
922939 if chinese_search (r"昨晚|昨夜" ):
923940 d = reference_date + timedelta (days = daypart_day_offset ("昨晚" ))
924- return constraint (d , d )
941+ return safe_constraint (d , d )
925942
926943 if chinese_search (r"前晚|前夜" ):
927944 d = reference_date + timedelta (days = daypart_day_offset ("前晚" ))
@@ -941,7 +958,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
941958 if relative_year_fixed_day_match :
942959 year = relative_year_number (relative_year_fixed_day_match .group (1 ))
943960 base = add_years (reference_date , year - reference_date .year )
944- d = base + timedelta ( days = fixed_day_offset (relative_year_fixed_day_match .group (2 )))
961+ d = add_days ( base , fixed_day_offset (relative_year_fixed_day_match .group (2 )))
945962 return constraint (d , d )
946963
947964 if chinese_search (r"昨天|昨日" ):
@@ -1085,15 +1102,15 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
10851102 end_amount = parse_chinese_number (amount_text [- 1 ])
10861103 unit = adjacent_fuzzy_future_match .group (2 )
10871104 if start_amount is not None and end_amount is not None :
1088- return constraint (
1105+ return safe_constraint (
10891106 relative_offset_datetime (start_amount , unit , 1 ),
10901107 relative_offset_datetime (end_amount , unit , 1 ),
10911108 )
10921109
10931110 few_future_match = chinese_search (rf"[几数]个?(天|日|周|星期|礼拜|月|年){ chinese_relative_future_suffix_pattern } " )
10941111 if few_future_match :
10951112 unit = few_future_match .group (1 )
1096- return constraint (relative_offset_datetime (2 , unit , 1 ), relative_offset_datetime (5 , unit , 1 ))
1113+ return safe_constraint (relative_offset_datetime (2 , unit , 1 ), relative_offset_datetime (5 , unit , 1 ))
10971114
10981115 exact_future_match = chinese_search (
10991116 rf"(?<![{ _CHINESE_NUMERAL_PREFIX_CHARS } ])([0-9]+|[{ _CHINESE_NUMERAL_CHARS } ]+)个?(天|日|周|星期|礼拜|月|年){ chinese_relative_future_suffix_pattern } "
@@ -1113,7 +1130,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
11131130 second_amount = parse_chinese_number (adjacent_fuzzy_past_match .group (2 ))
11141131 unit = adjacent_fuzzy_past_match .group (3 )
11151132 if first_amount is not None and second_amount is not None and second_amount == first_amount + 1 :
1116- return constraint (
1133+ return safe_constraint (
11171134 relative_offset_datetime (second_amount , unit , - 1 ),
11181135 relative_offset_datetime (first_amount , unit , - 1 ),
11191136 )
@@ -1144,7 +1161,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
11441161 return constraint (reference_date - timedelta (days = 150 ), reference_date - timedelta (days = 60 ))
11451162
11461163 if chinese_search (r"一两年前|[两二]三年前|三两年前" ):
1147- return constraint (add_years (reference_date , - 3 ), add_years (reference_date , - 1 ))
1164+ return safe_constraint (add_years (reference_date , - 3 ), add_years (reference_date , - 1 ))
11481165
11491166 rolling_this_adjacent_match = chinese_search (
11501167 r"这(一两|[两二]三|三两|三四|四五|五六|六七|七八|八九|九十)个?(天|日|周|星期|礼拜|月|年)"
@@ -1154,7 +1171,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
11541171 end_amount = 3 if amount_text in ("一两" , "三两" ) else parse_chinese_number (amount_text [- 1 ])
11551172 unit = rolling_this_adjacent_match .group (2 )
11561173 if end_amount is not None :
1157- return constraint (relative_offset_datetime (end_amount , unit , - 1 ), reference_date )
1174+ return safe_constraint (relative_offset_datetime (end_amount , unit , - 1 ), reference_date )
11581175
11591176 rolling_this_count_match = chinese_search (rf"这([0-9]+|[{ _CHINESE_NUMERAL_CHARS } ]+)个?(天|日|周|星期|礼拜|月|年)" )
11601177 if rolling_this_count_match :
@@ -1191,7 +1208,7 @@ def window_from_reference(amount: int, unit: str) -> DateRange:
11911208 end_amount = 3 if amount_text in ("一两" , "三两" ) else parse_chinese_number (amount_text [- 1 ])
11921209 unit = rolling_past_adjacent_match .group (3 )
11931210 if end_amount is not None :
1194- return constraint (relative_offset_datetime (end_amount , unit , - 1 ), reference_date )
1211+ return safe_constraint (relative_offset_datetime (end_amount , unit , - 1 ), reference_date )
11951212
11961213 rolling_past_few_match = chinese_search (r"(过去|近|最近)几个?(天|日|周|星期|礼拜|月|年)" )
11971214 if rolling_past_few_match :
0 commit comments