This commit is contained in:
+8
-3
@@ -64,8 +64,8 @@ def parse_rrule(value: str) -> dict[str, str]:
|
|||||||
raise HTTPException(422, "无效的 RRULE")
|
raise HTTPException(422, "无效的 RRULE")
|
||||||
key, val = part.split("=", 1)
|
key, val = part.split("=", 1)
|
||||||
parts[key] = val
|
parts[key] = val
|
||||||
if parts.get("FREQ") not in {"DAILY", "WEEKLY", "MONTHLY"}:
|
if parts.get("FREQ") not in {"DAILY", "WEEKLY", "MONTHLY", "YEARLY"}:
|
||||||
raise HTTPException(422, "仅支持 DAILY、WEEKLY、MONTHLY")
|
raise HTTPException(422, "仅支持 DAILY、WEEKLY、MONTHLY、YEARLY")
|
||||||
try:
|
try:
|
||||||
if "INTERVAL" in parts and int(parts["INTERVAL"]) < 1:
|
if "INTERVAL" in parts and int(parts["INTERVAL"]) < 1:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
@@ -103,10 +103,15 @@ def occurrences(rule: str, starts: datetime, start: datetime, end: datetime, cut
|
|||||||
elif parts["FREQ"] == "WEEKLY":
|
elif parts["FREQ"] == "WEEKLY":
|
||||||
days = {_WEEKDAYS[x] for x in parts.get("BYDAY", list(_WEEKDAYS)[starts.weekday()]).split(",")}
|
days = {_WEEKDAYS[x] for x in parts.get("BYDAY", list(_WEEKDAYS)[starts.weekday()]).split(",")}
|
||||||
include = cursor.weekday() in days and ((cursor.date() - starts.date()).days // 7) % interval == 0
|
include = cursor.weekday() in days and ((cursor.date() - starts.date()).days // 7) % interval == 0
|
||||||
else:
|
elif parts["FREQ"] == "MONTHLY":
|
||||||
month_delta = (cursor.year - starts.year) * 12 + cursor.month - starts.month
|
month_delta = (cursor.year - starts.year) * 12 + cursor.month - starts.month
|
||||||
month_days = {int(x) for x in parts.get("BYMONTHDAY", str(starts.day)).split(",")}
|
month_days = {int(x) for x in parts.get("BYMONTHDAY", str(starts.day)).split(",")}
|
||||||
include = month_delta % interval == 0 and cursor.day in month_days
|
include = month_delta % interval == 0 and cursor.day in month_days
|
||||||
|
else:
|
||||||
|
years = cursor.year - starts.year
|
||||||
|
months = {int(x) for x in parts.get("BYMONTH", str(starts.month)).split(",")}
|
||||||
|
month_days = {int(x) for x in parts.get("BYMONTHDAY", str(starts.day)).split(",")}
|
||||||
|
include = years % interval == 0 and cursor.month in months and cursor.day in month_days
|
||||||
if include and cursor >= starts:
|
if include and cursor >= starts:
|
||||||
emitted += 1
|
emitted += 1
|
||||||
if start <= cursor <= end:
|
if start <= cursor <= end:
|
||||||
|
|||||||
@@ -31,10 +31,18 @@ def test_recurring_calendar_exceptions_and_scopes(client):
|
|||||||
)
|
)
|
||||||
assert recurrence.status_code == 201
|
assert recurrence.status_code == 201
|
||||||
recurrence_id = recurrence.json()["id"]
|
recurrence_id = recurrence.json()["id"]
|
||||||
|
yearly = client.post(
|
||||||
|
"/api/v1/tasks",
|
||||||
|
json={"title": "年度任务", "list_id": inbox["id"], "due_at": "2026-09-06T09:00:00Z"},
|
||||||
|
).json()
|
||||||
|
assert client.post(
|
||||||
|
"/api/v1/recurrences", json={"task_id": yearly["id"], "rrule": "FREQ=YEARLY;INTERVAL=1;BYMONTH=9;BYMONTHDAY=6"}
|
||||||
|
).status_code == 201
|
||||||
calendar = client.get(
|
calendar = client.get(
|
||||||
"/api/v1/calendar", params={"start": "2026-09-01", "end": "2026-09-30"}
|
"/api/v1/calendar", params={"start": "2026-09-01", "end": "2026-09-30"}
|
||||||
).json()
|
).json()
|
||||||
assert any(row.get("id") == normal_task["id"] for row in calendar)
|
assert any(row.get("id") == normal_task["id"] for row in calendar)
|
||||||
|
assert any(row["title"] == "年度任务" for row in calendar)
|
||||||
assert not any(row["title"] == "月外普通任务" for row in calendar)
|
assert not any(row["title"] == "月外普通任务" for row in calendar)
|
||||||
occurrences = [row for row in calendar if row["recurrence_id"] == recurrence_id]
|
occurrences = [row for row in calendar if row["recurrence_id"] == recurrence_id]
|
||||||
assert len(occurrences) == 5
|
assert len(occurrences) == 5
|
||||||
|
|||||||
Reference in New Issue
Block a user