ical.event
A grouping of component properties that describe a calendar event.
An event can be an activity (e.g. a meeting from 8am to 9am tomorrow) grouping of properties such as a summary or a description. An event will take up time on a calendar as an opaque time interval, but can alternatively have transparency set to transparent to prevent blocking of time as busy.
An event start and end time may either be a date and time or just a day alone. Events may also span more than one day. Alternatively, an event can have a start and a duration.
1"""A grouping of component properties that describe a calendar event. 2 3An event can be an activity (e.g. a meeting from 8am to 9am tomorrow) 4grouping of properties such as a summary or a description. An event will 5take up time on a calendar as an opaque time interval, but can alternatively 6have transparency set to transparent to prevent blocking of time as busy. 7 8An event start and end time may either be a date and time or just a day 9alone. Events may also span more than one day. Alternatively, an event 10can have a start and a duration. 11""" 12 13# pylint: disable=unnecessary-lambda 14 15from __future__ import annotations 16 17import datetime 18import enum 19import logging 20from collections.abc import Iterable 21from typing import Any, Optional, Union 22 23try: 24 from pydantic.v1 import Field, root_validator 25except ImportError: 26 from pydantic import Field, root_validator # type: ignore[no-redef, assignment] 27 28from .alarm import Alarm 29from .component import ComponentModel, validate_until_dtstart, validate_recurrence_dates 30from .iter import RulesetIterable, as_rrule 31from .exceptions import CalendarParseError 32from .parsing.property import ParsedProperty 33from .timespan import Timespan 34from .types import ( 35 CalAddress, 36 Classification, 37 Geo, 38 Priority, 39 Recur, 40 RecurrenceId, 41 RequestStatus, 42 Uri, 43 RelatedTo, 44) 45from .util import dtstamp_factory, normalize_datetime, uid_factory 46 47_LOGGER = logging.getLogger(__name__) 48 49 50class EventStatus(str, enum.Enum): 51 """Status or confirmation of the event set by the organizer.""" 52 53 CONFIRMED = "CONFIRMED" 54 """Indicates event is definite.""" 55 56 TENTATIVE = "TENTATIVE" 57 """Indicates event is tentative.""" 58 59 CANCELLED = "CANCELLED" 60 """Indicates event was cancelled.""" 61 62 63class Event(ComponentModel): 64 """A single event on a calendar. 65 66 Can either be for a specific day, or with a start time and duration/end time. 67 68 The dtstamp and uid functions have factory methods invoked with a lambda to facilitate 69 mocking in unit tests. 70 71 72 Example: 73 ```python 74 import datetime 75 from ical.event import Event 76 77 event = Event( 78 dtstart=datetime.datetime(2022, 8, 31, 7, 00, 00), 79 dtend=datetime.datetime(2022, 8, 31, 7, 30, 00), 80 summary="Morning exercise", 81 ) 82 print("The event duration is: ", event.computed_duration) 83 ``` 84 85 An Event is a pydantic model, so all properties of a pydantic model apply here to such as 86 the constructor arguments, properties to return the model as a dictionary or json, as well 87 as other parsing methods. 88 """ 89 90 dtstamp: Union[datetime.datetime, datetime.date] = Field( 91 default_factory=lambda: dtstamp_factory() 92 ) 93 """Specifies the date and time the event was created.""" 94 95 uid: str = Field(default_factory=lambda: uid_factory()) 96 """A globally unique identifier for the event.""" 97 98 # Has an alias of 'start' 99 dtstart: Union[datetime.datetime, datetime.date] = Field( 100 default=None, 101 ) 102 """The start time or start day of the event.""" 103 104 # Has an alias of 'end' 105 dtend: Optional[Union[datetime.datetime, datetime.date]] = None 106 """The end time or end day of the event. 107 108 This may be specified as an explicit date. Alternatively, a duration 109 can be used instead. 110 """ 111 112 duration: Optional[datetime.timedelta] = None 113 """The duration of the event as an alternative to an explicit end date/time.""" 114 115 summary: str = "" 116 """Defines a short summary or subject for the event.""" 117 118 attendees: list[CalAddress] = Field(alias="attendee", default_factory=list) 119 """Specifies participants in a group-scheduled calendar.""" 120 121 categories: list[str] = Field(default_factory=list) 122 """Defines the categories for an event. 123 124 Specifies a category or subtype. Can be useful for searching for a particular 125 type of event. 126 """ 127 128 classification: Optional[Classification] = Field(alias="class", default=None) 129 """An access classification for a calendar event. 130 131 This provides a method of capturing the scope of access of a calendar, in 132 conjunction with an access control system. 133 """ 134 135 comment: list[str] = Field(default_factory=list) 136 """Specifies a comment to the calendar user.""" 137 138 contacts: list[str] = Field(alias="contact", default_factory=list) 139 """Contact information associated with the event.""" 140 141 created: Optional[datetime.datetime] = None 142 """The date and time the event information was created.""" 143 144 description: Optional[str] = None 145 """A more complete description of the event than provided by the summary.""" 146 147 geo: Optional[Geo] = None 148 """Specifies a latitude and longitude global position for the event activity.""" 149 150 last_modified: Optional[datetime.datetime] = Field( 151 alias="last-modified", default=None 152 ) 153 154 location: Optional[str] = None 155 """Defines the intended venue for the activity defined by this event.""" 156 157 organizer: Optional[CalAddress] = None 158 """The organizer of a group-scheduled calendar entity.""" 159 160 priority: Optional[Priority] = None 161 """Defines the relative priorirty of the calendar event.""" 162 163 recurrence_id: Optional[RecurrenceId] = Field(alias="recurrence-id", default=None) 164 """Defines a specific instance of a recurring event. 165 166 The full range of calendar events specified by a recurrence set is referenced 167 by referring to just the uid. The `recurrence_id` allows reference of an individual 168 instance within the recurrence set. 169 """ 170 171 related_to: list[RelatedTo] = Field(alias="related-to", default_factory=list) 172 """Used to represent a relationship or reference between events.""" 173 174 related: list[str] = Field(default_factory=list) 175 """Unused and will be deleted in a future release""" 176 177 resources: list[str] = Field(default_factory=list) 178 """Defines the equipment or resources anticipated for the calendar event.""" 179 180 rrule: Optional[Recur] = None 181 """A recurrence rule specification. 182 183 Defines a rule for specifying a repeated event. The recurrence set is the complete 184 set of recurrence instances for a calendar component (based on rrule, rdate, exdate). 185 The recurrence set is generated by gathering the rrule and rdate properties then 186 excluding any times specified by exdate. The recurrence is generated with the dtstart 187 property defining the first instance of the recurrence set. 188 189 Typically a dtstart should be specified with a date local time and timezone to make 190 sure all instances have the same start time regardless of time zone changing. 191 """ 192 193 rdate: list[Union[datetime.datetime, datetime.date]] = Field(default_factory=list) 194 """Defines the list of date/time values for recurring events. 195 196 Can appear along with the rrule property to define a set of repeating occurrences of the 197 event. The recurrence set is the complete set of recurrence instances for a calendar component 198 (based on rrule, rdate, exdate). The recurrence set is generated by gathering the rrule 199 and rdate properties then excluding any times specified by exdate. 200 """ 201 202 exdate: list[Union[datetime.datetime, datetime.date]] = Field(default_factory=list) 203 """Defines the list of exceptions for recurring events. 204 205 The exception dates are used in computing the recurrence set. The recurrence set is 206 the complete set of recurrence instances for a calendar component (based on rrule, rdate, 207 exdate). The recurrence set is generated by gathering the rrule and rdate properties 208 then excluding any times specified by exdate. 209 """ 210 211 request_status: Optional[RequestStatus] = Field( 212 alias="request-status", default_value=None 213 ) 214 215 sequence: Optional[int] = None 216 """The revision sequence number in the calendar component. 217 218 When an event is created, its sequence number is 0. It is monotonically incremented 219 by the organizers calendar user agent every time a significant revision is made to 220 the calendar event. 221 """ 222 223 status: Optional[EventStatus] = None 224 """Defines the overall status or confirmation of the event. 225 226 In a group-scheduled calendar, used by the organizer to provide a confirmation 227 of the event to attendees. 228 """ 229 230 transparency: Optional[str] = Field(alias="transp", default=None) 231 """Defines whether or not an event is transparenty to busy time searches.""" 232 233 url: Optional[Uri] = None 234 """Defines a url associated with the event. 235 236 May convey a location where a more dynamic rendition of the calendar event 237 information associated with the event can be found. 238 """ 239 240 # Unknown or unsupported properties 241 extras: list[ParsedProperty] = Field(default_factory=list) 242 243 alarm: list[Alarm] = Field(alias="valarm", default_factory=list) 244 """A grouping of reminder alarms for the event.""" 245 246 def __init__(self, **data: Any) -> None: 247 """Initialize a Calendar Event. 248 249 This method accepts keyword args with field names on the Calendar such as `summary`, 250 `start`, `end`, `description`, etc. 251 """ 252 if "start" in data: 253 data["dtstart"] = data.pop("start") 254 if "end" in data: 255 data["dtend"] = data.pop("end") 256 super().__init__(**data) 257 258 @property 259 def start(self) -> datetime.datetime | datetime.date: 260 """Return the start time for the event.""" 261 return self.dtstart 262 263 @property 264 def end(self) -> datetime.datetime | datetime.date: 265 """Return the end time for the event.""" 266 if self.duration: 267 return self.dtstart + self.duration 268 if self.dtend: 269 return self.dtend 270 271 if isinstance(self.dtstart, datetime.datetime): 272 return self.dtstart 273 return self.dtstart + datetime.timedelta(days=1) 274 275 @property 276 def start_datetime(self) -> datetime.datetime: 277 """Return the events start as a datetime in UTC""" 278 return normalize_datetime(self.start).astimezone(datetime.timezone.utc) 279 280 @property 281 def end_datetime(self) -> datetime.datetime: 282 """Return the events end as a datetime in UTC.""" 283 return normalize_datetime(self.end).astimezone(datetime.timezone.utc) 284 285 @property 286 def computed_duration(self) -> datetime.timedelta: 287 """Return the event duration.""" 288 if self.duration is not None: 289 return self.duration 290 return self.end - self.start 291 292 @property 293 def timespan(self) -> Timespan: 294 """Return a timespan representing the event start and end.""" 295 return Timespan.of(self.start, self.end) 296 297 def timespan_of(self, tzinfo: datetime.tzinfo) -> Timespan: 298 """Return a timespan representing the event start and end.""" 299 return Timespan.of( 300 normalize_datetime(self.start, tzinfo), normalize_datetime(self.end, tzinfo) 301 ) 302 303 def starts_within(self, other: "Event") -> bool: 304 """Return True if this event starts while the other event is active.""" 305 return self.timespan.starts_within(other.timespan) 306 307 def ends_within(self, other: "Event") -> bool: 308 """Return True if this event ends while the other event is active.""" 309 return self.timespan.ends_within(other.timespan) 310 311 def intersects(self, other: "Event") -> bool: 312 """Return True if this event overlaps with the other event.""" 313 return self.timespan.intersects(other.timespan) 314 315 def includes(self, other: "Event") -> bool: 316 """Return True if the other event starts and ends within this event.""" 317 return self.timespan.includes(other.timespan) 318 319 def is_included_in(self, other: "Event") -> bool: 320 """Return True if this event starts and ends within the other event.""" 321 return self.timespan.is_included_in(other.timespan) 322 323 def __lt__(self, other: Any) -> bool: 324 if not isinstance(other, Event): 325 return NotImplemented 326 return self.timespan < other.timespan 327 328 def __gt__(self, other: Any) -> bool: 329 if not isinstance(other, Event): 330 return NotImplemented 331 return self.timespan > other.timespan 332 333 def __le__(self, other: Any) -> bool: 334 if not isinstance(other, Event): 335 return NotImplemented 336 return self.timespan <= other.timespan 337 338 def __ge__(self, other: Any) -> bool: 339 if not isinstance(other, Event): 340 return NotImplemented 341 return self.timespan >= other.timespan 342 343 @property 344 def recurring(self) -> bool: 345 """Return true if this event is recurring. 346 347 A recurring event is typically evaluated specially on the timeline. The 348 data model has a single event, but the timeline evaluates the recurrence 349 to expand and copy the the event to multiple places on the timeline 350 using `as_rrule`. 351 """ 352 if self.rrule or self.rdate: 353 return True 354 return False 355 356 def as_rrule(self) -> Iterable[datetime.datetime | datetime.date] | None: 357 """Return an iterable containing the occurrences of a recurring event. 358 359 A recurring event is typically evaluated specially on the timeline. The 360 data model has a single event, but the timeline evaluates the recurrence 361 to expand and copy the the event to multiple places on the timeline. 362 363 This is only valid for events where `recurring` is True. 364 """ 365 return as_rrule(self.rrule, self.rdate, self.exdate, self.dtstart) 366 367 @root_validator(pre=True, allow_reuse=True) 368 def _inspect_date_types(cls, values: dict[str, Any]) -> dict[str, Any]: 369 """Debug the date and date/time values of the event.""" 370 dtstart = values.get("dtstart") 371 dtend = values.get("dtend") 372 if not dtstart or not dtend: 373 return values 374 _LOGGER.debug("Found initial values dtstart=%s, dtend=%s", dtstart, dtend) 375 return values 376 377 @root_validator(allow_reuse=True) 378 def _validate_date_types(cls, values: dict[str, Any]) -> dict[str, Any]: 379 """Validate that start and end values are the same date or datetime type.""" 380 dtstart = values.get("dtstart") 381 dtend = values.get("dtend") 382 383 if not dtstart or not dtend: 384 return values 385 if isinstance(dtstart, datetime.datetime): 386 if not isinstance(dtend, datetime.datetime): 387 _LOGGER.debug("Unexpected data types for values: %s", values) 388 raise ValueError( 389 f"Unexpected dtstart value '{dtstart}' was datetime but " 390 f"dtend value '{dtend}' was not datetime" 391 ) 392 elif isinstance(dtstart, datetime.date): 393 if isinstance(dtend, datetime.datetime): 394 raise ValueError( 395 f"Unexpected dtstart value '{dtstart}' was date but " 396 f"dtend value '{dtend}' was datetime" 397 ) 398 return values 399 400 @root_validator(allow_reuse=True) 401 def _validate_datetime_timezone(cls, values: dict[str, Any]) -> dict[str, Any]: 402 """Validate that start and end values have the same timezone information.""" 403 if ( 404 not (dtstart := values.get("dtstart")) 405 or not (dtend := values.get("dtend")) 406 or not isinstance(dtstart, datetime.datetime) 407 or not isinstance(dtend, datetime.datetime) 408 ): 409 return values 410 if dtstart.tzinfo is None and dtend.tzinfo is not None: 411 raise ValueError( 412 f"Expected end datetime value in localtime but was {dtend}" 413 ) 414 if dtstart.tzinfo is not None and dtend.tzinfo is None: 415 raise ValueError(f"Expected end datetime with timezone but was {dtend}") 416 return values 417 418 @root_validator(allow_reuse=True) 419 def _validate_one_end_or_duration(cls, values: dict[str, Any]) -> dict[str, Any]: 420 """Validate that only one of duration or end date may be set.""" 421 if values.get("dtend") and values.get("duration"): 422 raise ValueError("Only one of dtend or duration may be set." "") 423 return values 424 425 @root_validator(allow_reuse=True) 426 def _validate_duration_unit(cls, values: dict[str, Any]) -> dict[str, Any]: 427 """Validate the duration is the appropriate units.""" 428 if not (duration := values.get("duration")): 429 return values 430 dtstart = values["dtstart"] 431 if type(dtstart) is datetime.date: 432 if duration.seconds != 0: 433 raise ValueError("Event with start date expects duration in days only") 434 if duration < datetime.timedelta(seconds=0): 435 raise ValueError(f"Expected duration to be positive but was {duration}") 436 return values 437 438 _validate_until_dtstart = root_validator(allow_reuse=True)(validate_until_dtstart) 439 _validate_recurrence_dates = root_validator(allow_reuse=True)( 440 validate_recurrence_dates 441 )
51class EventStatus(str, enum.Enum): 52 """Status or confirmation of the event set by the organizer.""" 53 54 CONFIRMED = "CONFIRMED" 55 """Indicates event is definite.""" 56 57 TENTATIVE = "TENTATIVE" 58 """Indicates event is tentative.""" 59 60 CANCELLED = "CANCELLED" 61 """Indicates event was cancelled."""
Status or confirmation of the event set by the organizer.
64class Event(ComponentModel): 65 """A single event on a calendar. 66 67 Can either be for a specific day, or with a start time and duration/end time. 68 69 The dtstamp and uid functions have factory methods invoked with a lambda to facilitate 70 mocking in unit tests. 71 72 73 Example: 74 ```python 75 import datetime 76 from ical.event import Event 77 78 event = Event( 79 dtstart=datetime.datetime(2022, 8, 31, 7, 00, 00), 80 dtend=datetime.datetime(2022, 8, 31, 7, 30, 00), 81 summary="Morning exercise", 82 ) 83 print("The event duration is: ", event.computed_duration) 84 ``` 85 86 An Event is a pydantic model, so all properties of a pydantic model apply here to such as 87 the constructor arguments, properties to return the model as a dictionary or json, as well 88 as other parsing methods. 89 """ 90 91 dtstamp: Union[datetime.datetime, datetime.date] = Field( 92 default_factory=lambda: dtstamp_factory() 93 ) 94 """Specifies the date and time the event was created.""" 95 96 uid: str = Field(default_factory=lambda: uid_factory()) 97 """A globally unique identifier for the event.""" 98 99 # Has an alias of 'start' 100 dtstart: Union[datetime.datetime, datetime.date] = Field( 101 default=None, 102 ) 103 """The start time or start day of the event.""" 104 105 # Has an alias of 'end' 106 dtend: Optional[Union[datetime.datetime, datetime.date]] = None 107 """The end time or end day of the event. 108 109 This may be specified as an explicit date. Alternatively, a duration 110 can be used instead. 111 """ 112 113 duration: Optional[datetime.timedelta] = None 114 """The duration of the event as an alternative to an explicit end date/time.""" 115 116 summary: str = "" 117 """Defines a short summary or subject for the event.""" 118 119 attendees: list[CalAddress] = Field(alias="attendee", default_factory=list) 120 """Specifies participants in a group-scheduled calendar.""" 121 122 categories: list[str] = Field(default_factory=list) 123 """Defines the categories for an event. 124 125 Specifies a category or subtype. Can be useful for searching for a particular 126 type of event. 127 """ 128 129 classification: Optional[Classification] = Field(alias="class", default=None) 130 """An access classification for a calendar event. 131 132 This provides a method of capturing the scope of access of a calendar, in 133 conjunction with an access control system. 134 """ 135 136 comment: list[str] = Field(default_factory=list) 137 """Specifies a comment to the calendar user.""" 138 139 contacts: list[str] = Field(alias="contact", default_factory=list) 140 """Contact information associated with the event.""" 141 142 created: Optional[datetime.datetime] = None 143 """The date and time the event information was created.""" 144 145 description: Optional[str] = None 146 """A more complete description of the event than provided by the summary.""" 147 148 geo: Optional[Geo] = None 149 """Specifies a latitude and longitude global position for the event activity.""" 150 151 last_modified: Optional[datetime.datetime] = Field( 152 alias="last-modified", default=None 153 ) 154 155 location: Optional[str] = None 156 """Defines the intended venue for the activity defined by this event.""" 157 158 organizer: Optional[CalAddress] = None 159 """The organizer of a group-scheduled calendar entity.""" 160 161 priority: Optional[Priority] = None 162 """Defines the relative priorirty of the calendar event.""" 163 164 recurrence_id: Optional[RecurrenceId] = Field(alias="recurrence-id", default=None) 165 """Defines a specific instance of a recurring event. 166 167 The full range of calendar events specified by a recurrence set is referenced 168 by referring to just the uid. The `recurrence_id` allows reference of an individual 169 instance within the recurrence set. 170 """ 171 172 related_to: list[RelatedTo] = Field(alias="related-to", default_factory=list) 173 """Used to represent a relationship or reference between events.""" 174 175 related: list[str] = Field(default_factory=list) 176 """Unused and will be deleted in a future release""" 177 178 resources: list[str] = Field(default_factory=list) 179 """Defines the equipment or resources anticipated for the calendar event.""" 180 181 rrule: Optional[Recur] = None 182 """A recurrence rule specification. 183 184 Defines a rule for specifying a repeated event. The recurrence set is the complete 185 set of recurrence instances for a calendar component (based on rrule, rdate, exdate). 186 The recurrence set is generated by gathering the rrule and rdate properties then 187 excluding any times specified by exdate. The recurrence is generated with the dtstart 188 property defining the first instance of the recurrence set. 189 190 Typically a dtstart should be specified with a date local time and timezone to make 191 sure all instances have the same start time regardless of time zone changing. 192 """ 193 194 rdate: list[Union[datetime.datetime, datetime.date]] = Field(default_factory=list) 195 """Defines the list of date/time values for recurring events. 196 197 Can appear along with the rrule property to define a set of repeating occurrences of the 198 event. The recurrence set is the complete set of recurrence instances for a calendar component 199 (based on rrule, rdate, exdate). The recurrence set is generated by gathering the rrule 200 and rdate properties then excluding any times specified by exdate. 201 """ 202 203 exdate: list[Union[datetime.datetime, datetime.date]] = Field(default_factory=list) 204 """Defines the list of exceptions for recurring events. 205 206 The exception dates are used in computing the recurrence set. The recurrence set is 207 the complete set of recurrence instances for a calendar component (based on rrule, rdate, 208 exdate). The recurrence set is generated by gathering the rrule and rdate properties 209 then excluding any times specified by exdate. 210 """ 211 212 request_status: Optional[RequestStatus] = Field( 213 alias="request-status", default_value=None 214 ) 215 216 sequence: Optional[int] = None 217 """The revision sequence number in the calendar component. 218 219 When an event is created, its sequence number is 0. It is monotonically incremented 220 by the organizers calendar user agent every time a significant revision is made to 221 the calendar event. 222 """ 223 224 status: Optional[EventStatus] = None 225 """Defines the overall status or confirmation of the event. 226 227 In a group-scheduled calendar, used by the organizer to provide a confirmation 228 of the event to attendees. 229 """ 230 231 transparency: Optional[str] = Field(alias="transp", default=None) 232 """Defines whether or not an event is transparenty to busy time searches.""" 233 234 url: Optional[Uri] = None 235 """Defines a url associated with the event. 236 237 May convey a location where a more dynamic rendition of the calendar event 238 information associated with the event can be found. 239 """ 240 241 # Unknown or unsupported properties 242 extras: list[ParsedProperty] = Field(default_factory=list) 243 244 alarm: list[Alarm] = Field(alias="valarm", default_factory=list) 245 """A grouping of reminder alarms for the event.""" 246 247 def __init__(self, **data: Any) -> None: 248 """Initialize a Calendar Event. 249 250 This method accepts keyword args with field names on the Calendar such as `summary`, 251 `start`, `end`, `description`, etc. 252 """ 253 if "start" in data: 254 data["dtstart"] = data.pop("start") 255 if "end" in data: 256 data["dtend"] = data.pop("end") 257 super().__init__(**data) 258 259 @property 260 def start(self) -> datetime.datetime | datetime.date: 261 """Return the start time for the event.""" 262 return self.dtstart 263 264 @property 265 def end(self) -> datetime.datetime | datetime.date: 266 """Return the end time for the event.""" 267 if self.duration: 268 return self.dtstart + self.duration 269 if self.dtend: 270 return self.dtend 271 272 if isinstance(self.dtstart, datetime.datetime): 273 return self.dtstart 274 return self.dtstart + datetime.timedelta(days=1) 275 276 @property 277 def start_datetime(self) -> datetime.datetime: 278 """Return the events start as a datetime in UTC""" 279 return normalize_datetime(self.start).astimezone(datetime.timezone.utc) 280 281 @property 282 def end_datetime(self) -> datetime.datetime: 283 """Return the events end as a datetime in UTC.""" 284 return normalize_datetime(self.end).astimezone(datetime.timezone.utc) 285 286 @property 287 def computed_duration(self) -> datetime.timedelta: 288 """Return the event duration.""" 289 if self.duration is not None: 290 return self.duration 291 return self.end - self.start 292 293 @property 294 def timespan(self) -> Timespan: 295 """Return a timespan representing the event start and end.""" 296 return Timespan.of(self.start, self.end) 297 298 def timespan_of(self, tzinfo: datetime.tzinfo) -> Timespan: 299 """Return a timespan representing the event start and end.""" 300 return Timespan.of( 301 normalize_datetime(self.start, tzinfo), normalize_datetime(self.end, tzinfo) 302 ) 303 304 def starts_within(self, other: "Event") -> bool: 305 """Return True if this event starts while the other event is active.""" 306 return self.timespan.starts_within(other.timespan) 307 308 def ends_within(self, other: "Event") -> bool: 309 """Return True if this event ends while the other event is active.""" 310 return self.timespan.ends_within(other.timespan) 311 312 def intersects(self, other: "Event") -> bool: 313 """Return True if this event overlaps with the other event.""" 314 return self.timespan.intersects(other.timespan) 315 316 def includes(self, other: "Event") -> bool: 317 """Return True if the other event starts and ends within this event.""" 318 return self.timespan.includes(other.timespan) 319 320 def is_included_in(self, other: "Event") -> bool: 321 """Return True if this event starts and ends within the other event.""" 322 return self.timespan.is_included_in(other.timespan) 323 324 def __lt__(self, other: Any) -> bool: 325 if not isinstance(other, Event): 326 return NotImplemented 327 return self.timespan < other.timespan 328 329 def __gt__(self, other: Any) -> bool: 330 if not isinstance(other, Event): 331 return NotImplemented 332 return self.timespan > other.timespan 333 334 def __le__(self, other: Any) -> bool: 335 if not isinstance(other, Event): 336 return NotImplemented 337 return self.timespan <= other.timespan 338 339 def __ge__(self, other: Any) -> bool: 340 if not isinstance(other, Event): 341 return NotImplemented 342 return self.timespan >= other.timespan 343 344 @property 345 def recurring(self) -> bool: 346 """Return true if this event is recurring. 347 348 A recurring event is typically evaluated specially on the timeline. The 349 data model has a single event, but the timeline evaluates the recurrence 350 to expand and copy the the event to multiple places on the timeline 351 using `as_rrule`. 352 """ 353 if self.rrule or self.rdate: 354 return True 355 return False 356 357 def as_rrule(self) -> Iterable[datetime.datetime | datetime.date] | None: 358 """Return an iterable containing the occurrences of a recurring event. 359 360 A recurring event is typically evaluated specially on the timeline. The 361 data model has a single event, but the timeline evaluates the recurrence 362 to expand and copy the the event to multiple places on the timeline. 363 364 This is only valid for events where `recurring` is True. 365 """ 366 return as_rrule(self.rrule, self.rdate, self.exdate, self.dtstart) 367 368 @root_validator(pre=True, allow_reuse=True) 369 def _inspect_date_types(cls, values: dict[str, Any]) -> dict[str, Any]: 370 """Debug the date and date/time values of the event.""" 371 dtstart = values.get("dtstart") 372 dtend = values.get("dtend") 373 if not dtstart or not dtend: 374 return values 375 _LOGGER.debug("Found initial values dtstart=%s, dtend=%s", dtstart, dtend) 376 return values 377 378 @root_validator(allow_reuse=True) 379 def _validate_date_types(cls, values: dict[str, Any]) -> dict[str, Any]: 380 """Validate that start and end values are the same date or datetime type.""" 381 dtstart = values.get("dtstart") 382 dtend = values.get("dtend") 383 384 if not dtstart or not dtend: 385 return values 386 if isinstance(dtstart, datetime.datetime): 387 if not isinstance(dtend, datetime.datetime): 388 _LOGGER.debug("Unexpected data types for values: %s", values) 389 raise ValueError( 390 f"Unexpected dtstart value '{dtstart}' was datetime but " 391 f"dtend value '{dtend}' was not datetime" 392 ) 393 elif isinstance(dtstart, datetime.date): 394 if isinstance(dtend, datetime.datetime): 395 raise ValueError( 396 f"Unexpected dtstart value '{dtstart}' was date but " 397 f"dtend value '{dtend}' was datetime" 398 ) 399 return values 400 401 @root_validator(allow_reuse=True) 402 def _validate_datetime_timezone(cls, values: dict[str, Any]) -> dict[str, Any]: 403 """Validate that start and end values have the same timezone information.""" 404 if ( 405 not (dtstart := values.get("dtstart")) 406 or not (dtend := values.get("dtend")) 407 or not isinstance(dtstart, datetime.datetime) 408 or not isinstance(dtend, datetime.datetime) 409 ): 410 return values 411 if dtstart.tzinfo is None and dtend.tzinfo is not None: 412 raise ValueError( 413 f"Expected end datetime value in localtime but was {dtend}" 414 ) 415 if dtstart.tzinfo is not None and dtend.tzinfo is None: 416 raise ValueError(f"Expected end datetime with timezone but was {dtend}") 417 return values 418 419 @root_validator(allow_reuse=True) 420 def _validate_one_end_or_duration(cls, values: dict[str, Any]) -> dict[str, Any]: 421 """Validate that only one of duration or end date may be set.""" 422 if values.get("dtend") and values.get("duration"): 423 raise ValueError("Only one of dtend or duration may be set." "") 424 return values 425 426 @root_validator(allow_reuse=True) 427 def _validate_duration_unit(cls, values: dict[str, Any]) -> dict[str, Any]: 428 """Validate the duration is the appropriate units.""" 429 if not (duration := values.get("duration")): 430 return values 431 dtstart = values["dtstart"] 432 if type(dtstart) is datetime.date: 433 if duration.seconds != 0: 434 raise ValueError("Event with start date expects duration in days only") 435 if duration < datetime.timedelta(seconds=0): 436 raise ValueError(f"Expected duration to be positive but was {duration}") 437 return values 438 439 _validate_until_dtstart = root_validator(allow_reuse=True)(validate_until_dtstart) 440 _validate_recurrence_dates = root_validator(allow_reuse=True)( 441 validate_recurrence_dates 442 )
A single event on a calendar.
Can either be for a specific day, or with a start time and duration/end time.
The dtstamp and uid functions have factory methods invoked with a lambda to facilitate mocking in unit tests.
Example:
import datetime
from ical.event import Event
event = Event(
dtstart=datetime.datetime(2022, 8, 31, 7, 00, 00),
dtend=datetime.datetime(2022, 8, 31, 7, 30, 00),
summary="Morning exercise",
)
print("The event duration is: ", event.computed_duration)
An Event is a pydantic model, so all properties of a pydantic model apply here to such as the constructor arguments, properties to return the model as a dictionary or json, as well as other parsing methods.
247 def __init__(self, **data: Any) -> None: 248 """Initialize a Calendar Event. 249 250 This method accepts keyword args with field names on the Calendar such as `summary`, 251 `start`, `end`, `description`, etc. 252 """ 253 if "start" in data: 254 data["dtstart"] = data.pop("start") 255 if "end" in data: 256 data["dtend"] = data.pop("end") 257 super().__init__(**data)
Initialize a Calendar Event.
This method accepts keyword args with field names on the Calendar such as summary
,
start
, end
, description
, etc.
The end time or end day of the event.
This may be specified as an explicit date. Alternatively, a duration can be used instead.
The duration of the event as an alternative to an explicit end date/time.
Defines the categories for an event.
Specifies a category or subtype. Can be useful for searching for a particular type of event.
An access classification for a calendar event.
This provides a method of capturing the scope of access of a calendar, in conjunction with an access control system.
Specifies a latitude and longitude global position for the event activity.
Defines a specific instance of a recurring event.
The full range of calendar events specified by a recurrence set is referenced
by referring to just the uid. The recurrence_id
allows reference of an individual
instance within the recurrence set.
A recurrence rule specification.
Defines a rule for specifying a repeated event. The recurrence set is the complete set of recurrence instances for a calendar component (based on rrule, rdate, exdate). The recurrence set is generated by gathering the rrule and rdate properties then excluding any times specified by exdate. The recurrence is generated with the dtstart property defining the first instance of the recurrence set.
Typically a dtstart should be specified with a date local time and timezone to make sure all instances have the same start time regardless of time zone changing.
Defines the list of date/time values for recurring events.
Can appear along with the rrule property to define a set of repeating occurrences of the event. The recurrence set is the complete set of recurrence instances for a calendar component (based on rrule, rdate, exdate). The recurrence set is generated by gathering the rrule and rdate properties then excluding any times specified by exdate.
Defines the list of exceptions for recurring events.
The exception dates are used in computing the recurrence set. The recurrence set is the complete set of recurrence instances for a calendar component (based on rrule, rdate, exdate). The recurrence set is generated by gathering the rrule and rdate properties then excluding any times specified by exdate.
The revision sequence number in the calendar component.
When an event is created, its sequence number is 0. It is monotonically incremented by the organizers calendar user agent every time a significant revision is made to the calendar event.
Defines the overall status or confirmation of the event.
In a group-scheduled calendar, used by the organizer to provide a confirmation of the event to attendees.
Defines a url associated with the event.
May convey a location where a more dynamic rendition of the calendar event information associated with the event can be found.
259 @property 260 def start(self) -> datetime.datetime | datetime.date: 261 """Return the start time for the event.""" 262 return self.dtstart
Return the start time for the event.
264 @property 265 def end(self) -> datetime.datetime | datetime.date: 266 """Return the end time for the event.""" 267 if self.duration: 268 return self.dtstart + self.duration 269 if self.dtend: 270 return self.dtend 271 272 if isinstance(self.dtstart, datetime.datetime): 273 return self.dtstart 274 return self.dtstart + datetime.timedelta(days=1)
Return the end time for the event.
276 @property 277 def start_datetime(self) -> datetime.datetime: 278 """Return the events start as a datetime in UTC""" 279 return normalize_datetime(self.start).astimezone(datetime.timezone.utc)
Return the events start as a datetime in UTC
281 @property 282 def end_datetime(self) -> datetime.datetime: 283 """Return the events end as a datetime in UTC.""" 284 return normalize_datetime(self.end).astimezone(datetime.timezone.utc)
Return the events end as a datetime in UTC.
286 @property 287 def computed_duration(self) -> datetime.timedelta: 288 """Return the event duration.""" 289 if self.duration is not None: 290 return self.duration 291 return self.end - self.start
Return the event duration.
293 @property 294 def timespan(self) -> Timespan: 295 """Return a timespan representing the event start and end.""" 296 return Timespan.of(self.start, self.end)
Return a timespan representing the event start and end.
298 def timespan_of(self, tzinfo: datetime.tzinfo) -> Timespan: 299 """Return a timespan representing the event start and end.""" 300 return Timespan.of( 301 normalize_datetime(self.start, tzinfo), normalize_datetime(self.end, tzinfo) 302 )
Return a timespan representing the event start and end.
304 def starts_within(self, other: "Event") -> bool: 305 """Return True if this event starts while the other event is active.""" 306 return self.timespan.starts_within(other.timespan)
Return True if this event starts while the other event is active.
308 def ends_within(self, other: "Event") -> bool: 309 """Return True if this event ends while the other event is active.""" 310 return self.timespan.ends_within(other.timespan)
Return True if this event ends while the other event is active.
312 def intersects(self, other: "Event") -> bool: 313 """Return True if this event overlaps with the other event.""" 314 return self.timespan.intersects(other.timespan)
Return True if this event overlaps with the other event.
316 def includes(self, other: "Event") -> bool: 317 """Return True if the other event starts and ends within this event.""" 318 return self.timespan.includes(other.timespan)
Return True if the other event starts and ends within this event.
320 def is_included_in(self, other: "Event") -> bool: 321 """Return True if this event starts and ends within the other event.""" 322 return self.timespan.is_included_in(other.timespan)
Return True if this event starts and ends within the other event.
344 @property 345 def recurring(self) -> bool: 346 """Return true if this event is recurring. 347 348 A recurring event is typically evaluated specially on the timeline. The 349 data model has a single event, but the timeline evaluates the recurrence 350 to expand and copy the the event to multiple places on the timeline 351 using `as_rrule`. 352 """ 353 if self.rrule or self.rdate: 354 return True 355 return False
Return true if this event is recurring.
A recurring event is typically evaluated specially on the timeline. The
data model has a single event, but the timeline evaluates the recurrence
to expand and copy the the event to multiple places on the timeline
using as_rrule
.
357 def as_rrule(self) -> Iterable[datetime.datetime | datetime.date] | None: 358 """Return an iterable containing the occurrences of a recurring event. 359 360 A recurring event is typically evaluated specially on the timeline. The 361 data model has a single event, but the timeline evaluates the recurrence 362 to expand and copy the the event to multiple places on the timeline. 363 364 This is only valid for events where `recurring` is True. 365 """ 366 return as_rrule(self.rrule, self.rdate, self.exdate, self.dtstart)
Return an iterable containing the occurrences of a recurring event.
A recurring event is typically evaluated specially on the timeline. The data model has a single event, but the timeline evaluates the recurrence to expand and copy the the event to multiple places on the timeline.
This is only valid for events where recurring
is True.