diff --git a/calendar_utils/migrations/0002_auto_20200523_0243.py b/calendar_utils/migrations/0002_auto_20200523_0243.py
new file mode 100644
index 0000000000000000000000000000000000000000..adaa9a64bd2229daa8fcec337f7dd6efafc2cba0
--- /dev/null
+++ b/calendar_utils/migrations/0002_auto_20200523_0243.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.0.6 on 2020-05-23 00:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("calendar_utils", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="calendar", name="url", field=models.URLField(),
+        ),
+    ]
diff --git a/calendar_utils/models.py b/calendar_utils/models.py
index 6caf230f352a2887a93dcea798ee7e170091770b..d1980877a420e01daf5b3cfdb97190b482940294 100644
--- a/calendar_utils/models.py
+++ b/calendar_utils/models.py
@@ -4,7 +4,7 @@ from django.contrib.postgres.fields import JSONField
 from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 
-from .parser import parse_ical, split_events
+from .parser import process_ical
 
 
 def _convert_arrow_to_datetime(event):
@@ -28,7 +28,9 @@ class EventsJSONField(JSONField):
 
 
 class Calendar(models.Model):
-    url = models.URLField(unique=True)
+    ACTUAL_NUM = 6
+
+    url = models.URLField()
     source = models.TextField(null=True)
     last_update = models.DateTimeField(null=True)
     past_events = EventsJSONField(encoder=DjangoJSONEncoder, null=True)
@@ -38,12 +40,19 @@ class Calendar(models.Model):
         source = requests.get(self.url).text
         if self.source != source:
             self.source = source
-            past, future = split_events(parse_ical(source))
+            past, future = process_ical(source)
             self.past_events = list(map(_convert_arrow_to_datetime, past))
             self.future_events = list(map(_convert_arrow_to_datetime, future))
         self.last_update = arrow.utcnow().datetime
         self.save()
 
+    def actual_events(self):
+        events = self.future_events[-self.ACTUAL_NUM :]
+        num = len(events)
+        if num < 6:
+            events += self.past_events[: self.ACTUAL_NUM - num]
+        return events
+
 
 class CalendarMixin(models.Model):
     """
diff --git a/calendar_utils/parser.py b/calendar_utils/parser.py
index fe2fff3e793af1e53ce45a6621e3455bf71af295..4693a4ef2f8536bc5ca5a7118a8a6548c70fabaf 100644
--- a/calendar_utils/parser.py
+++ b/calendar_utils/parser.py
@@ -7,7 +7,7 @@ EVENT_KEYS = ("begin", "end", "all_day", "name", "description", "location")
 
 
 def parse_ical(source):
-    """Parses iCalendar source and returns events as list of dicts"""
+    """Parses iCalendar source and returns events as list of dicts."""
     cal = Calendar(source)
     events = []
     for event in sorted(cal.events, key=attrgetter("begin"), reverse=True):
@@ -16,8 +16,35 @@ def parse_ical(source):
 
 
 def split_events(events):
-    """Splits events and returns list of past events and future events"""
+    """Splits events and returns list of past events and future events."""
     now = arrow.utcnow()
     past = [ev for ev in events if ev["begin"] < now]
     future = [ev for ev in events if ev["begin"] > now]
     return past, future
+
+
+def set_event_duration(event):
+    """Sets duration for event."""
+    if event["all_day"]:
+        event["duration"] = "celý den"
+        return event
+
+    delta = event["end"] - event["begin"]
+    if delta.days < 1:
+        begin = event["begin"].format("H:mm")
+        end = event["end"].format("H:mm")
+        event["duration"] = f"{begin} - {end}"
+    else:
+        begin = event["begin"].format("H:mm")
+        end = event["end"].format("H:mm (D.M.)")
+        event["duration"] = f"{begin} - {end}"
+    return event
+
+
+def process_ical(source):
+    """Parses iCalendar source and returns events as list of dicts. Returns
+    tuple of past and future events.
+    """
+    events = parse_ical(source)
+    events = list(map(set_event_duration, events))
+    return split_events(events)
diff --git a/tests/calendar_utils/snapshots/snap_test_models.py b/tests/calendar_utils/snapshots/snap_test_models.py
index 2114165718b84c1fd6b2e99447221e1af00e12d6..a396ca4f2c0e6f75b798a0dc06bbd6ec298e9f5b 100644
--- a/tests/calendar_utils/snapshots/snap_test_models.py
+++ b/tests/calendar_utils/snapshots/snap_test_models.py
@@ -12,6 +12,7 @@ snapshots['test_calendar__update_source 1'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 1, 23, 17, 0, tzinfo=tzutc())'),
         'description': '',
+        'duration': '17:00 - 20:00',
         'end': GenericRepr('FakeDatetime(2020, 1, 23, 20, 0, tzinfo=tzutc())'),
         'location': 'The 27 Music Bar, Školní 24, 796 01 Prostějov, Česko',
         'name': 'Veřejná schůze Prostějovských Pirátů'
@@ -20,6 +21,7 @@ snapshots['test_calendar__update_source 1'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 1, 15, 18, 0, tzinfo=tzoffset(None, 3600))'),
         'description': '',
+        'duration': '18:00 - 21:00',
         'end': GenericRepr('FakeDatetime(2020, 1, 15, 21, 0, tzinfo=tzoffset(None, 3600))'),
         'location': '',
         'name': 'Veřejná schůze Pirátů MS Olomouc'
@@ -28,6 +30,7 @@ snapshots['test_calendar__update_source 1'] = [
         'all_day': True,
         'begin': GenericRepr('FakeDatetime(2020, 1, 11, 0, 0, tzinfo=tzutc())'),
         'description': '',
+        'duration': 'celý den',
         'end': GenericRepr('FakeDatetime(2020, 1, 13, 0, 0, tzinfo=tzutc())'),
         'location': '',
         'name': 'CF Ostrava'
@@ -36,6 +39,7 @@ snapshots['test_calendar__update_source 1'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 1, 7, 18, 0, tzinfo=tzoffset(None, 3600))'),
         'description': '',
+        'duration': '18:00 - 21:00',
         'end': GenericRepr('FakeDatetime(2020, 1, 7, 21, 0, tzinfo=tzoffset(None, 3600))'),
         'location': 'PiCOlo, 8. května 522/5, 779 00 Olomouc, Česko',
         'name': 'Veřejná schůze Pirátů Olomouckého kraje'
@@ -44,6 +48,7 @@ snapshots['test_calendar__update_source 1'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2019, 4, 10, 18, 0, tzinfo=tzoffset(None, 7200))'),
         'description': '',
+        'duration': '18:00 - 21:00',
         'end': GenericRepr('FakeDatetime(2019, 4, 10, 21, 0, tzinfo=tzoffset(None, 7200))'),
         'location': 'Jeseník',
         'name': 'Veřejná schůze Pirátů XOLK v Jeseníku'
@@ -55,6 +60,7 @@ snapshots['test_calendar__update_source 2'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 4, 8, 16, 0, tzinfo=tzutc())'),
         'description': '',
+        'duration': '16:00 - 17:30',
         'end': GenericRepr('FakeDatetime(2020, 4, 8, 17, 30, tzinfo=tzutc())'),
         'location': 'Picolo - Pirátské centrum Olomouc, 8. května 522/5, 779 00 Olomouc, Česko',
         'name': 'Setkání s místopředsedou Evropského parlamentu Marcelem Kolalokou'
@@ -63,6 +69,7 @@ snapshots['test_calendar__update_source 2'] = [
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 3, 24, 17, 0, tzinfo=tzutc())'),
         'description': 'Přednáší MUDr. Marti Moron',
+        'duration': '17:00 - 18:30',
         'end': GenericRepr('FakeDatetime(2020, 3, 24, 18, 30, tzinfo=tzutc())'),
         'location': '',
         'name': 'Je kouření stejně škodlivé jako vapování?'
@@ -73,6 +80,7 @@ snapshots['test_calendar__update_source 2'] = [
         'description': '''Tato událost má videohovor.
 Připojit se: https://meet.google.com/ahv-nrmw-kmp
 +1 484-696-1205 PIN: 546973991#''',
+        'duration': '14:00 - 17:00',
         'end': GenericRepr('FakeDatetime(2020, 3, 1, 17, 0, tzinfo=tzutc())'),
         'location': 'Koliba & Pivovar U Tří králů, Finská 4592/8, 796 01 Prostějov,Česko',
         'name': 'Veřejná schůze Prostějovských Pirátů 01.03.2020'
@@ -81,6 +89,7 @@ Připojit se: https://meet.google.com/ahv-nrmw-kmp
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 2, 21, 17, 30, tzinfo=tzutc())'),
         'description': 'Tradiční páteční deskohraní s Karlem Bezejmeným. K zapůjčení budou deskové hry všeho druhu – a to jak jednoduché, tak náročnější pro ty,kteří se nebojí u hry strávit delší dobu. Všechny pravidla Vám budou vysvětlena. Nemusíte se bát.',
+        'duration': '17:30 - 21:00',
         'end': GenericRepr('FakeDatetime(2020, 2, 21, 21, 0, tzinfo=tzutc())'),
         'location': 'Picolo - Pirátské centrum Olomouc, 8. května 522/5, 779 00 Olomouc, Česko',
         'name': 'Páteční deskovky v Picolu - Karel Bezejmenný'
@@ -89,6 +98,7 @@ Připojit se: https://meet.google.com/ahv-nrmw-kmp
         'all_day': False,
         'begin': GenericRepr('FakeDatetime(2020, 2, 17, 17, 30, tzinfo=tzutc())'),
         'description': '',
+        'duration': '17:30 - 20:30',
         'end': GenericRepr('FakeDatetime(2020, 2, 17, 20, 30, tzinfo=tzutc())'),
         'location': 'Nebe počká, 20, Kratochvílova 122, Město, 750 02 Přerov, Česko',
         'name': 'Schůzka Piráti Přerov'
diff --git a/tests/calendar_utils/snapshots/snap_test_parser.py b/tests/calendar_utils/snapshots/snap_test_parser.py
index e884ab98c4752c52a5d83bf398813cb8bfda51ac..0ba59b7d4e4f3a71454b863c7f2bea31e9f565f9 100644
--- a/tests/calendar_utils/snapshots/snap_test_parser.py
+++ b/tests/calendar_utils/snapshots/snap_test_parser.py
@@ -179,3 +179,101 @@ Připojit se: https://meet.google.com/ahv-nrmw-kmp
         'name': 'Schůzka Piráti Přerov'
     }
 ]
+
+snapshots['test_process_ical 1'] = [
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-01-23T17:00:00+00:00]>'),
+        'description': '',
+        'duration': '17:00 - 20:00',
+        'end': GenericRepr('<Arrow [2020-01-23T20:00:00+00:00]>'),
+        'location': 'The 27 Music Bar, Školní 24, 796 01 Prostějov, Česko',
+        'name': 'Veřejná schůze Prostějovských Pirátů'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-01-15T18:00:00+01:00]>'),
+        'description': '',
+        'duration': '18:00 - 21:00',
+        'end': GenericRepr('<Arrow [2020-01-15T21:00:00+01:00]>'),
+        'location': '',
+        'name': 'Veřejná schůze Pirátů MS Olomouc'
+    },
+    {
+        'all_day': True,
+        'begin': GenericRepr('<Arrow [2020-01-11T00:00:00+00:00]>'),
+        'description': '',
+        'duration': 'celý den',
+        'end': GenericRepr('<Arrow [2020-01-13T00:00:00+00:00]>'),
+        'location': '',
+        'name': 'CF Ostrava'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-01-07T18:00:00+01:00]>'),
+        'description': '',
+        'duration': '18:00 - 21:00',
+        'end': GenericRepr('<Arrow [2020-01-07T21:00:00+01:00]>'),
+        'location': 'PiCOlo, 8. května 522/5, 779 00 Olomouc, Česko',
+        'name': 'Veřejná schůze Pirátů Olomouckého kraje'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2019-04-10T18:00:00+02:00]>'),
+        'description': '',
+        'duration': '18:00 - 21:00',
+        'end': GenericRepr('<Arrow [2019-04-10T21:00:00+02:00]>'),
+        'location': 'Jeseník',
+        'name': 'Veřejná schůze Pirátů XOLK v Jeseníku'
+    }
+]
+
+snapshots['test_process_ical 2'] = [
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-04-08T16:00:00+00:00]>'),
+        'description': '',
+        'duration': '16:00 - 17:30',
+        'end': GenericRepr('<Arrow [2020-04-08T17:30:00+00:00]>'),
+        'location': 'Picolo - Pirátské centrum Olomouc, 8. května 522/5, 779 00 Olomouc, Česko',
+        'name': 'Setkání s místopředsedou Evropského parlamentu Marcelem Kolalokou'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-03-24T17:00:00+00:00]>'),
+        'description': 'Přednáší MUDr. Marti Moron',
+        'duration': '17:00 - 18:30',
+        'end': GenericRepr('<Arrow [2020-03-24T18:30:00+00:00]>'),
+        'location': '',
+        'name': 'Je kouření stejně škodlivé jako vapování?'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-03-01T14:00:00+00:00]>'),
+        'description': '''Tato událost má videohovor.
+Připojit se: https://meet.google.com/ahv-nrmw-kmp
++1 484-696-1205 PIN: 546973991#''',
+        'duration': '14:00 - 17:00',
+        'end': GenericRepr('<Arrow [2020-03-01T17:00:00+00:00]>'),
+        'location': 'Koliba & Pivovar U Tří králů, Finská 4592/8, 796 01 Prostějov,Česko',
+        'name': 'Veřejná schůze Prostějovských Pirátů 01.03.2020'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-02-21T17:30:00+00:00]>'),
+        'description': 'Tradiční páteční deskohraní s Karlem Bezejmeným. K zapůjčení budou deskové hry všeho druhu – a to jak jednoduché, tak náročnější pro ty,kteří se nebojí u hry strávit delší dobu. Všechny pravidla Vám budou vysvětlena. Nemusíte se bát.',
+        'duration': '17:30 - 21:00',
+        'end': GenericRepr('<Arrow [2020-02-21T21:00:00+00:00]>'),
+        'location': 'Picolo - Pirátské centrum Olomouc, 8. května 522/5, 779 00 Olomouc, Česko',
+        'name': 'Páteční deskovky v Picolu - Karel Bezejmenný'
+    },
+    {
+        'all_day': False,
+        'begin': GenericRepr('<Arrow [2020-02-17T17:30:00+00:00]>'),
+        'description': '',
+        'duration': '17:30 - 20:30',
+        'end': GenericRepr('<Arrow [2020-02-17T20:30:00+00:00]>'),
+        'location': 'Nebe počká, 20, Kratochvílova 122, Město, 750 02 Přerov, Česko',
+        'name': 'Schůzka Piráti Přerov'
+    }
+]
diff --git a/tests/calendar_utils/test_parser.py b/tests/calendar_utils/test_parser.py
index 3ad885209f75d4cfad79bd43a7cdc13b94c4112c..f5ed99ed6f50bcb80fa8428ad4b3ab65d0ecd663 100644
--- a/tests/calendar_utils/test_parser.py
+++ b/tests/calendar_utils/test_parser.py
@@ -1,6 +1,12 @@
+import arrow
 import pytest
 
-from calendar_utils.parser import parse_ical, split_events
+from calendar_utils.parser import (
+    parse_ical,
+    process_ical,
+    set_event_duration,
+    split_events,
+)
 
 
 def test_parse_ical(sample, snapshot):
@@ -13,3 +19,40 @@ def test_split_events(sample, snapshot):
     past_events, future_events = split_events(parse_ical(sample))
     snapshot.assert_match(past_events)
     snapshot.assert_match(future_events)
+
+
+def test_set_event_duration__all_day():
+    event = {
+        "begin": arrow.get("2020-02-03T00:00:00Z"),
+        "end": arrow.get("2020-02-04T00:00:00Z"),
+        "all_day": True,
+    }
+    out = set_event_duration(event)
+    assert out["duration"] == "celý den"
+
+
+def test_set_event_duration__hours():
+    event = {
+        "begin": arrow.get("2020-02-03T08:00:00Z"),
+        "end": arrow.get("2020-02-03T12:25:00Z"),
+        "all_day": False,
+    }
+    out = set_event_duration(event)
+    assert out["duration"] == "8:00 - 12:25"
+
+
+def test_set_event_duration__days():
+    event = {
+        "begin": arrow.get("2020-02-03T09:15:00Z"),
+        "end": arrow.get("2020-02-04T22:30:00Z"),
+        "all_day": False,
+    }
+    out = set_event_duration(event)
+    assert out["duration"] == "9:15 - 22:30 (4.2.)"
+
+
+@pytest.mark.freeze_time("2020-02-02")
+def test_process_ical(sample, snapshot):
+    past_events, future_events = process_ical(sample)
+    snapshot.assert_match(past_events)
+    snapshot.assert_match(future_events)