Skip to content

Commit

Permalink
Merge pull request #218 from niccokunzmann/issue-201
Browse files Browse the repository at this point in the history
Mixed date/datetime/datetime+timezone
  • Loading branch information
niccokunzmann authored Feb 13, 2025
2 parents 99a89b6 + 2527101 commit 2912832
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 27 deletions.
2 changes: 1 addition & 1 deletion recurring_ical_events/adapters/alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from icalendar import Alarm


class AbsoluteAlarmAdapter(ComponentAdapter):
class AbsoluteAlarmAdapter(ComponentAdapter): # TODO: remove
"""Adapter for absolute alarms."""

def __init__(self, alarm: Alarm, parent: ComponentAdapter):
Expand Down
19 changes: 17 additions & 2 deletions recurring_ical_events/adapters/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,29 @@ def end_property(self) -> str | None:
return None

@property
@abstractmethod
def start(self) -> Time:
"""The start time."""
return self.span[0]

@property
@abstractmethod
def end(self) -> Time:
"""The end time."""
return self.span[1]

@cached_property
def span(self):
"""Return (start, end)."""
return make_comparable((self.raw_start, self.raw_end))

@property
@abstractmethod
def raw_start(self):
"""Return the start property of the component."""

@property
@abstractmethod
def raw_end(self):
"""Return the start property of the component."""

@property
def uid(self) -> UID:
Expand Down
19 changes: 13 additions & 6 deletions recurring_ical_events/adapters/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
from typing import TYPE_CHECKING

from recurring_ical_events.adapters.component import ComponentAdapter
from recurring_ical_events.util import cached_property, is_date, normalize_pytz
from recurring_ical_events.util import (
convert_to_datetime,
is_date,
normalize_pytz,
)

if TYPE_CHECKING:
from recurring_ical_events.types import Time
Expand All @@ -25,15 +29,15 @@ def end_property(self) -> str:
"""DTEND"""
return "DTEND"

@cached_property
def start(self) -> Time:
@property
def raw_start(self) -> Time:
"""Return DTSTART"""
# Arguably, it may be considered a feature that this breaks
# if no DTSTART is set
return self._component["DTSTART"].dt

@cached_property
def end(self) -> Time:
@property
def raw_end(self) -> Time:
"""Yield DTEND or calculate the end of the event based on
DTSTART and DURATION.
"""
Expand All @@ -43,7 +47,10 @@ def end(self) -> Time:
return end.dt
duration = self._component.get("DURATION")
if duration is not None:
return normalize_pytz(self._component["DTSTART"].dt + duration.dt)
start = self._component["DTSTART"].dt
if duration.dt.seconds != 0 and is_date(start):
start = convert_to_datetime(start, None)
return normalize_pytz(start + duration.dt)
start = self._component["DTSTART"].dt
if is_date(start):
return start + datetime.timedelta(days=1)
Expand Down
8 changes: 4 additions & 4 deletions recurring_ical_events/adapters/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ def component_name() -> str:
def end_property(self) -> None:
"""There is no end property"""

@cached_property
def start(self) -> Time:
@property
def raw_start(self) -> Time:
"""Return DTSTART if it set, do not panic if it's not set."""
## according to the specification, DTSTART in a VJOURNAL is optional
dtstart = self._component.get("DTSTART")
Expand All @@ -28,12 +28,12 @@ def start(self) -> Time:
return DATE_MIN_DT

@cached_property
def end(self) -> Time:
def raw_end(self) -> Time:
"""The end time is the same as the start."""
## VJOURNAL cannot have a DTEND. We should consider a VJOURNAL to
## describe one day if DTSTART is a date, and we can probably
## consider it to have zero duration if a timestamp is given.
return self.start
return self.raw_start


__all__ = ["JournalAdapter"]
19 changes: 13 additions & 6 deletions recurring_ical_events/adapters/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from recurring_ical_events.adapters.component import ComponentAdapter
from recurring_ical_events.constants import DATE_MAX_DT, DATE_MIN_DT
from recurring_ical_events.types import Time
from recurring_ical_events.util import cached_property, normalize_pytz
from recurring_ical_events.util import (
convert_to_datetime,
is_date,
normalize_pytz,
)


class TodoAdapter(ComponentAdapter):
Expand All @@ -19,8 +23,8 @@ def end_property(self) -> str:
"""DUE"""
return "DUE"

@cached_property
def start(self) -> Time:
@property
def raw_start(self) -> Time:
"""Return DTSTART if it set, do not panic if it's not set."""
## easy case - DTSTART set
start = self._component.get("DTSTART")
Expand All @@ -36,8 +40,8 @@ def start(self) -> Time:
## (see the comments under _get_event_end)
return DATE_MIN_DT

@cached_property
def end(self) -> Time:
@property
def raw_end(self) -> Time:
"""Return DUE or DTSTART+DURATION or something"""
## Easy case - DUE is set
end = self._component.get("DUE")
Expand All @@ -52,7 +56,10 @@ def end(self) -> Time:
## Perhaps duration is a time estimate rather than an indirect
## way to set DUE.
if duration is not None and dtstart is not None:
return normalize_pytz(self._component["DTSTART"].dt + duration.dt)
start = dtstart.dt
if duration.dt.seconds != 0 and is_date(start):
start = convert_to_datetime(start, None)
return normalize_pytz(start + duration.dt)

## According to the RFC, a VEVENT without an end/duration
## is to be considered to have zero duration. Assuming the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;VALUE=DATE:20230724
CREATED:20230606T153716Z
STATUS:CONFIRMED
SUMMARY:Congés
TRANSP:TRANSPARENT
DTSTAMP:20230704T085547Z
DTEND:20230817T000000Z
SEQUENCE:1
LAST-MODIFIED:20230731T161724Z
UID:19970901T130000Z[email protected]
END:VEVENT
END:VCALENDAR
152 changes: 152 additions & 0 deletions recurring_ical_events/test/calendars/issue_201_test_matrix.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;VALUE=DATE:20000101
DTEND;VALUE=DATE:20000102
UID:VEVENT-DATE-DATE
END:VEVENT
BEGIN:VTODO
DTSTART;VALUE=DATE:20000101
DUE;VALUE=DATE:20000102
UID:VTODO-DATE-DATE
END:VTOOD
BEGIN:VEVENT
DTSTART;VALUE=DATE:20000101
DTEND:20000102T040000
UID:VEVENT-DATE-DATETIME
END:VEVENT
BEGIN:VTODO
DTSTART;VALUE=DATE:20000101
DUE:20000102T040000
UID:VTODO-DATE-DATETIME
END:VTOOD
BEGIN:VEVENT
DTSTART;VALUE=DATE:20000101
DTEND:20000103T020000Z
UID:VEVENT-DATE-UTC
END:VEVENT
BEGIN:VTODO
DTSTART;VALUE=DATE:20000101
DUE:20000103T020000Z
UID:VTODO-DATE-UTC
END:VTOOD
BEGIN:VEVENT
DTSTART;VALUE=DATE:20000101
DURATION:P3D
UID:VEVENT-DATE-DAYS
END:VEVENT
BEGIN:VTODO
DTSTART;VALUE=DATE:20000101
DURATION:P3D
UID:VTODO-DATE-DAYS
END:VTOOD
BEGIN:VEVENT
DTSTART;VALUE=DATE:20000101
DURATION:PT10H
UID:VEVENT-DATE-HOURS
END:VEVENT
BEGIN:VTODO
DTSTART;VALUE=DATE:20000101
DURATION:PT10H
UID:VTODO-DATE-HOURS
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000
DTEND;VALUE=DATE:20000102
UID:VEVENT-DATETIME-DATE
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000
DUE;VALUE=DATE:20000102
UID:VTODO-DATETIME-DATE
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000
DTEND:20000102T040000
UID:VEVENT-DATETIME-DATETIME
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000
DUE:20000102T040000
UID:VTODO-DATETIME-DATETIME
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000
DTEND:20000103T020000Z
UID:VEVENT-DATETIME-UTC
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000
DUE:20000103T020000Z
UID:VTODO-DATETIME-UTC
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000
DURATION:P3D
UID:VEVENT-DATETIME-DAYS
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000
DURATION:P3D
UID:VTODO-DATETIME-DAYS
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000
DURATION:PT10H
UID:VEVENT-DATETIME-HOURS
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000
DURATION:PT10H
UID:VTODO-DATETIME-HOURS
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000Z
DTEND;VALUE=DATE:20000102
UID:VEVENT-UTC-DATE
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000Z
DUE;VALUE=DATE:20000102
UID:VTODO-UTC-DATE
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000Z
DTEND:20000102T040000
UID:VEVENT-UTC-DATETIME
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000Z
DUE:20000102T040000
UID:VTODO-UTC-DATETIME
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000Z
DTEND:20000103T020000Z
UID:VEVENT-UTC-UTC
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000Z
DUE:20000103T020000Z
UID:VTODO-UTC-UTC
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000Z
DURATION:P3D
UID:VEVENT-UTC-DAYS
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000Z
DURATION:P3D
UID:VTODO-UTC-DAYS
END:VTOOD
BEGIN:VEVENT
DTSTART:20000101T000000Z
DURATION:PT10H
UID:VEVENT-UTC-HOURS
END:VEVENT
BEGIN:VTODO
DTSTART:20000101T000000Z
DURATION:PT10H
UID:VTODO-UTC-HOURS
END:VTOOD
END:VCALENDAR
Loading

0 comments on commit 2912832

Please sign in to comment.