Skip to content

Conversation

@chetmancini
Copy link
Owner

Summary

  • Optimize workdays_between from O(n) to O(1) using mathematical weekday calculation
  • Unify holidays parameter to Iterable[date] across business day functions for flexibility
  • Add missing constants to exports: DAYS_IN_MONTH_MAX, MONTHS_IN_YEAR, QUARTERS_IN_YEAR, WEEKDAYS_IN_WEEK
  • Expand documentation for pretty_date and time_until_next_occurrence with clearer examples

Test plan

  • All 112 tests pass
  • Linting and type checking pass
  • Verified O(1) calculation with 25-year date range
  • Tested with different Iterable types (list, set, tuple, generator)

…eter, expand exports, and enhance docs

- Optimize workdays_between from O(n) to O(1) using mathematical weekday calculation
- Unify holidays parameter to Iterable[date] across all business day functions (allows sets for O(1) lookup)
- Add missing constants to __all__: DAYS_IN_MONTH_MAX, MONTHS_IN_YEAR, QUARTERS_IN_YEAR, WEEKDAYS_IN_WEEK
- Expand pretty_date docstring with all return formats and examples for each input type
- Clarify time_until_next_occurrence behavior with detailed timezone handling and scheduling examples

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@chetmancini chetmancini merged commit 07ce27b into master Jan 25, 2026
6 checks passed
@chetmancini chetmancini deleted the chetmancini/nairobi branch January 25, 2026 17:38
@claude
Copy link

claude bot commented Jan 25, 2026

Code review

I found 2 issues during code review:

Issue 1: Bug - Duplicate holidays are counted multiple times

Location: dateutils/dateutils.py lines 867-870

Problem: The refactored workdays_between function no longer deduplicates holidays before processing them. The original implementation converted holidays to a set (holidays_set = set(holidays)), which automatically removed duplicates. The new implementation iterates directly over the holidays iterable without deduplication.

Example of incorrect behavior:

from datetime import date

start = date(2024, 7, 1)  # Monday
end = date(2024, 7, 5)    # Friday

# Duplicate holiday in the list
holidays_with_dups = [date(2024, 7, 4), date(2024, 7, 4)]

# Expected: 4 workdays (Mon, Tue, Wed, Fri - excluding July 4th once)
# Actual with bug: 3 workdays (subtracts July 4th TWICE)
result = workdays_between(start, end, holidays=holidays_with_dups)

Fix: Convert the holidays iterable to a set before iterating, consistent with how add_business_days handles it at line 917:

# Subtract holidays that fall on weekdays within the range
if holidays is not None:
    holidays_set = set(holidays)
    for holiday in holidays_set:
        if start_date <= holiday <= end_date and holiday.weekday() < WEEKDAYS_IN_WEEK:
            workdays -= 1

References:

  • Original implementation with deduplication:
    def workdays_between(start_date: date, end_date: date, holidays: list[date] | None = None) -> int:
    """
    Count workdays (Monday-Friday) between two dates, inclusive.
    Optionally excludes specified holidays. For basic US fixed holidays, you
    can generate them using `get_us_federal_holidays()`.
    Args:
    start_date: The start date (inclusive).
    end_date: The end date (inclusive).
    holidays: Optional list of holiday dates to exclude.
    Returns:
  • New implementation without deduplication:
    if holidays is not None:
    for holiday in holidays:
    if start_date <= holiday <= end_date and holiday.weekday() < WEEKDAYS_IN_WEEK:
    workdays -= 1

Issue 2: CLAUDE.md violation - Missing tests for new Iterable parameter types

Location: tests/test_dateutils.py (tests not added)

Problem: The PR changes the holidays parameter type from list[date] to Iterable[date] for 4 business day functions (workdays_between, add_business_days, next_business_day, previous_business_day), and the updated docstrings explicitly recommend using sets for O(1) lookup performance. However, no tests were added to validate that these functions work correctly with different iterable types (set, tuple, frozenset, generator).

CLAUDE.md rule: The Testing Strategy section requires "comprehensive testing" where "tests validate both success cases and error conditions." When a function's type signature changes to accept new input types, and the documentation actively recommends using those types, comprehensive testing demands validating those types work correctly.

Precedent: PR #21 established the pattern - when the same type change was made to is_business_day, comprehensive tests were added to validate set, tuple, and frozenset types (see test_dateutils.py:1683-1696).

Recommended fix: Add tests similar to the is_business_day pattern to validate that the 4 modified functions work correctly with different iterable types (set, tuple, frozenset, generator).


Checked for bugs and CLAUDE.md compliance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants