diff --git a/main/migrations/0055_remove_mainpersonpage_nextcloud_calendar_url_and_more.py b/main/migrations/0055_remove_mainpersonpage_nextcloud_calendar_url_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..94fc21034462628b80d7267a8654e31fbbb134f3
--- /dev/null
+++ b/main/migrations/0055_remove_mainpersonpage_nextcloud_calendar_url_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.1.8 on 2023-04-12 22:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0054_mainpersonpage_ical_calendar_url_and_more'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='mainpersonpage',
+            name='nextcloud_calendar_url',
+        ),
+        migrations.AlterField(
+            model_name='mainpersonpage',
+            name='ical_calendar_url',
+            field=models.URLField(blank=True, help_text='Podporuje Mrak, Google Kalendář a další. Návod na synchronizaci najdeš na pi2.cz/kalendare', max_length=256, null=True, verbose_name='iCal adresa kalendáře'),
+        ),
+    ]
diff --git a/main/models.py b/main/models.py
index 5fb242d62b1cfb90e0c227c9b429421e7d03011e..02d0f879c468051dd33d918ef4866718f203f086 100644
--- a/main/models.py
+++ b/main/models.py
@@ -1,14 +1,20 @@
 from functools import cached_property
 
+import requests
+
 from dateutil.relativedelta import relativedelta
+from datetime import date, timedelta
 from django.conf import settings
 from django.contrib import messages
+from django.core.cache import cache
 from django.core.paginator import Paginator
+from django.core.validators import RegexValidator
 from django.db import models
 from django.forms import ValidationError
 from django.http import HttpResponseRedirect, JsonResponse
 from django.shortcuts import render
 from django.utils import timezone
+from icalevnt import icalevents
 from modelcluster.contrib.taggit import ClusterTaggableManager
 from modelcluster.fields import ParentalKey
 from taggit.models import TaggedItemBase
@@ -795,17 +801,10 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
         max_length=256,
         blank=True,
         null=True,
-        help_text="Musí být ve formátu iCal. V Google kalendáři lze exportovat v nastavení (TODO).",
-    )
-    nextcloud_calendar_url = models.URLField(
-        "Adresa kalendáře v Mraku",
-        max_length=256,
-        blank=True,
-        null=True,
         help_text=(
-            'V nastavení kalendáře klikni na ikonu "+" vedle "odkaz na sdílení" '
-            "a vlož zkopírovaný odkaz."
-        )
+            "Podporuje Mrak, Google Kalendář a další. Návod na synchronizaci najdeš "
+            "na pi2.cz/kalendare"
+        ),
     )
 
     settings_panels = []
@@ -827,13 +826,7 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
         FieldPanel("text"),
         FieldPanel("email"),
         FieldPanel("phone"),
-        MultiFieldPanel(
-            [
-                FieldPanel("ical_calendar_url"),
-                FieldPanel("nextcloud_calendar_url"),
-            ],
-            "Kalendář",
-        ),
+        FieldPanel("ical_calendar_url"),
         FieldPanel("social_links"),
         FieldPanel("people"),
     ]
@@ -855,31 +848,34 @@ class MainPersonPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin,
                 order_by("-timestamp")
             )[:20]
 
+        if self.ical_calendar_url:
+            context["calendar_data"] = self.get_ical_data()
+
+            print(context["calendar_data"])
+
         return context
 
     ### OTHERS
 
-    def clean(self) -> None:
-        cleaned_data = super().clean()
+    def get_ical_data(self) -> list:
+        ical_response = cache.get(f"calendar_{self.ical_calendar_url}")
 
-        BOTH_CALENDARS_DEFINED_ERROR_MESSAGE = (
-            "Nemůžeš definovat kalendář z Mraku a v iCal formátu najednou."
-        )
+        if ical_response is None:
+            ical_response = requests.get(self.ical_calendar_url)
+            ical_response.raise_for_status()
+            ical_response = ical_response.text
 
-        if (
-            cleaned_data.get("ical_calendar_url")
-            and cleaned_data.get("nextcloud_calendar_url")
-        ):
-            self.add_error(
-                "ical_calendar_url",
-                BOTH_CALENDARS_DEFINED_ERROR_MESSAGE
-            )
-            self.add_error(
-                "nextcloud_calendar_url",
-                BOTH_CALENDARS_DEFINED_ERROR_MESSAGE
+            cache.set(
+                f"calendar_{self.ical_calendar_url}",
+                ical_response,
+                timeout=3600,  # 1 hour
             )
 
-        return cleaned_data
+        return icalevents.parse_events(
+            ical_response,
+            start=date.today() - timedelta(days=30),
+            end=date.today() + timedelta(days=60),
+        )
 
     class Meta:
         verbose_name = "Detail osoby"
diff --git a/shared/blocks.py b/shared/blocks.py
index 6b741f2baf48294ec7b8f23bca85d3d0296e0cda..1ee97829a9e0549d8b8515ea9d7b3dacacc48121 100644
--- a/shared/blocks.py
+++ b/shared/blocks.py
@@ -777,7 +777,11 @@ class ChartRedmineIssueDataset(blocks.StructBlock):
             issues_response.raise_for_status()
             issues_response = issues_response.json()
 
-            cache.set(f"redmine_{issues_url}", issues_response)
+            cache.set(
+                f"redmine_{issues_url}",
+                issues_response,
+                timeout=604800,  # 1 week
+            )
 
         only_grow = value.get("only_grow", False)
 
@@ -795,7 +799,11 @@ class ChartRedmineIssueDataset(blocks.StructBlock):
                 issues_response.raise_for_status()
                 issues_response = issues_response.json()
 
-                cache.set(f"redmine_{url_with_offset}", issues_response)
+                cache.set(
+                    f"redmine_{url_with_offset}",
+                    issues_response,
+                    timeout=604800,  # 1 week
+                )
 
             collected_issues += issues_response["issues"]