From de6a1f70bfb9f7df4ab2da0e3156d334eacffb49 Mon Sep 17 00:00:00 2001 From: Nicco Kunzmann Date: Mon, 22 Sep 2025 19:41:11 +0100 Subject: [PATCH] Check type and range of REFRESH-INTERVAL in setter See https://github.com/collective/icalendar/pull/877#discussion_r2366353723 --- src/icalendar/cal/calendar.py | 12 ++++++++++- src/icalendar/tests/test_rfc_7986.py | 30 +++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/icalendar/cal/calendar.py b/src/icalendar/cal/calendar.py index 2bfdf8d..3312fe9 100644 --- a/src/icalendar/cal/calendar.py +++ b/src/icalendar/cal/calendar.py @@ -2,6 +2,7 @@ from __future__ import annotations +from datetime import timedelta from typing import TYPE_CHECKING, Sequence from icalendar.attr import ( @@ -21,7 +22,7 @@ from icalendar.version import __version__ if TYPE_CHECKING: import uuid - from datetime import date, datetime, timedelta + from datetime import date, datetime from icalendar.cal.availability import Availability from icalendar.cal.event import Event @@ -480,6 +481,9 @@ Description: the calendar data. The value of this property SHOULD be used by calendar user agents to limit the polling interval for calendar data updates to the minimum interval specified. + + Raises: + ValueError: When setting a negative duration. """ refresh_interval = self.get("REFRESH-INTERVAL") return refresh_interval.dt if refresh_interval else None @@ -487,6 +491,12 @@ Description: @refresh_interval.setter def refresh_interval(self, value: timedelta | None): """Set the REFRESH-INTERVAL.""" + if not isinstance(value, timedelta) and value is not None: + raise TypeError( + "REFRESH-INTERVAL must be a positive timedelta or None (to delete it)." + ) + if value is not None and value.total_seconds() < 0: + raise ValueError("REFRESH-INTERVAL must be a positive timedelta.") del self.refresh_interval if value is not None: self.add("REFRESH-INTERVAL", value) diff --git a/src/icalendar/tests/test_rfc_7986.py b/src/icalendar/tests/test_rfc_7986.py index 512782b..98ddd67 100644 --- a/src/icalendar/tests/test_rfc_7986.py +++ b/src/icalendar/tests/test_rfc_7986.py @@ -6,7 +6,7 @@ They are also considered. from __future__ import annotations -from datetime import datetime, timedelta, timezone +from datetime import date, datetime, time, timedelta, timezone from typing import Union import pytest @@ -219,6 +219,34 @@ def test_refresh_interval_default(calendar: Calendar): assert calendar.refresh_interval is None +@pytest.mark.parametrize( + "invalid_value", + [ + datetime(2020, 1, 1), + date(2020, 1, 1), + "invalid", + (date(2020, 1, 1), timedelta(days=1)), + time(12, 0), + ], +) +def test_invalid_refresh_interval_type(calendar: Calendar, invalid_value): + """Invalid REFRESH-INTERVAL""" + with pytest.raises(TypeError): + calendar.refresh_interval = invalid_value + + +def test_invalid_refresh_interval(calendar: Calendar): + with pytest.raises(ValueError): + calendar.refresh_interval = timedelta(seconds=-1) + + +def test_0_refresh_interval(calendar: Calendar): + """REFRESH-INTERVAL zero.""" + calendar.refresh_interval = timedelta(0) + assert calendar.refresh_interval == timedelta(0) + assert calendar["REFRESH-INTERVAL"].dt == timedelta(0) + + def test_refresh_interval_set_to_value(calendar: Calendar): """REFRESH-INTERVAL setting.""" calendar.refresh_interval = timedelta(hours=1)