Repository: pimutils/khal
Pull request #1049 (2021-05-24 21:06:46)

Fix minor doc typos


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 10c2a0f..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,36 +0,0 @@
----
-name: CI
-
-on:
-  push:
-    branches: ["master"]
-  pull_request:
-    branches: ["master"]
-  workflow_dispatch:
-
-jobs:
-  tests:
-    name: "Python ${{ matrix.python-version }}"
-    runs-on: "ubuntu-latest"
-    env:
-      USING_COVERAGE: '3.6,3.8'
-
-    strategy:
-      matrix:
-        python-version: ["3.9"]
-
-    steps:
-      - uses: "actions/checkout@v2"
-      - uses: "actions/setup-python@v2"
-        with:
-          python-version: "${{ matrix.python-version }}"
-      - name: "Install dependencies"
-        run: |
-          set -xe
-          python -VV
-          python -m site
-          python -m pip install --upgrade pip setuptools wheel
-          python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions          
-
-      - name: "Run tox targets for ${{ matrix.python-version }}"
-        run: "python -m tox"
diff --git a/.travis.yml b/.travis.yml
index 00ac835..db5f42f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ language: python
 os: linux
 
 python:
+    - 3.5
     - 3.6
     - 3.7
     - 3.8
@@ -23,8 +24,6 @@ jobs:
         env: BUILD=pytz201610
       - python: 3.9
         env: BUILD=pytz201702
-      - python: 3.9
-        env: BUILD=pytz_latest
     allow_failures:
       - python: "nightly"
 
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 35a51c1..5416ac0 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,16 +8,9 @@ may want to subscribe to `GitHub's tag feed
 <https://github.com/geier/khal/tags.atom>`_.
 
 
-0.10.4
-======
-not released
-
-* DROPPED support for Python 3.5
-* CHANGE: ikhal: tab (and shift tab) jump from the events back to the calendar
-
 0.10.3
 ======
-2021-04-27
+not released
 
 * DROPPED support for Python 3.4
 * FIX `khal interactive` now accepts -a/-d options (as documented)
@@ -31,7 +24,6 @@ not released
    `blank_line_before_day` option
 * FIX `new` keybinding in search no longer crash `ikhal`
 * NEW Improved sorting of events. Sort by `DTSTART`, `DTEND` then `SUMMARY`.
-* NEW Add url input and `{url}` template option
 
 0.10.2
 ======
@@ -39,7 +31,7 @@ not released
 
 * NEW Parse `X-ANNIVERSARY`, `ANNIVERSARY` and `X-ABDATE` fields from vcards
 * NEW Add ability to change default event duration with
-   `default_event_duration` and `default_dayevent_duration` for an day-long 
+   `default_event_duration` and `default_dayevent_duration` for a day-long 
    event
 * NEW Add `{uid}` property to template options in `--format`
 * FIX No warning when importing event with Windows timezone format
diff --git a/README.rst b/README.rst
index 6d32e82..0713dc9 100644
--- a/README.rst
+++ b/README.rst
@@ -24,7 +24,7 @@ Features
 - fast and easy way to add new events
 - ikhal (interactive khal) lets you browse and edit calendars and events
 - no support for editing the timezones of events yet
-- works with python 3.6+
+- works with python 3.5+
 - khal should run on all major operating systems [1]_
 
 .. [1] except for Microsoft Windows
diff --git a/doc/source/conf.py b/doc/source/conf.py
index ec33174..8fb86c0 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 #
 # khal documentation build configuration file, created by
 # sphinx-quickstart on Fri Jul  4 00:00:47 2014.
@@ -35,19 +36,20 @@ def write_section(specsection, secname, key, comment, output):
     # why is _parse_check a "private" method? seems to be rather useful...
     # we don't need fun_kwargs
     fun_name, fun_args, fun_kwargs, default = validator._parse_check(specsection)
-    output.write(f'\n.. _{secname}-{key}:')
+    output.write('\n.. _{}-{}:'.format(secname, key))
     output.write('\n')
-    output.write(f'\n.. object:: {key}\n')
+    output.write('\n.. object:: {}\n'.format(key))
     output.write('\n')
     output.write('    ' + '\n    '.join([line.strip('# ') for line in comment]))
     output.write('\n')
     if fun_name == 'option':
-        fun_args = [f'*{arg}*' for arg in fun_args]
+        fun_args = ['*{}*'.format(arg) for arg in fun_args]
         fun_args = fun_args[:-2] + [fun_args[-2] + ' and ' + fun_args[-1]]
-        fun_name += f", allowed values are {', '.join(fun_args)}"
+        fun_name += ', allowed values are {}'.format(', '.join(fun_args))
         fun_args = []
     if fun_name == 'integer' and len(fun_args) == 2:
-        fun_name += f', allowed values are between {fun_args[0]} and {fun_args[1]}'
+        fun_name += ', allowed values are between {} and {}'.format(
+            fun_args[0], fun_args[1])
         fun_args = []
     output.write('\n')
     if fun_name in ['expand_db_path', 'expand_path']:
@@ -58,20 +60,20 @@ def write_section(specsection, secname, key, comment, output):
             default = ['space' if one == ' ' else one for one in default]
             default = ', '.join(default)
 
-    output.write(f'      :type: {fun_name}')
+    output.write('      :type: {}'.format(fun_name))
     output.write('\n')
     if fun_args != []:
-        output.write(f'      :args: {fun_args}')
+        output.write('      :args: {}'.format(fun_args))
         output.write('\n')
-    output.write(f'      :default: {default}')
+    output.write('      :default: {}'.format(default))
     output.write('\n')
 
 
 with open('configspec.rst', 'w') as f:
     for secname in sorted(spec):
         f.write('\n')
-        heading = f'The [{secname}] section'
-        f.write(f'{heading}\n{ len(heading) * "~"}')
+        heading = 'The [{}] section'.format(secname)
+        f.write('{}\n{}'.format(heading, len(heading) * '~'))
         f.write('\n')
         comment = spec.comments[secname]
         f.write('\n'.join([line[2:] for line in comment]))
diff --git a/doc/source/index.rst b/doc/source/index.rst
index e1dcd08..6332244 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -17,7 +17,7 @@ Features
 - ikhal (interactive khal) lets you browse and edit calendars and events
 - only rudimentary support for creating and editing recursion rules
 - you cannot edit the timezones of events
-- works with python 3.6+
+- works with python 3.5+
 - khal should run on all major operating systems [1]_
 
 .. [1] except for Microsoft Windows
diff --git a/doc/source/install.rst b/doc/source/install.rst
index 0040363..ced2656 100644
--- a/doc/source/install.rst
+++ b/doc/source/install.rst
@@ -48,7 +48,7 @@ or better::
 
 in the unpacked distribution folder.
 
-Since version 0.10, *khal* **only supports python 3.6+**. If you have
+Since version 0.10, *khal* **only supports python 3.5+**. If you have
 python 2 and 3 installed in parallel you might need to use `pip3` instead of
 `pip` and `python3` instead of `python`. In case your operating system cannot
 deal with python 2 and 3 packages concurrently, we suggest installing *khal* in
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
index af77cfb..52c8ada 100644
--- a/doc/source/usage.rst
+++ b/doc/source/usage.rst
@@ -162,9 +162,6 @@ Several options are common to almost all of :program:`khal`'s commands
        returns
        name@mail.com
 
-   url
-       The URL embedded in the event, otherwise nothing. 
-
    By default, all-day events have no times. To see a start and end time anyway simply
    add `-full` to the end of any template with start/end, for instance
    `start-time` becomes `start-time-full` and will always show start and end times (instead
@@ -412,8 +409,6 @@ Options
 
 * **-u, --until=UNTIL** specify until when a recurring event should run
 
-* **--url** specify the URL element of the event
-
 * **--alarms DURATION,...** will add alarm times as DELTAs comma separated for this event,
   *DURATION* should look like `1day 10minutes` or `1d3H10m`, negative
   *DURATIONs* will set alarm after the start of the event.
diff --git a/khal/cli.py b/khal/cli.py
index 6ff4e2d..a49e68d 100644
--- a/khal/cli.py
+++ b/khal/cli.py
@@ -70,8 +70,8 @@ def multi_calendar_select(ctx, include_calendars, exclude_calendars):
         for cal_name in include_calendars:
             if cal_name not in ctx.obj['conf']['calendars']:
                 raise click.BadParameter(
-                    f'Unknown calendar {cal_name}, run `khal printcalendars` '
-                    'to get a list of all configured calendars.'
+                    'Unknown calendar {}, run `khal printcalendars` to get a '
+                    'list of all configured calendars.'.format(cal_name)
                 )
 
         selection.update(include_calendars)
@@ -107,8 +107,8 @@ def _select_one_calendar_callback(ctx, option, calendar):
 def _calendar_select_callback(ctx, option, calendar):
     if calendar and calendar not in ctx.obj['conf']['calendars']:
         raise click.BadParameter(
-            f'Unknown calendar {calendar}, run `khal printcalendars` to get a '
-            'list of all configured calendars.'
+            'Unknown calendar {}, run `khal printcalendars` to get a '
+            'list of all configured calendars.'.format(calendar)
         )
     return calendar
 
@@ -185,7 +185,7 @@ def build_collection(conf, selection):
     return collection
 
 
-class _NoConfig:
+class _NoConfig(object):
     def __getitem__(self, key):
         logger.fatal(
             'Cannot find a config file. If you have no configuration file '
@@ -217,14 +217,14 @@ def stringify_conf(conf):
     # really worth it
     out = list()
     for key, value in conf.items():
-        out.append(f'[{key}]')
+        out.append('[{}]'.format(key))
         for subkey, subvalue in value.items():
             if isinstance(subvalue, dict):
-                out.append(f'  [[{subkey}]]')
+                out.append('  [[{}]]'.format(subkey))
                 for subsubkey, subsubvalue in subvalue.items():
-                    out.append(f'    {subsubkey}: {subsubvalue}')
+                    out.append('    {}: {}'.format(subsubkey, subsubvalue))
             else:
-                out.append(f'  {subkey}: {subvalue}')
+                out.append('  {}: {}'.format(subkey, subvalue))
     return '\n'.join(out)
 
 
@@ -345,12 +345,10 @@ def _get_cli():
                   help=('The format to print the event.'))
     @click.option('--alarms', '-m',
                   help=('Alarm times for the new event as DELTAs comma separated'))
-    @click.option('--url', help=("URI for the event."))
     @click.argument('info', metavar='[START [END | DELTA] [TIMEZONE] [SUMMARY] [:: DESCRIPTION]]',
                     nargs=-1)
     @click.pass_context
-    def new(ctx, calendar, info, location, categories, repeat, until, alarms, url, format,
-            interactive):
+    def new(ctx, calendar, info, location, categories, repeat, until, alarms, format, interactive):
         '''Create a new event from arguments.
 
         START and END can be either dates, times or datetimes, please have a
@@ -396,7 +394,6 @@ def _get_cli():
                 env={"calendars": ctx.obj['conf']['calendars']},
                 until=until,
                 alarms=alarms,
-                url=url,
                 format=format,
             )
         except FatalError as error:
@@ -442,7 +439,7 @@ def _get_cli():
                 ics_strs = (sys.stdin.read(),)
                 if not batch:
                     if os.stat('/dev/tty').st_mode & stat.S_IFCHR > 0:
-                        sys.stdin = open('/dev/tty')
+                        sys.stdin = open('/dev/tty', 'r')
                     else:
                         logger.warning('/dev/tty does not exist, importing might not work')
             else:
@@ -519,7 +516,7 @@ def _get_cli():
                     'longdatetimeformat', 'datetimeformat', 'longdateformat',
                     'dateformat', 'timeformat']:
                 dt_str = time.strftime(ctx.obj['conf']['locale'][strftime_format])
-                click.echo(f'{strftime_format}: {dt_str}')
+                click.echo('{}: {}'.format(strftime_format, dt_str))
         except FatalError as error:
             logger.debug(error, exc_info=True)
             logger.fatal(error)
diff --git a/khal/configwizard.py b/khal/configwizard.py
index 50a957f..853e1dc 100644
--- a/khal/configwizard.py
+++ b/khal/configwizard.py
@@ -44,7 +44,7 @@ def validate_int(input, min_value, max_value):
     if min_value <= number <= max_value:
         return number
     else:
-        raise UsageError(f'Input must be between {min_value} and {max_value}')
+        raise UsageError('Input must be between {} and {}'.format(min_value, max_value))
 
 
 DATE_FORMAT_INFO = [
@@ -60,7 +60,7 @@ def present_date_format_info(example_date):
     for title, formats in DATE_FORMAT_INFO:
         newcol = [title]
         for f in formats:
-            newcol.append(f'{f}={example_date.strftime(f)}')
+            newcol.append('{}={}'.format(f, example_date.strftime(f)))
         widths.append(max(len(s) for s in newcol) + 2)
         columns.append(newcol)
 
@@ -83,7 +83,7 @@ def choose_datetime_format():
     today = dt.date.today()
     print("What ordering of year, month, date do you want to use?")
     for num, (desc, fmt) in enumerate(choices):
-        print(f'[{num}] {desc} (today: {today.strftime(fmt)})')
+        print('[{}] {} (today: {})'.format(num, desc, today.strftime(fmt)))
     print('[3] Custom')
     choice_no = prompt("Please choose one of the above options", value_proc=validate)
     if choice_no == 3:
@@ -91,8 +91,8 @@ def choose_datetime_format():
         dateformat = prompt('Make your date format')
     else:
         dateformat = choices[choice_no][1]
-    print(f"Date format: {dateformat} "
-          f"(today as an example: {today.strftime(dateformat)})")
+    print("Date format: {} "
+          "(today as an example: {})".format(dateformat, today.strftime(dateformat)))
     return dateformat
 
 
@@ -105,8 +105,8 @@ def choose_time_format():
     prompt_text = "Please choose one of the above options"
     timeformat = choices[prompt(prompt_text, default=0, value_proc=validate)]
     now = dt.datetime.now()
-    print(f"Time format: {timeformat} "
-          f"(current time as an example: {now.strftime(timeformat)})")
+    print("Time format: {} "
+          "(current time as an example: {})".format(timeformat, now.strftime(timeformat)))
     return timeformat
 
 
@@ -114,7 +114,7 @@ def choose_default_calendar(vdirs):
     names = [name for name, _, _ in sorted(vdirs or ())]
     print("Which calendar do you want as a default calendar?")
     print("(The default calendar is specified, when no calendar is specified.)")
-    print(f"Configured calendars: {', '.join(names)}")
+    print("Configured calendars: {}".format(', '.join(names)))
     default_calendar = prompt(
         "Please type one of the above options",
         default=names[0],
@@ -143,7 +143,7 @@ def get_vdirs_from_vdirsyncer_config():
               "error message:")
         print(error)
         return None
-    vdirs = []
+    vdirs = list()
     for storage in vdir_config.storages.values():
         if storage['type'] == 'filesystem':
             # TODO detect type of storage properly
@@ -152,18 +152,17 @@ def get_vdirs_from_vdirsyncer_config():
                 path += '/'
             path += '*'
             vdirs.append((storage['instance_name'], path, 'discover'))
-    if vdirs == []:
+    if vdirs == list():
         print("No usable collections were found")
         return None
     else:
         print("The following collections were found:")
         for name, path, _ in vdirs:
-            print(f'  {name}: {path}')
+            print('  {}: {}'.format(name, path))
         return vdirs
 
 
-def create_vdir(names=None):
-    names = names or []
+def create_vdir(names=[]):
     if not confirm("Do you want to create a local calendar? (You can always "
                    "set it up to synchronize with a server in vdirsyncer "
                    "later)."):
@@ -179,18 +178,18 @@ def create_vdir(names=None):
     try:
         makedirs(path)
     except OSError as error:
-        print(f"Could not create directory {path} because of {error}. Exiting")
+        print("Could not create directory {} because of {}. Exiting".format(path, error))
         raise
-    print(f"Created new vdir at {path}")
+    print("Created new vdir at {}".format(path))
     return [(name, path, 'calendar')]
 
 
 def create_config(vdirs, dateformat, timeformat, default_calendar=None):
     config = ['[calendars]']
     for name, path, type_ in sorted(vdirs or ()):
-        config.append(f'\n[[{name}]]')
-        config.append(f'path = {path}')
-        config.append(f'type = {type_}')
+        config.append('\n[[{name}]]'.format(name=name))
+        config.append('path = {path}'.format(path=path))
+        config.append('type = {type}'.format(type=type_))
 
     config.append('\n[locale]')
     config.append('timeformat = {timeformat}\n'
@@ -203,7 +202,7 @@ def create_config(vdirs, dateformat, timeformat, default_calendar=None):
                           longdateformat=dateformat))
     if default_calendar:
         config.append('[default]')
-        config.append(f'default_calendar = {default_calendar}\n')
+        config.append('default_calendar = {}\n'.format(default_calendar))
     config = '\n'.join(config)
 
     return config
@@ -212,7 +211,7 @@ def create_config(vdirs, dateformat, timeformat, default_calendar=None):
 def configwizard():
     config_file = settings.find_configuration_file()
     if config_file is not None:
-        logger.fatal(f"Found an existing config file at {config_file}.")
+        logger.fatal("Found an existing config file at {}.".format(config_file))
         logger.fatal(
             "If you want to create a new configuration file, "
             "please remove the old one first. Exiting.")
@@ -244,8 +243,8 @@ def configwizard():
     )
     config_path = join(xdg.BaseDirectory.xdg_config_home, 'khal', 'config')
     if not confirm(
-            f"Do you want to write the config to {config_path}? "
-            "(Choosing `No` will abort)", default=True):
+            "Do you want to write the config to {}? "
+            "(Choosing `No` will abort)".format(config_path), default=True):
         raise FatalError('User aborted...')
     config_dir = join(xdg.BaseDirectory.xdg_config_home, 'khal')
     if not exists(config_dir) and not isdir(config_dir):
@@ -253,12 +252,12 @@ def configwizard():
             makedirs(config_dir)
         except OSError as error:
             print(
-                f"Could not write config file at {config_dir} because of "
-                f"{error}. Aborting"
+                "Could not write config file at {} because of {}. "
+                "Aborting".format(config_dir, error)
             )
             raise FatalError(error)
         else:
-            print(f'created directory {config_dir}')
+            print('created directory {}'.format(config_dir))
     with open(config_path, 'w') as config_file:
         config_file.write(config)
-    print(f"Successfully wrote configuration to {config_path}")
+    print("Successfully wrote configuration to {}".format(config_path))
diff --git a/khal/controllers.py b/khal/controllers.py
index b1afeb4..e4dbc47 100644
--- a/khal/controllers.py
+++ b/khal/controllers.py
@@ -226,7 +226,7 @@ def khal_list(collection, daterange=None, conf=None, agenda_format=None,
             default_timedelta_date=conf['default']['timedelta'],
             default_timedelta_datetime=conf['default']['timedelta'],
         )
-        logger.debug(f'Getting all events between {start} and {end}')
+        logger.debug('Getting all events between {} and {}'.format(start, end))
 
     elif datepoint is not None:
         if not datepoint:
@@ -236,9 +236,9 @@ def khal_list(collection, daterange=None, conf=None, agenda_format=None,
                 datepoint, conf['locale'], dt.date.today(),
             )
         except ValueError:
-            raise FatalError('Invalid value of `{' '.join(datepoint)}` for a datetime')
+            raise FatalError('Invalid value of `{}` for a datetime'.format(' '.join(datepoint)))
         if allday:
-            logger.debug(f'Got date {start}')
+            logger.debug('Got date {}'.format(start))
             raise FatalError('Please supply a datetime, not a date.')
         end = start + dt.timedelta(seconds=1)
         if day_format is None:
@@ -246,7 +246,7 @@ def khal_list(collection, daterange=None, conf=None, agenda_format=None,
                 start.strftime(conf['locale']['longdatetimeformat']),
                 bold=True,
             )
-        logger.debug(f'Getting all events between {start} and {end}')
+        logger.debug('Getting all events between {} and {}'.format(start, end))
 
     event_column = []
     once = set() if once else None
@@ -280,7 +280,7 @@ def khal_list(collection, daterange=None, conf=None, agenda_format=None,
 
 def new_interactive(collection, calendar_name, conf, info, location=None,
                     categories=None, repeat=None, until=None, alarms=None,
-                    format=None, env=None, url=None):
+                    format=None, env=None):
     try:
         info = parse_datetime.eventinfofstr(
             info, conf['locale'],
@@ -289,7 +289,7 @@ def new_interactive(collection, calendar_name, conf, info, location=None,
             adjust_reasonably=True, localize=False,
         )
     except DateTimeParseError:
-        info = {}
+        info = dict()
 
     while True:
         summary = info.get('summary')
@@ -333,7 +333,7 @@ def new_interactive(collection, calendar_name, conf, info, location=None,
     event = new_from_args(
         collection, calendar_name, conf, format=format, env=env,
         location=location, categories=categories,
-        repeat=repeat, until=until, alarms=alarms, url=url,
+        repeat=repeat, until=until, alarms=alarms,
         **info)
 
     echo("event saved")
@@ -344,7 +344,7 @@ def new_interactive(collection, calendar_name, conf, info, location=None,
 
 def new_from_string(collection, calendar_name, conf, info, location=None,
                     categories=None, repeat=None, until=None, alarms=None,
-                    url=None, format=None, env=None):
+                    format=None, env=None):
     """construct a new event from a string and add it"""
     info = parse_datetime.eventinfofstr(
         info, conf['locale'],
@@ -355,22 +355,22 @@ def new_from_string(collection, calendar_name, conf, info, location=None,
     new_from_args(
         collection, calendar_name, conf, format=format, env=env,
         location=location, categories=categories, repeat=repeat,
-        until=until, alarms=alarms, url=url, **info
+        until=until, alarms=alarms, **info
     )
 
 
 def new_from_args(collection, calendar_name, conf, dtstart=None, dtend=None,
                   summary=None, description=None, allday=None, location=None,
                   categories=None, repeat=None, until=None, alarms=None,
-                  timezone=None, url=None, format=None, env=None):
+                  timezone=None, format=None, env=None):
     """Create a new event from arguments and add to vdirs"""
     if isinstance(categories, str):
-        categories = [category.strip() for category in categories.split(',')]
+        categories = list([category.strip() for category in categories.split(',')])
     try:
         event = new_vevent(
             locale=conf['locale'], location=location, categories=categories,
             repeat=repeat, until=until, alarms=alarms, dtstart=dtstart,
-            dtend=dtend, summary=summary, description=description, timezone=timezone, url=url,
+            dtend=dtend, summary=summary, description=description, timezone=timezone,
         )
     except ValueError as error:
         raise FatalError(error)
@@ -381,7 +381,7 @@ def new_from_args(collection, calendar_name, conf, dtstart=None, dtend=None,
         collection.new(event)
     except ReadOnlyCalendarError:
         raise FatalError(
-            f'ERROR: Cannot modify calendar "{calendar_name}" as it is read-only'
+            'ERROR: Cannot modify calendar "{}" as it is read-only'.format(calendar_name)
         )
 
     if conf['default']['print_new'] == 'event':
@@ -428,7 +428,6 @@ def edit_event(event, collection, locale, allow_quit=False, width=80):
     options["categories"] = {"short": "c", "attr": "categories", "none": True}
     options["alarm"] = {"short": "a"}
     options["Delete"] = {"short": "D"}
-    options["url"] = {"short": "u", "attr": "url", "none": True}
 
     now = dt.datetime.now()
 
@@ -508,7 +507,7 @@ def edit_event(event, collection, locale, allow_quit=False, width=80):
             if allow_none and value == "None":
                 value = ""
             if attr == 'categories':
-                getattr(event, "update_" + attr)([cat.strip() for cat in value.split(',')])
+                getattr(event, "update_" + attr)(list([cat.strip() for cat in value.split(',')]))
             else:
                 getattr(event, "update_" + attr)(value)
             edited = True
@@ -546,7 +545,7 @@ def interactive(collection, conf):
         collection, conf, title="select an event", description="do something")
     ui.start_pane(
         pane, pane.cleanup,
-        program_info=f'{__productname__} v{__version__}',
+        program_info='{0} v{1}'.format(__productname__, __version__),
         quit_keys=conf['keybindings']['quit'],
     )
 
@@ -594,11 +593,11 @@ def import_event(vevent, collection, locale, batch, format=None, env=None):
     else:
         calendar_names = sorted(collection.writable_names)
         choices = ', '.join(
-            [f'{name}({num})' for num, name in enumerate(calendar_names)])
+            ['{}({})'.format(name, num) for num, name in enumerate(calendar_names)])
         while True:
             value = prompt(
                 "Which calendar do you want to import to? (unique prefixes are fine)\n"
-                f"{choices}",
+                "{}".format(choices),
                 default=collection.default_calendar_name,
             )
             try:
@@ -612,7 +611,7 @@ def import_event(vevent, collection, locale, batch, format=None, env=None):
             echo('invalid choice')
     assert calendar_name in collection.writable_names
 
-    if batch or confirm(f"Do you want to import this event into `{calendar_name}`?"):
+    if batch or confirm("Do you want to import this event into `{}`?".format(calendar_name)):
         try:
             collection.new(Item(vevent), collection=calendar_name)
         except DuplicateUid:
@@ -620,7 +619,7 @@ def import_event(vevent, collection, locale, batch, format=None, env=None):
                     "An event with the same UID already exists. Do you want to update it?"):
                 collection.force_update(Item(vevent), collection=calendar_name)
             else:
-                logger.warning(f"Not importing event with UID `{event.uid}`")
+                logger.warning("Not importing event with UID `{}`".format(event.uid))
 
 
 def print_ics(conf, name, ics, format):
@@ -632,11 +631,11 @@ def print_ics(conf, name, ics, format):
     for event in events:
         events_grouped[event['UID']].append(event)
 
-    vevents = []
+    vevents = list()
     for uid in events_grouped:
         vevents.append(sorted(events_grouped[uid], key=sort_vevent_key))
 
-    echo(f'{len(vevents)} events found in {name}')
+    echo('{} events found in {}'.format(len(vevents), name))
     for sub_event in vevents:
         event = Event.fromVEvents(sub_event, locale=conf['locale'])
         echo(event.format(format, dt.datetime.now()))
diff --git a/khal/icalendar.py b/khal/icalendar.py
index a35fd2f..735f4ce 100644
--- a/khal/icalendar.py
+++ b/khal/icalendar.py
@@ -71,7 +71,7 @@ def split_ics(ics, random_uid=False, default_timezone=None):
 
 def new_event(locale, dtstart=None, dtend=None, summary=None, timezone=None,
               allday=False, description=None, location=None, categories=None,
-              repeat=None, until=None, alarms=None, url=None):
+              repeat=None, until=None, alarms=None):
     """create a new event
 
     :param dtstart: starttime of that event
@@ -87,8 +87,6 @@ def new_event(locale, dtstart=None, dtend=None, summary=None, timezone=None,
     :param allday: if set to True, we will not transform dtstart and dtend to
         datetime
     :type allday: bool
-    :param url: url of the event
-    :type url: string
     :returns: event
     :rtype: icalendar.Event
     """
@@ -118,8 +116,6 @@ def new_event(locale, dtstart=None, dtend=None, summary=None, timezone=None,
         event.add('location', location)
     if categories:
         event.add('categories', categories)
-    if url:
-        event.add('url', icalendar.vUri(url))
     if repeat and repeat != "none":
         rrule = rrulefstr(repeat, until, locale)
         event.add('rrule', rrule)
@@ -178,8 +174,8 @@ def ics_from_list(events, tzs, random_uid=False, default_timezone=None):
                 if datetime_.tzinfo is None and 'TZID' in item.params and \
                         item.params['TZID'] not in missing_tz:
                     logger.warning(
-                        f"Cannot find timezone `{item.params['TZID']}` in .ics file, "
-                        "using default timezone. This can lead to erroneous time shifts"
+                        'Cannot find timezone `{}` in .ics file, using default timezone. '
+                        'This can lead to erroneous time shifts'.format(item.params['TZID'])
                     )
                     missing_tz.add(item.params['TZID'])
                 elif datetime_.tzinfo and datetime_.tzinfo != pytz.UTC and \
@@ -191,8 +187,8 @@ def ics_from_list(events, tzs, random_uid=False, default_timezone=None):
             calendar.add_component(tzs[str(tzid)])
         else:
             logger.warning(
-                f'Cannot find timezone `{tzid}` in .ics file, this could be a bug, '
-                'please report this issue at http://github.com/pimutils/khal/.')
+                'Cannot find timezone `{}` in .ics file, this could be a bug, '
+                'please report this issue at http://github.com/pimutils/khal/.'.format(tzid))
     for sub_event in events:
         calendar.add_component(sub_event)
     return calendar.to_ical().decode('utf-8')
@@ -277,19 +273,19 @@ def expand(vevent, href=''):
 
             if testuntil < teststart:
                 logger.warning(
-                    f'{href}: Unsupported recurrence. UNTIL is before DTSTART.\n'
-                    'This event will not be available in khal.')
+                    '{0}: Unsupported recurrence. UNTIL is before DTSTART.\n'
+                    'This event will not be available in khal.'.format(href))
                 return False
 
         if rrule.count() == 0:
             logger.warning(
-                f'{href}: Recurrence defined but will never occur.\n'
-                'This event will not be available in khal.')
+                '{0}: Recurrence defined but will never occur.\n'
+                'This event will not be available in khal.'.format(href))
             return False
 
         rrule = map(sanitize_datetime, rrule)
 
-        logger.debug(f'calculating recurrence dates for {href}, this might take some time.')
+        logger.debug('calculating recurrence dates for {}, this might take some time.'.format(href))
 
         # RRULE and RDATE may specify the same date twice, it is recommended by
         # the RFC to consider this as only one instance
@@ -322,8 +318,8 @@ def expand(vevent, href=''):
                 dtstartl.remove(date)
             except KeyError:
                 logger.warning(
-                    f'In event {href}, excluded instance starting at {date} '
-                    'not found, event might be invalid.')
+                    'In event {}, excluded instance starting at {} not found, '
+                    'event might be invalid.'.format(href, date))
 
     dtstartend = [(start, start + duration) for start in dtstartl]
     # not necessary, but I prefer deterministic output
@@ -371,9 +367,9 @@ def sanitize(vevent, default_timezone, href='', calendar=''):
             value = default_timezone.localize(vevent.pop(prop).dt)
             vevent.add(prop, value)
             logger.warning(
-                f"{prop} localized in invalid or incomprehensible timezone "
-                f"`{timezone}` in {calendar}/{href}. This could lead to this "
-                "event being wrongly displayed."
+                "{} localized in invalid or incomprehensible timezone `{}` in {}/{}. "
+                "This could lead to this event being wrongly displayed."
+                "".format(prop, timezone, calendar, href)
             )
 
     vdtstart = vevent.pop('DTSTART', None)
diff --git a/khal/khalendar/backend.py b/khal/khalendar/backend.py
index f0c039b..bbe7067 100644
--- a/khal/khalendar/backend.py
+++ b/khal/khalendar/backend.py
@@ -57,7 +57,7 @@ class EventType(IntEnum):
     DATETIME = 1
 
 
-class SQLiteDb:
+class SQLiteDb(object):
     """
     This class should provide a caching database for a calendar, keeping raw
     vevents in one table and allowing to retrieve them by dates (via the help
@@ -111,7 +111,7 @@ class SQLiteDb:
                 makedirs(dbdir, mode=0o770)
                 logger.debug('success')
             except OSError as error:
-                logger.critical(f'failed to create {dbdir}: {error}')
+                logger.critical('failed to create {0}: {1}'.format(dbdir, error))
                 raise CouldNotCreateDbDir()
 
     def _check_table_version(self) -> None:
@@ -181,7 +181,7 @@ class SQLiteDb:
             result = self.cursor.fetchone()
 
             if result[0] != 0:
-                logger.debug(f"tables for calendar {cal} exist")
+                logger.debug("tables for calendar {0} exist".format(cal))
             else:
                 sql_s = 'INSERT INTO calendars (calendar, resource) VALUES (?, ?);'
                 stuple = (cal, '')
@@ -218,11 +218,12 @@ class SQLiteDb:
         check_for_errors(ical, calendar, href)
         if not assert_only_one_uid(ical):
             logger.warning(
-                f"The .ics file at {calendar}/{href} contains multiple UIDs.\n"
+                "The .ics file at {}/{} contains multiple UIDs.\n"
                 "This should not occur in vdir .ics files.\n"
                 "If you didn't edit the file by hand, please report a bug "
                 "at https://github.com/pimutils/khal/issues .\n"
                 "If you want to import it, please use `khal import FILE`."
+                "".format(calendar, href)
             )
             raise NonUniqueUID
         vevents = (sanitize_vevent(c, self.locale['default_timezone'], href, calendar) for
@@ -270,8 +271,9 @@ class SQLiteDb:
                 date = vcard[key]
                 if isinstance(date, list):
                     logger.warning(
-                        f'Vcard {href} in collection {calendar} has more than one '
-                        f'{key}, will be skipped and not be available in khal.'
+                        'Vcard {0} in collection {1} has more than one '
+                        '{2}, will be skipped and not be available '
+                        'in khal.'.format(href, calendar, key)
                     )
                     continue
                 try:
@@ -283,7 +285,7 @@ class SQLiteDb:
                     date = parser.parse(date).date()
                 except ValueError:
                     logger.warning(
-                        f'cannot parse {key} in {href} in collection {calendar}')
+                        'cannot parse {0} in {1} in collection {2}'.format(key, href, calendar))
                     continue
                 if 'FN' in vcard:
                     name = vcard['FN']
@@ -307,10 +309,10 @@ class SQLiteDb:
                         xtag = 'x-abdate'
                         vevent.add('x-ablabel', description)
                     vevent.add(xtag,
-                               f'{date.year:04}{date.month:02}{date.day:02}')
+                               '{:04}{:02}{:02}'.format(date.year, date.month, date.day))
                     vevent.add('x-fname', name)
                 vevent.add('summary',
-                           f'{name}\'s {description}')
+                           '{0}\'s {1}'.format(name, description))
                 vevent.add('uid', href + key)
                 vevent_str = vevent.to_ical().decode('utf-8')
                 self._update_impl(vevent, href + key, calendar)
@@ -374,8 +376,8 @@ class SQLiteDb:
 
             if thisandfuture:
                 recs_sql_s = (
-                    f'UPDATE {recs_table} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, '
-                    'ref = ? WHERE rec_inst >= ? AND href = ? AND calendar = ?;')
+                    'UPDATE {0} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, ref = ? '
+                    'WHERE rec_inst >= ? AND href = ? AND calendar = ?;'.format(recs_table))
                 stuple_f = (
                     start_shift_seconds, start_shift_seconds + duration_seconds,
                     ref, rec_inst, href, calendar,
@@ -383,9 +385,9 @@ class SQLiteDb:
                 self.sql_ex(recs_sql_s, stuple_f)
             else:
                 recs_sql_s = (
-                    f'INSERT OR REPLACE INTO {recs_table} '
+                    'INSERT OR REPLACE INTO {0} '
                     '(dtstart, dtend, href, ref, dtype, rec_inst, calendar)'
-                    'VALUES (?, ?, ?, ?, ?, ?, ?);')
+                    'VALUES (?, ?, ?, ?, ?, ?, ?);'.format(recs_table))
                 stuple_n = (dbstart, dbend, href, ref, dtype, rec_inst, calendar)
                 self.sql_ex(recs_sql_s, stuple_n)
 
@@ -428,7 +430,7 @@ class SQLiteDb:
         """
         assert calendar is not None
         for table in ['recs_loc', 'recs_float']:
-            sql_s = f'DELETE FROM {table} WHERE href = ? AND calendar = ?;'
+            sql_s = 'DELETE FROM {0} WHERE href = ? AND calendar = ?;'.format(table)
             self.sql_ex(sql_s, (href, calendar))
         sql_s = 'DELETE FROM events WHERE href = ? AND calendar = ?;'
         self.sql_ex(sql_s, (href, calendar))
@@ -445,7 +447,7 @@ class SQLiteDb:
         """
         assert calendar is not None
         for table in ['recs_loc', 'recs_float']:
-            sql_s = f'DELETE FROM {table} WHERE href LIKE ? AND calendar = ?;'
+            sql_s = 'DELETE FROM {0} WHERE href LIKE ? AND calendar = ?;'.format(table)
             self.sql_ex(sql_s, (href, calendar))
         sql_s = 'DELETE FROM events WHERE href LIKE ? AND calendar = ?;'
         self.sql_ex(sql_s, (href, calendar))
@@ -502,7 +504,7 @@ class SQLiteDb:
             'ORDER BY dtstart')
         stuple = tuple([start, end, start, end, start, end] + list(self.calendars))
         result = self.sql_ex(sql_s.format(','.join(["?"] * len(self.calendars))), stuple)
-        for item, href, start, end, ref, etag, _dtype, calendar in result:
+        for item, href, start, end, ref, etag, dtype, calendar in result:
             start = pytz.UTC.localize(dt.datetime.utcfromtimestamp(start))
             end = pytz.UTC.localize(dt.datetime.utcfromtimestamp(end))
             yield item, href, start, end, ref, etag, calendar
@@ -575,7 +577,7 @@ class SQLiteDb:
             'recs_loc.calendar = events.calendar '
             'WHERE item LIKE (?) and events.calendar in ({0});'
         )
-        stuple = tuple([f'%{search_string}%'] + list(self.calendars))
+        stuple = tuple(['%{0}%'.format(search_string)] + list(self.calendars))
         result = self.sql_ex(sql_s.format(','.join(["?"] * len(self.calendars))), stuple)
         for item, href, start, end, ref, etag, dtype, calendar in result:
             start = pytz.UTC.localize(dt.datetime.utcfromtimestamp(start))
@@ -592,7 +594,7 @@ class SQLiteDb:
             'recs_float.calendar = events.calendar '
             'WHERE item LIKE (?) and events.calendar in ({0});'
         )
-        stuple = tuple([f'%{search_string}%'] + list(self.calendars))
+        stuple = tuple(['%{0}%'.format(search_string)] + list(self.calendars))
         result = self.sql_ex(sql_s.format(','.join(["?"] * len(self.calendars))), stuple)
         for item, href, start, end, ref, etag, dtype, calendar in result:
             start = dt.datetime.utcfromtimestamp(start)
@@ -615,16 +617,18 @@ def check_support(vevent: icalendar.cal.Event, href: str, calendar: str):
         raise UpdateFailed(
             'The parameter `THISANDPRIOR` is not (and will not be) '
             'supported by khal (as applications supporting the latest '
-            f'standard MUST NOT create those. Therefore event {href} from '
-            f'calendar {calendar} will not be shown in khal'
+            'standard MUST NOT create those. Therefore event {0} from '
+            'calendar {1} will not be shown in khal'
+            .format(href, calendar)
         )
     rdate = vevent.get('RDATE')
     if rdate is not None and hasattr(rdate, 'params') and rdate.params.get('VALUE') == 'PERIOD':
         raise UpdateFailed(
             '`RDATE;VALUE=PERIOD` is currently not supported by khal. '
-            f'Therefore event {href} from calendar {calendar} will not be shown in khal.\n'
+            'Therefore event {0} from calendar {1} will not be shown in khal.\n'
             'Please post exemplary events (please remove any private data) '
             'to https://github.com/pimutils/khal/issues/152 .'
+            .format(href, calendar)
         )
 
 
@@ -632,8 +636,8 @@ def check_for_errors(component: icalendar.cal.Component, calendar: str, href: st
     """checking if component.errors exists, is not empty and if so warn the user"""
     if hasattr(component, 'errors') and component.errors:
         logger.error(
-            f'Errors occurred when parsing {calendar}/{href} for '
-            'the following reasons:')
+            'Errors occurred when parsing {0}/{1} for the following '
+            'reasons:'.format(calendar, href))
         for error in component.errors:
             logger.error(error)
         logger.error('This might lead to this event being shown wrongly or not at all.')
diff --git a/khal/khalendar/event.py b/khal/khalendar/event.py
index 42dfa40..8c5f6ff 100644
--- a/khal/khalendar/event.py
+++ b/khal/khalendar/event.py
@@ -39,7 +39,7 @@ from ..parse_datetime import timedelta2str
 logger = logging.getLogger('khal')
 
 
-class Event:
+class Event(object):
     """base Event class for representing a *recurring instance* of an Event
 
     (in case of non-recurring events this distinction is irrelevant)
@@ -121,7 +121,7 @@ class Event:
         """
         assert isinstance(events_list, list)
 
-        vevents = {}
+        vevents = dict()
         for event in events_list:
             if 'RECURRENCE-ID' in event:
                 if invalid_timezone(event['RECURRENCE-ID']):
@@ -184,12 +184,12 @@ class Event:
             try:
                 return end < other_end
             except TypeError:
-                raise ValueError(f'Cannot compare events {end} and {other_end}')
+                raise ValueError('Cannot compare events {} and {}'.format(end, other_end))
 
         try:
             return start < other_start
         except TypeError:
-            raise ValueError(f'Cannot compare events {start} and {other_start}')
+            raise ValueError('Cannot compare events {} and {}'.format(start, other_start))
 
     def update_start_end(self, start, end):
         """update start and end time of this event
@@ -218,18 +218,9 @@ class Event:
 
     @property
     def recurring(self):
-        try:
-            rval = 'RRULE' in self._vevents[self.ref] or \
-                '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 "
-                "file an issue at https://github.com/pimutils/khal/issues"
-            )
-            raise
-        else:
-            return rval
+        return 'RRULE' in self._vevents[self.ref] or \
+            'RECURRENCE-ID' in self._vevents[self.ref] or \
+            'RDATE' in self._vevents[self.ref]
 
     @property
     def recurpattern(self):
@@ -271,21 +262,21 @@ class Event:
     @property
     def symbol_strings(self):
         if self._locale['unicode_symbols']:
-            return {
-                'recurring': '\N{Clockwise gapped circle arrow}',
-                'range': '\N{Left right arrow}',
-                'range_end': '\N{Rightwards arrow to bar}',
-                'range_start': '\N{Rightwards arrow from bar}',
-                'right_arrow': '\N{Rightwards arrow}'
-            }
+            return dict(
+                recurring='\N{Clockwise gapped circle arrow}',
+                range='\N{Left right arrow}',
+                range_end='\N{Rightwards arrow to bar}',
+                range_start='\N{Rightwards arrow from bar}',
+                right_arrow='\N{Rightwards arrow}'
+            )
         else:
-            return {
-                'recurring': '(R)',
-                'range': '<->',
-                'range_end': '->|',
-                'range_start': '|->',
-                'right_arrow': '->'
-            }
+            return dict(
+                recurring='(R)',
+                range='<->',
+                range_end='->|',
+                range_start='|->',
+                right_arrow='->'
+            )
 
     @property
     def start_local(self):
@@ -327,22 +318,10 @@ class Event:
         cn = organizer.params.get('CN', '')
         email = organizer.split(':')[-1]
         if cn:
-            return f'{cn} ({email})'
+            return '{} ({})'.format(cn, email)
         else:
             return email
 
-    @property
-    def url(self):
-        if 'URL' not in self._vevents[self.ref]:
-            return ''
-        return self._vevents[self.ref]['URL']
-
-    def update_url(self, url):
-        if url:
-            self._vevents[self.ref]['URL'] = url
-        else:
-            self._vevents[self.ref].pop('URL')
-
     @staticmethod
     def _create_calendar():
         """
@@ -365,7 +344,7 @@ class Event:
         return text
         """
         calendar = self._create_calendar()
-        tzs = []
+        tzs = list()
         for vevent in self._vevents.values():
             if hasattr(vevent['DTSTART'].dt, 'tzinfo') and vevent['DTSTART'].dt.tzinfo is not None:
                 tzs.append(vevent['DTSTART'].dt.tzinfo)
@@ -506,14 +485,12 @@ class Event:
             recurstr = ''
         return recurstr
 
-    def format(self, format_string, relative_to, env=None, colors=True):
+    def format(self, format_string, relative_to, env={}, colors=True):
         """
         :param colors: determines if colors codes should be printed or not
         :type colors: bool
         """
-        env = env or {}
-
-        attributes = {}
+        attributes = dict()
         try:
             relative_to_start, relative_to_end = relative_to
         except TypeError:
@@ -641,7 +618,6 @@ class Event:
         attributes["all-day"] = allday
         attributes["categories"] = self.categories
         attributes['uid'] = self.uid
-        attributes['url'] = self.url
 
         if "calendars" in env and self.calendar in env["calendars"]:
             cal = env["calendars"][self.calendar]
@@ -691,7 +667,7 @@ class Event:
 
         # in case the instance we want to delete is specified as a RECURRENCE-ID
         # event, we should delete that as well
-        to_pop = []
+        to_pop = list()
         for key in self._vevents:
             if key == 'PROTO':
                 continue
@@ -722,9 +698,10 @@ class LocalizedEvent(DatetimeEvent):
             starttz = getattr(self._vevents[self.ref]['DTSTART'].dt, 'tzinfo', None)
         except KeyError:
             msg = (
-                f"Cannot understand event {kwargs.get('href')} from "
-                f"calendar {kwargs.get('calendar')}, you might want to file an issue at "
+                "Cannot understand event {} from "
+                "calendar {}, you might want to file an issue at "
                 "https://github.com/pimutils/khal/issues"
+                .format(kwargs.get('href'), kwargs.get('calendar'))
             )
             logger.fatal(msg)
             raise FatalError(  # because in ikhal you won't see the logger's output
@@ -784,14 +761,14 @@ class AllDayEvent(Event):
 
     @property
     def end(self):
-        end = super().end
+        end = super(AllDayEvent, self).end
         if end == self.start:
             # https://github.com/pimutils/khal/issues/129
-            logger.warning(f'{self.href} ("{self.summary}"): The event\'s end '
-                           'date property contains the same value as the start '
-                           'date, which is invalid as per RFC 5545. Khal will '
-                           'assume this is meant to be a single-day event on '
-                           f'{self.start}')
+            logger.warning('{} ("{}"): The event\'s end date property '
+                           'contains the same value as the start date, '
+                           'which is invalid as per RFC 5545. Khal will '
+                           'assume this is meant to be a single-day event '
+                           'on {}'.format(self.href, self.summary, self.start))
             end += dt.timedelta(days=1)
         return end - dt.timedelta(days=1)
 
@@ -863,7 +840,7 @@ def create_timezone(tz, first_date=None, last_date=None):
             last_num = num
             last_tt = transtime
 
-    timezones = {}
+    timezones = dict()
     for num in range(first_num, last_num + 1):
         name = tz._transition_info[num][2]
         if name in timezones:
diff --git a/khal/khalendar/exceptions.py b/khal/khalendar/exceptions.py
index 9dcc058..1629176 100644
--- a/khal/khalendar/exceptions.py
+++ b/khal/khalendar/exceptions.py
@@ -31,7 +31,7 @@ class UnsupportedRruleExceptionError(UnsupportedFeatureError):
     def __init__(self, message=''):
         x = 'This kind of recurrence exception is currently unsupported'
         if message:
-            x += f': {message.strip()}'
+            x += ': {}'.format(message.strip())
         UnsupportedFeatureError.__init__(self, x)
 
 
diff --git a/khal/khalendar/khalendar.py b/khal/khalendar/khalendar.py
index 3ddc85a..317d937 100644
--- a/khal/khalendar/khalendar.py
+++ b/khal/khalendar/khalendar.py
@@ -46,15 +46,15 @@ logger = logging.getLogger('khal')
 def create_directory(path: str):
     if not os.path.isdir(path):
         if os.path.exists(path):
-            raise RuntimeError(f'{path} is not a directory.')
+            raise RuntimeError('{0} is not a directory.'.format(path))
         try:
             os.makedirs(path, mode=0o750)
         except OSError as error:
-            logger.critical(f'failed to create {path}: {error}')
+            logger.critical('failed to create {0}: {1}'.format(path, error))
             raise CouldNotCreateDbDir()
 
 
-class CalendarCollection:
+class CalendarCollection(object):
     """CalendarCollection allows access to various calendars stored in vdirs
 
     all calendars are cached in an sqlitedb for performance reasons"""
@@ -67,15 +67,14 @@ class CalendarCollection:
                  color: str='',
                  priority: int=10,
                  highlight_event_days: bool=False,
-                 locale: Optional[Dict[str, Any]]=None,
+                 locale: Dict[str, Any]=dict(),
                  dbpath: Optional[str]=None,
                  ) -> None:
-        locale = locale or {}
         assert dbpath is not None
         assert calendars is not None
         self._calendars = calendars
         self._default_calendar_name = None  # type: Optional[str]
-        self._storages = {}  # type: Dict[str, Vdir]
+        self._storages = dict()  # type: Dict[str, Vdir]
         for name, calendar in self._calendars.items():
             ctype = calendar.get('ctype', 'calendar')
             if ctype == 'calendar':
@@ -88,7 +87,7 @@ class CalendarCollection:
                 self._storages[name] = Vdir(calendar['path'], file_ext)
             except CollectionNotFoundError:
                 os.makedirs(calendar['path'])
-                logger.info(f"created non-existing vdir {calendar['path']}")
+                logger.info('created non-existing vdir {}'.format(calendar['path']))
                 self._storages[name] = Vdir(calendar['path'], file_ext)
 
         self.hmethod = hmethod
@@ -99,7 +98,7 @@ class CalendarCollection:
         self.highlight_event_days = highlight_event_days
         self._locale = locale
         self._backend = backend.SQLiteDb(self.names, dbpath, self._locale)
-        self._last_ctags = {}  # type: Dict[str, str]
+        self._last_ctags = dict()  # type: Dict[str, str]
         self.update_db()
 
     @property
@@ -123,7 +122,7 @@ class CalendarCollection:
         if default is None:
             self._default_calendar_name = default
         elif default not in self.names:
-            raise ValueError(f'Unknown calendar: {default}')
+            raise ValueError('Unknown calendar: {0}'.format(default))
 
         readonly = self._calendars[default].get('readonly', False)
 
@@ -131,7 +130,7 @@ class CalendarCollection:
             self._default_calendar_name = default
         else:
             raise ValueError(
-                f'Calendar "{default}" is read-only and cannot be used as default')
+                'Calendar "{0}" is read-only and cannot be used as default'.format(default))
 
     def _local_ctag(self, calendar: str) -> str:
         return get_etag_from_file(self._calendars[calendar]['path'])
@@ -304,7 +303,7 @@ class CalendarCollection:
     def _db_update(self, calendar: str):
         """implements the actual db update on a per calendar base"""
         local_ctag = self._local_ctag(calendar)
-        db_hrefs = {href for href, etag in self._backend.list(calendar)}
+        db_hrefs = set(href for href, etag in self._backend.list(calendar))
         storage_hrefs = set()
         bdays = self._calendars[calendar].get('ctype') == 'birthdays'
 
@@ -313,7 +312,7 @@ class CalendarCollection:
                 storage_hrefs.add(href)
                 db_etag = self._backend.get_etag(href, calendar=calendar)
                 if etag != db_etag:
-                    logger.debug(f'Updating {href} because {etag} != {db_etag}')
+                    logger.debug('Updating {0} because {1} != {2}'.format(href, etag, db_etag))
                     self._update_vevent(href, calendar=calendar)
             for href in db_hrefs - storage_hrefs:
                 if bdays:
@@ -342,8 +341,8 @@ class CalendarCollection:
             if not isinstance(e, (UpdateFailed, UnsupportedFeatureError, NonUniqueUID)):
                 logger.exception('Unknown exception happened.')
             logger.warning(
-                f'Skipping {calendar}/{href}: {str(e)}\n'
-                'This event will not be available in khal.')
+                'Skipping {0}/{1}: {2}\n'
+                'This event will not be available in khal.'.format(calendar, href, str(e)))
             return False
 
     def search(self, search_string: str) -> Iterable[Event]:
diff --git a/khal/khalendar/vdir.py b/khal/khalendar/vdir.py
index 1a6ce26..0365a36 100644
--- a/khal/khalendar/vdir.py
+++ b/khal/khalendar/vdir.py
@@ -87,17 +87,17 @@ def get_etag_from_file(f):
     mtime = getattr(stat, 'st_mtime_ns', None)
     if mtime is None:
         mtime = stat.st_mtime
-    return f'{mtime:.9f}'
+    return '{:.9f}'.format(mtime)
 
 
 class VdirError(IOError):
     def __init__(self, *args, **kwargs):
         for key, value in kwargs.items():
             if getattr(self, key, object()) is not None:  # pragma: no cover
-                raise TypeError(f'Invalid argument: {key}')
+                raise TypeError('Invalid argument: {}'.format(key))
             setattr(self, key, value)
 
-        super().__init__(*args)
+        super(VdirError, self).__init__(*args)
 
 
 class NotFoundError(VdirError):
@@ -176,7 +176,7 @@ class VdirBase:
         if not os.path.exists(path):
             os.makedirs(path, mode=cls.default_mode)
         elif not os.path.isdir(path):
-            raise OSError(f'{repr(path)} is not a directory.')
+            raise IOError('{} is not a directory.'.format(repr(path)))
 
         kwargs['path'] = path
         return kwargs
@@ -199,7 +199,7 @@ class VdirBase:
             with open(fpath, 'rb') as f:
                 return (Item(f.read().decode(self.encoding)),
                         get_etag_from_file(fpath))
-        except OSError as e:
+        except IOError as e:
             if e.errno == errno.ENOENT:
                 raise NotFoundError(href)
             else:
@@ -268,7 +268,7 @@ class VdirBase:
         try:
             with open(fpath, 'rb') as f:
                 return f.read().decode(self.encoding).strip() or None
-        except OSError as e:
+        except IOError as e:
             if e.errno == errno.ENOENT:
                 return None
             else:
@@ -304,7 +304,8 @@ class Color:
         if len(r) == len(g) == len(b) == 2:
             return int(r, 16), int(g, 16), int(b, 16)
         else:
-            raise ValueError(f'Unable to parse color value: {self.value}')
+            raise ValueError('Unable to parse color value: {}'
+                             .format(self.value))
 
 
 class ColorMixin:
diff --git a/khal/parse_datetime.py b/khal/parse_datetime.py
index 19edf66..0bb3a12 100644
--- a/khal/parse_datetime.py
+++ b/khal/parse_datetime.py
@@ -237,10 +237,11 @@ def guessdatetimefstr(dtime_list, locale, default_day=None, in_future=True):
         else:
             return dtstart, all_day
     raise DateTimeParseError(
-        f"Could not parse \"{dtime_list}\".\nPlease check your configuration "
-        "or run `khal printformats` to see if this does match your configured "
+        "Could not parse \"{}\".\nPlease check your configuration or run "
+        "`khal printformats` to see if this does match your configured "
         "[long](date|time|datetime)format.\nIf you suspect a bug, please "
         "file an issue at https://github.com/pimutils/khal/issues/ "
+        "".format(dtime_list)
     )
 
 
@@ -294,7 +295,7 @@ def guesstimedeltafstr(delta_string):
             numint = int(num)
         except ValueError:
             raise DateTimeParseError(
-                f'Invalid number in timedelta string "{delta_string}": "{num}"')
+                'Invalid number in timedelta string "%s": "%s"' % (delta_string, num))
 
         ulower = unit.lower().strip()
         if ulower == 'd' or ulower == 'day' or ulower == 'days':
@@ -406,10 +407,11 @@ def guessrangefstr(daterange, locale,
             pass
 
     raise DateTimeParseError(
-        f"Could not parse \"{daterange}\".\nPlease check your configuration or "
-        "run `khal printformats` to see if this does match your configured "
+        "Could not parse \"{}\".\nPlease check your configuration or run "
+        "`khal printformats` to see if this does match your configured "
         "[long](date|time|datetime)format.\nIf you suspect a bug, please "
         "file an issue at https://github.com/pimutils/khal/issues/ "
+        "".format(daterange)
     )
 
 
@@ -473,11 +475,11 @@ def eventinfofstr(info_string, locale, default_event_duration, default_dayevent_
 
     if start is None or end is None:
         raise DateTimeParseError(
-            f"Could not parse \"{info_string}\".\nPlease check your "
-            "configuration or run `khal printformats` to see if this does "
-            "match your configured [long](date|time|datetime)format.\nIf you "
-            "suspect a bug, please file an issue at "
-            "https://github.com/pimutils/khal/issues/ "
+            "Could not parse \"{}\".\nPlease check your configuration or run "
+            "`khal printformats` to see if this does match your configured "
+            "[long](date|time|datetime)format.\nIf you suspect a bug, please "
+            "file an issue at https://github.com/pimutils/khal/issues/ "
+            "".format(info_string)
         )
 
     if tz is None:
diff --git a/khal/settings/khal.spec b/khal/settings/khal.spec
index 2da4bff..55975f4 100644
--- a/khal/settings/khal.spec
+++ b/khal/settings/khal.spec
@@ -124,7 +124,7 @@ longdatetimeformat = string(default='%c')
 # weeknumbers, so will only work properly if `firstweekday` is set to 0
 weeknumbers = weeknumbers(default='off')
 
-# Keybindings for :command:`ikhal` are set here. You can bind more then one key
+# Keybindings for :command:`ikhal` are set here. You can bind more than one key
 # (combination) to a command by supplying a comma-separated list of keys.
 # For binding key combinations concatenate them keys (with a space in
 # between), e.g. **ctrl n**.
@@ -278,7 +278,7 @@ agenda_event_format = string(default='{calendar-color}{cancelled}{start-end-time
 # Specifies how each *day header* is formatted.
 agenda_day_format = string(default='{bold}{name}, {date-long}{reset}')
 
-# Display month name on row when the week contains the first day of
+# Display month name on row when the week contains the first day
 # of the month ('firstday') or when the first day of the week is in the
 # month ('firstfullweek')
 monthdisplay = monthdisplay(default='firstday')
diff --git a/khal/settings/settings.py b/khal/settings/settings.py
index 4ef959a..55aeb39 100644
--- a/khal/settings/settings.py
+++ b/khal/settings/settings.py
@@ -60,10 +60,10 @@ def find_configuration_file():
     for path in paths:
         if os.path.exists(path):
             logger.warning(
-                f'Deprecation Warning: configuration file path `{path}` will '
-                'not be supported from v0.11.0 onwards, please move it to '
-                f'`{xdg.BaseDirectory.xdg_config_dirs[0]}/khal/config`.'
-            )
+                'Deprecation Warning: configuration file path `{}` will not be '
+                'supported from v0.11.0 onwards, please move it to '
+                '`{}/khal/config`.'
+                ''.format(path, xdg.BaseDirectory.xdg_config_dirs[0]))
             return path
 
     return None
@@ -88,7 +88,7 @@ def get_config(
     if config_path is None or not os.path.exists(config_path):
         raise NoConfigFile()
 
-    logger.debug(f'using the config file at {config_path}')
+    logger.debug('using the config file at {}'.format(config_path))
 
     try:
         user_config = ConfigObj(config_path,
@@ -98,7 +98,7 @@ def get_config(
                                 )
     except ConfigObjError as error:
         logger.fatal('parsing the config file with the following error: '
-                     f'{error}')
+                     '{}'.format(error))
         logger.fatal('if you recently updated khal, the config file format '
                      'might have changed, in that case please consult the '
                      'CHANGELOG or other documentation')
@@ -120,15 +120,15 @@ def get_config(
         abort = True
         if isinstance(config_error, Exception):
             logger.fatal(
-                f'config error:\n'
-                f'in [{section[0]}] {subsection}: {config_error}')
+                'config error:\n'
+                'in [{}] {}: {}'.format(section[0], subsection, config_error))
         else:
             for key in config_error:
                 if isinstance(config_error[key], Exception):
-                    logger.fatal(
-                        'config error:\n'
-                        f'in {sectionize(section + [subsection])} {key}: '
-                        f'{str(config_error[key])}'
+                    logger.fatal('config error:\nin {} {}: {}'.format(
+                        sectionize(section + [subsection]),
+                        key,
+                        str(config_error[key]))
                     )
 
     if abort or not results:
@@ -139,17 +139,17 @@ def get_config(
     extras = get_extra_values(user_config)
     for section, value in extras:
         if section == ():
-            logger.warning(f'unknown section "{value}" in config file')
+            logger.warning('unknown section "{}" in config file'.format(value))
         else:
             section = sectionize(section)
             logger.warning(
-                f'unknown key or subsection "{value}" in section "{section}"')
+                'unknown key or subsection "{}" in section "{}"'.format(value, section))
 
             deprecated = [{'value': 'default_command', 'section': 'default'}]
             for d in deprecated:
                 if (value == d['value']) and (section == d['section']):
-                    logger.warning(f'Key "{value}" in section "{section}" was '
-                                   'deprecated. See the FAQ to find out when and why!')
+                    logger.warning('Key "{}" in section "{}" was deprecated. '
+                                   'See the FAQ to find out when and why!'.format(value, section))
     return user_config
 
 
diff --git a/khal/settings/utils.py b/khal/settings/utils.py
index 567222b..df1e352 100644
--- a/khal/settings/utils.py
+++ b/khal/settings/utils.py
@@ -48,14 +48,14 @@ def is_timezone(tzstring):
     try:
         return pytz.timezone(tzstring)
     except pytz.UnknownTimeZoneError:
-        raise VdtValueError(f"Unknown timezone {tzstring}")
+        raise VdtValueError("Unknown timezone {}".format(tzstring))
 
 
 def is_timedelta(string):
     try:
         return guesstimedeltafstr(string)
     except ValueError:
-        raise VdtValueError(f"Invalid timedelta: {string}")
+        raise VdtValueError("Invalid timedelta: {}".format(string))
 
 
 def weeknumber_option(option):
@@ -75,8 +75,8 @@ def weeknumber_option(option):
         return False
     else:
         raise VdtValueError(
-            f"Invalid value '{option}' for option 'weeknumber', must be one of "
-            "'off', 'left' or 'right'")
+            "Invalid value '{}' for option 'weeknumber', must be one of "
+            "'off', 'left' or 'right'".format(option))
 
 
 def monthdisplay_option(option):
@@ -94,9 +94,8 @@ def monthdisplay_option(option):
         return 'firstfullweek'
     else:
         raise VdtValueError(
-            f"Invalid value '{option}' for option 'monthdisplay', must be one "
-            "of 'firstday' or 'firstfullweek'"
-        )
+            "Invalid value '{}' for option 'monthdisplay', must be one of "
+            "'firstday' or 'firstfullweek'".format(option))
 
 
 def expand_path(path):
@@ -139,9 +138,9 @@ def test_default_calendar(config):
         pass
     elif config['default']['default_calendar'] not in config['calendars']:
         logger.fatal(
-            f"in section [default] {config['default']['default_calendar']} is "
-            "not valid for 'default_calendar', must be one of "
-            f"{config['calendars'].keys()}"
+            "in section [default] {} is not valid for 'default_calendar', "
+            "must be one of {}".format(config['default']['default_calendar'],
+                                       config['calendars'].keys())
         )
         raise InvalidSettingsError()
     elif config['calendars'][config['default']['default_calendar']]['readonly']:
@@ -155,26 +154,22 @@ def get_color_from_vdir(path):
     except CollectionNotFoundError:
         color = None
     if color is None or color == '':
-        logger.debug(f'Found no or empty file `color` in {path}')
+        logger.debug('Found no or empty file `color` in {}'.format(path))
         return None
     color = color.strip()
     try:
         is_color(color)
     except VdtValueError:
-        logger.warning(f"Found invalid color `{color}` in {path}color")
+        logger.warning("Found invalid color `{}` in {}color".format(color, path))
         color = None
     return color
 
 
 def get_unique_name(path, names):
     # TODO take care of edge cases, make unique name finding less brain-dead
-    try:
-        name = Vdir(path, '.ics').get_meta('displayname')
-    except CollectionNotFoundError:
-        logger.fatal(f'The calendar at `{path}` is not a directory.')
-        raise
+    name = Vdir(path, '.ics').get_meta('displayname')
     if name is None or name == '':
-        logger.debug(f'Found no or empty file `displayname` in {path}')
+        logger.debug('Found no or empty file `displayname` in {}'.format(path))
         name = os.path.split(path)[-1]
     if name in names:
         while name in names:
@@ -219,7 +214,7 @@ def config_checks(
             raise InvalidSettingsError
         if config['calendars'][calendar]['type'] == 'discover':
             logger.debug(
-                f"discovering calendars in {config['calendars'][calendar]['path']}"
+                'discovering calendars in {}'.format(config['calendars'][calendar]['path'])
             )
             vdirs = get_all_vdirs(config['calendars'][calendar]['path'])
             vdirs_complete += vdirs
@@ -238,7 +233,7 @@ def config_checks(
         # get color from config if not defined in vdir
 
         if calendar['color'] is None and vdir in vdir_colors_from_config:
-            logger.debug(f"using collection's color for {vdir}")
+            logger.debug("using collection's color for {}".format(vdir))
             calendar['color'] = vdir_colors_from_config[vdir]
 
         name = get_unique_name(vdir, config['calendars'].keys())
diff --git a/khal/terminal.py b/khal/terminal.py
index 1d47bd2..af223f6 100644
--- a/khal/terminal.py
+++ b/khal/terminal.py
@@ -105,9 +105,9 @@ def get_color(fg=None, bg=None, bold_for_light_color=False):
                     g = int(colorstring[3:5], 16)
                     b = int(colorstring[5:7], 16)
                 if not is_bg:
-                    color += f'38;2;{r!s};{g!s};{b!s}'
+                    color += '38;2;{!s};{!s};{!s}'.format(r, g, b)
                 else:
-                    color += f'48;2;{r!s};{g!s};{b!s}'
+                    color += '48;2;{!s};{!s};{!s}'.format(r, g, b)
             color += 'm'
             result += color
     return result
diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py
index 466b7fd..ddf987e 100644
--- a/khal/ui/__init__.py
+++ b/khal/ui/__init__.py
@@ -31,7 +31,7 @@ from .. import icalendar as icalendar_helpers, utils
 from ..khalendar.event import Event
 from ..khalendar.exceptions import ReadOnlyCalendarError
 from . import colors
-from .widgets import ExtendedEdit as Edit, NPile, NColumns, linebox
+from .widgets import ExtendedEdit as Edit, NPile, NColumns, NListBox, linebox
 from .base import Pane, Window
 from .editor import EventEditor, ExportDialog
 from .calendarwidget import CalendarWidget
@@ -131,11 +131,11 @@ class DateHeader(SelectableText):
         weekday = day.strftime('%A')
         daystr = day.strftime(dtformat)
         if day == dt.date.today():
-            return f'Today ({weekday}, {daystr})'
+            return 'Today ({}, {})'.format(weekday, daystr)
         elif day == dt.date.today() + dt.timedelta(days=1):
-            return f'Tomorrow ({weekday}, {daystr})'
+            return 'Tomorrow ({}, {})'.format(weekday, daystr)
         elif day == dt.date.today() - dt.timedelta(days=1):
-            return f'Yesterday ({weekday}, {daystr})'
+            return 'Yesterday ({}, {})'.format(weekday, daystr)
 
         approx_delta = utils.relative_timedelta_str(day)
 
@@ -167,8 +167,8 @@ class U_Event(urwid.Text):
         """
         if relative:
             if isinstance(this_date, dt.datetime) or not isinstance(this_date, dt.date):
-                raise ValueError(f'`this_date` is of type `{type(this_date)}`, '
-                                 'should be `datetime.date`')
+                raise ValueError('`this_date` is of type `{}`, sould be '
+                                 '`datetime.date`'.format(type(this_date)))
         self.event = event
         self.delete_status = delete_status
         self.this_date = this_date
@@ -357,7 +357,7 @@ class DayWalker(urwid.SimpleFocusListWalker):
         self._first_day = this_date
         self._collection = collection
 
-        super().__init__([])
+        super().__init__(list())
         self.ensure_date(this_date)
 
     def ensure_date(self, day):
@@ -470,7 +470,7 @@ class DayWalker(urwid.SimpleFocusListWalker):
 
         :type day: datetime.date
         """
-        event_list = []
+        event_list = list()
         date_header = DateHeader(
             day=day,
             dateformat=self._conf['locale']['longdateformat'],
@@ -555,7 +555,7 @@ class StaticDayWalker(DayWalker):
         return urwid.SimpleFocusListWalker.set_focus(self, position)
 
 
-class DateListBox(urwid.ListBox):
+class DateListBox(NListBox):
     """A ListBox container for a SimpleFocusListWalker, that contains one day
     worth of events"""
 
@@ -566,7 +566,7 @@ class DateListBox(urwid.ListBox):
         super().__init__(content)
 
     def __repr__(self):
-        return f'<DateListBox {self.date}>'
+        return '<DateListBox {}>'.format(self.date)
 
     __str__ = __repr__
 
@@ -696,7 +696,7 @@ class EventColumn(urwid.WidgetWrap):
         """
         if event.readonly:
             self.pane.window.alert(
-                ('alert', f'Calendar `{event.calendar}` is read-only.'))
+                ('alert', 'Calendar `{}` is read-only.'.format(event.calendar)))
             return
 
         if isinstance(event.start_local, dt.datetime):
@@ -811,7 +811,7 @@ class EventColumn(urwid.WidgetWrap):
 
         if event.event.readonly:
             self.pane.window.alert(
-                ('alert', f'Calendar {event.event.calendar} is read-only.'),
+                ('alert', 'Calendar {} is read-only.'.format(event.event.calendar)),
             )
             return
         status = self.delete_status(event.recuid)
@@ -960,24 +960,21 @@ class EventDisplay(urwid.WidgetWrap):
         if event.categories != '':
             lines.append(urwid.Text('Categories: ' + event.categories))
 
-        if event.url != '':
-            lines.append(urwid.Text('URL: ' + event.url))
-
         # start and end time/date
         if event.allday:
             startstr = event.start_local.strftime(self._conf['locale']['dateformat'])
             endstr = event.end_local.strftime(self._conf['locale']['dateformat'])
         else:
             startstr = event.start_local.strftime(
-                f"{self._conf['locale']['dateformat']} "
-                f"{self._conf['locale']['timeformat']}"
+                '{} {}'.format(self._conf['locale']['dateformat'],
+                               self._conf['locale']['timeformat'])
             )
             if event.start_local.date == event.end_local.date:
                 endstr = event.end_local.strftime(self._conf['locale']['timeformat'])
             else:
                 endstr = event.end_local.strftime(
-                    f"{self._conf['locale']['dateformat']} "
-                    f"{self._conf['locale']['timeformat']}"
+                    '{} {}'.format(self._conf['locale']['dateformat'],
+                                   self._conf['locale']['timeformat'])
                 )
 
         if startstr == endstr:
@@ -1153,14 +1150,14 @@ class ClassicView(Pane):
         )
         pane = Pane(
             columns,
-            title=f"Search results for \"{search_term}\" (Esc for backtrack)",
+            title="Search results for \"{}\" (Esc for backtrack)".format(search_term),
         )
         pane._conf = self._conf
         columns.set_focus_column(1)
         self.window.open(pane)
 
     def render(self, size, focus=False):
-        rval = super().render(size, focus)
+        rval = super(ClassicView, self).render(size, focus)
         if self.init:
             # starting with today's events
             self.eventscolumn.current_date = dt.date.today()
@@ -1265,12 +1262,11 @@ def _add_calendar_colors(palette, collection):
     return palette
 
 
-def start_pane(pane, callback, program_info='', quit_keys=None):
+def start_pane(pane, callback, program_info='', quit_keys=['q']):
     """Open the user interface with the given initial pane."""
-    quit_keys = quit_keys or ['q']
 
     frame = Window(
-        footer=program_info + f' | {quit_keys[0]}: quit, ?: help',
+        footer=program_info + ' | {}: quit, ?: help'.format(quit_keys[0]),
         quit_keys=quit_keys,
     )
 
@@ -1294,7 +1290,7 @@ def start_pane(pane, callback, program_info='', quit_keys=None):
         def format(self, record):
             return (
                 super().format(record)[:30] + '... '
-                f"[Press `{pane._conf['keybindings']['log'][0]}` to view log]"
+                '[Press `{}` to view log]'.format(pane._conf['keybindings']['log'][0])
             )
 
     class LogPaneHandler(logging.Handler):
@@ -1323,8 +1319,7 @@ def start_pane(pane, callback, program_info='', quit_keys=None):
         frame, palette, unhandled_input=frame.on_key_press, pop_ups=True)
     frame.loop = loop
 
-    def redraw_today(loop, pane, meta=None):
-        meta = meta or {'last_today': None}
+    def redraw_today(loop, pane, meta={'last_today': None}):
         # XXX TODO this currently assumes, today moves forward by exactly one
         # day, but it could either move forward more (suspend-to-disk/ram) or
         # even move backwards
diff --git a/khal/ui/base.py b/khal/ui/base.py
index c257505..877439d 100644
--- a/khal/ui/base.py
+++ b/khal/ui/base.py
@@ -114,11 +114,11 @@ class Pane(urwid.WidgetWrap):
             return super().keypress(size, key)
 
     def show_keybindings(self):
-        lines = []
+        lines = list()
         lines.append('  Command              Keys')
         lines.append('  =======              ====')
         for command, keys in self._conf['keybindings'].items():
-            lines.append(f'  {command:20} {keys}')
+            lines.append('  {:20} {}'.format(command, keys))
         self.scrollable_dialog(
             '\n'.join(lines),
             title="Press `ESC` to close this window, arrows to scroll",
@@ -144,8 +144,7 @@ class Window(urwid.Frame):
     to carry data between them.
     """
 
-    def __init__(self, footer='', quit_keys=None):
-        quit_keys = quit_keys or ['q']
+    def __init__(self, footer='', quit_keys=['q']):
         self._track = []
 
         header = urwid.AttrWrap(urwid.Text(''), 'header')
diff --git a/khal/ui/calendarwidget.py b/khal/ui/calendarwidget.py
index 6656647..c188ee0 100644
--- a/khal/ui/calendarwidget.py
+++ b/khal/ui/calendarwidget.py
@@ -50,7 +50,7 @@ class DatePart(urwid.Text):
     """used in the Date widget (single digit)"""
 
     def __init__(self, digit):
-        super().__init__(digit)
+        super(DatePart, self).__init__(digit)
 
     @classmethod
     def selectable(cls):
@@ -80,7 +80,7 @@ class Date(urwid.WidgetWrap):
                        urwid.AttrMap(DatePart(dstr[1:]), None, None)]
         self.date = date
         self._get_styles = get_styles
-        super().__init__(urwid.Columns(self.halves))
+        super(Date, self).__init__(urwid.Columns(self.halves))
 
     def set_styles(self, styles):
         """If single string, sets the same style for both halves, if two
@@ -135,10 +135,10 @@ class DateCColumns(urwid.Columns):
         self.keybindings = keybindings
         self.get_styles = get_styles
         self._init = True
-        super().__init__(widget_list, **kwargs)
+        super(DateCColumns, self).__init__(widget_list, **kwargs)
 
     def __repr__(self):
-        return f'<DateCColumns from {self[1].date} to {self[7].date}>'
+        return '<DateCColumns from {} to {}>'.format(self[1].date, self[7].date)
 
     def _clear_cursor(self):
         old_pos = self.focus_position
@@ -156,7 +156,7 @@ class DateCColumns(urwid.Columns):
             self.contents[position][0].set_styles(
                 self.get_styles(self.contents[position][0].date, True))
             self.on_date_change(self.contents[position][0].date)
-        super()._set_focus_position(position)
+        super(DateCColumns, self)._set_focus_position(position)
 
     def set_focus_date(self, a_date):
         for num, day in enumerate(self.contents[1:8], 1):
@@ -196,7 +196,7 @@ class DateCColumns(urwid.Columns):
         exit_row = False  # set this, if we are leaving the current row
         old_pos = self.focus_position
 
-        key = super().keypress(size, key)
+        key = super(DateCColumns, self).keypress(size, key)
 
         # make sure we don't leave the calendar
         if old_pos == 7 and key == 'right':
@@ -232,7 +232,7 @@ class CListBox(urwid.ListBox):
         self._marked = False
         self._pos_old = False
 
-        super().__init__(walker)
+        super(CListBox, self).__init__(walker)
 
     def render(self, size, focus=False):
         while 'bottom' in self.ends_visible(size):
@@ -241,7 +241,7 @@ class CListBox(urwid.ListBox):
             self.set_focus_valign('middle')
             self._init = False
 
-        return super().render(size, focus)
+        return super(CListBox, self).render(size, focus)
 
     def mouse_event(self, *args):
         size, event, button, col, row, focus = args
@@ -340,7 +340,7 @@ class CListBox(urwid.ListBox):
             self.set_focus_date(dt.date.today())
             self.set_focus_valign(('relative', 10))
 
-        key = super().keypress(size, key)
+        key = super(CListBox, self).keypress(size, key)
         if self._marked:
             self._mark()
         return key
@@ -485,7 +485,7 @@ class CalendarWalker(urwid.SimpleFocusListWalker):
                   if today is in this week
         :rtype: tuple(urwid.CColumns, bool)
         """
-        if self.monthdisplay == 'firstday' and 1 in (day.day for day in week):
+        if self.monthdisplay == 'firstday' and 1 in [day.day for day in week]:
             month_name = calendar.month_abbr[week[-1].month].ljust(4)
             attr = 'monthname'
         elif self.monthdisplay == 'firstfullweek' and week[0].day <= 7:
@@ -499,7 +499,7 @@ class CalendarWalker(urwid.SimpleFocusListWalker):
             attr = None
 
         this_week = [(get_month_abbr_len(), urwid.AttrMap(urwid.Text(month_name), attr))]
-        for _number, day in enumerate(week):
+        for number, day in enumerate(week):
             new_date = Date(day, self.get_styles)
             this_week.append((2, new_date))
             new_date.set_styles(self.get_styles(new_date.date, False))
@@ -543,8 +543,8 @@ class CalendarWalker(urwid.SimpleFocusListWalker):
 
         plain_weeks = calendar.Calendar(
             self.firstweekday).monthdatescalendar(year, month)
-        weeks = []
-        for _number, week in enumerate(plain_weeks):
+        weeks = list()
+        for number, week in enumerate(plain_weeks):
             week = self._construct_week(week)
             weeks.append(week)
         if clean_first_row and weeks[0][1].date.month != weeks[0][7].date.month:
diff --git a/khal/ui/editor.py b/khal/ui/editor.py
index 2120253..058226b 100644
--- a/khal/ui/editor.py
+++ b/khal/ui/editor.py
@@ -30,7 +30,7 @@ from .widgets import (AlarmsEditor, Choice, DateConversionError, DateWidget,
                       TimeWidget, ValidatedEdit)
 
 
-class StartEnd:
+class StartEnd(object):
 
     def __init__(self, startdate, starttime, enddate, endtime):
         """collecting some common properties"""
@@ -339,7 +339,6 @@ class EventEditor(urwid.WidgetWrap):
         self.description = event.description
         self.location = event.location
         self.categories = event.categories
-        self.url = event.url
         self.startendeditor = StartEndEditor(
             event.start_local, event.end_local, self._conf,
             self.start_datechange, self.end_datechange,
@@ -377,9 +376,6 @@ class EventEditor(urwid.WidgetWrap):
         self.categories = urwid.AttrMap(ExtendedEdit(
             caption=('', 'Categories:  '), edit_text=self.categories), 'edit'
         )
-        self.url = urwid.AttrMap(ExtendedEdit(
-            caption=('', 'URL:         '), edit_text=self.url), 'edit'
-        )
         self.alarms = AlarmsEditor(self.event)
         self.pile = NListBox(urwid.SimpleFocusListWalker([
             self.summary,
@@ -388,7 +384,6 @@ class EventEditor(urwid.WidgetWrap):
             self.location,
             self.categories,
             self.description,
-            self.url,
             divider,
             self.startendeditor,
             self.recurrenceeditor,
@@ -410,7 +405,7 @@ class EventEditor(urwid.WidgetWrap):
 
     @property
     def title(self):  # Window title
-        return f'Edit: {get_wrapped_text(self.summary)}'
+        return 'Edit: {}'.format(get_wrapped_text(self.summary))
 
     @classmethod
     def selectable(cls):
@@ -607,9 +602,12 @@ class RecurrenceEditor(urwid.WidgetWrap):
     def _rebuild_monthly_choice(self):
         weekday, xth = get_weekday_occurrence(self._startdt)
         ords = {1: 'st', 2: 'nd', 3: 'rd', 21: 'st', 22: 'nd', 23: 'rd', 31: 'st'}
-        self._xth_weekday = f"on every {xth}{ords.get(xth, 'th')} {WEEKDAYS[weekday]}"
-        self._xth_monthday = (f"on every {self._startdt.day}"
-                              f"{ords.get(self._startdt.day, 'th')} of the month")
+        self._xth_weekday = 'on every {}{} {}'.format(
+            xth, ords.get(xth, 'th'), WEEKDAYS[weekday],
+        )
+        self._xth_monthday = 'on every {}{} of the month'.format(
+            self._startdt.day, ords.get(self._startdt.day, 'th'),
+        )
         self.monthly_choice = Choice(
             [self._xth_monthday, self._xth_weekday], self._xth_monthday, callback=self.rebuild,
         )
@@ -727,7 +725,7 @@ class RecurrenceEditor(urwid.WidgetWrap):
             rrule['byday'] = self.weekday_checks.days
         if rrule['freq'] == ['monthly'] and self.monthly_choice.active == self._xth_weekday:
             weekday, occurrence = get_weekday_occurrence(self._startdt)
-            rrule['byday'] = [f'{occurrence}{WEEKDAYS[weekday]}']
+            rrule['byday'] = ['{}{}'.format(occurrence, WEEKDAYS[weekday])]
         if self.until_choice.active == 'Until':
             if isinstance(self._startdt, dt.datetime):
                 rrule['until'] = dt.datetime.combine(
diff --git a/khal/ui/widgets.py b/khal/ui/widgets.py
index d11f9fc..de3f337 100644
--- a/khal/ui/widgets.py
+++ b/khal/ui/widgets.py
@@ -85,7 +85,7 @@ class ExtendedEdit(urwid.Edit):
         elif key == 'ctrl e':
             self._goto_end_of_line()
         else:
-            return super().keypress(size, key)
+            return super(ExtendedEdit, self).keypress(size, key)
 
     def _delete_word(self):
         """delete word before cursor"""
@@ -143,7 +143,7 @@ class DateTimeWidget(ExtendedEdit):
                 pass
             else:
                 self.on_date_change(new_date)
-        return super().keypress(size, key)
+        return super(DateTimeWidget, self).keypress(size, key)
 
     def increase(self):
         """call to increase the datefield by self.timedelta"""
@@ -262,7 +262,7 @@ class ChoiceList(urwid.WidgetWrap):
         self._emit("close")
 
 
-class SupportsNext:
+class SupportsNext(object):
     """classes inheriting from SupportsNext must implement the following methods:
     _select_first_selectable
     _select_last_selectable
@@ -271,7 +271,7 @@ class SupportsNext:
         self.outermost = kwargs.get('outermost', False)
         if 'outermost' in kwargs:
             kwargs.pop('outermost')
-        super().__init__(*args, **kwargs)
+        super(SupportsNext, self).__init__(*args, **kwargs)
 
 
 class NextMixin(SupportsNext):
@@ -305,7 +305,7 @@ class NextMixin(SupportsNext):
         return False
 
     def keypress(self, size, key):
-        key = super().keypress(size, key)
+        key = super(NextMixin, self).keypress(size, key)
 
         if key == 'tab':
             if self.outermost and self.focus_position == self._last_selectable():
diff --git a/khal/utils.py b/khal/utils.py
index dede681..c02c1c9 100644
--- a/khal/utils.py
+++ b/khal/utils.py
@@ -29,10 +29,9 @@ import re
 import string
 from calendar import month_abbr, timegm
 from textwrap import wrap
-from typing import Iterator, List, Tuple, Optional
 
 
-def generate_random_uid() -> str:
+def generate_random_uid():
     """generate a random uid
 
     when random isn't broken, getting a random UID from a pool of roughly 10^56
@@ -50,8 +49,8 @@ ansi_sgr = re.compile(r'\x1b\['
                       'm')
 
 
-def find_last_reset(string: str) -> Tuple[int, int, str]:
-    for match in re.finditer(ansi_reset, string):  # noqa B007: this is actually used below.
+def find_last_reset(string):
+    for match in re.finditer(ansi_reset, string):
         pass
     try:
         return match.start(), match.end(), match.group(0)
@@ -59,8 +58,8 @@ def find_last_reset(string: str) -> Tuple[int, int, str]:
         return -2, -1, ''
 
 
-def find_last_sgr(string: str) -> Tuple[int, int, str]:
-    for match in re.finditer(ansi_sgr, string):  # noqa B007: this is actually used below.
+def find_last_sgr(string):
+    for match in re.finditer(ansi_sgr, string):
         pass
     try:
         return match.start(), match.end(), match.group(0)
@@ -68,16 +67,16 @@ def find_last_sgr(string: str) -> Tuple[int, int, str]:
         return -2, -1, ''
 
 
-def find_unmatched_sgr(string: str) -> Optional[str]:
+def find_unmatched_sgr(string):
     reset_pos, _, _ = find_last_reset(string)
     sgr_pos, _, sgr = find_last_sgr(string)
     if sgr_pos > reset_pos:
         return sgr
     else:
-        return None
+        return False
 
 
-def color_wrap(text: str, width: int = 70) -> List[str]:
+def color_wrap(text, width=70):
     """A variant of wrap that takes SGR codes (somewhat) into account.
 
     This doesn't actually adjust the length, but makes sure that
@@ -88,30 +87,32 @@ def color_wrap(text: str, width: int = 70) -> List[str]:
     lines = wrap(text, width)
     for num, _ in enumerate(lines):
         sgr = find_unmatched_sgr(lines[num])
-        if sgr is not None:
+        if sgr:
             lines[num] += RESET
-            if (num + 1) < len(lines):
+            if num != len(lines):
                 lines[num + 1] = sgr + lines[num + 1]
     return lines
 
 
-def get_weekday_occurrence(day: dt.date) -> Tuple[int, int]:
+def get_weekday_occurrence(day):
     """Calculate how often this weekday has already occurred in a given month.
 
+    :type day: datetime.date
     :returns: weekday (0=Monday, ..., 6=Sunday), occurrence
+    :rtype: tuple(int, int)
     """
     xthday = 1 + (day.day - 1) // 7
     return day.weekday(), xthday
 
 
-def get_month_abbr_len() -> int:
+def get_month_abbr_len():
     """Calculate the number of characters we need to display the month
     abbreviated name. It depends on the locale.
     """
     return max(len(month_abbr[i]) for i in range(1, 13)) + 1
 
 
-def localize_strip_tz(dates: List[dt.datetime], timezone: dt.tzinfo) -> Iterator[dt.datetime]:
+def localize_strip_tz(dates, timezone):
     """converts a list of dates to timezone, than removes tz info"""
     for one_date in dates:
         if getattr(one_date, 'tzinfo', None) is not None:
@@ -128,7 +129,7 @@ def to_unix_time(dtime: dt.datetime) -> float:
     return unix_time
 
 
-def to_naive_utc(dtime: dt.datetime) -> dt.datetime:
+def to_naive_utc(dtime):
     """convert a datetime object to UTC and than remove the tzinfo, if
     datetime is naive already, return it
     """
@@ -140,7 +141,7 @@ def to_naive_utc(dtime: dt.datetime) -> dt.datetime:
     return dtime_naive
 
 
-def is_aware(dtime: dt.datetime) -> bool:
+def is_aware(dtime):
     """test if a datetime instance is timezone aware"""
     if dtime.tzinfo is not None and dtime.tzinfo.utcoffset(dtime) is not None:
         return True
@@ -148,8 +149,11 @@ def is_aware(dtime: dt.datetime) -> bool:
         return False
 
 
-def relative_timedelta_str(day: dt.date) -> str:
+def relative_timedelta_str(day):
     """Converts the timespan from `day` to today into a human readable string.
+
+    :type day: datetime.date
+    :rtype: str
     """
     days = (day - dt.date.today()).days
     if days < 0:
diff --git a/setup.py b/setup.py
index 179cc33..b25f199 100755
--- a/setup.py
+++ b/setup.py
@@ -3,8 +3,8 @@ import sys
 
 from setuptools import setup
 
-if sys.version_info < (3, 6):
-    errstr = "khal only supports python version 3.6+. Please Upgrade.\n"
+if sys.version_info < (3, 5):
+    errstr = "khal only supports python version 3.5+. Please Upgrade.\n"
     sys.stderr.write("#" * len(errstr) + '\n')
     sys.stderr.write(errstr)
     sys.stderr.write("#" * len(errstr) + '\n')
@@ -64,7 +64,7 @@ setup(
         "Environment :: Console :: Curses",
         "Intended Audience :: End Users/Desktop",
         "Operating System :: POSIX",
-        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3 :: Only",
diff --git a/tox.ini b/tox.ini
index f8572eb..0950c8c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = style,mypy,{py36,py37,py38}-tests,py39-tests-{pytz201702,pytz201610,pytz_latest}
+envlist = {py35,py36,py37,py38,py39}-{tests,style,mypy}-{pytz201702,pytz201610,pytz_latest}
 skip_missing_interpreters = True
 
 [testenv]
@@ -26,18 +26,12 @@ deps =
 commands =
     py.test {posargs}
 
-[gh-actions]
-python =
-    3.9: style, mypy
-
 [testenv:style]
 skip_install=True
 whitelist_externals = sh
-deps =
-  flake8
-  flake8-bugbear
+deps = flake8
 commands =
-    flake8 tests khal setup.py
+    flake8 --ignore E252,W504,E121 tests khal setup.py
     sh -c '! grep -ri seperat */*'
 
 [testenv:docs]
@@ -56,5 +50,4 @@ commands =
 
 [flake8]
 max-line-length = 100
-ignore = E252,W504,E121,B008
 exclude = .tox,examples,doc


Test patch:
diff --git a/tests/backend_test.py b/tests/backend_test.py
index f966464..5ef0ff2 100644
--- a/tests/backend_test.py
+++ b/tests/backend_test.py
@@ -1,3 +1,4 @@
+
 import datetime as dt
 from operator import itemgetter
 
@@ -21,12 +22,12 @@ def test_new_db_version():
 
 def test_event_rrule_recurrence_id():
     dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert dbi.list(calname) == []
+    assert dbi.list(calname) == list()
     events = dbi.get_localized(
         BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
         BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)),
     )
-    assert list(events) == []
+    assert list(events) == list()
     dbi.update(_get_text('event_rrule_recuid'), href='12345.ics', etag='abcd', calendar=calname)
     assert dbi.list(calname) == [('12345.ics', 'abcd')]
     events = dbi.get_localized(
@@ -95,11 +96,11 @@ def test_event_rrule_recurrence_id_reverse():
     deal with `reverse` ordered icalendar files
     """
     dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert dbi.list(calname) == []
+    assert dbi.list(calname) == list()
     events = dbi.get_localized(
         BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
         BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)))
-    assert list(events) == []
+    assert list(events) == list()
     dbi.update(event_rrule_recurrence_id_reverse, href='12345.ics', etag='abcd', calendar=calname)
     assert dbi.list(calname) == [('12345.ics', 'abcd')]
     events = dbi.get_localized(
@@ -183,7 +184,7 @@ def test_no_valid_timezone():
                href='12345.ics', etag='abcd', calendar=calname)
     events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 4, 9, 0, 0)),
                                BERLIN.localize(dt.datetime(2014, 4, 10, 0, 0)))
-    events = sorted(events)
+    events = sorted(list(events))
     assert len(events) == 1
     event = events[0]
     assert event[2] == BERLIN.localize(dt.datetime(2014, 4, 9, 9, 30))
@@ -192,10 +193,10 @@ def test_no_valid_timezone():
 
 def test_event_delete():
     dbi = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert dbi.list(calname) == []
+    assert dbi.list(calname) == list()
     events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
                                BERLIN.localize(dt.datetime(2014, 8, 26, 0, 0)))
-    assert list(events) == []
+    assert list(events) == list()
     dbi.update(event_rrule_recurrence_id_reverse, href='12345.ics', etag='abcd', calendar=calname)
     assert dbi.list(calname) == [('12345.ics', 'abcd')]
     events = dbi.get_localized(BERLIN.localize(dt.datetime(2014, 6, 30, 0, 0)),
@@ -281,7 +282,7 @@ def test_event_rrule_this_and_future():
     assert events[5][3] == BERLIN.localize(dt.datetime(2014, 8, 4, 18, 0))
 
     assert 'SUMMARY:Arbeit\n' in events[0][0]
-    for _num, event in enumerate(events[1:]):
+    for num, event in enumerate(events[1:]):
         assert 'SUMMARY:Arbeit (lang)\n' in event[0]
 
 
@@ -690,7 +691,7 @@ end = dt.datetime.combine(day, dt.time.max)
 
 def test_birthdays():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 1
@@ -713,7 +714,7 @@ def test_birthdays_update():
 def test_birthdays_no_fn():
     db = backend.SQLiteDb(['home'], ':memory:', locale=LOCALE_BERLIN)
     assert list(db.get_floating(dt.datetime(1941, 9, 9, 0, 0),
-                                dt.datetime(1941, 9, 9, 23, 59, 59, 9999))) == []
+                                dt.datetime(1941, 9, 9, 23, 59, 59, 9999))) == list()
     db.update_vcf_dates(card_no_fn, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(dt.datetime(1941, 9, 9, 0, 0),
                                   dt.datetime(1941, 9, 9, 23, 59, 59, 9999)))
@@ -723,7 +724,7 @@ def test_birthdays_no_fn():
 
 def test_birthday_does_not_parse():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_does_not_parse, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 0
@@ -731,7 +732,7 @@ def test_birthday_does_not_parse():
 
 def test_vcard_two_birthdays():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_two_birthdays, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 0
@@ -739,7 +740,7 @@ def test_vcard_two_birthdays():
 
 def test_anniversary():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_anniversary, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 1
@@ -754,7 +755,7 @@ def test_anniversary():
 
 def test_abdate():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_abdate, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 1
@@ -769,7 +770,7 @@ def test_abdate():
 
 def test_abdate_nolabel():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_abdate_nolabel, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 1
@@ -784,7 +785,7 @@ def test_abdate_nolabel():
 
 def test_birthday_v3():
     db = backend.SQLiteDb([calname], ':memory:', locale=LOCALE_BERLIN)
-    assert list(db.get_floating(start, end)) == []
+    assert list(db.get_floating(start, end)) == list()
     db.update_vcf_dates(card_v3, 'unix.vcf', calendar=calname)
     events = list(db.get_floating(start, end))
     assert len(events) == 1
diff --git a/tests/cal_display_test.py b/tests/cal_display_test.py
index d5e69f5..e63a307 100644
--- a/tests/cal_display_test.py
+++ b/tests/cal_display_test.py
@@ -302,6 +302,7 @@ example_fr_darwin = [
     '\x1b[1mmar \x1b[0m27 28 29  1  2  3  4 ']
 
 
+
 def test_vertical_month():
     try:
         locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
diff --git a/tests/cli_test.py b/tests/cli_test.py
index 5ff8e16..8938e3a 100644
--- a/tests/cli_test.py
+++ b/tests/cli_test.py
@@ -20,11 +20,11 @@ class CustomCliRunner(CliRunner):
         self.xdg_config_home = xdg_config_home
         self.tmpdir = tmpdir
 
-        super().__init__(**kwargs)
+        super(CustomCliRunner, self).__init__(**kwargs)
 
     def invoke(self, cli, args=None, *a, **kw):
         args = ['-c', str(self.config_file)] + (args or [])
-        return super().invoke(cli, args, *a, **kw)
+        return super(CustomCliRunner, self).invoke(cli, args, *a, **kw)
 
 
 @pytest.fixture
@@ -58,7 +58,7 @@ def runner(tmpdir, monkeypatch):
             print_new=print_new,
             dbpath=str(db), **kwargs))
         runner = CustomCliRunner(
-            config_file=config_file, db=db, calendars={"one": calendar},
+            config_file=config_file, db=db, calendars=dict(one=calendar),
             xdg_data_home=xdg_data_home, xdg_config_home=xdg_config_home,
             tmpdir=tmpdir,
         )
@@ -130,7 +130,7 @@ def test_simple(runner):
 
     now = dt.datetime.now().strftime('%d.%m.%Y')
     result = runner.invoke(
-        main_khal, f'new {now} 18:00 myevent'.split())
+        main_khal, 'new {} 18:00 myevent'.format(now).split())
     assert result.output == ''
     assert not result.exception
 
@@ -146,7 +146,7 @@ def test_simple(runner):
 def test_simple_color(runner):
     runner = runner(days=2)
     now = dt.datetime.now().strftime('%d.%m.%Y')
-    result = runner.invoke(main_khal, f'new {now} 18:00 myevent'.split())
+    result = runner.invoke(main_khal, 'new {} 18:00 myevent'.format(now).split())
     assert result.output == ''
     assert not result.exception
 
@@ -159,12 +159,12 @@ def test_days(runner):
     runner = runner(days=9)
 
     when = (dt.datetime.now() + dt.timedelta(days=7)).strftime('%d.%m.%Y')
-    result = runner.invoke(main_khal, f'new {when} 18:00 nextweek'.split())
+    result = runner.invoke(main_khal, 'new {} 18:00 nextweek'.format(when).split())
     assert result.output == ''
     assert not result.exception
 
     when = (dt.datetime.now() + dt.timedelta(days=30)).strftime('%d.%m.%Y')
-    result = runner.invoke(main_khal, f'new {when} 18:00 nextmonth'.split())
+    result = runner.invoke(main_khal, 'new {} 18:00 nextmonth'.format(when).split())
     assert result.output == ''
     assert not result.exception
 
@@ -320,13 +320,13 @@ def test_invalid_calendar(runner):
 def test_attach_calendar(runner):
     runner = runner(days=2)
     result = runner.invoke(main_khal, ['printcalendars'])
-    assert set(result.output.split('\n')[:3]) == {'one', 'two', 'three'}
+    assert set(result.output.split('\n')[:3]) == set(['one', 'two', 'three'])
     assert not result.exception
     result = runner.invoke(main_khal, ['printcalendars', '-a', 'one'])
     assert result.output == 'one\n'
     assert not result.exception
     result = runner.invoke(main_khal, ['printcalendars', '-d', 'one'])
-    assert set(result.output.split('\n')[:2]) == {'two', 'three'}
+    assert set(result.output.split('\n')[:2]) == set(['two', 'three'])
     assert not result.exception
 
 
@@ -364,8 +364,8 @@ def test_repeating(runner):
     now = dt.datetime.now().strftime('%d.%m.%Y')
     end_date = dt.datetime.now() + dt.timedelta(days=10)
     result = runner.invoke(
-        main_khal, (f"new {now} 18:00 myevent -r weekly -u "
-                    f"{end_date.strftime('%d.%m.%Y')}").split())
+        main_khal, 'new {} 18:00 myevent -r weekly -u {}'.format(
+            now, end_date.strftime('%d.%m.%Y')).split())
     assert not result.exception
     assert result.output == ''
 
@@ -376,7 +376,7 @@ def test_at(runner):
     end_date = dt.datetime.now() + dt.timedelta(days=10)
     result = runner.invoke(
         main_khal,
-        f"new {now} {end_date.strftime('%d.%m.%Y')} 18:00 myevent".split())
+        'new {} {} 18:00 myevent'.format(now, end_date.strftime('%d.%m.%Y')).split())
     args = ['--color', 'at', '--format', '{start-time}{title}', '--day-format', '', '18:30']
     result = runner.invoke(main_khal, args)
     assert not result.exception
@@ -389,7 +389,7 @@ def test_at_day_format(runner):
     end_date = dt.datetime.now() + dt.timedelta(days=10)
     result = runner.invoke(
         main_khal,
-        f"new {now} {end_date.strftime('%d.%m.%Y')} 18:00 myevent".split())
+        'new {} {} 18:00 myevent'.format(now, end_date.strftime('%d.%m.%Y')).split())
     args = ['--color', 'at', '--format', '{start-time}{title}', '--day-format', '{name}', '18:30']
     result = runner.invoke(main_khal, args)
     assert not result.exception
@@ -401,7 +401,7 @@ def test_list(runner):
     now = dt.datetime.now().strftime('%d.%m.%Y')
     result = runner.invoke(
         main_khal,
-        f'new {now} 18:00 myevent'.split())
+        'new {} 18:00 myevent'.format(now).split())
     format = '{red}{start-end-time-style}{reset} {title} :: {description}'
     args = ['--color', 'list', '--format', format, '--day-format', 'header', '18:30']
     result = runner.invoke(main_khal, args)
@@ -413,7 +413,7 @@ def test_list(runner):
 def test_search(runner):
     runner = runner(days=2)
     now = dt.datetime.now().strftime('%d.%m.%Y')
-    result = runner.invoke(main_khal, f'new {now} 18:00 myevent'.split())
+    result = runner.invoke(main_khal, 'new {} 18:00 myevent'.format(now).split())
     format = '{red}{start-end-time-style}{reset} {title} :: {description}'
     result = runner.invoke(main_khal, ['--color', 'search', '--format', format, 'myevent'])
     assert not result.exception
@@ -451,12 +451,12 @@ def test_import(runner, monkeypatch):
     monkeypatch.setattr('khal.controllers.import_ics', fake.import_ics)
     # as we are not actually parsing the file we want to import, we can use
     # any readable file at all, therefore re-using the configuration file
-    result = runner.invoke(main_khal, f'import -a one {runner.config_file}'.split())
+    result = runner.invoke(main_khal, 'import -a one {}'.format(runner.config_file).split())
     assert not result.exception
     assert {cal['name'] for cal in fake.args[0].calendars} == {'one'}
 
     fake.clean()
-    result = runner.invoke(main_khal, f'import {runner.config_file}'.split())
+    result = runner.invoke(main_khal, 'import {}'.format(runner.config_file).split())
     assert not result.exception
     assert {cal['name'] for cal in fake.args[0].calendars} == {'one', 'two', 'three'}
 
@@ -620,7 +620,7 @@ def test_configure_command(runner):
     runner.config_file.remove()
 
     result = runner.invoke(main_khal, ['configure'], input=choices())
-    assert f'Successfully wrote configuration to {runner.config_file}' in result.output
+    assert 'Successfully wrote configuration to {}'.format(runner.config_file) in result.output
     assert result.exit_code == 0
     with open(str(runner.config_file)) as f:
         actual_config = ''.join(f.readlines())
@@ -724,15 +724,15 @@ def test_configure_command_create_vdir(runner):
         main_khal, ['configure'],
         input=choices(parse_vdirsyncer_conf=False, create_vdir=True),
     )
-    assert f'Successfully wrote configuration to {str(runner.config_file)}' in result.output
+    assert 'Successfully wrote configuration to {}'.format(str(runner.config_file)) in result.output
     assert result.exit_code == 0
     with open(str(runner.config_file)) as f:
         actual_config = ''.join(f.readlines())
 
-    assert actual_config == f'''[calendars]
+    assert actual_config == '''[calendars]
 
 [[private]]
-path = {str(runner.xdg_data_home)}/khal/calendars/private
+path = {}/khal/calendars/private
 type = calendar
 
 [locale]
@@ -744,7 +744,7 @@ longdatetimeformat = %Y-%m-%d %H:%M
 
 [default]
 default_calendar = private
-'''
+'''.format(runner.xdg_data_home)
 
     # running configure again, should yield another vdir path, as the old
     # one still exists
@@ -753,12 +753,12 @@ default_calendar = private
         main_khal, ['configure'],
         input=choices(parse_vdirsyncer_conf=False, create_vdir=True),
     )
-    assert f'Successfully wrote configuration to {str(runner.config_file)}' in result.output
+    assert 'Successfully wrote configuration to {}'.format(str(runner.config_file)) in result.output
     assert result.exit_code == 0
     with open(str(runner.config_file)) as f:
         actual_config = ''.join(f.readlines())
 
-    assert f'{runner.xdg_data_home}/khal/calendars/private1' in actual_config
+    assert '{}/khal/calendars/private1' .format(runner.xdg_data_home) in actual_config
 
 
 def cleanup(paths):
@@ -766,7 +766,7 @@ def cleanup(paths):
     for path in paths:
         if os.path.exists(path):
             os.chmod(str(path), 0o755)
-        for dirpath, _dirnames, filenames in os.walk(path):
+        for dirpath, dirnames, filenames in os.walk(path):
             os.chmod(str(dirpath), 0o755)
             for filename in filenames:
                 os.chmod(str(os.path.join(dirpath, filename)), 0o644)
@@ -816,7 +816,7 @@ def test_edit(runner):
 
     for name in ['event_dt_simple', 'event_d_15']:
         cal_dt = _get_text(name)
-        event = runner.calendars['one'].join(f'{name}.ics')
+        event = runner.calendars['one'].join('{}.ics'.format(name))
         event.write(cal_dt)
 
     format = '{start-end-time-style}: {title}'
diff --git a/tests/event_test.py b/tests/event_test.py
index 3de88ca..cc6e488 100644
--- a/tests/event_test.py
+++ b/tests/event_test.py
@@ -56,17 +56,6 @@ def test_update_simple():
     assert normalize_component(event.raw) == normalize_component(event_updated.raw)
 
 
-def test_add_url():
-    event = Event.fromString(_get_text('event_dt_simple'), **EVENT_KWARGS)
-    event.update_url('https://github.com/pimutils/khal')
-    assert 'URL:https://github.com/pimutils/khal' in event.raw
-
-
-def test_get_url():
-    event = Event.fromString(_get_text('event_dt_url'), **EVENT_KWARGS)
-    assert event.url == "https://github.com/pimutils/khal"
-
-
 def test_no_end():
     """reading an event with neither DTEND nor DURATION"""
     event = Event.fromString(_get_text('event_dt_no_end'), **EVENT_KWARGS)
@@ -88,12 +77,6 @@ def test_do_not_save_empty_description():
     assert 'DESCRIPTION' not in event.raw
 
 
-def test_do_not_save_empty_url():
-    event = Event.fromString(_get_text('event_dt_simple'), **EVENT_KWARGS)
-    event.update_url('')
-    assert 'URL' not in event.raw
-
-
 def test_remove_existing_location_if_set_to_empty():
     event = Event.fromString(_get_text('event_dt_simple_updated'), **EVENT_KWARGS)
     event.update_location('')
@@ -106,12 +89,6 @@ def test_remove_existing_description_if_set_to_empty():
     assert 'DESCRIPTION' not in event.raw
 
 
-def test_remove_existing_url_if_set_to_empty():
-    event = Event.fromString(_get_text('event_dt_url'), **EVENT_KWARGS)
-    event.update_url('')
-    assert 'URL' not in event.raw
-
-
 def test_update_remove_categories():
     event = Event.fromString(_get_text('event_dt_simple_updated'), **EVENT_KWARGS)
     event_nocat = Event.fromString(_get_text('event_dt_simple_nocat'), **EVENT_KWARGS)
@@ -523,13 +500,11 @@ END:VEVENT
 END:VCALENDAR"""
         )
 
-
 def test_sort_date_vs_datetime():
     event1 = Event.fromString(_get_text('event_d'), **EVENT_KWARGS)
     event2 = Event.fromString(_get_text('event_dt_floating'), **EVENT_KWARGS)
     assert event1 < event2
 
-
 def test_sort_event_start():
     event_dt = _get_text('event_dt_simple')
     start = BERLIN.localize(dt.datetime(2014, 4, 9, 9, 45))
@@ -538,7 +513,6 @@ def test_sort_event_start():
     event2 = Event.fromString(event_dt, start=start, end=end, **EVENT_KWARGS)
     assert event1 < event2
 
-
 def test_sort_event_end():
     event_dt = _get_text('event_dt_simple')
     start = BERLIN.localize(dt.datetime(2014, 4, 9, 9, 30))
@@ -547,7 +521,6 @@ def test_sort_event_end():
     event2 = Event.fromString(event_dt, start=start, end=end, **EVENT_KWARGS)
     assert event1 < event2
 
-
 def test_sort_event_summary():
     event_dt = _get_text('event_dt_simple')
     event1 = Event.fromString(event_dt, **EVENT_KWARGS)
diff --git a/tests/icalendar_test.py b/tests/icalendar_test.py
index 5a4aaba..5538802 100644
--- a/tests/icalendar_test.py
+++ b/tests/icalendar_test.py
@@ -9,7 +9,7 @@ from .utils import (LOCALE_BERLIN, _get_text, normalize_component)
 
 def _get_TZIDs(lines):
     """from a list of strings, get all unique strings that start with TZID"""
-    return sorted(line for line in lines if line.startswith('TZID'))
+    return sorted((line for line in lines if line.startswith('TZID')))
 
 
 def test_normalize_component():
diff --git a/tests/ics/event_dt_url.ics b/tests/ics/event_dt_url.ics
deleted file mode 100644
index b2cf7ca..0000000
--- a/tests/ics/event_dt_url.ics
+++ /dev/null
@@ -1,12 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//PIMUTILS.ORG//NONSGML khal / icalendar //EN
-BEGIN:VEVENT
-SUMMARY:An Event
-URL:https://github.com/pimutils/khal
-DTSTART;TZID=Europe/Berlin;VALUE=DATE-TIME:20140409T093000
-DTEND;TZID=Europe/Berlin;VALUE=DATE-TIME:20140409T103000
-DTSTAMP;VALUE=DATE-TIME:20140401T234817Z
-UID:V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU
-END:VEVENT
-END:VCALENDAR
diff --git a/tests/khalendar_test.py b/tests/khalendar_test.py
index f2afdfd..6b93306 100644
--- a/tests/khalendar_test.py
+++ b/tests/khalendar_test.py
@@ -41,7 +41,7 @@ item_today = Item(event_today)
 SIMPLE_EVENT_UID = 'V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU'
 
 
-class TestCalendar:
+class TestCalendar(object):
 
     def test_create(self, coll_vdirs):
         assert True
@@ -60,8 +60,8 @@ class TestCalendar:
 
     def test_sanity(self, coll_vdirs):
         coll, vdirs = coll_vdirs
-        mtimes = {}
-        for _ in range(100):
+        mtimes = dict()
+        for i in range(100):
             for cal in coll._calendars:
                 mtime = coll._local_ctag(cal)
                 if mtimes.get(cal):
@@ -74,8 +74,8 @@ class TestCalendar:
 
         print('init')
         for calendar in coll._calendars:
-            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
-                  f'vdir ctag: {coll._backend.get_ctag(calendar)}')
+            print('{}: saved ctag: {}, vdir ctag: {}'.format(
+                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
         assert len(list(vdirs[cal1].list())) == 0
         assert coll._needs_update(cal1) is False
         sleep(sleep_time)
@@ -83,19 +83,19 @@ class TestCalendar:
         vdirs[cal1].upload(item_today)
         print('upload')
         for calendar in coll._calendars:
-            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
-                  f'vdir ctag: {coll._backend.get_ctag(calendar)}')
+            print('{}: saved ctag: {}, vdir ctag: {}'.format(
+                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
         assert len(list(vdirs[cal1].list())) == 1
         assert coll._needs_update(cal1) is True
         coll.update_db()
         print('updated')
         for calendar in coll._calendars:
-            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
-                  f'vdir ctag: {coll._backend.get_ctag(calendar)}')
+            print('{}: saved ctag: {}, vdir ctag: {}'.format(
+                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
         assert coll._needs_update(cal1) is False
 
 
-class TestVdirsyncerCompat:
+class TestVdirsyncerCompat(object):
     def test_list(self, coll_vdirs):
         coll, vdirs = coll_vdirs
         event = Event.fromString(_get_text('event_d'), calendar=cal1, locale=LOCALE_BERLIN)
@@ -107,13 +107,13 @@ class TestVdirsyncerCompat:
         event = Event.fromString(event_today, calendar=cal1, locale=LOCALE_BERLIN)
         coll.new(event)
         hrefs = sorted(href for href, etag in coll._backend.list(cal1))
-        assert {str(coll.get_event(href, calendar=cal1).uid) for href in hrefs} == {
+        assert set(str(coll.get_event(href, calendar=cal1).uid) for href in hrefs) == set((
             'uid3@host1.com',
             'V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU',
-        }
+        ))
 
 
-class TestCollection:
+class TestCollection(object):
 
     astart = dt.datetime.combine(aday, dt.time.min)
     aend = dt.datetime.combine(aday, dt.time.max)
@@ -148,9 +148,9 @@ class TestCollection:
         coll, vdirs = coll_vdirs
         start = dt.datetime.combine(today, dt.time.min)
         end = dt.datetime.combine(today, dt.time.max)
-        assert list(coll.get_floating(start, end)) == []
+        assert list(coll.get_floating(start, end)) == list()
         assert list(coll.get_localized(utils.BERLIN.localize(start),
-                                       utils.BERLIN.localize(end))) == []
+                                       utils.BERLIN.localize(end))) == list()
 
     def test_insert(self, coll_vdirs):
         """insert a localized event"""
@@ -298,7 +298,7 @@ class TestCollection:
         event = Event.fromString(
             _get_text('event_dt_multi_recuid_no_master'), calendar=cal1, locale=LOCALE_BERLIN)
         coll.new(event, cal1)
-        events = sorted(coll.search('Event'))
+        events = list(sorted(coll.search('Event')))
         assert len(events) == 2
         assert events[0].format(
             '{start} {end} {title}', dt.date.today()) == '30.06. 07:30 30.06. 12:00 Arbeit\x1b[0m'
@@ -334,7 +334,7 @@ class TestCollection:
         event = Event.fromString(
             _get_text('invalid_tzoffset'), calendar=cal1, locale=LOCALE_BERLIN)
         coll.new(event, cal1)
-        events = sorted(coll.search('Event'))
+        events = list(sorted(coll.search('Event')))
         assert len(events) == 1
         assert events[0].format('{start} {end} {title}', dt.date.today()) == \
             '02.12. 08:00 02.12. 09:30 Some event\x1b[0m'
@@ -354,7 +354,7 @@ class TestCollection:
         )
 
 
-class TestDbCreation:
+class TestDbCreation(object):
 
     def test_create_db(self, tmpdir):
         vdirpath = str(tmpdir) + '/' + cal1
@@ -529,7 +529,7 @@ def test_birthdays(coll_vdirs_birthday):
     coll, vdirs = coll_vdirs_birthday
     assert list(
         coll.get_floating(dt.datetime(1971, 3, 11), dt.datetime(1971, 3, 11, 23, 59, 59))
-    ) == []
+    ) == list()
     vdirs[cal1].upload(DumbItem(card, 'unix'))
     coll.update_db()
     assert 'Unix\'s 41st birthday' == list(
@@ -576,7 +576,7 @@ def test_birthdays_no_year(coll_vdirs_birthday):
     coll, vdirs = coll_vdirs_birthday
     assert list(
         coll.get_floating(dt.datetime(1971, 3, 11), dt.datetime(1971, 3, 11, 23, 59, 59))
-    ) == []
+    ) == list()
     vdirs[cal1].upload(DumbItem(card_no_year, 'vcard.vcf'))
     coll.update_db()
     events = list(coll.get_floating(dt.datetime(1971, 3, 11), dt.datetime(1971, 3, 11, 23, 59, 59)))
diff --git a/tests/khalendar_utils_test.py b/tests/khalendar_utils_test.py
index cbe35d7..60b6628 100644
--- a/tests/khalendar_utils_test.py
+++ b/tests/khalendar_utils_test.py
@@ -230,7 +230,7 @@ def _get_vevent(event):
             return component
 
 
-class TestExpand:
+class TestExpand(object):
     dtstartend_berlin = [
         (berlin.localize(dt.datetime(2013, 3, 1, 14, 0, )),
          berlin.localize(dt.datetime(2013, 3, 1, 16, 0, ))),
@@ -370,7 +370,7 @@ class TestExpand:
         ]
 
 
-class TestExpandNoRR:
+class TestExpandNoRR(object):
     dtstartend_berlin = [
         (berlin.localize(dt.datetime(2013, 3, 1, 14, 0)),
          berlin.localize(dt.datetime(2013, 3, 1, 16, 0))),
@@ -575,7 +575,7 @@ END:VEVENT
 """
 
 
-class TestSpecial:
+class TestSpecial(object):
     """collection of strange test cases that don't fit anywhere else really"""
 
     def test_count(self):
@@ -690,7 +690,7 @@ class TestSpecial:
         # TODO test for logging message
         assert dtstart is False
 
-    def test_event_invalid_rrule(self):
+    def test_event_dt_rrule_until_before_start(self):
         """test handling if an event with RRULE will never occur"""
         vevent = _get_vevent(_get_text('event_rrule_no_occurence'))
         dtstart = icalendar_helpers.expand(vevent, berlin)
@@ -718,7 +718,7 @@ UID:datetime123
 END:VEVENT"""
 
 
-class TestRDate:
+class TestRDate(object):
     """Testing expanding of recurrence rules"""
     def test_simple_rdate(self):
         vevent = _get_vevent(simple_rdate)
@@ -780,7 +780,7 @@ END:VCALENDAR
 """
 
 
-class TestSanitize:
+class TestSanitize(object):
 
     def test_noend_date(self):
         vevent = _get_vevent(noend_date)
diff --git a/tests/settings_test.py b/tests/settings_test.py
index b483179..60f7185 100644
--- a/tests/settings_test.py
+++ b/tests/settings_test.py
@@ -16,7 +16,7 @@ from .utils import LOCALE_BERLIN
 PATH = __file__.rsplit('/', 1)[0] + '/configs/'
 
 
-class TestSettings:
+class TestSettings(object):
     def test_simple_config(self):
         config = get_config(
             PATH + 'simple.conf',
diff --git a/tests/ui/canvas_render.py b/tests/ui/canvas_render.py
index bb92fde..ad077e8 100644
--- a/tests/ui/canvas_render.py
+++ b/tests/ui/canvas_render.py
@@ -52,6 +52,6 @@ class CanvasTranslator:
         else:
             fmt = self._palette[fmt]
             if fmt[0]:
-                self.output.write(f'{fmt[1]}{click.style(text)}')
+                self.output.write('{}{}'.format(fmt[1], click.style(text)))
             else:
                 self.output.write(click.style(text, fg=fmt[1]))
diff --git a/tests/utils_test.py b/tests/utils_test.py
index ac1d153..98074ac 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -36,9 +36,9 @@ def test_last_sgr():
 
 
 def test_find_unmatched_sgr():
-    assert utils.find_unmatched_sgr(weekheader) is None
-    assert utils.find_unmatched_sgr(today_line) is None
-    assert utils.find_unmatched_sgr(calendarline) is None
+    assert utils.find_unmatched_sgr(weekheader) is False
+    assert utils.find_unmatched_sgr(today_line) is False
+    assert utils.find_unmatched_sgr(calendarline) is False
     assert utils.find_unmatched_sgr('\x1b[31mHello World') == '\x1b[31m'
     assert utils.find_unmatched_sgr('\x1b[31mHello\x1b[0m \x1b[32mWorld') == '\x1b[32m'
     assert utils.find_unmatched_sgr('foo\x1b[1;31mbar') == '\x1b[1;31m'
@@ -74,31 +74,6 @@ def test_color_wrap_256():
     assert utils.color_wrap(text, 30) == expected
 
 
-def test_color_wrap_multiple_colors_and_tabs():
-    text = (
-        "\x1b[31m14:00-14:50    AST-1002-102 INTRO AST II/STAR GALAX (R) Classes",
-        "15:30-16:45    PHL-2000-104 PHILOSOPHY, SOCIETY & ETHICS (R) Classes",
-        "\x1b[38;2;255;0m17:00-18:00    Pay Ticket Deadline Calendar",
-        "09:30-10:45    PHL-1501-101 MIND, KNOWLEDGE & REALITY (R) Classes",
-        "\x1b[38;2;255;0m11:00-14:00    Rivers Street (noodles and pizza) (R) Calendar",
-    )
-    expected = [
-      '\x1b[31m14:00-14:50    AST-1002-102 INTRO AST II/STAR GALAX (R)\x1b[0m',
-      '\x1b[31mClasses\x1b[0m',
-      '15:30-16:45    PHL-2000-104 PHILOSOPHY, SOCIETY & ETHICS (R)',
-      'Classes',
-      '\x1b[38;2;255;0m17:00-18:00    Pay Ticket Deadline Calendar\x1b[0m',
-      '09:30-10:45    PHL-1501-101 MIND, KNOWLEDGE & REALITY (R)',
-      'Classes',
-      '\x1b[38;2;255;0m11:00-14:00    Rivers Street (noodles and\x1b[0m',
-      '\x1b[38;2;255;0mpizza) (R) Calendar\x1b[0m'
-    ]
-    actual = list()
-    for line in text:
-        actual += utils.color_wrap(line, 60)
-    assert actual == expected
-
-
 def test_get_weekday_occurrence():
     assert utils.get_weekday_occurrence(dt.datetime(2017, 3, 1)) == (2, 1)
     assert utils.get_weekday_occurrence(dt.datetime(2017, 3, 2)) == (3, 1)