feat: add is_nth_weekday() and parse_occurrence() for scheduled comment gating
This commit is contained in:
parent
a06582c028
commit
ca9b2ac0b2
2 changed files with 96 additions and 0 deletions
45
app/services/platforms/reddit_comment.py
Normal file
45
app/services/platforms/reddit_comment.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import date, timedelta
|
||||
|
||||
import httpx
|
||||
|
||||
from app.services.platforms.base import PostingStrategy, PostResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Weekday names → int (0=Mon, 6=Sun)
|
||||
_WEEKDAY_MAP = {
|
||||
"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3,
|
||||
"friday": 4, "saturday": 5, "sunday": 6,
|
||||
}
|
||||
# Ordinal names → n
|
||||
_ORDINAL_MAP = {"first": 1, "second": 2, "third": 3}
|
||||
|
||||
|
||||
def is_nth_weekday(dt: date, weekday: int, n: int) -> bool:
|
||||
"""True if dt is the nth occurrence of weekday (0=Mon, 6=Sun) in its month."""
|
||||
first_of_month = dt.replace(day=1)
|
||||
days_until = (weekday - first_of_month.weekday()) % 7
|
||||
first_occurrence = first_of_month + timedelta(days=days_until)
|
||||
nth_occurrence = first_occurrence + timedelta(weeks=n - 1)
|
||||
return dt == nth_occurrence
|
||||
|
||||
|
||||
def parse_occurrence(occurrence: str | None) -> tuple[int, int] | None:
|
||||
"""Parse an occurrence string into (weekday, n) or None for 'every'.
|
||||
|
||||
Supported: "first_sunday", "second_monday", "third_friday", etc.
|
||||
Returns None for "every" or None input.
|
||||
Raises ValueError for unrecognised patterns.
|
||||
"""
|
||||
if occurrence is None or occurrence == "every":
|
||||
return None
|
||||
parts = occurrence.lower().split("_", 1)
|
||||
if len(parts) != 2:
|
||||
raise ValueError(f"Unrecognised occurrence format: {occurrence!r}")
|
||||
ordinal, weekday_name = parts
|
||||
if ordinal not in _ORDINAL_MAP or weekday_name not in _WEEKDAY_MAP:
|
||||
raise ValueError(f"Unrecognised occurrence: {occurrence!r}")
|
||||
return _WEEKDAY_MAP[weekday_name], _ORDINAL_MAP[ordinal]
|
||||
51
tests/services/platforms/test_reddit_comment.py
Normal file
51
tests/services/platforms/test_reddit_comment.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from datetime import date
|
||||
from app.services.platforms.reddit_comment import is_nth_weekday, parse_occurrence
|
||||
|
||||
|
||||
# --- is_nth_weekday ---
|
||||
|
||||
def test_first_sunday_of_april_2026():
|
||||
# 2026-04-05 is the first Sunday of April 2026
|
||||
assert is_nth_weekday(date(2026, 4, 5), weekday=6, n=1) is True
|
||||
|
||||
|
||||
def test_second_sunday_of_april_2026_is_not_first():
|
||||
assert is_nth_weekday(date(2026, 4, 12), weekday=6, n=1) is False
|
||||
|
||||
|
||||
def test_first_sunday_of_may_2026():
|
||||
# 2026-05-03 is the first Sunday of May 2026
|
||||
assert is_nth_weekday(date(2026, 5, 3), weekday=6, n=1) is True
|
||||
|
||||
|
||||
def test_last_friday_not_matched_by_first():
|
||||
# 2026-04-24 is the last Friday of April — not the first
|
||||
assert is_nth_weekday(date(2026, 4, 24), weekday=4, n=1) is False
|
||||
|
||||
|
||||
def test_first_friday_of_april_2026():
|
||||
# 2026-04-03 is the first Friday
|
||||
assert is_nth_weekday(date(2026, 4, 3), weekday=4, n=1) is True
|
||||
|
||||
|
||||
# --- parse_occurrence ---
|
||||
|
||||
def test_parse_occurrence_every_returns_none():
|
||||
assert parse_occurrence("every") is None
|
||||
|
||||
|
||||
def test_parse_occurrence_none_returns_none():
|
||||
assert parse_occurrence(None) is None
|
||||
|
||||
|
||||
def test_parse_occurrence_first_sunday():
|
||||
weekday, n = parse_occurrence("first_sunday")
|
||||
assert weekday == 6 and n == 1
|
||||
|
||||
|
||||
def test_parse_occurrence_unknown_raises():
|
||||
try:
|
||||
parse_occurrence("fourth_wednesday")
|
||||
assert False, "Expected ValueError"
|
||||
except ValueError:
|
||||
pass
|
||||
Loading…
Reference in a new issue