Skip to content
Open
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
76 changes: 65 additions & 11 deletions khal/khalendar/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(self,
color: Optional[str] = None,
start: Optional[dt.datetime] = None,
end: Optional[dt.datetime] = None,
addresses: Optional[list[str]] =None,
addresses: Optional[list[str]] = None,
):
"""
:param start: start datetime of this event instance
Expand Down Expand Up @@ -106,6 +106,14 @@ def __init__(self,
else:
self._end = end

# set CREATED time and initialize LAST-MODIFIED time
if 'CREATED' not in self._vevents[self.ref]:
now_utc = dt.datetime.now(pytz.utc)
self._vevents[self.ref]['CREATED'] = icalendar.prop.vDatetime(now_utc)
if 'LAST-MODIFIED' not in self._vevents[self.ref]:
now_utc = dt.datetime.now(pytz.utc)
self._vevents[self.ref]['LAST-MODIFIED'] = icalendar.prop.vDatetime(now_utc)

@classmethod
def _get_type_from_vDDD(cls, start: icalendar.prop.vDDDTypes) -> type:
"""infere the type of the class from the START type of the event"""
Expand All @@ -128,8 +136,8 @@ def _get_type_from_date(cls, start: dt.datetime) -> type['Event']:
@classmethod
def fromVEvents(cls,
events_list: list[icalendar.Event],
ref: Optional[str]=None,
start: Optional[dt.datetime]=None,
ref: Optional[str] = None,
start: Optional[dt.datetime] = None,
**kwargs) -> 'Event':
assert isinstance(events_list, list)

Expand Down Expand Up @@ -227,13 +235,14 @@ def update_start_end(self, start: dt.datetime, end: dt.datetime) -> None:
else:
self._vevents[self.ref].pop('DURATION')
self._vevents[self.ref].add('DURATION', end - start)
self._set_last_modified()

@property
def recurring(self) -> bool:
try:
rval = 'RRULE' in self._vevents[self.ref] or \
'RECURRENCE-ID' in self._vevents[self.ref] or \
'RDATE' in self._vevents[self.ref]
'RECURRENCE-ID' in self._vevents[self.ref] or \
'RDATE' in self._vevents[self.ref]
except KeyError:
logger.fatal(
f"The event at {self.href} might be broken. You might want to "
Expand Down Expand Up @@ -261,6 +270,7 @@ def update_rrule(self, rrule: str) -> None:
self._vevents['PROTO'].pop('RRULE')
if rrule is not None:
self._vevents['PROTO'].add('RRULE', rrule)
self._set_last_modified()

@property
def recurrence_id(self) -> Union[dt.datetime, str]:
Expand Down Expand Up @@ -366,6 +376,34 @@ def update_url(self, url: str) -> None:
self._vevents[self.ref]['URL'] = url
else:
self._vevents[self.ref].pop('URL')
self._set_last_modified()

@property
def created(self) -> Optional[dt.datetime]:
"""
Returns the CREATED datetime of the event, if available.
"""
created_prop = self._vevents[self.ref].get('CREATED', None)
if created_prop:
return created_prop.dt
return None

def _set_last_modified(self) -> None:
"""
Sets the LAST-MODIFIED property of the event to the current UTC time.
"""
now_utc = dt.datetime.now(pytz.utc)
self._vevents[self.ref]['LAST-MODIFIED'] = icalendar.prop.vDatetime(now_utc)

@property
def last_modified(self) -> Optional[dt.datetime]:
"""
Returns the LAST-MODIFIED datetime of the event, if available.
"""
last_modified_prop = self._vevents[self.ref].get('LAST-MODIFIED', None)
if last_modified_prop:
return last_modified_prop.dt
return None

@staticmethod
def _create_calendar() -> icalendar.Calendar:
Expand Down Expand Up @@ -444,6 +482,7 @@ def summary(self) -> str:

def update_summary(self, summary: str) -> None:
self._vevents[self.ref]['SUMMARY'] = summary
self._set_last_modified()

@staticmethod
def _can_handle_alarm(alarm) -> bool:
Expand Down Expand Up @@ -479,6 +518,7 @@ def update_alarms(self, alarms: list[tuple[dt.timedelta, str]]) -> None:
new.add('DESCRIPTION', alarm[1])
components.append(new)
self._vevents[self.ref].subcomponents = components
self._set_last_modified()

@property
def location(self) -> str:
Expand All @@ -489,6 +529,7 @@ def update_location(self, location: str) -> None:
self._vevents[self.ref]['LOCATION'] = location
else:
self._vevents[self.ref].pop('LOCATION')
self._set_last_modified()

@property
def attendees(self) -> str:
Expand Down Expand Up @@ -526,6 +567,7 @@ def update_attendees(self, attendees: list[str]):
self._vevents[self.ref]['ATTENDEE'] = vCalAddresses
else:
self._vevents[self.ref].pop('ATTENDEE')
self._set_last_modified()

@property
def categories(self) -> str:
Expand All @@ -540,6 +582,7 @@ def update_categories(self, categories: list[str]) -> None:
self._vevents[self.ref].pop('CATEGORIES', False)
if categories:
self._vevents[self.ref].add('CATEGORIES', categories)
self._set_last_modified()

@property
def description(self) -> str:
Expand All @@ -550,6 +593,7 @@ def update_description(self, description: str):
self._vevents[self.ref]['DESCRIPTION'] = description
else:
self._vevents[self.ref].pop('DESCRIPTION')
self._set_last_modified()

@property
def _recur_str(self) -> str:
Expand Down Expand Up @@ -596,7 +640,7 @@ def attributes(
self,
relative_to: Union[tuple[dt.date, dt.date], dt.date],
env=None,
colors: bool=True,
colors: bool = True,
):
"""
:param colors: determines if colors codes should be printed or not
Expand Down Expand Up @@ -692,7 +736,7 @@ def attributes(
attributes["start-end-time-style"] = self.symbol_strings["range"]
else:
attributes["start-end-time-style"] = attributes["start-style"] + \
tostr + attributes["end-style"]
tostr + attributes["end-style"]

if allday:
if self.start == self.end:
Expand Down Expand Up @@ -731,7 +775,8 @@ def attributes(
if len(formatters) == 1:
fmt: Callable[[str], str] = list(formatters)[0]
else:
def fmt(s: str) -> str: return s.strip()
def fmt(s: str) -> str:
return s.strip()

attributes["description"] = fmt(self.description)
attributes["description-separator"] = ""
Expand All @@ -747,6 +792,15 @@ def fmt(s: str) -> str: return s.strip()
if attributes['url']:
attributes['url-separator'] = " :: "

attributes['created'] = ''
if self.created:
attributes['created'] = self.created.strftime(self._locale['datetimeformat'])

attributes['last-modified'] = ''
if self.last_modified:
last_modified = self.last_modified.strftime(self._locale['datetimeformat'])
attributes['last-modified'] = last_modified

if "calendars" in env and self.calendar in env["calendars"]:
cal = env["calendars"][self.calendar]
attributes["calendar-color"] = cal.get('color', '')
Expand Down Expand Up @@ -919,9 +973,9 @@ def duration(self) -> dt.timedelta:


def create_timezone(
tz: pytz.BaseTzInfo,
first_date: Optional[dt.datetime]=None,
last_date: Optional[dt.datetime]=None
tz: pytz.BaseTzInfo,
first_date: Optional[dt.datetime] = None,
last_date: Optional[dt.datetime] = None
) -> icalendar.Timezone:
"""
create an icalendar vtimezone from a pytz.tzinfo object
Expand Down
5 changes: 3 additions & 2 deletions tests/event_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_update_remove_categories():
def test_raw_d():
event_d = _get_text('event_d')
event = Event.fromString(event_d, **EVENT_KWARGS)
assert event.raw.split('\r\n') == _get_text('cal_d').split('\n')
assert normalize_component(event.raw) == normalize_component(_get_text('cal_d'))
assert LIST_FORMATTER(event.attributes(dt.date(2014, 4, 9))) == ' An Event\x1b[0m'
assert SEARCH_FORMATTER(event.attributes(dt.date(2014, 4, 9))) == '09.04.2014 An Event\x1b[0m'

Expand Down Expand Up @@ -396,7 +396,7 @@ def test_event_raw_UTC():
"""test .raw() on events which are localized in UTC"""
event_utc = _get_text('event_dt_simple_zulu')
event = Event.fromString(event_utc, **EVENT_KWARGS)
assert event.raw == '\r\n'.join([
expected_raw_content = '\r\n'.join([
'''BEGIN:VCALENDAR''',
'''VERSION:2.0''',
'''PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN''',
Expand All @@ -408,6 +408,7 @@ def test_event_raw_UTC():
'''UID:V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU''',
'''END:VEVENT''',
'''END:VCALENDAR\r\n'''])
assert normalize_component(event.raw) == normalize_component(expected_raw_content)


def test_zulu_events():
Expand Down
3 changes: 2 additions & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ def normalize_component(x):
def inner(c):
contentlines = icalendar.cal.Contentlines()
for name, value in c.property_items(sorted=True, recursive=False):
contentlines.append(c.content_line(name, value, sorted=True))
if name not in ['LAST-MODIFIED', 'CREATED']:
contentlines.append(c.content_line(name, value, sorted=True))
contentlines.append('')

return (c.name, contentlines.to_ical(),
Expand Down