diff --git a/src/core/management/commands/housekeeping.py b/src/core/management/commands/housekeeping.py
index 6eb9106ed954662506dd8087d5f76298e27b26ce..7143ecdeef87bd98ca147bd23fa9724a49f6dc9d 100644
--- a/src/core/management/commands/housekeeping.py
+++ b/src/core/management/commands/housekeeping.py
@@ -1,10 +1,12 @@
 import time
 
 from django.core.management.base import BaseCommand, CommandError
+from django.db.models import Max
 from django.utils import timezone
 
+from ...models.conference import Conference
 from ...models.messages import DirectMessage
-from ...models.schedules import ScheduleSource, ScheduleSourceImport
+from ...models.schedules import CachedSchedule, ScheduleSource, ScheduleSourceImport
 from ...models.voucher import Voucher
 
 
@@ -14,16 +16,24 @@ class Command(BaseCommand):
         parser.add_argument('--forever-delay', type=int, default=300, help='seconds to wait between housekeeping runs')
 
     def _do_housekeeping(self):
+        self._housekeeping_directmessages()
+        self._housekeeping_vouchers()
+        self._housekeeping_scheduleimports()
+        self._housekeeping_schedulecaching()
+
+    def _housekeeping_directmessages(self):
         # clear all direct messages which are after their expiry date
         print('Deleting messages ... ', end='', flush=True)
         deleted_msgs_count, _ = DirectMessage.objects.filter(autodelete_after__isnull=False, autodelete_after__lte=timezone.now()).delete()
         print(deleted_msgs_count)
 
+    def _housekeeping_vouchers(self):
         # do auto-assignments
         print('Auto-assigning vouchers ... ', end='', flush=True)
         vouchers_assigned = Voucher.do_auto_assignments()
         print(vouchers_assigned)
 
+    def _housekeeping_scheduleimports(self):
         # schedules
         print('Schedule imports ... ', end='', flush=True)
         schedule_results = {}
@@ -60,6 +70,35 @@ class Command(BaseCommand):
         for k, v in schedule_results.items():
             print('  ', k, ' => ', v, sep='')
 
+    def _housekeeping_schedulecaching(self):
+        ref = {}
+        for c in Conference.objects.all():
+            ts = []
+            ts.append(c.tracks.aggregate(Max('last_update'))['last_update__max'])
+            ts.append(c.events.aggregate(Max('last_update'))['last_update__max'])
+            ts.append(c.rooms.aggregate(Max('last_update'))['last_update__max'])
+            ref[c.id] = max([x for x in ts if x is not None], default=None)
+
+        changed, total = 0, 0
+        for entry in CachedSchedule.objects.all():
+            total += 1
+
+            # entries already marked for regeneration don't need further action
+            if entry.needs_regeneration:
+                continue
+
+            # ignore cache entries with a newer generation entry
+            ref_ts = ref[entry.conference_id]
+            if ref_ts is not None and entry.last_generated > ref_ts:
+                continue
+
+            # flag this entry for regeneration
+            entry.needs_regeneration = True
+            entry.save(update_fields=['needs_regeneration'])
+            changed += 1
+
+        print('Flagged', changed, 'out of', total, 'cached schedules for regeneration.')
+
     def handle(self, *args, **options):
         # call _do_housekeeping repeatedly (unless --forever is not set)
         forever = options.get('forever')