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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions