Skip to content

Commit

Permalink
Start calculating event start and end
Browse files Browse the repository at this point in the history
  • Loading branch information
niccokunzmann committed Oct 10, 2024
1 parent 888d1d3 commit 5e0a0e0
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 14 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Breaking changes:

New features:

- ...
- Add ``Event.end``, ``Event.start``, ``Event.dtstart`` and ``Event.dtend`` attributes, see `Issue 662 <https://github.com/collective/icalendar/issues/662>`_

Bug fixes:

Expand Down
2 changes: 2 additions & 0 deletions src/icalendar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
TimezoneDaylight,
TimezoneStandard,
Todo,
InvalidCalendar,
IncompleteComponent
)

# Parameters and helper methods for splitting and joining string with escaped
Expand Down
111 changes: 98 additions & 13 deletions src/icalendar/cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@
These are the defined components.
"""
from __future__ import annotations
from datetime import datetime, timedelta

import os
from datetime import date, datetime, timedelta
from typing import List, Tuple

import dateutil.rrule
import dateutil.tz
from icalendar.caselessdict import CaselessDict
from icalendar.parser import Contentline
from icalendar.parser import Contentlines
from icalendar.parser import Parameters
from icalendar.parser import q_join
from icalendar.parser import q_split
from icalendar.parser import Contentline, Contentlines, Parameters, q_join, q_split
from icalendar.parser_tools import DEFAULT_ENCODING
from icalendar.prop import TypesFactory
from icalendar.prop import vText, vDDDLists
from icalendar.prop import TypesFactory, vDDDLists, vDDDTypes, vText, vDuration
from icalendar.timezone import tzp
from typing import Tuple, List
import dateutil.rrule
import dateutil.tz
import os


def get_example(component_directory: str, example_name: str) -> bytes:
Expand Down Expand Up @@ -67,6 +64,21 @@ def __init__(self, *args, **kwargs):

_marker = []

class InvalidCalendar(ValueError):
"""The calendar given is not valid.
This calendar does not conform with RFC 5545 or breaks other RFCs.
"""

class IncompleteComponent(ValueError):
"""The component is missing attributes.
The attributes are not required, otherwise this would be
an InvalidCalendar. But in order to perform calculations,
this attribute is required.
"""



class Component(CaselessDict):
"""Component is the base object for calendar, Event and the other
Expand Down Expand Up @@ -485,6 +497,37 @@ def __eq__(self, other):
#######################################
# components defined in RFC 5545

def create_single_property(prop:str, value_attr:str, value_type:tuple[type]|type, type_def:type):
"""Create a single property getter and setter."""

def p_get(self : Component) -> type_def | None:
result = self.get(prop)
if result is None:
return None
if isinstance(result, list):
raise InvalidCalendar(f"Multiple {prop} defined.")
value = getattr(result, value_attr)
if not isinstance(value, value_type):
raise InvalidCalendar(f"{prop} must be either a date or a datetime, not {value}.")
return value

def p_set(self:Component, value: type_def) -> None:
self[prop] = vDDDTypes(value)

def p_del(self:Component):
self.pop(prop)

p_doc = f"""The {prop} property.
If the attribute has invalid values, we raise InvalidCalendar.
If the value is absent, we return None.
You can also delete the value with del.
"""
return property(p_get, p_set, p_del, p_doc)




class Event(Component):

name = 'VEVENT'
Expand Down Expand Up @@ -514,8 +557,49 @@ def example(cls, name) -> Event:
"""Return the calendar example with the given name."""
return cls.from_ical(get_example("events", name))

dtstart = create_single_property("DTSTART", "dt", date, date)
dtend = create_single_property("DTEND", "dt", date, date)

@property
def start(self) -> date | datetime:
"""The start of the component.
Invalid values raise an InvalidCalendar.
If there is no start, we also raise an IncompleteComponent error.
"""
start = self.dtstart
if start is None:
raise IncompleteComponent("No DTSTART given.")
return self.dtstart

@start.setter
def start(self, start: date | datetime):
"""Set the start."""
self.dtstart = start

@property
def end(self) -> date | datetime:
"""The end of the component.
Invalid values raise an InvalidCalendar error.
If there is no end, we also raise an IncompleteComponent error.
"""
end = self.dtend
duration : vDuration = self.get("duration")
if end is None and duration is None:
raise IncompleteComponent("No DTEND or DURATION+DTSTART given.")
if isinstance(duration, vDDDTypes):
return self.start + duration.dt
if isinstance(duration, vDuration):
return self.start + duration.td
return end

@end.setter
def end(self, end: date | datetime):
"""Set the start."""
self.dtend = end


class Todo(Component):

name = 'VTODO'
Expand Down Expand Up @@ -767,4 +851,5 @@ def example(cls, name) -> Calendar:

__all__ = ["Alarm", "Calendar", "Component", "ComponentFactory", "Event",
"FreeBusy", "INLINE", "Journal", "Timezone", "TimezoneDaylight",
"TimezoneStandard", "Todo", "component_factory", "get_example"]
"TimezoneStandard", "Todo", "component_factory", "get_example",
"IncompleteComponent", "InvalidCalendar"]
Loading

0 comments on commit 5e0a0e0

Please sign in to comment.