Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Contributors
- can submit pull requests
- can work on branches inside the repository

Contributor changes should include focused tests where practical and should
update the changelog in `README.rst` unless the change is trivial.

## Merge requirements

- Passing tests
Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ Changelog

- v3.0.0

- Handle calendars that provide multiple ``X-WR-TIMEZONE`` values.
If all values match, use the repeated timezone; if values conflict,
ignore them because the target timezone is ambiguous.
- Use ``icalendar.is_utc()`` instead of ``is_UTC()`` to identify UTC, catching aliases like ``Etc/GMT``.
- Remove ``is_UTC()`` from ``CalendarWalker``.
- Test ``pytz.timezone("UTC")`` and ``datetime.timezone.utc`` as arguments to ``to_standard()``.
Expand Down
11 changes: 11 additions & 0 deletions test/calendars/duplicate-conflicting-x-wr-timezone-values.in.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//x-wr-timezone test//
X-WR-TIMEZONE:Europe/Berlin
X-WR-TIMEZONE:America/New_York
BEGIN:VEVENT
UID:1
DTSTART:20240101T120000Z
DTEND:20240101T130000Z
END:VEVENT
END:VCALENDAR
11 changes: 11 additions & 0 deletions test/calendars/duplicate-matching-x-wr-timezone-values.in.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//x-wr-timezone test//
X-WR-TIMEZONE:Europe/Berlin
X-WR-TIMEZONE:Europe/Berlin
BEGIN:VEVENT
UID:1
DTSTART:20240101T120000Z
DTEND:20240101T130000Z
END:VEVENT
END:VCALENDAR
22 changes: 22 additions & 0 deletions test/test_convert_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,25 @@ def test_calendar_walker_is_utc_compatibility_hook():

assert walker.is_UTC(utc_dt)
assert not walker.is_UTC(floating_dt)


def test_duplicate_matching_x_wr_timezone_values_are_used(calendars):
calendar = calendars["duplicate-matching-x-wr-timezone-values.in.ics"].as_icalendar()
output_bytes = x_wr_timezone.to_standard(calendar).to_ical()

assert_has_line(
output_bytes,
("DTSTART", "TZID=Europe/Berlin", "20240101T130000"),
"Duplicate matching time zones are used.",
)


def test_duplicate_conflicting_x_wr_timezone_values_are_ignored(calendars):
calendar = calendars["duplicate-conflicting-x-wr-timezone-values.in.ics"].as_icalendar()
output_bytes = x_wr_timezone.to_standard(calendar).to_ical()

assert_has_line(
output_bytes,
("DTSTART", "20240101T120000Z"),
"Conflicting time zones are ignored.",
)
11 changes: 11 additions & 0 deletions x_wr_timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
X_WR_TIMEZONE = "X-WR-TIMEZONE"


def _as_single_timezone(timezone):
"""Return one time zone value from calendar metadata, or None if ambiguous."""
if not isinstance(timezone, list):
return timezone
values = set(map(str, timezone))
if len(values) == 1:
return timezone[0]
return None


def _is_utc_fallback(dt):
"""Fallback UTC check for icalendar < 7.0.0."""
if dt.tzname() is None:
Expand Down Expand Up @@ -193,6 +203,7 @@ def to_standard(
"""
if timezone is None:
timezone = calendar.get(X_WR_TIMEZONE, None)
timezone = _as_single_timezone(timezone)
if timezone is not None and not isinstance(timezone, datetime.tzinfo):
timezone = zoneinfo.ZoneInfo(str(timezone))
result : icalendar.Calendar = calendar
Expand Down
Loading