Skip to content

Instantly share code, notes, and snippets.

@dceoy
Last active May 14, 2025 07:27
Show Gist options
  • Save dceoy/57e6108cee98ac06bb2ed33ab80688b4 to your computer and use it in GitHub Desktop.
Save dceoy/57e6108cee98ac06bb2ed33ab80688b4 to your computer and use it in GitHub Desktop.
[Python] Check if now is any event in iCalendar data
"""Utilities for iCalendar (ICS) data."""
from datetime import datetime
from zoneinfo import ZoneInfo
import recurring_ical_events # type: ignore[reportMissingTypeStubs]
from aws_lambda_powertools import Logger
from icalendar import Calendar
logger = Logger()
def check_if_now_in_event(ics: str, timezone: str = "Asia/Tokyo") -> bool:
"""Check if the current time is within at least one VEVENT in an iCalendar string.
Args:
ics (str): iCalendar string.
timezone (str | None): IANA time-zone. Defaults to "Asia/Tokyo".
Returns:
bool: Whether the current time is within any VEVENT in the iCalendar string.
"""
logger.info("ics: %s", ics)
if not ics:
logger.info("No iCalendar data")
return False
else:
now = datetime.now(ZoneInfo(timezone))
logger.info("now: %s", now)
try:
events = recurring_ical_events.of(Calendar.from_ical(ics)).at(now)
except Exception:
logger.exception("Error parsing iCalendar data")
return False
else:
logger.info("events: %s", events)
return len(events) > 0
"""Unit tests for ics module."""
import pytest
from freezegun import freeze_time
from ics import check_if_now_in_event
_MEETING_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=Asia/Tokyo:20250419T090000
DTEND;TZID=Asia/Tokyo:20250419T100000
SUMMARY:Morning meeting
END:VEVENT
END:VCALENDAR
"""
_ALL_DAY_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;VALUE=DATE:20250419
DTEND;VALUE=DATE:20250420
SUMMARY:Holiday
END:VEVENT
END:VCALENDAR
"""
_EMPTY_ICS = """\
BEGIN:VCALENDAR
END:VCALENDAR
"""
_DURATION_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=Asia/Tokyo:20250419T130000
DURATION:PT1H
SUMMARY:Lunch meeting
END:VEVENT
END:VCALENDAR
"""
_NO_END_OR_DURATION_DATE_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=DATE:20250419
SUMMARY:Quick check-in
END:VEVENT
END:VCALENDAR
"""
_NO_END_OR_DURATION_DATETIME_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=Asia/Tokyo:20250419T150000
SUMMARY:Quick check-in
END:VEVENT
END:VCALENDAR
"""
_NO_SUMMARY_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:[email protected]
DTSTART;TZID=Asia/Tokyo:20250419T170000
DTEND;TZID=Asia/Tokyo:20250419T180000
END:VEVENT
END:VCALENDAR
"""
_RRULE_UNTIL_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;TZID=Asia/Tokyo:20250701T090000
DTEND;TZID=Asia/Tokyo:20250701T170000
RRULE:FREQ=DAILY;UNTIL=20250831T170000;BYMONTH=7,8
EXDATE;TZID=Asia/Tokyo:20250814T090000
EXDATE;TZID=Asia/Tokyo:20250815T090000
END:VEVENT
END:VCALENDAR
"""
_RRULE_COUNT_ICS = """\
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;TZID=Asia/Tokyo:20250701T090000
DTEND;TZID=Asia/Tokyo:20250701T170000
RRULE:FREQ=DAILY;COUNT=90
EXDATE;TZID=Asia/Tokyo:20250814T090000
EXDATE;TZID=Asia/Tokyo:20250815T090000
END:VEVENT
END:VCALENDAR
"""
@pytest.mark.parametrize(
("ics_str", "now", "expected"),
[
("", "2025-04-19 09:30:00+09:00", False),
("invalid-ics", "2025-04-19 09:30:00+09:00", False),
(_MEETING_ICS, "2025-04-19 09:30:00+09:00", True),
(_MEETING_ICS, "2025-04-19 08:59:00+09:00", False),
(_MEETING_ICS, "2025-04-19 10:01:00+09:00", False),
(_ALL_DAY_ICS, "2025-04-19 12:00:00+09:00", True),
(_ALL_DAY_ICS, "2025-04-21 00:00:00+09:00", False),
(_EMPTY_ICS, "2025-04-19 12:00:00+09:00", False),
(_DURATION_ICS, "2025-04-19 13:30:00+09:00", True),
(_DURATION_ICS, "2025-04-19 14:01:00+09:00", False),
(_NO_END_OR_DURATION_DATE_ICS, "2025-04-19 15:00:00+09:00", True),
(_NO_END_OR_DURATION_DATE_ICS, "2025-04-20 15:00:00+09:00", False),
(_NO_END_OR_DURATION_DATETIME_ICS, "2025-04-19 15:00:00+09:00", True),
(_NO_END_OR_DURATION_DATETIME_ICS, "2025-04-19 15:01:00+09:00", False),
(_NO_SUMMARY_ICS, "2025-04-19 17:30:00+09:00", True),
(_RRULE_UNTIL_ICS, "2025-07-01 10:00:00+09:00", True),
(_RRULE_UNTIL_ICS, "2025-07-01 18:00:00+09:00", False),
(_RRULE_UNTIL_ICS, "2025-08-15 10:00:00+09:00", False),
(_RRULE_UNTIL_ICS, "2025-08-15 18:00:00+09:00", False),
(_RRULE_UNTIL_ICS, "2025-08-31 10:00:00+09:00", True),
(_RRULE_UNTIL_ICS, "2025-08-31 18:00:00+09:00", False),
(_RRULE_COUNT_ICS, "2025-07-01 10:00:00+09:00", True),
(_RRULE_COUNT_ICS, "2025-07-01 18:00:00+09:00", False),
(_RRULE_COUNT_ICS, "2025-08-15 10:00:00+09:00", False),
(_RRULE_COUNT_ICS, "2025-08-15 18:00:00+09:00", False),
(_RRULE_COUNT_ICS, "2025-08-31 10:00:00+09:00", True),
(_RRULE_COUNT_ICS, "2025-08-31 18:00:00+09:00", False),
],
)
def test_check_if_now_in_event(ics_str: str, now: str, expected: bool) -> None:
with freeze_time(now):
assert check_if_now_in_event(ics_str) is expected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment