diff --git a/src/core/models/conference.py b/src/core/models/conference.py
index b84c50cb0048857d7e9ad66f6cc09ba484b5936f..b3e84fd1b56609fc737e21fb965f2a6370fa8b8e 100644
--- a/src/core/models/conference.py
+++ b/src/core/models/conference.py
@@ -433,13 +433,20 @@ class Conference(models.Model):
             return days
         end = self.end if self.end is not None else timezone.now().replace(hour=23, minute=59, second=59)
         for i in range(self.days_count):
-            date = self.start.date() + timedelta(days=i)
+            date = self._day0 + timedelta(days=i + 1)
             days.append(ConferenceDay(i, date, self.timezone))
 
         days[0].start = self.start
         days[-1].end = end
         return days
 
+    def day(self, official_number: int) -> 'ConferenceDay':
+        """Returns the n-th day of the conference, starting with 1."""
+        for day in self.days:
+            if day.index == official_number:
+                return day
+        return None
+
     @cached_property
     def _day0(self) -> datetime:
         if self.start is None:
@@ -670,10 +677,13 @@ class ConferenceDay:
     start: datetime
     end: datetime
 
-    def __init__(self, i: int, date: date, time_zone: tz):
+    def __init__(self, i: int, date: date | datetime, time_zone: tz):
+        if isinstance(date, datetime):
+            date = date.date()
+
         self.index = i + 1
         self.start = datetime.combine(date, time(6, 0)).astimezone(time_zone)
-        self.end = datetime.combine(date + timedelta(days=1), time(6, 0)).astimezone(time_zone)
+        self.end = datetime.combine(self.start + timedelta(days=1), time(6, 0), tzinfo=None).astimezone(time_zone)
 
     def __eq__(self, value: object) -> bool:
         if not isinstance(value, ConferenceDay):
@@ -681,7 +691,7 @@ class ConferenceDay:
         return self.index == value.index and self.start == value.start and self.end == value.end
 
     def __repr__(self):
-        return f'{self.index} ({self.start.date()} - {self.end.date()})'
+        return f'{self.index} ({self.start} - {self.end})'
 
 
 class ConferenceExportCache(models.Model):
diff --git a/src/core/tests/conference.py b/src/core/tests/conference.py
index 3ed7df0ec677b36116043bf74eec19f1fc60b494..0e44aec38db2a95d59ed3e69e44bee737608cf74 100644
--- a/src/core/tests/conference.py
+++ b/src/core/tests/conference.py
@@ -1,12 +1,23 @@
-from datetime import UTC, datetime, timedelta
+from datetime import UTC, date, datetime, timedelta
 from unittest.mock import patch
 
+import pytz
+
 from django.test import TestCase
 from django.utils import timezone
 
 from core.models.conference import Conference, ConferenceDay
 
 
+class ConferenceDayTests(TestCase):
+    def test_construction(self):
+        tz = pytz.timezone('Europe/Berlin')
+        day = ConferenceDay(0, date(2042, 12, 27), tz)
+        self.assertEqual(1, day.index)
+        self.assertEqual(datetime(2042, 12, 27, 6, 0).astimezone(tz), day.start)
+        self.assertEqual(datetime(2042, 12, 28, 6, 0).astimezone(tz), day.end)
+
+
 class ConferenceTests(TestCase):
     def setUp(self):
         self.conference = Conference(slug='foo', name='Foo Conference', is_public=True)
@@ -25,6 +36,33 @@ class ConferenceTests(TestCase):
         self.assertEqual(self.conference.get_conference_day(datetime(2042, 12, 31, 5, 30, tzinfo=UTC)), 4)
         self.assertEqual(self.conference.get_conference_day(datetime(2042, 12, 31, 5, 30, tzinfo=UTC)), 4)
 
+    def test_days(self):
+        self.conference.start = datetime(2042, 12, 27, 9, 00, tzinfo=UTC)
+        self.conference.end = datetime(2042, 12, 30, 15, 00, tzinfo=UTC)
+        self.conference.timezone = 'Europe/Berlin'
+        tz = pytz.timezone('Europe/Berlin')
+
+        expected_days = [
+            ConferenceDay(0, date(2042, 12, 27), tz),
+            ConferenceDay(1, date(2042, 12, 28), tz),
+            ConferenceDay(2, date(2042, 12, 29), tz),
+            ConferenceDay(3, date(2042, 12, 30), tz),
+        ]
+        expected_days[0].start = self.conference.start.astimezone(tz)
+        expected_days[-1].end = self.conference.end.astimezone(tz)
+
+        self.assertEqual(len(expected_days), self.conference.days_count)
+        actual_days = self.conference.days
+        self.assertEqual(len(expected_days), len(actual_days))
+
+        with self.subTest('timezones'):
+            self.assertTrue(all(d.start.tzinfo is not None for d in actual_days))
+            self.assertTrue(all(d.end.tzinfo is not None for d in actual_days))
+
+        for x in range(4):
+            with self.subTest(f'day {x +1}'):
+                self.assertEqual(expected_days[x], actual_days[x])
+
     def test_days_in(self):
         with patch.object(timezone, 'now', return_value=datetime(2042, 12, 27, 12, 34, 0, 0, tzinfo=UTC)):
             self.conference.start = timezone.now() - timedelta(days=1)
@@ -79,9 +117,9 @@ class ConferenceTests(TestCase):
             self.assertFalse(self.conference.has_ended)
             self.assertEqual(self.conference.days_count, 3)
             days = [
-                ConferenceDay(0, self.conference.start.astimezone(self.conference.timezone), self.conference.timezone),
-                ConferenceDay(1, self.conference.start.astimezone(self.conference.timezone) + timedelta(days=1), self.conference.timezone),
-                ConferenceDay(2, self.conference.start.astimezone(self.conference.timezone) + timedelta(days=2), self.conference.timezone),
+                ConferenceDay(0, self.conference.start.date(), self.conference.timezone),
+                ConferenceDay(1, (self.conference.start + timedelta(days=1)).date(), self.conference.timezone),
+                ConferenceDay(2, (self.conference.start + timedelta(days=2)).date(), self.conference.timezone),
             ]
             # The first day starts whenever the conference starts
             days[0].start = self.conference.start
diff --git a/src/plainui/jinja2/plainui/fahrplan.html.j2 b/src/plainui/jinja2/plainui/fahrplan.html.j2
index 68955ffcb129ac117fd034e77c360748bd8f75e1..1e2b05bfc66fc9e4d9449a48933263cce1c7b34a 100644
--- a/src/plainui/jinja2/plainui/fahrplan.html.j2
+++ b/src/plainui/jinja2/plainui/fahrplan.html.j2
@@ -40,7 +40,7 @@
   <form method="get" action="#now" class="hub-card mb-2">
     <input type="hidden" name="mode" value="{{ mode }}">
     {% if show_assembly_filters %}<input type="hidden" name="show_assembly_filters" value="y">{% endif %}
-    {% if day %}<input type="hidden" name="day" value="{{ day }}">{% endif %}
+    {% if day %}<input type="hidden" name="day" value="{{ day.index }}">{% endif %}
     {% if kind %}<input type="hidden" name="kind" value="{{ kind }}">{% endif %}
     {% if assembly %}<input type="hidden" name="assembly" value="{{ assembly.slug }}">{% endif %}
     {% if track %}<input type="hidden" name="track" value="{{ track.slug }}">{% endif %}
@@ -78,8 +78,8 @@
     </div>
 
     <div class="hub-tags mb-2">
-      {% for n in range(days) %}
-        {{ filter_button('d' ~ (n if n != day else '') , n == day, _("Day %(n)s", n=n + 1)) }}
+      {% for d in days %}
+        {{ filter_button('d' ~ (d.index if d != day else '') , d == day, _("Day %(n)s", n=d.index)) }}
       {%- endfor %}
 
       <div class="hub-tag-divider"></div>
diff --git a/src/plainui/views/fahrplan.py b/src/plainui/views/fahrplan.py
index a1b79f94b30dbc440cf91bdf8e0af0df4ce6188d..39b247bbcc8e5b93fd031823e12b4077ed908667 100644
--- a/src/plainui/views/fahrplan.py
+++ b/src/plainui/views/fahrplan.py
@@ -3,7 +3,7 @@ __all__ = (
     'PublicFahrplanView',
 )
 
-from datetime import datetime, timedelta
+from datetime import datetime
 
 from django.http import Http404
 from django.utils import timezone
@@ -37,15 +37,10 @@ class FahrplanView(ConferenceRequiredMixin, TemplateView):
 
         context['my_favorite_events'] = session_get_favorite_events(self.request.session, self.request.user)
 
-        min_date = self.conf.start
-        max_date = self.conf.end
-        if min_date is None or max_date is None:
+        if self.conf.days_count == 0:
             raise Http404
-        n_days = (max_date - min_date).days
-        if (max_date - min_date) != timedelta(n_days):
-            n_days += 1
 
-        context['days'] = n_days
+        context['days'] = self.conf.days
         mode = self.request.GET.get('mode', 'calendar')
         day = self.request.GET.get('day', None)
         kind = self.request.GET.get('kind', 'official')
@@ -100,13 +95,11 @@ class FahrplanView(ConferenceRequiredMixin, TemplateView):
         if day:
             try:
                 day = int(day)
-                if day < 0 or day >= n_days:
-                    day = None
             except ValueError:
                 day = None
         else:
             day = None
-        context['day'] = day
+        context['day'] = self.conf.day(day) if day else None
 
         if kind == 'all':
             public_fahrplan = None
diff --git a/src/plainui/views/utils.py b/src/plainui/views/utils.py
index df4f9decff92c37c197cd5d605069a1025863280..6e19387aeb2e8591f8bad5731c3414c4ed267e8b 100644
--- a/src/plainui/views/utils.py
+++ b/src/plainui/views/utils.py
@@ -198,8 +198,12 @@ def event_filter(
 
     filters = {}
     if day is not None:
-        filters['schedule_start__gte'] = min_date + timedelta(day)
-        filters['schedule_start__lt'] = min_date + timedelta(day + 1)
+        if conf_day := conf.day(day):
+            filters['schedule_start__gte'] = conf_day.start
+            filters['schedule_start__lt'] = conf_day.end
+        else:
+            # an invalid day has been specified so there can't be any events by definition
+            return Event.objects.none()
     if kinds is not None:
         filters['kind__in'] = kinds
     if assembly: