diff --git a/config.py.example b/config.py.example
index ea125480f1c12157f4828c571dc381fed5ac37ab..7dcd0a60639727197f6c2f5bf5b031efb39cb876 100644
--- a/config.py.example
+++ b/config.py.example
@@ -60,6 +60,11 @@ CALENDAR_URL = "https://user:password@calendar.example.com/dav/"
 CALENDAR_DEFAULT_DURATION = 3 # default meeting length in hours
 CALENDAR_MAX_REQUESTS = 10 # number of retries before giving up (some caldav servers like to randomly reply with errors)
 
+CALENDAR_TIMEZONE_MAP = {
+    "CET": "Europe/Berlin",
+    "CEST": "Europe/Berlin",
+}
+
 SESSION_PROTECTION = "strong" # do not change
 
 # authentication
diff --git a/models/database.py b/models/database.py
index b7b9fa5b990c54fa90292840f9ef3a7485cabfe5..f97fc04f99564cae697003bc4d85b79e83fc4c25 100644
--- a/models/database.py
+++ b/models/database.py
@@ -9,6 +9,7 @@ from uuid import uuid4
 from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY, current_user
 from utils import random_string, get_etherpad_url, split_terms, check_ip_in_networks
 from models.errors import DateNotMatchingException
+from dateutil import tz
 
 import os
 
@@ -331,6 +332,10 @@ class Protocol(DatabaseModel):
                     tops_before.append(top)
         return tops_before + self.tops + tops_after
 
+    def get_timezone_aware_start_date(self):
+        return datetime.combine(self.date, self.get_time()).replace(
+            tzinfo=tz.tzlocal())
+
     @staticmethod
     def create_new_protocol(protocoltype, date, start_time=None):
         if start_time is None:
diff --git a/server.py b/server.py
index 98bd9391c3d1719e6972ce01601464cb3759e789..e5c6bcd4cc63442a5796edff1b0c137116bc6369 100755
--- a/server.py
+++ b/server.py
@@ -14,9 +14,10 @@ from apscheduler.triggers.cron import CronTrigger
 from apscheduler.triggers.interval import IntervalTrigger
 import atexit
 import feedgen.feed
+import icalendar
 from io import StringIO, BytesIO
 import os
-from datetime import datetime, time
+from datetime import datetime, time, timedelta
 import math
 import mimetypes
 import subprocess
@@ -1352,9 +1353,7 @@ def create_protocols_feed(protocoltype):
         entry.title(protocol.get_title())
         entry.summary(",\n".join(top.name for top in protocol.get_tops()))
         entry.content(protocol.content_public)
-        aware_date = datetime.combine(protocol.date, protocoltype.usual_time).replace(
-            tzinfo=tz.tzlocal())
-        entry.published(aware_date)
+        entry.published(protocol.get_timezone_aware_start_date())
     return feed
 
 
@@ -1381,10 +1380,13 @@ def create_appointments_feed(protocoltype):
             _external=True), rel="alternate")
         entry.title(protocol.get_title())
         entry.summary("\n".join(
-            [",\n".join(
-                "{}: {}".format(meta.name, meta.value)
-                for meta in protocol.metas
-                if not meta.internal
+            [",\n".join([
+                    "Beginn: {}".format(protocol.get_time())
+                ] + [
+                    "{}: {}".format(meta.name, meta.value)
+                    for meta in protocol.metas
+                    if not meta.internal
+                ]
             ),
             "Tagesordnung:",
             ",\n".join(
@@ -1418,6 +1420,37 @@ def feed_appointments_atom(protocoltype):
     return Response(create_appointments_feed(protocoltype).atom_str(),
         mimetype="application/atom+xml")
 
+@app.route("/feed/appointments/ical/<int:protocoltype_id>")
+@db_lookup(ProtocolType)
+def feed_appointsments_ical(protocoltype):
+    if not protocoltype.has_public_anonymous_view_right():
+        abort(403)
+    protocols = [protocol
+        for protocol in protocoltype.protocols
+        if not protocol.is_done()
+    ]
+    calendar = icalendar.Calendar()
+    calendar["summary"] = protocoltype.short_name
+    calendar["prodid"] = "Protokollsystem 3"
+    calendar["version"] = "2.0"
+    for protocol in protocols:
+        event = icalendar.Event()
+        event["uid"] = protocol.id
+        to_datetime = icalendar.prop.vDatetime
+        start = protocol.get_timezone_aware_start_date()
+        event["dtstamp"] = to_datetime(start)
+        event["dtstart"] = to_datetime(start)
+        event["dtend"] = to_datetime(start + timedelta(hours=3))
+        event["summary"] = protocoltype.short_name
+        event["description"] = "\n".join(top.name
+            for top in protocol.get_tops())
+        calendar.add_component(event)
+    content = calendar.to_ical().decode("utf-8")
+    for key in config.CALENDAR_TIMEZONE_MAP:
+        content = content.replace("TZID={}:".format(key),
+            "TZID={}:".format(config.CALENDAR_TIMEZONE_MAP[key]))
+    return Response(content.encode("utf-8"), mimetype="text/calendar")
+
 
 @app.route("/like/new")
 @login_required
@@ -1445,6 +1478,7 @@ def new_like():
     flash("Like!", "alert-success")
     return redirect(request.args.get("next") or url_for("index"))
 
+
 @app.route("/login", methods=["GET", "POST"])
 def login():
     if "auth" in session and current_user() is not None: