Skip to content

Commit

Permalink
Merge pull request #1266 from pimutils/fix/1046
Browse files Browse the repository at this point in the history
Performance optimizations in ikhal when moving many days at once
  • Loading branch information
geier authored Jul 3, 2023
2 parents cce6554 + d56b15a commit 21ee686
Show file tree
Hide file tree
Showing 14 changed files with 407 additions and 312 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ Package maintainers and users who have to manually update their installation
may want to subscribe to `GitHub's tag feed
<https://github.com/geier/khal/tags.atom>`_.

0.11.3
======
not released yet

* optimization in ikhal when editing events in the far future or past
* FIX an issue in ikhal with updating the view of the event list after editing
an event

0.11.2
======
Expand Down
52 changes: 31 additions & 21 deletions khal/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
from click import confirm, echo, prompt, style

from khal import __productname__, __version__, calendar_display, parse_datetime, utils
from khal.custom_types import EventCreationTypes, LocaleConfiguration
from khal.custom_types import (
EventCreationTypes,
LocaleConfiguration,
MonthDisplayType,
WeekNumbersType,
)
from khal.exceptions import DateTimeParseError, FatalError
from khal.khalendar import CalendarCollection
from khal.khalendar.event import Event
Expand All @@ -48,7 +53,7 @@
logger = logging.getLogger('khal')


def format_day(day, format_string: str, locale, attributes=None):
def format_day(day: dt.date, format_string: str, locale, attributes=None):
if attributes is None:
attributes = {}

Expand All @@ -68,23 +73,28 @@ def format_day(day, format_string: str, locale, attributes=None):
raise KeyError("cannot format day with: %s" % format_string)


def calendar(collection, agenda_format=None, notstarted=False, once=False, daterange=None,
day_format=None,
locale=None,
conf=None,
firstweekday=0,
weeknumber=False,
monthdisplay='firstday',
hmethod='fg',
default_color='',
multiple='',
multiple_on_overflow=False,
color='',
highlight_event_days=0,
full=False,
bold_for_light_color=True,
env=None,
):
def calendar(
collection: CalendarCollection,
agenda_format=None,
notstarted: bool=False,
once=False,
daterange=None,
day_format=None,
locale=None,
conf=None,
firstweekday: int=0,
weeknumber: WeekNumbersType=False,
monthdisplay: MonthDisplayType='firstday',
hmethod: str='fg',
default_color: str='',
multiple='',
multiple_on_overflow: bool=False,
color='',
highlight_event_days=0,
full=False,
bold_for_light_color: bool=True,
env=None,
):
term_width, _ = get_terminal_size()
lwidth = 27 if conf['locale']['weeknumbers'] == 'right' else 25
rwidth = term_width - lwidth - 4
Expand Down Expand Up @@ -163,7 +173,7 @@ def get_events_between(
agenda_format: str,
notstarted: bool,
env: dict,
width,
width: Optional[int],
seen,
original_start: dt.datetime,
) -> List[str]:
Expand Down Expand Up @@ -230,7 +240,7 @@ def khal_list(
day_format: Optional[str]=None,
once=False,
notstarted: bool = False,
width: bool = False,
width: Optional[int] = None,
env=None,
datepoint=None,
):
Expand Down
5 changes: 4 additions & 1 deletion khal/custom_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime as dt
import os
from typing import List, Optional, Protocol, Tuple, TypedDict, Union
from typing import List, Literal, Optional, Protocol, Tuple, TypedDict, Union

import pytz

Expand Down Expand Up @@ -77,3 +77,6 @@ class EventCreationTypes(TypedDict):


PathLike = Union[str, os.PathLike]

WeekNumbersType = Literal['left', 'right', False]
MonthDisplayType = Literal['firstday', 'firstfullweek']
45 changes: 24 additions & 21 deletions khal/icalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,12 @@
logger = logging.getLogger('khal')


def split_ics(ics: str, random_uid: bool=False, default_timezone=None):
def split_ics(ics: str, random_uid: bool=False, default_timezone=None) -> List:
"""split an ics string into several according to VEVENT's UIDs
and sort the right VTIMEZONEs accordingly
ignores all other ics components
:param random_uid: assign random uids to all events
:rtype list:
"""
cal = cal_from_ics(ics)
tzs = {}
Expand Down Expand Up @@ -217,7 +216,10 @@ def ics_from_list(
return calendar.to_ical().decode('utf-8')


def expand(vevent, href=''):
def expand(
vevent: icalendar.Event,
href: str='',
) -> Optional[List[Tuple[dt.datetime, dt.datetime]]]:
"""
Constructs a list of start and end dates for all recurring instances of the
event defined in vevent.
Expand All @@ -229,12 +231,9 @@ def expand(vevent, href=''):
the function still returns a tuple of start and end (date)times.
:param vevent: vevent to be expanded
:type vevent: icalendar.cal.Event
:param href: the href of the vevent, used for more informative logging and
nothing else
:type href: str
:returns: list of start and end (date)times of the expanded event
:rtype: list(tuple(datetime, datetime))
"""
# we do this now and than never care about the "real" end time again
if 'DURATION' in vevent:
Expand All @@ -249,7 +248,7 @@ def expand(vevent, href=''):
events_tz = getattr(vevent['DTSTART'].dt, 'tzinfo', None)
allday = not isinstance(vevent['DTSTART'].dt, dt.datetime)

def sanitize_datetime(date):
def sanitize_datetime(date: dt.date) -> dt.date:
if allday and isinstance(date, dt.datetime):
date = date.date()
if events_tz is not None:
Expand All @@ -274,20 +273,24 @@ def sanitize_datetime(date):
ignoretz=True,
)

if rrule._until is None:
# telling mypy, that _until exists
# we are very sure (TM) that rrulestr always returns a rrule, not a
# rruleset (which wouldn't have a _until attribute)
if rrule._until is None: # type: ignore
# rrule really doesn't like to calculate all recurrences until
# eternity, so we only do it until 2037, because a) I'm not sure
# if python can deal with larger datetime values yet and b) pytz
# doesn't know any larger transition times
rrule._until = dt.datetime(2037, 12, 31)
rrule._until = dt.datetime(2037, 12, 31) # type: ignore
else:
if events_tz and 'Z' in rrule_param.to_ical().decode():
rrule._until = pytz.UTC.localize(
rrule._until).astimezone(events_tz).replace(tzinfo=None)
assert isinstance(rrule._until, dt.datetime) # type: ignore
rrule._until = pytz.UTC.localize( # type: ignore
rrule._until).astimezone(events_tz).replace(tzinfo=None) # type: ignore

# rrule._until and dtstart could be dt.date or dt.datetime. They
# need to be the same for comparison
testuntil = rrule._until
testuntil = rrule._until # type: ignore
if (type(dtstart) == dt.date and type(testuntil) == dt.datetime):
testuntil = testuntil.date()
teststart = dtstart
Expand All @@ -298,15 +301,15 @@ def sanitize_datetime(date):
logger.warning(
f'{href}: Unsupported recurrence. UNTIL is before DTSTART.\n'
'This event will not be available in khal.')
return False
return None

if rrule.count() == 0:
logger.warning(
f'{href}: Recurrence defined but will never occur.\n'
'This event will not be available in khal.')
return False
return None

rrule = map(sanitize_datetime, rrule)
rrule = map(sanitize_datetime, rrule) # type: ignore

logger.debug(f'calculating recurrence dates for {href}, this might take some time.')

Expand Down Expand Up @@ -362,24 +365,24 @@ def assert_only_one_uid(cal: icalendar.Calendar):
return True


def sanitize(vevent, default_timezone, href='', calendar=''):
def sanitize(
vevent: icalendar.Event,
default_timezone: pytz.BaseTzInfo,
href: str='',
calendar: str='',
) -> icalendar.Event:
"""
clean up vevents we do not understand
:param vevent: the vevent that needs to be cleaned
:type vevent: icalendar.cal.Event
:param default_timezone: timezone to apply to start and/or end dates which
were supposed to be localized but which timezone was not understood
by icalendar
:type timezone: pytz.timezone
:param href: used for logging to inform user which .ics files are
problematic
:type href: str
:param calendar: used for logging to inform user which .ics files are
problematic
:type calendar: str
:returns: clean vevent
:rtype: icalendar.cal.Event
"""
# convert localized datetimes with timezone information we don't
# understand to the default timezone
Expand Down
6 changes: 3 additions & 3 deletions khal/khalendar/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
import sqlite3
from enum import IntEnum
from os import makedirs, path
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union

import icalendar
import icalendar.cal
import pytz
from dateutil import parser

from .. import utils
from ..custom_types import EventTuple
from ..custom_types import EventTuple, LocaleConfiguration
from ..icalendar import assert_only_one_uid, cal_from_ics
from ..icalendar import expand as expand_vevent
from ..icalendar import sanitize as sanitize_vevent
Expand Down Expand Up @@ -75,7 +75,7 @@ class SQLiteDb:
def __init__(self,
calendars: Iterable[str],
db_path: Optional[str],
locale: Dict[str, str],
locale: LocaleConfiguration,
) -> None:
assert db_path is not None
self.calendars: List[str] = list(calendars)
Expand Down
6 changes: 3 additions & 3 deletions khal/khalendar/khalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import os.path
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union # noqa

from ..custom_types import CalendarConfiguration, EventCreationTypes
from ..custom_types import CalendarConfiguration, EventCreationTypes, LocaleConfiguration
from ..icalendar import new_vevent
from . import backend
from .event import Event
Expand Down Expand Up @@ -69,10 +69,10 @@ def __init__(self,
color: str='',
priority: int=10,
highlight_event_days: bool=False,
locale: Optional[Dict[str, Any]]=None,
locale: Optional[LocaleConfiguration]=None,
dbpath: Optional[str]=None,
) -> None:
locale = locale or {}
assert locale
assert dbpath is not None
assert calendars is not None

Expand Down
3 changes: 0 additions & 3 deletions khal/parse_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,6 @@ def guesstimedeltafstr(delta_string: str) -> dt.timedelta:
"""parses a timedelta from a string
:param delta_string: string encoding time-delta, e.g. '1h 15m'
:type delta_string: str
:rtype: datetime.timedelta
"""

tups = re.split(r'(-?\d+)', delta_string)
Expand Down Expand Up @@ -330,7 +328,6 @@ def guessrangefstr(daterange: Union[str, List[str]],
"""parses a range string
:param daterange: date1 [date2 | timedelta]
:type daterange: str or list
:param locale:
:returns: start and end of the date(time) range and if
this is an all-day time range or not,
Expand Down
Loading

0 comments on commit 21ee686

Please sign in to comment.