Skip to content

fix: strict date parsing — eliminate timezone ghost and invalid date crashes #2

@EmanueleMinotto

Description

@EmanueleMinotto

Problem

Python's datetime.strptime and date.fromisoformat handle dates differently across Python versions, and the comparison with datetime.now() uses local time — so a test disabled until "2026-04-01" re-enables at different wall-clock moments depending on the runner's timezone. In Sydney it re-enables 11 hours earlier than in London.

Additionally, non-padded formats like "2026-4-1" silently parse in some contexts but raise in others.

Proposed fix

Replace all date parsing with a strict validator that:

  1. Enforces YYYY-MM-DD format with a regex — throws loudly on bad input
  2. Compares against UTC midnight of the day after the given date
import re
from datetime import timezone, datetime, timedelta

DATE_RE = re.compile(r'^\d{4}-\d{2}-\d{2}$')

def parse_disabled_until(raw: str | None, row_num: int) -> datetime | None:
    if not raw or not raw.strip():
        return None
    if not DATE_RE.match(raw.strip()):
        raise ValueError(
            f"[skipper] Row {row_num}: invalid disabledUntil '{raw}'. Use YYYY-MM-DD."
        )
    # Parse as UTC midnight of the day AFTER — disabled through end of that calendar day UTC
    d = datetime.strptime(raw.strip(), "%Y-%m-%d").replace(tzinfo=timezone.utc)
    return d + timedelta(days=1)

def is_disabled(row: dict, row_num: int) -> bool:
    until = parse_disabled_until(row.get("disabledUntil"), row_num)
    return until is not None and datetime.now(timezone.utc) < until

Acceptance criteria

  • Non-padded dates (e.g. "2026-4-1") raise a descriptive error with the row number
  • "2026-04-01" is consistently treated as UTC midnight, regardless of runner timezone
  • A test disabled until 2026-04-01 stays disabled until 2026-04-02T00:00:00Z
  • All existing tests pass
  • Semantics documented in code comment: disabled through end of that calendar day in UTC

Effort estimate

~8 lines in the core resolver. No architecture changes required.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions