Repository: pimutils/khal
Pull request #1032 (2021-04-28 07:17:42)

Upgrade syntax for Python 3.6
This change updates lots of syntax dropping anything that was necessary for Python < 3.6.

Honestly, I just ran:

    pyupgrade --py36-plus $(find . -name "*.py")


Patch:
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 8fb86c0..ec33174 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # khal documentation build configuration file, created by
 # sphinx-quickstart on Fri Jul  4 00:00:47 2014.
@@ -36,20 +35,19 @@ 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('\n.. _{}-{}:'.format(secname, key))
+    output.write(f'\n.. _{secname}-{key}:')
     output.write('\n')
-    output.write('\n.. object:: {}\n'.format(key))
+    output.write(f'\n.. object:: {key}\n')
     output.write('\n')
     output.write('    ' + '\n    '.join([line.strip('# ') for line in comment]))
     output.write('\n')
     if fun_name == 'option':
-        fun_args = ['*{}*'.format(arg) for arg in fun_args]
+        fun_args = [f'*{arg}*' for arg in fun_args]
         fun_args = fun_args[:-2] + [fun_args[-2] + ' and ' + fun_args[-1]]
-        fun_name += ', allowed values are {}'.format(', '.join(fun_args))
+        fun_name += f", allowed values are {', '.join(fun_args)}"
         fun_args = []
     if fun_name == 'integer' and len(fun_args) == 2:
-        fun_name += ', allowed values are between {} and {}'.format(
-            fun_args[0], fun_args[1])
+        fun_name += f', allowed values are between {fun_args[0]} and {fun_args[1]}'
         fun_args = []
     output.write('\n')
     if fun_name in ['expand_db_path', 'expand_path']:
@@ -60,20 +58,20 @@ def write_section(specsection, secname, key, comment, output):
             default = ['space' if one == ' ' else one for one in default]
             default = ', '.join(default)
 
-    output.write('      :type: {}'.format(fun_name))
+    output.write(f'      :type: {fun_name}')
     output.write('\n')
     if fun_args != []:
-        output.write('      :args: {}'.format(fun_args))
+        output.write(f'      :args: {fun_args}')
         output.write('\n')
-    output.write('      :default: {}'.format(default))
+    output.write(f'      :default: {default}')
     output.write('\n')
 
 
 with open('configspec.rst', 'w') as f:
     for secname in sorted(spec):
         f.write('\n')
-        heading = 'The [{}] section'.format(secname)
-        f.write('{}\n{}'.format(heading, len(heading) * '~'))
+        heading = f'The [{secname}] section'
+        f.write(f'{heading}\n{ len(heading) * "~"}')
         f.write('\n')
         comment = spec.comments[secname]
         f.write('\n'.join([line[2:] for line in comment]))
diff --git a/khal/cli.py b/khal/cli.py
index e56f793..6ff4e2d 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(
-                    'Unknown calendar {}, run `khal printcalendars` to get a '
-                    'list of all configured calendars.'.format(cal_name)
+                    f'Unknown calendar {cal_name}, run `khal printcalendars` '
+                    'to get a list of all configured calendars.'
                 )
 
         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(
-            'Unknown calendar {}, run `khal printcalendars` to get a '
-            'list of all configured calendars.'.format(calendar)
+            f'Unknown calendar {calendar}, run `khal printcalendars` to get a '
+            'list of all configured calendars.'
         )
     return calendar
 
@@ -185,7 +185,7 @@ def build_collection(conf, selection):
     return collection
 
 
-class _NoConfig(object):
+class _NoConfig:
     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('[{}]'.format(key))
+        out.append(f'[{key}]')
         for subkey, subvalue in value.items():
             if isinstance(subvalue, dict):
-                out.append('  [[{}]]'.format(subkey))
+                out.append(f'  [[{subkey}]]')
                 for subsubkey, subsubvalue in subvalue.items():
-                    out.append('    {}: {}'.format(subsubkey, subsubvalue))
+                    out.append(f'    {subsubkey}: {subsubvalue}')
             else:
-                out.append('  {}: {}'.format(subkey, subvalue))
+                out.append(f'  {subkey}: {subvalue}')
     return '\n'.join(out)
 
 
@@ -442,7 +442,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', 'r')
+                        sys.stdin = open('/dev/tty')
                     else:
                         logger.warning('/dev/tty does not exist, importing might not work')
             else:
@@ -519,7 +519,7 @@ def _get_cli():
                     'longdatetimeformat', 'datetimeformat', 'longdateformat',
                     'dateformat', 'timeformat']:
                 dt_str = time.strftime(ctx.obj['conf']['locale'][strftime_format])
-                click.echo('{}: {}'.format(strftime_format, dt_str))
+                click.echo(f'{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 853e1dc..8ea3f6a 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('Input must be between {} and {}'.format(min_value, max_value))
+        raise UsageError(f'Input must be between {min_value} and {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('{}={}'.format(f, example_date.strftime(f)))
+            newcol.append(f'{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('[{}] {} (today: {})'.format(num, desc, today.strftime(fmt)))
+        print(f'[{num}] {desc} (today: {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("Date format: {} "
-          "(today as an example: {})".format(dateformat, today.strftime(dateformat)))
+    print(f"Date format: {dateformat} "
+          f"(today as an example: {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("Time format: {} "
-          "(current time as an example: {})".format(timeformat, now.strftime(timeformat)))
+    print(f"Time format: {timeformat} "
+          f"(current time as an example: {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("Configured calendars: {}".format(', '.join(names)))
+    print(f"Configured calendars: {', '.join(names)}")
     default_calendar = prompt(
         "Please type one of the above options",
         default=names[0],
@@ -158,7 +158,7 @@ def get_vdirs_from_vdirsyncer_config():
     else:
         print("The following collections were found:")
         for name, path, _ in vdirs:
-            print('  {}: {}'.format(name, path))
+            print(f'  {name}: {path}')
         return vdirs
 
 
@@ -178,18 +178,18 @@ def create_vdir(names=[]):
     try:
         makedirs(path)
     except OSError as error:
-        print("Could not create directory {} because of {}. Exiting".format(path, error))
+        print(f"Could not create directory {path} because of {error}. Exiting")
         raise
-    print("Created new vdir at {}".format(path))
+    print(f"Created new vdir at {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('\n[[{name}]]'.format(name=name))
-        config.append('path = {path}'.format(path=path))
-        config.append('type = {type}'.format(type=type_))
+        config.append(f'\n[[{name}]]')
+        config.append(f'path = {path}')
+        config.append(f'type = {type_}')
 
     config.append('\n[locale]')
     config.append('timeformat = {timeformat}\n'
@@ -202,7 +202,7 @@ def create_config(vdirs, dateformat, timeformat, default_calendar=None):
                           longdateformat=dateformat))
     if default_calendar:
         config.append('[default]')
-        config.append('default_calendar = {}\n'.format(default_calendar))
+        config.append(f'default_calendar = {default_calendar}\n')
     config = '\n'.join(config)
 
     return config
@@ -211,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("Found an existing config file at {}.".format(config_file))
+        logger.fatal(f"Found an existing config file at {config_file}.")
         logger.fatal(
             "If you want to create a new configuration file, "
             "please remove the old one first. Exiting.")
@@ -243,8 +243,8 @@ def configwizard():
     )
     config_path = join(xdg.BaseDirectory.xdg_config_home, 'khal', 'config')
     if not confirm(
-            "Do you want to write the config to {}? "
-            "(Choosing `No` will abort)".format(config_path), default=True):
+            f"Do you want to write the config to {config_path}? "
+            "(Choosing `No` will abort)", 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):
@@ -252,12 +252,12 @@ def configwizard():
             makedirs(config_dir)
         except OSError as error:
             print(
-                "Could not write config file at {} because of {}. "
-                "Aborting".format(config_dir, error)
+                f"Could not write config file at {config_dir} because of "
+                f"{error}. Aborting"
             )
             raise FatalError(error)
         else:
-            print('created directory {}'.format(config_dir))
+            print(f'created directory {config_dir}')
     with open(config_path, 'w') as config_file:
         config_file.write(config)
-    print("Successfully wrote configuration to {}".format(config_path))
+    print(f"Successfully wrote configuration to {config_path}")
diff --git a/khal/controllers.py b/khal/controllers.py
index 56cd8ba..d244097 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('Getting all events between {} and {}'.format(start, end))
+        logger.debug(f'Getting all events between {start} and {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 `{}` for a datetime'.format(' '.join(datepoint)))
+            raise FatalError('Invalid value of `{' '.join(datepoint)}` for a datetime')
         if allday:
-            logger.debug('Got date {}'.format(start))
+            logger.debug(f'Got date {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('Getting all events between {} and {}'.format(start, end))
+        logger.debug(f'Getting all events between {start} and {end}')
 
     event_column = []
     once = set() if once else None
@@ -381,7 +381,7 @@ def new_from_args(collection, calendar_name, conf, dtstart=None, dtend=None,
         collection.new(event)
     except ReadOnlyCalendarError:
         raise FatalError(
-            'ERROR: Cannot modify calendar "{}" as it is read-only'.format(calendar_name)
+            f'ERROR: Cannot modify calendar "{calendar_name}" as it is read-only'
         )
 
     if conf['default']['print_new'] == 'event':
@@ -546,7 +546,7 @@ def interactive(collection, conf):
         collection, conf, title="select an event", description="do something")
     ui.start_pane(
         pane, pane.cleanup,
-        program_info='{0} v{1}'.format(__productname__, __version__),
+        program_info=f'{__productname__} v{__version__}',
         quit_keys=conf['keybindings']['quit'],
     )
 
@@ -594,11 +594,11 @@ def import_event(vevent, collection, locale, batch, format=None, env=None):
     else:
         calendar_names = sorted(collection.writable_names)
         choices = ', '.join(
-            ['{}({})'.format(name, num) for num, name in enumerate(calendar_names)])
+            [f'{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"
-                "{}".format(choices),
+                f"{choices}",
                 default=collection.default_calendar_name,
             )
             try:
@@ -612,7 +612,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("Do you want to import this event into `{}`?".format(calendar_name)):
+    if batch or confirm(f"Do you want to import this event into `{calendar_name}`?"):
         try:
             collection.new(Item(vevent), collection=calendar_name)
         except DuplicateUid:
@@ -620,7 +620,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("Not importing event with UID `{}`".format(event.uid))
+                logger.warning(f"Not importing event with UID `{event.uid}`")
 
 
 def print_ics(conf, name, ics, format):
@@ -636,7 +636,7 @@ def print_ics(conf, name, ics, format):
     for uid in events_grouped:
         vevents.append(sorted(events_grouped[uid], key=sort_vevent_key))
 
-    echo('{} events found in {}'.format(len(vevents), name))
+    echo(f'{len(vevents)} events found in {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 121c07e..a35fd2f 100644
--- a/khal/icalendar.py
+++ b/khal/icalendar.py
@@ -178,8 +178,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(
-                        'Cannot find timezone `{}` in .ics file, using default timezone. '
-                        'This can lead to erroneous time shifts'.format(item.params['TZID'])
+                        f"Cannot find timezone `{item.params['TZID']}` in .ics file, "
+                        "using default timezone. This can lead to erroneous time shifts"
                     )
                     missing_tz.add(item.params['TZID'])
                 elif datetime_.tzinfo and datetime_.tzinfo != pytz.UTC and \
@@ -191,8 +191,8 @@ def ics_from_list(events, tzs, random_uid=False, default_timezone=None):
             calendar.add_component(tzs[str(tzid)])
         else:
             logger.warning(
-                'Cannot find timezone `{}` in .ics file, this could be a bug, '
-                'please report this issue at http://github.com/pimutils/khal/.'.format(tzid))
+                f'Cannot find timezone `{tzid}` in .ics file, this could be a bug, '
+                'please report this issue at http://github.com/pimutils/khal/.')
     for sub_event in events:
         calendar.add_component(sub_event)
     return calendar.to_ical().decode('utf-8')
@@ -277,19 +277,19 @@ def expand(vevent, href=''):
 
             if testuntil < teststart:
                 logger.warning(
-                    '{0}: Unsupported recurrence. UNTIL is before DTSTART.\n'
-                    'This event will not be available in khal.'.format(href))
+                    f'{href}: Unsupported recurrence. UNTIL is before DTSTART.\n'
+                    'This event will not be available in khal.')
                 return False
 
         if rrule.count() == 0:
             logger.warning(
-                '{0}: Recurrence defined but will never occur.\n'
-                'This event will not be available in khal.'.format(href))
+                f'{href}: Recurrence defined but will never occur.\n'
+                'This event will not be available in khal.')
             return False
 
         rrule = map(sanitize_datetime, rrule)
 
-        logger.debug('calculating recurrence dates for {}, this might take some time.'.format(href))
+        logger.debug(f'calculating recurrence dates for {href}, this might take some time.')
 
         # RRULE and RDATE may specify the same date twice, it is recommended by
         # the RFC to consider this as only one instance
@@ -322,8 +322,8 @@ def expand(vevent, href=''):
                 dtstartl.remove(date)
             except KeyError:
                 logger.warning(
-                    'In event {}, excluded instance starting at {} not found, '
-                    'event might be invalid.'.format(href, date))
+                    f'In event {href}, excluded instance starting at {date} '
+                    'not found, event might be invalid.')
 
     dtstartend = [(start, start + duration) for start in dtstartl]
     # not necessary, but I prefer deterministic output
@@ -371,9 +371,9 @@ def sanitize(vevent, default_timezone, href='', calendar=''):
             value = default_timezone.localize(vevent.pop(prop).dt)
             vevent.add(prop, value)
             logger.warning(
-                "{} localized in invalid or incomprehensible timezone `{}` in {}/{}. "
-                "This could lead to this event being wrongly displayed."
-                "".format(prop, timezone, calendar, href)
+                f"{prop} localized in invalid or incomprehensible timezone "
+                f"`{timezone}` in {calendar}/{href}. This could lead to this "
+                "event being wrongly displayed."
             )
 
     vdtstart = vevent.pop('DTSTART', None)
diff --git a/khal/khalendar/backend.py b/khal/khalendar/backend.py
index bbe7067..f96e795 100644
--- a/khal/khalendar/backend.py
+++ b/khal/khalendar/backend.py
@@ -57,7 +57,7 @@ class EventType(IntEnum):
     DATETIME = 1
 
 
-class SQLiteDb(object):
+class SQLiteDb:
     """
     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(object):
                 makedirs(dbdir, mode=0o770)
                 logger.debug('success')
             except OSError as error:
-                logger.critical('failed to create {0}: {1}'.format(dbdir, error))
+                logger.critical(f'failed to create {dbdir}: {error}')
                 raise CouldNotCreateDbDir()
 
     def _check_table_version(self) -> None:
@@ -181,7 +181,7 @@ class SQLiteDb(object):
             result = self.cursor.fetchone()
 
             if result[0] != 0:
-                logger.debug("tables for calendar {0} exist".format(cal))
+                logger.debug(f"tables for calendar {cal} exist")
             else:
                 sql_s = 'INSERT INTO calendars (calendar, resource) VALUES (?, ?);'
                 stuple = (cal, '')
@@ -218,12 +218,11 @@ class SQLiteDb(object):
         check_for_errors(ical, calendar, href)
         if not assert_only_one_uid(ical):
             logger.warning(
-                "The .ics file at {}/{} contains multiple UIDs.\n"
+                f"The .ics file at {calendar}/{href} 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
@@ -271,9 +270,8 @@ class SQLiteDb(object):
                 date = vcard[key]
                 if isinstance(date, list):
                     logger.warning(
-                        'Vcard {0} in collection {1} has more than one '
-                        '{2}, will be skipped and not be available '
-                        'in khal.'.format(href, calendar, key)
+                        f'Vcard {href} in collection {calendar} has more than one '
+                        f'{key}, will be skipped and not be available in khal.'
                     )
                     continue
                 try:
@@ -285,7 +283,7 @@ class SQLiteDb(object):
                     date = parser.parse(date).date()
                 except ValueError:
                     logger.warning(
-                        'cannot parse {0} in {1} in collection {2}'.format(key, href, calendar))
+                        f'cannot parse {key} in {href} in collection {calendar}')
                     continue
                 if 'FN' in vcard:
                     name = vcard['FN']
@@ -309,10 +307,10 @@ class SQLiteDb(object):
                         xtag = 'x-abdate'
                         vevent.add('x-ablabel', description)
                     vevent.add(xtag,
-                               '{:04}{:02}{:02}'.format(date.year, date.month, date.day))
+                               f'{date.year:04}{date.month:02}{date.day:02}')
                     vevent.add('x-fname', name)
                 vevent.add('summary',
-                           '{0}\'s {1}'.format(name, description))
+                           f'{name}\'s {description}')
                 vevent.add('uid', href + key)
                 vevent_str = vevent.to_ical().decode('utf-8')
                 self._update_impl(vevent, href + key, calendar)
@@ -376,8 +374,8 @@ class SQLiteDb(object):
 
             if thisandfuture:
                 recs_sql_s = (
-                    'UPDATE {0} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, ref = ? '
-                    'WHERE rec_inst >= ? AND href = ? AND calendar = ?;'.format(recs_table))
+                    f'UPDATE {recs_table} SET dtstart = rec_inst + ?, dtend = rec_inst + ?, '
+                    'ref = ? WHERE rec_inst >= ? AND href = ? AND calendar = ?;')
                 stuple_f = (
                     start_shift_seconds, start_shift_seconds + duration_seconds,
                     ref, rec_inst, href, calendar,
@@ -385,9 +383,9 @@ class SQLiteDb(object):
                 self.sql_ex(recs_sql_s, stuple_f)
             else:
                 recs_sql_s = (
-                    'INSERT OR REPLACE INTO {0} '
+                    f'INSERT OR REPLACE INTO {recs_table} '
                     '(dtstart, dtend, href, ref, dtype, rec_inst, calendar)'
-                    'VALUES (?, ?, ?, ?, ?, ?, ?);'.format(recs_table))
+                    'VALUES (?, ?, ?, ?, ?, ?, ?);')
                 stuple_n = (dbstart, dbend, href, ref, dtype, rec_inst, calendar)
                 self.sql_ex(recs_sql_s, stuple_n)
 
@@ -430,7 +428,7 @@ class SQLiteDb(object):
         """
         assert calendar is not None
         for table in ['recs_loc', 'recs_float']:
-            sql_s = 'DELETE FROM {0} WHERE href = ? AND calendar = ?;'.format(table)
+            sql_s = f'DELETE FROM {table} WHERE href = ? AND calendar = ?;'
             self.sql_ex(sql_s, (href, calendar))
         sql_s = 'DELETE FROM events WHERE href = ? AND calendar = ?;'
         self.sql_ex(sql_s, (href, calendar))
@@ -447,7 +445,7 @@ class SQLiteDb(object):
         """
         assert calendar is not None
         for table in ['recs_loc', 'recs_float']:
-            sql_s = 'DELETE FROM {0} WHERE href LIKE ? AND calendar = ?;'.format(table)
+            sql_s = f'DELETE FROM {table} WHERE href LIKE ? AND calendar = ?;'
             self.sql_ex(sql_s, (href, calendar))
         sql_s = 'DELETE FROM events WHERE href LIKE ? AND calendar = ?;'
         self.sql_ex(sql_s, (href, calendar))
@@ -577,7 +575,7 @@ class SQLiteDb(object):
             'recs_loc.calendar = events.calendar '
             'WHERE item LIKE (?) and events.calendar in ({0});'
         )
-        stuple = tuple(['%{0}%'.format(search_string)] + list(self.calendars))
+        stuple = tuple([f'%{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))
@@ -594,7 +592,7 @@ class SQLiteDb(object):
             'recs_float.calendar = events.calendar '
             'WHERE item LIKE (?) and events.calendar in ({0});'
         )
-        stuple = tuple(['%{0}%'.format(search_string)] + list(self.calendars))
+        stuple = tuple([f'%{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)
@@ -617,18 +615,16 @@ 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 '
-            'standard MUST NOT create those. Therefore event {0} from '
-            'calendar {1} will not be shown in khal'
-            .format(href, calendar)
+            f'standard MUST NOT create those. Therefore event {href} from '
+            f'calendar {calendar} will not be shown in khal'
         )
     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. '
-            'Therefore event {0} from calendar {1} will not be shown in khal.\n'
+            f'Therefore event {href} from calendar {calendar} 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)
         )
 
 
@@ -636,8 +632,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(
-            'Errors occurred when parsing {0}/{1} for the following '
-            'reasons:'.format(calendar, href))
+            f'Errors occurred when parsing {calendar}/{href} for '
+            'the following reasons:')
         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 79991da..12eac9e 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(object):
+class Event:
     """base Event class for representing a *recurring instance* of an Event
 
     (in case of non-recurring events this distinction is irrelevant)
@@ -184,12 +184,12 @@ class Event(object):
             try:
                 return end < other_end
             except TypeError:
-                raise ValueError('Cannot compare events {} and {}'.format(end, other_end))
+                raise ValueError(f'Cannot compare events {end} and {other_end}')
 
         try:
             return start < other_start
         except TypeError:
-            raise ValueError('Cannot compare events {} and {}'.format(start, other_start))
+            raise ValueError(f'Cannot compare events {start} and {other_start}')
 
     def update_start_end(self, start, end):
         """update start and end time of this event
@@ -318,7 +318,7 @@ class Event(object):
         cn = organizer.params.get('CN', '')
         email = organizer.split(':')[-1]
         if cn:
-            return '{} ({})'.format(cn, email)
+            return f'{cn} ({email})'
         else:
             return email
 
@@ -711,10 +711,9 @@ class LocalizedEvent(DatetimeEvent):
             starttz = getattr(self._vevents[self.ref]['DTSTART'].dt, 'tzinfo', None)
         except KeyError:
             msg = (
-                "Cannot understand event {} from "
-                "calendar {}, you might want to file an issue at "
+                f"Cannot understand event {kwargs.get('href')} from "
+                f"calendar {kwargs.get('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
@@ -774,14 +773,14 @@ class AllDayEvent(Event):
 
     @property
     def end(self):
-        end = super(AllDayEvent, self).end
+        end = super().end
         if end == self.start:
             # https://github.com/pimutils/khal/issues/129
-            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))
+            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}')
             end += dt.timedelta(days=1)
         return end - dt.timedelta(days=1)
 
diff --git a/khal/khalendar/exceptions.py b/khal/khalendar/exceptions.py
index 1629176..9dcc058 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 += ': {}'.format(message.strip())
+            x += f': {message.strip()}'
         UnsupportedFeatureError.__init__(self, x)
 
 
diff --git a/khal/khalendar/khalendar.py b/khal/khalendar/khalendar.py
index 317d937..cef7f94 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('{0} is not a directory.'.format(path))
+            raise RuntimeError(f'{path} is not a directory.')
         try:
             os.makedirs(path, mode=0o750)
         except OSError as error:
-            logger.critical('failed to create {0}: {1}'.format(path, error))
+            logger.critical(f'failed to create {path}: {error}')
             raise CouldNotCreateDbDir()
 
 
-class CalendarCollection(object):
+class CalendarCollection:
     """CalendarCollection allows access to various calendars stored in vdirs
 
     all calendars are cached in an sqlitedb for performance reasons"""
@@ -87,7 +87,7 @@ class CalendarCollection(object):
                 self._storages[name] = Vdir(calendar['path'], file_ext)
             except CollectionNotFoundError:
                 os.makedirs(calendar['path'])
-                logger.info('created non-existing vdir {}'.format(calendar['path']))
+                logger.info(f"created non-existing vdir {calendar['path']}")
                 self._storages[name] = Vdir(calendar['path'], file_ext)
 
         self.hmethod = hmethod
@@ -122,7 +122,7 @@ class CalendarCollection(object):
         if default is None:
             self._default_calendar_name = default
         elif default not in self.names:
-            raise ValueError('Unknown calendar: {0}'.format(default))
+            raise ValueError(f'Unknown calendar: {default}')
 
         readonly = self._calendars[default].get('readonly', False)
 
@@ -130,7 +130,7 @@ class CalendarCollection(object):
             self._default_calendar_name = default
         else:
             raise ValueError(
-                'Calendar "{0}" is read-only and cannot be used as default'.format(default))
+                f'Calendar "{default}" is read-only and cannot be used as default')
 
     def _local_ctag(self, calendar: str) -> str:
         return get_etag_from_file(self._calendars[calendar]['path'])
@@ -303,7 +303,7 @@ class CalendarCollection(object):
     def _db_update(self, calendar: str):
         """implements the actual db update on a per calendar base"""
         local_ctag = self._local_ctag(calendar)
-        db_hrefs = set(href for href, etag in self._backend.list(calendar))
+        db_hrefs = {href for href, etag in self._backend.list(calendar)}
         storage_hrefs = set()
         bdays = self._calendars[calendar].get('ctype') == 'birthdays'
 
@@ -312,7 +312,7 @@ class CalendarCollection(object):
                 storage_hrefs.add(href)
                 db_etag = self._backend.get_etag(href, calendar=calendar)
                 if etag != db_etag:
-                    logger.debug('Updating {0} because {1} != {2}'.format(href, etag, db_etag))
+                    logger.debug(f'Updating {href} because {etag} != {db_etag}')
                     self._update_vevent(href, calendar=calendar)
             for href in db_hrefs - storage_hrefs:
                 if bdays:
@@ -341,8 +341,8 @@ class CalendarCollection(object):
             if not isinstance(e, (UpdateFailed, UnsupportedFeatureError, NonUniqueUID)):
                 logger.exception('Unknown exception happened.')
             logger.warning(
-                'Skipping {0}/{1}: {2}\n'
-                'This event will not be available in khal.'.format(calendar, href, str(e)))
+                f'Skipping {calendar}/{href}: {str(e)}\n'
+                'This event will not be available in khal.')
             return False
 
     def search(self, search_string: str) -> Iterable[Event]:
diff --git a/khal/khalendar/vdir.py b/khal/khalendar/vdir.py
index 0365a36..1a6ce26 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 '{:.9f}'.format(mtime)
+    return f'{mtime:.9f}'
 
 
 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('Invalid argument: {}'.format(key))
+                raise TypeError(f'Invalid argument: {key}')
             setattr(self, key, value)
 
-        super(VdirError, self).__init__(*args)
+        super().__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 IOError('{} is not a directory.'.format(repr(path)))
+            raise OSError(f'{repr(path)} is not a directory.')
 
         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 IOError as e:
+        except OSError 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 IOError as e:
+        except OSError as e:
             if e.errno == errno.ENOENT:
                 return None
             else:
@@ -304,8 +304,7 @@ class Color:
         if len(r) == len(g) == len(b) == 2:
             return int(r, 16), int(g, 16), int(b, 16)
         else:
-            raise ValueError('Unable to parse color value: {}'
-                             .format(self.value))
+            raise ValueError(f'Unable to parse color value: {self.value}')
 
 
 class ColorMixin:
diff --git a/khal/parse_datetime.py b/khal/parse_datetime.py
index 0bb3a12..19edf66 100644
--- a/khal/parse_datetime.py
+++ b/khal/parse_datetime.py
@@ -237,11 +237,10 @@ def guessdatetimefstr(dtime_list, locale, default_day=None, in_future=True):
         else:
             return dtstart, all_day
     raise DateTimeParseError(
-        "Could not parse \"{}\".\nPlease check your configuration or run "
-        "`khal printformats` to see if this does match your configured "
+        f"Could not parse \"{dtime_list}\".\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)
     )
 
 
@@ -295,7 +294,7 @@ def guesstimedeltafstr(delta_string):
             numint = int(num)
         except ValueError:
             raise DateTimeParseError(
-                'Invalid number in timedelta string "%s": "%s"' % (delta_string, num))
+                f'Invalid number in timedelta string "{delta_string}": "{num}"')
 
         ulower = unit.lower().strip()
         if ulower == 'd' or ulower == 'day' or ulower == 'days':
@@ -407,11 +406,10 @@ def guessrangefstr(daterange, locale,
             pass
 
     raise DateTimeParseError(
-        "Could not parse \"{}\".\nPlease check your configuration or run "
-        "`khal printformats` to see if this does match your configured "
+        f"Could not parse \"{daterange}\".\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)
     )
 
 
@@ -475,11 +473,11 @@ def eventinfofstr(info_string, locale, default_event_duration, default_dayevent_
 
     if start is None or end is None:
         raise DateTimeParseError(
-            "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)
+            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/ "
         )
 
     if tz is None:
diff --git a/khal/settings/settings.py b/khal/settings/settings.py
index 55aeb39..4ef959a 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(
-                '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]))
+                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`.'
+            )
             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('using the config file at {}'.format(config_path))
+    logger.debug(f'using the config file at {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: '
-                     '{}'.format(error))
+                     f'{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(
-                'config error:\n'
-                'in [{}] {}: {}'.format(section[0], subsection, config_error))
+                f'config error:\n'
+                f'in [{section[0]}] {subsection}: {config_error}')
         else:
             for key in config_error:
                 if isinstance(config_error[key], Exception):
-                    logger.fatal('config error:\nin {} {}: {}'.format(
-                        sectionize(section + [subsection]),
-                        key,
-                        str(config_error[key]))
+                    logger.fatal(
+                        'config error:\n'
+                        f'in {sectionize(section + [subsection])} {key}: '
+                        f'{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('unknown section "{}" in config file'.format(value))
+            logger.warning(f'unknown section "{value}" in config file')
         else:
             section = sectionize(section)
             logger.warning(
-                'unknown key or subsection "{}" in section "{}"'.format(value, section))
+                f'unknown key or subsection "{value}" in section "{section}"')
 
             deprecated = [{'value': 'default_command', 'section': 'default'}]
             for d in deprecated:
                 if (value == d['value']) and (section == d['section']):
-                    logger.warning('Key "{}" in section "{}" was deprecated. '
-                                   'See the FAQ to find out when and why!'.format(value, section))
+                    logger.warning(f'Key "{value}" in section "{section}" was '
+                                   'deprecated. See the FAQ to find out when and why!')
     return user_config
 
 
diff --git a/khal/settings/utils.py b/khal/settings/utils.py
index 01d9288..567222b 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("Unknown timezone {}".format(tzstring))
+        raise VdtValueError(f"Unknown timezone {tzstring}")
 
 
 def is_timedelta(string):
     try:
         return guesstimedeltafstr(string)
     except ValueError:
-        raise VdtValueError("Invalid timedelta: {}".format(string))
+        raise VdtValueError(f"Invalid timedelta: {string}")
 
 
 def weeknumber_option(option):
@@ -75,8 +75,8 @@ def weeknumber_option(option):
         return False
     else:
         raise VdtValueError(
-            "Invalid value '{}' for option 'weeknumber', must be one of "
-            "'off', 'left' or 'right'".format(option))
+            f"Invalid value '{option}' for option 'weeknumber', must be one of "
+            "'off', 'left' or 'right'")
 
 
 def monthdisplay_option(option):
@@ -94,8 +94,9 @@ def monthdisplay_option(option):
         return 'firstfullweek'
     else:
         raise VdtValueError(
-            "Invalid value '{}' for option 'monthdisplay', must be one of "
-            "'firstday' or 'firstfullweek'".format(option))
+            f"Invalid value '{option}' for option 'monthdisplay', must be one "
+            "of 'firstday' or 'firstfullweek'"
+        )
 
 
 def expand_path(path):
@@ -138,9 +139,9 @@ def test_default_calendar(config):
         pass
     elif config['default']['default_calendar'] not in config['calendars']:
         logger.fatal(
-            "in section [default] {} is not valid for 'default_calendar', "
-            "must be one of {}".format(config['default']['default_calendar'],
-                                       config['calendars'].keys())
+            f"in section [default] {config['default']['default_calendar']} is "
+            "not valid for 'default_calendar', must be one of "
+            f"{config['calendars'].keys()}"
         )
         raise InvalidSettingsError()
     elif config['calendars'][config['default']['default_calendar']]['readonly']:
@@ -154,13 +155,13 @@ def get_color_from_vdir(path):
     except CollectionNotFoundError:
         color = None
     if color is None or color == '':
-        logger.debug('Found no or empty file `color` in {}'.format(path))
+        logger.debug(f'Found no or empty file `color` in {path}')
         return None
     color = color.strip()
     try:
         is_color(color)
     except VdtValueError:
-        logger.warning("Found invalid color `{}` in {}color".format(color, path))
+        logger.warning(f"Found invalid color `{color}` in {path}color")
         color = None
     return color
 
@@ -170,10 +171,10 @@ def get_unique_name(path, names):
     try:
         name = Vdir(path, '.ics').get_meta('displayname')
     except CollectionNotFoundError:
-        logger.fatal('The calendar at `{}` is not a directory.'.format(path))
+        logger.fatal(f'The calendar at `{path}` is not a directory.')
         raise
     if name is None or name == '':
-        logger.debug('Found no or empty file `displayname` in {}'.format(path))
+        logger.debug(f'Found no or empty file `displayname` in {path}')
         name = os.path.split(path)[-1]
     if name in names:
         while name in names:
@@ -218,7 +219,7 @@ def config_checks(
             raise InvalidSettingsError
         if config['calendars'][calendar]['type'] == 'discover':
             logger.debug(
-                'discovering calendars in {}'.format(config['calendars'][calendar]['path'])
+                f"discovering calendars in {config['calendars'][calendar]['path']}"
             )
             vdirs = get_all_vdirs(config['calendars'][calendar]['path'])
             vdirs_complete += vdirs
@@ -237,7 +238,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("using collection's color for {}".format(vdir))
+            logger.debug(f"using collection's color for {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 af223f6..1d47bd2 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 += '38;2;{!s};{!s};{!s}'.format(r, g, b)
+                    color += f'38;2;{r!s};{g!s};{b!s}'
                 else:
-                    color += '48;2;{!s};{!s};{!s}'.format(r, g, b)
+                    color += f'48;2;{r!s};{g!s};{b!s}'
             color += 'm'
             result += color
     return result
diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py
index b08a17d..f083533 100644
--- a/khal/ui/__init__.py
+++ b/khal/ui/__init__.py
@@ -131,11 +131,11 @@ class DateHeader(SelectableText):
         weekday = day.strftime('%A')
         daystr = day.strftime(dtformat)
         if day == dt.date.today():
-            return 'Today ({}, {})'.format(weekday, daystr)
+            return f'Today ({weekday}, {daystr})'
         elif day == dt.date.today() + dt.timedelta(days=1):
-            return 'Tomorrow ({}, {})'.format(weekday, daystr)
+            return f'Tomorrow ({weekday}, {daystr})'
         elif day == dt.date.today() - dt.timedelta(days=1):
-            return 'Yesterday ({}, {})'.format(weekday, daystr)
+            return f'Yesterday ({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('`this_date` is of type `{}`, sould be '
-                                 '`datetime.date`'.format(type(this_date)))
+                raise ValueError(f'`this_date` is of type `{type(this_date)}`, '
+                                 'should be `datetime.date`')
         self.event = event
         self.delete_status = delete_status
         self.this_date = this_date
@@ -566,7 +566,7 @@ class DateListBox(NListBox):
         super().__init__(content)
 
     def __repr__(self):
-        return '<DateListBox {}>'.format(self.date)
+        return f'<DateListBox {self.date}>'
 
     __str__ = __repr__
 
@@ -696,7 +696,7 @@ class EventColumn(urwid.WidgetWrap):
         """
         if event.readonly:
             self.pane.window.alert(
-                ('alert', 'Calendar `{}` is read-only.'.format(event.calendar)))
+                ('alert', f'Calendar `{event.calendar}` is read-only.'))
             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', 'Calendar {} is read-only.'.format(event.event.calendar)),
+                ('alert', f'Calendar {event.event.calendar} is read-only.'),
             )
             return
         status = self.delete_status(event.recuid)
@@ -969,15 +969,15 @@ class EventDisplay(urwid.WidgetWrap):
             endstr = event.end_local.strftime(self._conf['locale']['dateformat'])
         else:
             startstr = event.start_local.strftime(
-                '{} {}'.format(self._conf['locale']['dateformat'],
-                               self._conf['locale']['timeformat'])
+                f"{self._conf['locale']['dateformat']} "
+                f"{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(
-                    '{} {}'.format(self._conf['locale']['dateformat'],
-                                   self._conf['locale']['timeformat'])
+                    f"{self._conf['locale']['dateformat']} "
+                    f"{self._conf['locale']['timeformat']}"
                 )
 
         if startstr == endstr:
@@ -1153,14 +1153,14 @@ class ClassicView(Pane):
         )
         pane = Pane(
             columns,
-            title="Search results for \"{}\" (Esc for backtrack)".format(search_term),
+            title=f"Search results for \"{search_term}\" (Esc for backtrack)",
         )
         pane._conf = self._conf
         columns.set_focus_column(1)
         self.window.open(pane)
 
     def render(self, size, focus=False):
-        rval = super(ClassicView, self).render(size, focus)
+        rval = super().render(size, focus)
         if self.init:
             # starting with today's events
             self.eventscolumn.current_date = dt.date.today()
@@ -1269,7 +1269,7 @@ def start_pane(pane, callback, program_info='', quit_keys=['q']):
     """Open the user interface with the given initial pane."""
 
     frame = Window(
-        footer=program_info + ' | {}: quit, ?: help'.format(quit_keys[0]),
+        footer=program_info + f' | {quit_keys[0]}: quit, ?: help',
         quit_keys=quit_keys,
     )
 
@@ -1293,7 +1293,7 @@ def start_pane(pane, callback, program_info='', quit_keys=['q']):
         def format(self, record):
             return (
                 super().format(record)[:30] + '... '
-                '[Press `{}` to view log]'.format(pane._conf['keybindings']['log'][0])
+                f"[Press `{pane._conf['keybindings']['log'][0]}` to view log]"
             )
 
     class LogPaneHandler(logging.Handler):
diff --git a/khal/ui/base.py b/khal/ui/base.py
index 877439d..fd8ad2c 100644
--- a/khal/ui/base.py
+++ b/khal/ui/base.py
@@ -118,7 +118,7 @@ class Pane(urwid.WidgetWrap):
         lines.append('  Command              Keys')
         lines.append('  =======              ====')
         for command, keys in self._conf['keybindings'].items():
-            lines.append('  {:20} {}'.format(command, keys))
+            lines.append(f'  {command:20} {keys}')
         self.scrollable_dialog(
             '\n'.join(lines),
             title="Press `ESC` to close this window, arrows to scroll",
diff --git a/khal/ui/calendarwidget.py b/khal/ui/calendarwidget.py
index c188ee0..001256f 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(DatePart, self).__init__(digit)
+        super().__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(Date, self).__init__(urwid.Columns(self.halves))
+        super().__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(DateCColumns, self).__init__(widget_list, **kwargs)
+        super().__init__(widget_list, **kwargs)
 
     def __repr__(self):
-        return '<DateCColumns from {} to {}>'.format(self[1].date, self[7].date)
+        return f'<DateCColumns from {self[1].date} to {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(DateCColumns, self)._set_focus_position(position)
+        super()._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(DateCColumns, self).keypress(size, key)
+        key = super().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(CListBox, self).__init__(walker)
+        super().__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(CListBox, self).render(size, focus)
+        return super().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(CListBox, self).keypress(size, key)
+        key = super().keypress(size, key)
         if self._marked:
             self._mark()
         return key
diff --git a/khal/ui/editor.py b/khal/ui/editor.py
index c278eb6..2120253 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(object):
+class StartEnd:
 
     def __init__(self, startdate, starttime, enddate, endtime):
         """collecting some common properties"""
@@ -410,7 +410,7 @@ class EventEditor(urwid.WidgetWrap):
 
     @property
     def title(self):  # Window title
-        return 'Edit: {}'.format(get_wrapped_text(self.summary))
+        return f'Edit: {get_wrapped_text(self.summary)}'
 
     @classmethod
     def selectable(cls):
@@ -607,12 +607,9 @@ 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 = '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._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.monthly_choice = Choice(
             [self._xth_monthday, self._xth_weekday], self._xth_monthday, callback=self.rebuild,
         )
@@ -730,7 +727,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'] = ['{}{}'.format(occurrence, WEEKDAYS[weekday])]
+            rrule['byday'] = [f'{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 de3f337..d11f9fc 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(ExtendedEdit, self).keypress(size, key)
+            return super().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(DateTimeWidget, self).keypress(size, key)
+        return super().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(object):
+class SupportsNext:
     """classes inheriting from SupportsNext must implement the following methods:
     _select_first_selectable
     _select_last_selectable
@@ -271,7 +271,7 @@ class SupportsNext(object):
         self.outermost = kwargs.get('outermost', False)
         if 'outermost' in kwargs:
             kwargs.pop('outermost')
-        super(SupportsNext, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
 
 class NextMixin(SupportsNext):
@@ -305,7 +305,7 @@ class NextMixin(SupportsNext):
         return False
 
     def keypress(self, size, key):
-        key = super(NextMixin, self).keypress(size, key)
+        key = super().keypress(size, key)
 
         if key == 'tab':
             if self.outermost and self.focus_position == self._last_selectable():


Test patch:
diff --git a/tests/backend_test.py b/tests/backend_test.py
index 5ef0ff2..34d783e 100644
--- a/tests/backend_test.py
+++ b/tests/backend_test.py
@@ -1,4 +1,3 @@
-
 import datetime as dt
 from operator import itemgetter
 
diff --git a/tests/cli_test.py b/tests/cli_test.py
index 8938e3a..1dd81ac 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(CustomCliRunner, self).__init__(**kwargs)
+        super().__init__(**kwargs)
 
     def invoke(self, cli, args=None, *a, **kw):
         args = ['-c', str(self.config_file)] + (args or [])
-        return super(CustomCliRunner, self).invoke(cli, args, *a, **kw)
+        return super().invoke(cli, args, *a, **kw)
 
 
 @pytest.fixture
@@ -130,7 +130,7 @@ def test_simple(runner):
 
     now = dt.datetime.now().strftime('%d.%m.%Y')
     result = runner.invoke(
-        main_khal, 'new {} 18:00 myevent'.format(now).split())
+        main_khal, f'new {now} 18:00 myevent'.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, 'new {} 18:00 myevent'.format(now).split())
+    result = runner.invoke(main_khal, f'new {now} 18:00 myevent'.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, 'new {} 18:00 nextweek'.format(when).split())
+    result = runner.invoke(main_khal, f'new {when} 18:00 nextweek'.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, 'new {} 18:00 nextmonth'.format(when).split())
+    result = runner.invoke(main_khal, f'new {when} 18:00 nextmonth'.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]) == set(['one', 'two', 'three'])
+    assert set(result.output.split('\n')[:3]) == {'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]) == set(['two', 'three'])
+    assert set(result.output.split('\n')[:2]) == {'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, 'new {} 18:00 myevent -r weekly -u {}'.format(
-            now, end_date.strftime('%d.%m.%Y')).split())
+        main_khal, (f"new {now} 18:00 myevent -r weekly -u "
+                    f"{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,
-        'new {} {} 18:00 myevent'.format(now, end_date.strftime('%d.%m.%Y')).split())
+        f"new {now} {end_date.strftime('%d.%m.%Y')} 18:00 myevent".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,
-        'new {} {} 18:00 myevent'.format(now, end_date.strftime('%d.%m.%Y')).split())
+        f"new {now} {end_date.strftime('%d.%m.%Y')} 18:00 myevent".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,
-        'new {} 18:00 myevent'.format(now).split())
+        f'new {now} 18:00 myevent'.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, 'new {} 18:00 myevent'.format(now).split())
+    result = runner.invoke(main_khal, f'new {now} 18:00 myevent'.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, 'import -a one {}'.format(runner.config_file).split())
+    result = runner.invoke(main_khal, f'import -a one {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, 'import {}'.format(runner.config_file).split())
+    result = runner.invoke(main_khal, f'import {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 'Successfully wrote configuration to {}'.format(runner.config_file) in result.output
+    assert f'Successfully wrote configuration to {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 'Successfully wrote configuration to {}'.format(str(runner.config_file)) in result.output
+    assert f'Successfully wrote configuration to {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 == '''[calendars]
+    assert actual_config == f'''[calendars]
 
 [[private]]
-path = {}/khal/calendars/private
+path = {str(runner.xdg_data_home)}/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 'Successfully wrote configuration to {}'.format(str(runner.config_file)) in result.output
+    assert f'Successfully wrote configuration to {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 '{}/khal/calendars/private1' .format(runner.xdg_data_home) in actual_config
+    assert f'{runner.xdg_data_home}/khal/calendars/private1' in actual_config
 
 
 def cleanup(paths):
@@ -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('{}.ics'.format(name))
+        event = runner.calendars['one'].join(f'{name}.ics')
         event.write(cal_dt)
 
     format = '{start-end-time-style}: {title}'
diff --git a/tests/icalendar_test.py b/tests/icalendar_test.py
index 5538802..5a4aaba 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/khalendar_test.py b/tests/khalendar_test.py
index 6b93306..34d7def 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(object):
+class TestCalendar:
 
     def test_create(self, coll_vdirs):
         assert True
@@ -74,8 +74,8 @@ class TestCalendar(object):
 
         print('init')
         for calendar in coll._calendars:
-            print('{}: saved ctag: {}, vdir ctag: {}'.format(
-                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
+            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
+                  f'vdir ctag: {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(object):
         vdirs[cal1].upload(item_today)
         print('upload')
         for calendar in coll._calendars:
-            print('{}: saved ctag: {}, vdir ctag: {}'.format(
-                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
+            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
+                  f'vdir ctag: {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('{}: saved ctag: {}, vdir ctag: {}'.format(
-                calendar, coll._local_ctag(calendar), coll._backend.get_ctag(calendar)))
+            print(f'{calendar}: saved ctag: {coll._local_ctag(calendar)}, '
+                  f'vdir ctag: {coll._backend.get_ctag(calendar)}')
         assert coll._needs_update(cal1) is False
 
 
-class TestVdirsyncerCompat(object):
+class TestVdirsyncerCompat:
     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(object):
         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 set(str(coll.get_event(href, calendar=cal1).uid) for href in hrefs) == set((
+        assert {str(coll.get_event(href, calendar=cal1).uid) for href in hrefs} == {
             'uid3@host1.com',
             'V042MJ8B3SJNFXQOJL6P53OFMHJE8Z3VZWOU',
-        ))
+        }
 
 
-class TestCollection(object):
+class TestCollection:
 
     astart = dt.datetime.combine(aday, dt.time.min)
     aend = dt.datetime.combine(aday, dt.time.max)
@@ -354,7 +354,7 @@ class TestCollection(object):
         )
 
 
-class TestDbCreation(object):
+class TestDbCreation:
 
     def test_create_db(self, tmpdir):
         vdirpath = str(tmpdir) + '/' + cal1
diff --git a/tests/khalendar_utils_test.py b/tests/khalendar_utils_test.py
index be14c4a..cbe35d7 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(object):
+class TestExpand:
     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(object):
         ]
 
 
-class TestExpandNoRR(object):
+class TestExpandNoRR:
     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(object):
+class TestSpecial:
     """collection of strange test cases that don't fit anywhere else really"""
 
     def test_count(self):
@@ -718,7 +718,7 @@ UID:datetime123
 END:VEVENT"""
 
 
-class TestRDate(object):
+class TestRDate:
     """Testing expanding of recurrence rules"""
     def test_simple_rdate(self):
         vevent = _get_vevent(simple_rdate)
@@ -780,7 +780,7 @@ END:VCALENDAR
 """
 
 
-class TestSanitize(object):
+class TestSanitize:
 
     def test_noend_date(self):
         vevent = _get_vevent(noend_date)
diff --git a/tests/settings_test.py b/tests/settings_test.py
index 60f7185..b483179 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(object):
+class TestSettings:
     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 ad077e8..bb92fde 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('{}{}'.format(fmt[1], click.style(text)))
+                self.output.write(f'{fmt[1]}{click.style(text)}')
             else:
                 self.output.write(click.style(text, fg=fmt[1]))