From a6a21af0135fb3663a5f6d5985d8150e05740f97 Mon Sep 17 00:00:00 2001 From: Joshua King Date: Mon, 14 Sep 2026 14:47:26 -0400 Subject: [PATCH] test: pre-start sweeps cover prior nominal occurrences (clamped monthly day, prior-year yearly, prior 2nd-Tuesday); MONTHLY_WEEKDAY post-end probe; end==start single-day for all six types --- tests/test_recurrence_bounds.py | 51 ++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/tests/test_recurrence_bounds.py b/tests/test_recurrence_bounds.py index 57875ca..1633470 100644 --- a/tests/test_recurrence_bounds.py +++ b/tests/test_recurrence_bounds.py @@ -108,11 +108,26 @@ BOUNDARY_RULES = [ def test_no_firing_before_start_date(index) -> None: fixture = BOUNDARY_RULES[index] rule = ScheduleRule(**fixture["kwargs"]) - first = fixture["first_firing"] - # Every day BEFORE start_date never fires — sweep the week before - # plus the full prior month. before_start = _d(rule.start_date) - sweep_from = before_start - datetime.timedelta(days=30) + # The sweep must cover the shape's PRIOR nominal occurrence: + # - MONTHLY_DAY: the prior month's clamped day (e.g. Feb 28/31) + # - YEARLY: the prior year's nominal day + # - WEEKLY/CUSTOM_DAYS: the prior week's matching weekday + # - MONTHLY_WEEKDAY: the prior month's nth weekday + if rule.rule_type is RuleType.MONTHLY_DAY: + sweep_from = _month_end(2026, 2) # Feb 28, a nominal clamp day + elif rule.rule_type is RuleType.YEARLY: + sweep_from = datetime.date( + _d(rule.start_date).year - 1, rule.month, rule.day_of_month + ) + elif rule.rule_type is RuleType.MONTHLY_WEEKDAY: + sweep_from = _tuesday(2026, 2, 2) # prior month's 2nd Tuesday + elif rule.rule_type is RuleType.WEEKLY: + sweep_from = before_start - datetime.timedelta(days=7) + elif rule.rule_type is RuleType.CUSTOM_DAYS: + sweep_from = before_start - datetime.timedelta(days=7) + else: + sweep_from = before_start - datetime.timedelta(days=30) cursor = sweep_from while cursor < before_start: assert occurs_on(rule, cursor) is False, cursor @@ -176,6 +191,15 @@ def test_no_firing_after_end_date_at_nominal_shapes() -> None: assert occurs_on(rule_weekly, _d("2026-09-29")) is True assert occurs_on(rule_weekly, _d("2026-10-06")) is False + rule_monthly_weekday = ScheduleRule( + rule_type=RuleType.MONTHLY_WEEKDAY, nth_weekday=2, + nth_weekday_weekday=1, start_date="2026-03-03", + end_date="2026-10-05", + ) + # The next nominal second Tuesday (Oct 13) is past end_date: silent. + assert occurs_on(rule_monthly_weekday, _d("2026-09-08")) is True + assert occurs_on(rule_monthly_weekday, _d("2026-10-13")) is False + def test_no_end_date_means_no_upper_bound() -> None: """A rule without end_date keeps firing arbitrarily far out — for @@ -241,12 +265,31 @@ def test_end_date_equal_to_start_date_allows_one_day_window() -> None: assert occurs_on(weekly, _d("2026-03-10")) is True assert occurs_on(weekly, _d("2026-03-17")) is False + custom = ScheduleRule( + rule_type=RuleType.CUSTOM_DAYS, weekday_set={2, 4}, + start_date="2026-03-11", end_date="2026-03-11", + ) + # Mar 11 2026 is a Wednesday: fires. Next Wed (18) is out of window. + assert _d("2026-03-11").weekday() == 2 + assert occurs_on(custom, _d("2026-03-11")) is True + assert occurs_on(custom, _d("2026-03-18")) is False + monthly_day = ScheduleRule( rule_type=RuleType.MONTHLY_DAY, day_of_month=15, start_date="2026-03-15", end_date="2026-03-15", ) assert occurs_on(monthly_day, _d("2026-03-15")) is True + monthly_weekday = ScheduleRule( + rule_type=RuleType.MONTHLY_WEEKDAY, nth_weekday=2, + nth_weekday_weekday=1, start_date="2026-03-10", + end_date="2026-03-10", + ) + # Mar 10 2026 is the second Tuesday: the one-day window admits it. + assert _d("2026-03-10").weekday() == 1 + assert occurs_on(monthly_weekday, _d("2026-03-10")) is True + assert occurs_on(monthly_weekday, _d("2026-04-14")) is False + yearly = ScheduleRule( rule_type=RuleType.YEARLY, month=3, day_of_month=15, start_date="2026-03-15", end_date="2026-03-15",