diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index f3420767f460dc9dd32f97c81bc6500e64e261d7..f90ccb9cd4eb8c149d9d428bdc43b2d963846e88 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -74,6 +74,9 @@
   - Als Ersatz für viele Methoden der Seminar-Klasse dienen die Klassen `Course`, `CourseDate` und `SeminarCycleDate`, sowie die neue `CourseDateList`-Klasse.
 - Die Klassen `TreeAbstract`, `TreeView` und `SemBrowse` wurden ausgebaut. ([Issue #4392](https://gitlab.studip.de/studip/studip/-/issues/4392))
   - Zur Anzeige von Baumstrukturen können als Ersatz die Implementierungen des `StudipTreeNode`-Interfaces genutzt werden.
+- Die Zuordnung von Veranstaltungen zu Semestern anhand von Timestamps wurde entfernt. In der Datenbank wurden die Spalten `start_time` und `duration_time` der Tabelle `seminare` entfernt. ([Issue #4391]https://gitlab.studip.de/studip/studip/-/issues/4391))
+  - Plugins, die Veranstaltungen anhand von Timestamps laden oder anderweitig verwenden, müssen angepasst werden!
+  - Das Mapping von Veranstaltungen zu Semestern findet nun ausschließlich anhand der Semester-ID über die Verknüpfungstabelle `semester_courses` statt.
 
 ## Security related issues
 
diff --git a/app/controllers/admin/tree.php b/app/controllers/admin/tree.php
index ec35368c262516aeb57733c18c1e8edaaf76fd82..320f10efd9cd884ec567d2f36164ce6df1011f94 100644
--- a/app/controllers/admin/tree.php
+++ b/app/controllers/admin/tree.php
@@ -238,8 +238,8 @@ class Admin_TreeController extends AuthenticatedController
         $courseIds = Request::optionArray('assign_semtree');
 
         $order = Config::get()->IMPORTANT_SEMNUMBER
-            ? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
-            : "ORDER BY `start_time` DESC,  `Name`";
+            ? "ORDER BY `VeranstaltungsNummer`, `Name`"
+            : "ORDER BY `Name`";
         $this->courses = array_filter(
             Course::findMany($courseIds, $order),
             function (Course $course): bool {
diff --git a/app/controllers/admin/user.php b/app/controllers/admin/user.php
index 17375d13aa844b22bdad4f38162cb3a74b4548b6..bf8b9282121954bf10fbd0fbce567ed65f000253 100644
--- a/app/controllers/admin/user.php
+++ b/app/controllers/admin/user.php
@@ -1634,8 +1634,8 @@ class Admin_UserController extends AuthenticatedController
 
         $courseIds = Request::optionArray('export_members');
         $order = Config::get()->IMPORTANT_SEMNUMBER
-            ? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
-            : "ORDER BY `start_time` DESC,  `Name`";
+            ? "ORDER BY `VeranstaltungsNummer`, `Name`"
+            : "ORDER BY `Name`";
         $this->courses = array_filter(
             Course::findMany($courseIds, $order),
             function (Course $course): bool {
diff --git a/app/controllers/admission/restricted_courses.php b/app/controllers/admission/restricted_courses.php
index aafb589e44da1701e198aaa6e50831979f04782d..371ea5de5ff749ef2e9a472604612779b6aa6f36 100644
--- a/app/controllers/admission/restricted_courses.php
+++ b/app/controllers/admission/restricted_courses.php
@@ -86,6 +86,7 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
                     _("Endzeitpunkt")];
             $data = [];
             foreach ($this->courses as $course) {
+                $sorm_course = Course::find($course['seminare.seminar_id']);
                 $row = [];
                 $row[] = $course['cs_name'];
                 $row[] = $course['course_number'];
@@ -96,8 +97,8 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
                 $row[] = (int)$course['count_prelim'];
                 $row[] = (int)$course['count_waiting'];
                 $row[] = $course['distribution_time'] ? strftime('%x %R', $course['distribution_time']) : '';
-                $row[] = isset($course['start_time']) ? strftime('%x %R', $course['start_time']) : '';
-                $row[] = isset($course['end_time']) ? strftime('%x %R', $course['end_time']) : '';
+                $row[] = $sorm_course?->getStartSemester()?->beginn ?? '';
+                $row[] = $sorm_course?->getEndSemester()?->ende ?? '';
                 $data[] = $row;
             }
 
diff --git a/app/controllers/contents/courseware.php b/app/controllers/contents/courseware.php
index 0337e6a2fa2308d41858b5f5f0c5d8b8693907aa..00627e9400b726c4e9a7d35c2a1d52e06d9a7b99 100644
--- a/app/controllers/contents/courseware.php
+++ b/app/controllers/contents/courseware.php
@@ -171,46 +171,6 @@ class Contents_CoursewareController extends CoursewareController
         $this->sem_courses  = $this->getCoursewareCourses($sem_key);
     }
 
-    public function pdf_export_action($element_id, $with_children): void
-    {
-        $element = \Courseware\StructuralElement::findOneById($element_id);
-
-        $this->render_pdf($element->pdfExport($this->user, $with_children), trim($element->title).'.pdf');
-    }
-
-    /**
-     * To display the shared courseware
-     *
-     * @param string $entry_element_id the shared struct element id
-     */
-    public function shared_content_courseware_action($entry_element_id): void
-    {
-        global $user;
-
-        $navigation = new Navigation(_('Geteiltes Lernmaterial'), 'dispatch.php/contents/courseware/shared_content_courseware/' . $entry_element_id);
-        Navigation::addItem('/contents/courseware/shared_content_courseware', $navigation);
-        Navigation::activateItem('/contents/courseware/shared_content_courseware');
-
-        $this->entry_element_id = $entry_element_id;
-
-        $struct = \Courseware\StructuralElement::findOneBySQL(
-            "id = ? AND range_type = 'user'",
-            [$this->entry_element_id]
-        );
-
-        if (!$struct) {
-            throw new Trails\Exception(404, _('Der geteilte Inhalt kann nicht gefunden werden.'));
-        }
-
-        if (!$struct->canRead($user) && !$struct->canEdit($user)) {
-            throw new AccessDeniedException();
-        }
-
-        $this->user_id = $struct->owner_id;
-
-        $this->setCoursewareSidebar();
-    }
-
     /**
      * Return list of coursewares grouped by semester_id
      *
@@ -255,7 +215,7 @@ class Contents_CoursewareController extends CoursewareController
             $courses = $courses->filter(function (Course $course) use ($semester) {
                 return $course->isInSemester($semester);
             });
-        } 
+        }
         $sem_courses = [];
         foreach ($courses as $course) {
             $units = Unit::findCoursesUnits($course);
diff --git a/app/controllers/course/dates.php b/app/controllers/course/dates.php
index bd889327489d626beb9fe1aa30bba544b54efe6f..88a61259a1ba6d0095cc1776314b282efb06693c 100644
--- a/app/controllers/course/dates.php
+++ b/app/controllers/course/dates.php
@@ -102,22 +102,18 @@ class Course_DatesController extends AuthenticatedController
         );
         $sidebar->addWidget($actions);
 
-        $course_end_time = $this->course->getEnd_Time();
-        if (($course_end_time == -1) || ($course_end_time > 0)) {
+        if (count($this->course->semesters) !== 1) {
             //The course has more than one semester:
             $semester_widget = new SemesterSelectorWidget(
                 $this->url_for('course/dates/index')
             );
-            $semester_end_range = $course_end_time;
-            if ($semester_end_range == -1) {
-                //The end semester is set to unlimited.
-                $semester_end_range = PHP_INT_MAX;
-            }
             $semester_widget->includeAll();
-            $semester_widget->setRange(
-                $this->course->start_time,
-                $semester_end_range
-            );
+            if ($this->course->start_semester && $this->course->end_semester) {
+                $semester_widget->setRange(
+                    $this->course->start_semester->beginn,
+                    $this->course->end_semester->ende
+                );
+            }
             $sidebar->addWidget($semester_widget);
         }
 
diff --git a/app/controllers/course/details.php b/app/controllers/course/details.php
index 7f9c078255f96fb550b4becd87784679e6521869..707b0ed990d30387f18e89bee0c07e526e38b04e 100644
--- a/app/controllers/course/details.php
+++ b/app/controllers/course/details.php
@@ -98,11 +98,9 @@ class Course_DetailsController extends AuthenticatedController
 
         // Ausgabe der Modulzuordnung MVV
         if ($this->course->getSemClass()->offsetGet('module')) {
-            $course_start = $this->course->start_time;
-            $course_end = ($this->course->end_time < 0 || is_null($this->course->end_time))
-                    ? PHP_INT_MAX
-                    : $this->course->end_time;
-            // set filter to show only pathes with valid semester data
+            $course_start = $this->course->start_semester?->beginn ?? 0;
+            $course_end   = $this->course->end_semester?->ende ?? PHP_INT_MAX;
+            //Set the filter to show only paths with valid semester data:
             ModuleManagementModelTreeItem::setObjectFilter('Modul',
                 function ($modul) use ($course_start, $course_end) {
                     // check for public status
diff --git a/app/controllers/lti.php b/app/controllers/lti.php
index 070682ef8a12c844a3c2ea6c84297fdbcc6153ac..82d9840fb81b0f281cfd9070240661d74de0de56 100644
--- a/app/controllers/lti.php
+++ b/app/controllers/lti.php
@@ -69,8 +69,10 @@ class LtiController extends AuthenticatedController
         ];
 
         $sql = "JOIN seminar_user USING(Seminar_id)
+                LEFT JOIN semester_courses sc ON seminare.seminar_id = sc.course_id
+                LEFT JOIN semester_data s USING (semester_id)
                 WHERE user_id = ? AND seminar_user.status IN ('dozent', 'tutor')
-                ORDER BY start_time DESC, Name";
+                ORDER BY s.beginn DESC, Name";
         $this->courses = Course::findBySQL($sql, [$GLOBALS['user']->id]);
     }
 
diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php
index d21f2d508f2b0a1d249061097f8240ac79205be0..b3677aed330086d04f1d47798ee8f72f11316c92 100644
--- a/app/controllers/my_courses.php
+++ b/app/controllers/my_courses.php
@@ -577,7 +577,7 @@ class MyCoursesController extends AuthenticatedController
                   LEFT JOIN archiv USING (seminar_id)
                   WHERE user_id = :user_id
                   GROUP BY seminar_id
-                  ORDER BY start_time DESC, :sortby";
+                  ORDER BY mkdate DESC, :sortby";
         $statement = DBManager::get()->prepare($query);
         $statement->bindValue(':user_id', $GLOBALS['user']->id);
         $statement->bindValue(':sortby', $sortby, StudipPDO::PARAM_COLUMN);
diff --git a/app/controllers/news.php b/app/controllers/news.php
index 375f48b25205352f47756bd0c93839a7fbf87893..973646e687da723587ec6a09ab6b133a5a51c61c 100644
--- a/app/controllers/news.php
+++ b/app/controllers/news.php
@@ -621,150 +621,6 @@ class NewsController extends StudipController
         return strtotime($date);
     }
 
-    /**
-     * Searchs for studip areas using given search term
-     *
-     * @param string $term search term
-     * @return array area data
-     */
-    private function search_area($term)
-    {
-        global $perm;
-        $result = $tmp_result = [];
-        if (mb_strlen($term) < 3) {
-            PageLayout::postError(_('Der Suchbegriff muss mindestens drei Zeichen lang sein.'));
-            return $result;
-        }
-
-        if ($term === '__THIS_SEMESTER__') {
-            $current_semester = Semester::findCurrent();
-            $query = "SELECT seminare.Name AS sem_name, seminare.Seminar_id, seminare.visible
-                      FROM seminar_user
-                          LEFT JOIN seminare USING (Seminar_id)
-                          LEFT JOIN semester_courses ON (semester_courses.course_id = seminar_user.Seminar_id)
-                      WHERE seminar_user.user_id = :user_id
-                          AND seminar_user.status IN('tutor', 'dozent')
-                          AND (semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)
-                          ";
-            if (Config::get()->DEPUTIES_ENABLE) {
-                $query .= " UNION SELECT CONCAT(seminare.Name, ' ["._("Vertretung")."]') AS sem_name, seminare.Seminar_id,
-                            seminare.visible
-                            FROM deputies
-                                LEFT JOIN seminare ON (deputies.range_id=seminare.Seminar_id)
-                                LEFT JOIN semester_courses ON (semester_courses.course_id = deputies.range_id)
-                            WHERE deputies.user_id = :user_id
-                            AND (semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)";
-            }
-            $query .= " ORDER BY sem_name ASC";
-            $statement = DBManager::get()->prepare($query);
-            $statement->bindValue(':user_id', $GLOBALS['user']->id);
-            $statement->bindValue(':semester_id', $current_semester->semester_id);
-            $statement->execute();
-            $seminars = $statement->fetchAll(PDO::FETCH_ASSOC);
-            foreach($seminars as $sem) {
-                $tmp_result[$sem['Seminar_id']] = [
-                    'name' => $sem['sem_name'],
-                    'type' => 'sem',
-                ];
-            }
-            $term = '';
-        } elseif ($term === '__NEXT_SEMESTER__') {
-            $next_semester = Semester::findNext();
-            $query = "SELECT seminare.Name AS sem_name, seminare.Seminar_id, seminare.visible
-                      FROM seminar_user
-                          LEFT JOIN seminare  USING (Seminar_id)
-                          LEFT JOIN semester_courses ON (semester_courses.course_id = seminar_user.Seminar_id)
-                      WHERE seminar_user.user_id = :user_id
-                          AND seminar_user.status IN('tutor', 'dozent')
-                          AND (semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)";
-            if (Config::get()->DEPUTIES_ENABLE) {
-                $query .= " UNION SELECT CONCAT(seminare.Name, ' ["._("Vertretung")."]') AS sem_name, seminare.Seminar_id,
-                            seminare.visible
-                            FROM deputies
-                                LEFT JOIN seminare ON (deputies.range_id=seminare.Seminar_id)
-                                LEFT JOIN semester_courses ON (semester_courses.course_id = deputies.range_id)
-                            WHERE deputies.user_id = :user_id
-                                AND (semester_courses.semester_id = :semester_id OR semester_courses.semester_id IS NULL)";
-            }
-            $query .= " ORDER BY sem_name ASC";
-            $statement = DBManager::get()->prepare($query);
-            $statement->bindValue(':user_id', $GLOBALS['user']->id);
-            $statement->bindValue(':semester_id', $next_semester->semester_id);
-            $statement->execute();
-            $seminars = $statement->fetchAll(PDO::FETCH_ASSOC);
-            foreach($seminars as $sem) {
-                $tmp_result[$sem['Seminar_id']] = [
-                    'name' => $sem['sem_name'],
-                    'type' => 'sem',
-                ];
-            }
-            $term = '';
-        } elseif ($term === '__MY_INSTITUTES__') {
-            $term = '';
-            if ($perm->have_perm('root')) {
-                $tmp_result['studip'] = [
-                    'name' => 'Stud.IP',
-                    'type' => 'global'
-                ];
-            }
-            $inst_list = Institute::getMyInstitutes();
-            if (count($inst_list)) {
-                foreach($inst_list as $data) {
-                    $tmp_result[$data['Institut_id']] = [
-                        'name' => $data['Name'],
-                        'type' => $data['is_fak'] ? 'fak' : 'inst'
-                    ];
-                }
-            }
-        } else {
-            $tmp_result = search_range($term, true) ?: [];
-            // add users
-            if (mb_stripos(get_fullname(), $term) !== false) {
-                $tmp_result[$GLOBALS['user']->id] = [
-                    'name' => get_fullname(),
-                    'type' => 'user'
-                ];
-            }
-            if (Deputy::isEditActivated()) {
-                $query = "SELECT DISTINCT a.user_id
-                          FROM deputies d
-                          JOIN auth_user_md5 a ON (d.range_id = a.user_id)
-                          JOIN user_info u ON (a.user_id=u.user_id)
-                          WHERE d.user_id = ?
-                            AND CONCAT(u.title_front, ' ', a.Vorname, ' ', a.Nachname, ', ', u.title_rear) LIKE CONCAT('%',?,'%')";
-                $statement = DBManager::get()->prepare($query);
-                $statement->execute([$GLOBALS['user']->id, $term]);
-                while ($data = $statement->fetch(PDO::FETCH_ASSOC)) {
-                    $tmp_result[$data['user_id']] = [
-                        'name' => get_fullname($data['user_id']),
-                        'type' => 'user',
-                    ];
-                }
-            }
-        }
-        // workaround: apply search term (ignored by search_range below admin)
-        if (count($tmp_result) && !$GLOBALS['perm']->have_perm('admin') && $term) {
-            foreach ($tmp_result as $id => $data) {
-                if (mb_stripos($data['name'], $term) === false) {
-                    unset($tmp_result[$id]);
-                }
-            }
-        }
-        // prepare result
-
-        if (count($tmp_result)) {
-            foreach ($tmp_result as $id => $data) {
-                $index = $data['type'] === 'fak'
-                       ? 'inst'
-                       : $data['type'];
-                $result[$index][$id] = $data['name'];
-            }
-        } elseif ($term) {
-            PageLayout::postError(_('Zu diesem Suchbegriff wurden keine Bereiche gefunden.'));
-        }
-        return $result;
-    }
-
     public function rss_config_action($range_id)
     {
         if (!Config::get()->NEWS_RSS_EXPORT_ENABLE || !StudipNews::haveRangePermission('edit', $range_id)) {
diff --git a/app/views/admission/courseset/configure.php b/app/views/admission/courseset/configure.php
index 773624e5bc052164bc5345b64d50aa8c4d0be10b..6380499dfcfd64ebcafe91f9c288b2854a018c36 100644
--- a/app/views/admission/courseset/configure.php
+++ b/app/views/admission/courseset/configure.php
@@ -158,12 +158,18 @@ if (isset($flash['error'])) {
                 <?= sprintf(_("%s zugewiesene Veranstaltungen"), count($courseIds)) ?>
             <? else : ?>
             <?
-            Course::findEachMany(function($c) {
-                echo htmlReady($c->getFullName('number-name-semester'));
-                echo '<br>';
-            },
-                $courseIds,
-                'ORDER BY start_time,VeranstaltungsNummer,Name');
+            Course::findEachBySQL(
+                function($c) {
+                    echo htmlReady($c->getFullName('number-name-semester'));
+                    echo '<br>';
+                },
+                "JOIN `semester_courses`
+                ON `seminare`.`seminar_id` = `semester_courses`.`course_id`
+                JOIN `semester_data` USING (`semester_id`)
+                WHERE `seminare`.`seminar_id` IN ( :course_ids )
+                'ORDER BY `semester_data`.`beginn`, `VeranstaltungsNummer`, `Name`",
+                ['course_ids' => $courseIds],
+            )
             ?>
             <? endif ?>
         <? endif ?>
diff --git a/app/views/admission/restricted_courses/index.php b/app/views/admission/restricted_courses/index.php
index a7b18192bb2cd357c9d9fc9b5abdee67264f141a..a7503e48c3c8e5bdbfde686a52fdeff85bbf7403 100644
--- a/app/views/admission/restricted_courses/index.php
+++ b/app/views/admission/restricted_courses/index.php
@@ -41,11 +41,11 @@
                 <td style="white-space:nowrap" data-sort-value="<?= (int) $course['distribution_time']?>">
                     <?= htmlReady($course['distribution_time'] ? strftime('%x %R', $course['distribution_time']) : '-') ?>
                 </td>
-                <td style="white-space:nowrap" data-sort-value="<?= (int) ($course['start_time'] ?? null) ?>">
-                    <?= htmlReady(isset($course['start_time']) ? strftime('%x %R', $course['start_time']) : '-') ?>
+                <td style="white-space:nowrap" data-sort-value="<?= (int) ($course->start_semester->beginn ?? null) ?>">
+                    <?= htmlReady(($course->start_semester instanceof Semester) ? strftime('%x %R', $course->start_semester->beginn) : '-') ?>
                 </td>
-                <td style="white-space:nowrap" data-sort-value="<?= (int) ($course['end_time'] ?? null) ?>">
-                    <?= htmlReady(isset($course['end_time']) ? strftime('%x %R', $course['end_time']) : '-') ?>
+                <td style="white-space:nowrap" data-sort-value="<?= (int) ($course->end_semester->ende ?? null) ?>">
+                    <?= htmlReady(($course->end_semester instanceof Semester) ? strftime('%x %R', $course->end_semester->ende) : '-') ?>
                 </td>
             </tr>
         <? endforeach ?>
diff --git a/app/views/course/wizard/steps/basicdata/index.php b/app/views/course/wizard/steps/basicdata/index.php
index f19643a0731da1254126a0f51683cf25b52c8b38..372d587821d3a68e8b2922ed9bc3378c5d9415ba 100644
--- a/app/views/course/wizard/steps/basicdata/index.php
+++ b/app/views/course/wizard/steps/basicdata/index.php
@@ -1,3 +1,8 @@
+<?php
+/**
+ * @var Semester[] $semesters
+ */
+?>
 <legend>
     <?= _('Grunddaten') ?>
 </legend>
@@ -21,13 +26,18 @@
     <label for="wizard-start-time" class="required">
         <?= _('Semester') ?>
     </label>
-    <select name="start_time" id="wizard-start-time" >
-        <?php foreach (array_reverse($semesters) as $semester) { ?>
-            <? ($values['start_time'] < time()) ? (($semester->beginn <= time() && time() <= $semester->ende) ? $values['start_time'] = $semester->beginn : '' ): '' ?>
-            <option value="<?= $semester->beginn ?>"<?= $semester->beginn == $values['start_time'] ? ' selected="selected"' : '' ?>>
+    <select name="semester_id" id="wizard-start-time" >
+        <?php
+        $default_semester = Semester::findDefault();
+        if ($default_semester && empty($values['start_semester'])) {
+            $values['start_semester'] = $default_semester;
+        }
+        ?>
+        <?php foreach (array_reverse($semesters) as $semester) : ?>
+            <option value="<?= htmlReady($semester->id) ?>" <?= $semester->id == $values['start_semester'] ? ' selected' : '' ?>>
                 <?= htmlReady($semester->name) ?>
             </option>
-        <?php } ?>
+        <?php endforeach ?>
     </select>
 </section>
 <section>
diff --git a/app/views/course/wizard/steps/basicdata/index_studygroup.php b/app/views/course/wizard/steps/basicdata/index_studygroup.php
index d7417abd8b27017f572507e4d151b2bf068c2334..9f75da56436212f639eb46ea8cf9531892345e13 100644
--- a/app/views/course/wizard/steps/basicdata/index_studygroup.php
+++ b/app/views/course/wizard/steps/basicdata/index_studygroup.php
@@ -71,9 +71,9 @@
     <?= _('Einverstanden') ?>
 </label>
 
-<input type="hidden" name="institute" value="<?= htmlReady($values['institute']) ?>">
-<input type="hidden" name="start_time" value="<?= htmlReady($values['start_time']) ?>">
+<input type="hidden" name="institute" value="<?= $values['institute'] ?>"/>
+<input type="hidden" name="start_semester" value="<?= htmlReady($values['start_semester']) ?>">
 <input type="hidden" name="studygroup" value="1"/>
 <?php foreach ($values['lecturers'] as $id => $assigned) : ?>
-    <input type="hidden" name="lecturers[<?= htmlReady($id) ?>]" value="1">
+    <input type="hidden" name="lecturers[<?= $id ?>]" value="1"/>
 <?php endforeach ?>
diff --git a/app/views/lvgruppen/lvgruppen/details.php b/app/views/lvgruppen/lvgruppen/details.php
index 6a9c3afc690da1e3d23202b1baeeb545e74f01e1..ed9dc9a854193ab4bd22e2a551f9b4da88a38b27 100644
--- a/app/views/lvgruppen/lvgruppen/details.php
+++ b/app/views/lvgruppen/lvgruppen/details.php
@@ -1,3 +1,8 @@
+<?php
+/**
+ * @var Course[] $courses
+ */
+?>
 <td colspan="6">
     <table class="default nohover">
         <colgroup>
@@ -80,7 +85,7 @@
                                     <? foreach ($courses[$semester->id] as $course) : ?>
                                         <li>
                                             <a href="<?= URLHelper::getLink('dispatch.php/course/details', ['sem_id' => $course['seminar_id']]) ?>">
-                                                <?= htmlReady(($course['VeranstaltungsNummer'] ? $course['VeranstaltungsNummer'] . ' - ' : '') . $course['Name']) ?>
+                                                <?= htmlReady($course->getFullName('number-name')) ?>
                                             </a>
                                         </li>
                                     <? endforeach; ?>
@@ -99,7 +104,7 @@
                                     <? foreach ($courses[$semester->id] as $course) : ?>
                                         <li>
                                             <a href="<?= URLHelper::getLink('dispatch.php/course/details', ['sem_id' => $course['seminar_id']]) ?>">
-                                                <?= htmlReady(($course['VeranstaltungsNummer'] ? $course['VeranstaltungsNummer'] . ' - ' : '') . $course['Name']) ?>
+                                                <?= htmlReady($course->getFullName('number-name')) ?>
                                             </a>
                                         </li>
                                     <? endforeach; ?>
diff --git a/db/migrations/6.0.18_remove_course_mapping_by_timestamps.php b/db/migrations/6.0.18_remove_course_mapping_by_timestamps.php
new file mode 100644
index 0000000000000000000000000000000000000000..015618ca42f99ceb105987aed9aa43af84f02a39
--- /dev/null
+++ b/db/migrations/6.0.18_remove_course_mapping_by_timestamps.php
@@ -0,0 +1,30 @@
+<?php
+
+
+class RemoveCourseMappingByTimestamps extends Migration
+{
+    public function description()
+    {
+        return 'Removes the mapping of courses to semesters by timestamps (by removing seminare.start_time and seminare.duration_time).';
+    }
+
+    public function up()
+    {
+        $db = DBManager::get();
+        $db->exec(
+            "ALTER TABLE `seminare`
+            DROP COLUMN `start_time`,
+            DROP COLUMN `duration_time`"
+        );
+    }
+
+    protected function down()
+    {
+        $db = DBManager::get();
+        $db->exec(
+            "ALTER TABLE `seminare`
+            ADD COLUMN start_time INT(11) UNSIGNED NULL DEFAULT 0,
+            ADD COLUMN duration_time INT(11) NULL DEFAULT NULL"
+        );
+    }
+}
diff --git a/lib/classes/AutoInsert.php b/lib/classes/AutoInsert.php
index 89d87da498dc72af6e504e2d85d53c58134be5b7..a88ec5547c0c6cd84d374a817375fb2a61024523 100644
--- a/lib/classes/AutoInsert.php
+++ b/lib/classes/AutoInsert.php
@@ -52,7 +52,7 @@ class AutoInsert
 
     private function loadSettings()
     {
-        $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time ";
+        $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff ";
         $query .= "FROM auto_insert_sem a ";
         $query .= "JOIN seminare AS s USING (Seminar_id) ";
         $query .= "GROUP BY s.seminar_id ";
@@ -68,8 +68,7 @@ class AutoInsert
                     $key                                         = $array[1] . '.' . $array[0];
                     $this->settings[$key][$result['seminar_id']] = ['Seminar_id'     => $result['seminar_id'],
                                                                     'name'           => $result['Name'],
-                                                                    'Schreibzugriff' => $result['Schreibzugriff'],
-                                                                    'start_time'     => $result['start_time']];
+                                                                    'Schreibzugriff' => $result['Schreibzugriff']];
                 }
             }
         }
@@ -78,7 +77,7 @@ class AutoInsert
 
     private function getUserSeminars($user_id, $seminare)
     {
-        $statement = DBManager::get()->prepare("SELECT Seminar_id,s.name,s.Schreibzugriff,s.start_time,su.status
+        $statement = DBManager::get()->prepare("SELECT Seminar_id, s.name, s.Schreibzugriff, su.status
             FROM seminar_user su
             INNER JOIN seminare s USING(Seminar_id)
             WHERE user_id = ? AND Seminar_id IN(?)");
@@ -256,7 +255,7 @@ class AutoInsert
             $statement = DBManager::get()->query($query);
             $results   = $statement->fetchAll(PDO::FETCH_COLUMN);
         } else {
-            $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff, s.start_time ";
+            $query = "SELECT a.seminar_id, GROUP_CONCAT(a.status,IF(LENGTH(a.domain_id)=0,':keine',CONCAT(':',a.domain_id))) AS domain_status, s.Name, s.Schreibzugriff ";
             $query .= "FROM auto_insert_sem a ";
             $query .= "JOIN seminare AS s USING (Seminar_id) ";
 
diff --git a/lib/classes/CoursesetModel.php b/lib/classes/CoursesetModel.php
index e12d3b9682bf76d43c1c23a6a562cc5170e3ecb8..076355d9c0717618acd2f78aca918a1e2f254b22 100644
--- a/lib/classes/CoursesetModel.php
+++ b/lib/classes/CoursesetModel.php
@@ -46,13 +46,11 @@ class CoursesetModel
                       INNER JOIN `seminare` s USING (`Seminar_id`)
                       LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id)
                       WHERE s.status NOT IN(?)
-                        AND s.`start_time` <= ?
                         AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = ?)
                         AND su.`user_id` = ?
                       GROUP BY su.`Seminar_id`  ";
             $parameters = [
                 $excludeTypes,
-                $currentSemester->beginn,
                 $currentSemester->id,
                 $GLOBALS['user']->id
             ];
@@ -63,14 +61,13 @@ class CoursesetModel
                            FROM `seminare` s
                            INNER JOIN `deputies` d ON (s.`Seminar_id`=d.`range_id`)
                            LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id)
-                           WHERE s.`start_time` <= ?
-                             AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = ?)
+                           WHERE (semester_courses.semester_id IS NULL OR semester_courses.semester_id = ?)
                              AND d.`user_id` = ?
                            GROUP BY s.`Seminar_id`
                              ";
                 $parameters = array_merge(
                     $parameters,
-                    [$currentSemester->beginn, $currentSemester->id, $GLOBALS['user']->id]
+                    [$currentSemester->id, $GLOBALS['user']->id]
                 );
             }
             $courses = $db->fetchFirst($query, $parameters);
@@ -88,7 +85,6 @@ class CoursesetModel
                       LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id)
                       INNER JOIN auth_user_md5 aum USING (user_id)
                       WHERE s.status NOT IN (:exclude_types)
-                        AND s.start_time <= :sembegin
                         AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id)
                         AND $sem_inst.Institut_id IN (:institutes)
                         AND (
@@ -98,7 +94,6 @@ class CoursesetModel
                         )";
             $courses = $db->fetchFirst($query, [
                 'exclude_types' => $excludeTypes,
-                'sembegin'      => $currentSemester->beginn,
                 'semester_id'   => $currentSemester->id,
                 'institutes'    => $instituteIds,
                 'filter'        => '%' . $filter .'%',
@@ -136,13 +131,15 @@ class CoursesetModel
                 'visible'              => $course->visible,
             ];
 
-            $query = "SELECT type
-                      FROM seminar_courseset
-                      INNER JOIN courseset_rule USING (set_id)
-                      WHERE type IN ('LockedAdmission','PasswordAdmission')
-                        AND seminar_id = ?";
+            $query = "SELECT `type`
+                      FROM `seminar_courseset`
+                      JOIN `courseset_rule` USING (`set_id`)
+                      LEFT JOIN `semester_courses` ON `seminar_courseset`.`seminar_id` = `semester_courses`.`course_id`
+                      LEFT JOIN `semester_data` USING (`semester_id`)
+                      WHERE `type` IN ('LockedAdmission','PasswordAdmission')
+                        AND `seminar_id` = ?";
             if ($coursesetId) {
-                $query .= "AND set_id <> ?";
+                $query .= "AND `set_id` <> ?";
             }
 
             $data[$course->id]['admission_type'] = DBManager::get()->fetchColumn(
@@ -151,7 +148,7 @@ class CoursesetModel
             );
 
         };
-        Course::findEachMany($callable, array_unique($courses),"ORDER BY start_time DESC, VeranstaltungsNummer ASC, Name ASC");
+        Course::findEachMany($callable, array_unique($courses),"ORDER BY `semester_data`.`beginn` DESC, `VeranstaltungsNummer` ASC, `Name` ASC");
 
         return $data;
     }
@@ -177,6 +174,7 @@ class CoursesetModel
                   LEFT JOIN courseset_rule cr ON c.set_id = cr.set_id
                   LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id
                   LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id
+                  LEFT JOIN semester_courses ON s.seminar_id = semester_courses.course_id
                   WHERE ci.institute_id = ?";
         if ($filter['course_set_name']) {
             $query .= " AND c.name LIKE ?";
@@ -187,8 +185,8 @@ class CoursesetModel
             $parameters[] = $filter['rule_types'];
         }
         if ($filter['semester_id']) {
-            $query .= " AND s.start_time = ?";
-            $parameters[] = Semester::find($filter['semester_id'])->beginn;
+            $query .= " AND semester_courses.semester_id = ?";
+            $parameters[] = $filter['semester_id'];
         }
         $cs_count_statement = DBManager::get()->prepare($query);
         $query = str_replace('ci.institute_id', '1', $query);
diff --git a/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php b/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
index ba2536909c07896a471366cfb2e53ba34416b77f..9f0843aa44b855d3352839b2759d8a9a6bacf899 100644
--- a/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
+++ b/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
@@ -98,9 +98,14 @@ class CoursesByUserIndex extends JsonApiController
      */
     private function findCoursesByUser(User $user, ?Semester $semester): array
     {
-        $courses = Course::findMany(
-            $user->course_memberships->pluck('seminar_id'),
-            'ORDER BY start_time, name'
+        $courses = Course::findBySQL(
+            'LEFT JOIN `semester_courses`
+            ON `seminare`.`seminar_id` = `semester_courses`.`course_id`
+            LEFT JOIN `semester_data` USING (`semester_id`)
+            WHERE
+            `seminare`.`seminar_id` IN ( :course_ids )
+            ORDER BY `semester_data`.`beginn`, `seminare`.`name`',
+            ['course_ids' => $user->course_memberships->pluck('seminar_id')]
         );
 
         if ($semester) {
diff --git a/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php b/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
index 033916cc408f7a585dce4379c1ed490a9feaac28..b96a29093c025929bf0f8c046f60e7f45f088c24 100644
--- a/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
+++ b/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
@@ -57,11 +57,12 @@ class UserScheduleShow extends JsonApiController
     {
         // get all virtually added seminars
         $stmt = \DBManager::get()->prepare(
-            'SELECT c.course_id FROM schedule_courses as c
-             LEFT JOIN seminare ON seminare.seminar_id = c.course_id
-             WHERE user_id = ? AND start_time = ?'
+            'SELECT c.`course_id` FROM `schedule_courses` as c
+             LEFT JOIN `seminare` ON `seminare`.`seminar_id` = c.`course_id`
+             JOIN `semester_courses` ON `seminare`.`seminar_id` = `semester_courses`.`course_id`
+             WHERE user_id = :user_id AND semester_id = :semester_id'
         );
-        $stmt->execute([$user->id, $semester['beginn']]);
+        $stmt->execute(['user_id' => $user->id, 'semester_id' => $semester->id]);
         $ids = $stmt->fetchFirst();
 
         // fetch seminar-entries
@@ -71,12 +72,10 @@ class UserScheduleShow extends JsonApiController
                 LEFT JOIN seminare as s USING (seminar_id)
                 LEFT JOIN semester_courses ON (s.Seminar_id = semester_courses.course_id)
             WHERE su.user_id = :userid
-                AND s.start_time <= :begin
                 AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id)
         ');
         $stmt->execute([
             'userid' => $user->id,
-            'begin' => $semester->beginn,
             'semester_id' => $semester->id,
         ]);
 
diff --git a/lib/classes/JsonApi/Schemas/Course.php b/lib/classes/JsonApi/Schemas/Course.php
index b6fe04a63b49f3bc2d19058e47eef6135b9b75df..5ffb6ee878e2237ed529c364f0c63767e3c14527 100644
--- a/lib/classes/JsonApi/Schemas/Course.php
+++ b/lib/classes/JsonApi/Schemas/Course.php
@@ -107,29 +107,29 @@ class Course extends SchemaProvider
 
     private function getStartSemester(\Course $course)
     {
-        if (!$semester = \Semester::findByTimestamp($course->start_time)) {
+        if (!$course->start_semester) {
             return null;
         }
 
         return [
             self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->createLinkToResource($semester),
+                Link::RELATED => $this->createLinkToResource($course->start_semester),
             ],
-            self::RELATIONSHIP_DATA => $semester,
+            self::RELATIONSHIP_DATA => $course->start_semester,
         ];
     }
 
     private function getEndSemester(\Course $course)
     {
-        if (!$semester = \Semester::findByTimestamp($course->end_time)) {
+        if (!$course->end_semester) {
             return null;
         }
 
         return [
             self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->createLinkToResource($semester),
+                Link::RELATED => $this->createLinkToResource($course->end_semester),
             ],
-            self::RELATIONSHIP_DATA => $semester,
+            self::RELATIONSHIP_DATA => $course->end_semester,
         ];
     }
 
diff --git a/lib/classes/ModulesNotification.php b/lib/classes/ModulesNotification.php
index 1dc361c1f38daff33c0ac80e69febdaf3fd0cbc9..f16ea1201cb09be9fcdaf0128a59a1b5dcd84007 100644
--- a/lib/classes/ModulesNotification.php
+++ b/lib/classes/ModulesNotification.php
@@ -86,7 +86,7 @@ class ModulesNotification
         }
 
         $my_sem = [];
-        $query = "SELECT s.Seminar_id, s.Name, s.chdate, s.start_time, IFNULL(visitdate, :threshold) AS visitdate
+        $query = "SELECT s.Seminar_id, s.Name, s.chdate, IFNULL(visitdate, :threshold) AS visitdate
                   FROM seminar_user_notifications su
                   JOIN seminar_user USING (user_id, seminar_id)
                   JOIN seminare s USING (Seminar_id)
@@ -114,7 +114,6 @@ class ModulesNotification
             $my_sem[$seminar_id] = [
                 'name'         => $row['Name'],
                 'chdate'       => $row['chdate'],
-                'start_time'   => $row['start_time'],
                 'tools'        => new SimpleCollection($tools),
                 'visitdate'    => $row['visitdate'],
                 'notification' => $notification->notification_data->getArrayCopy(),
diff --git a/lib/classes/MyRealmModel.php b/lib/classes/MyRealmModel.php
index 8997e780bef971c45fe3b3cbab17c12a98fdbea8..8fe3fee4d5d1cfef1aa4da66f55152aa1450d946 100644
--- a/lib/classes/MyRealmModel.php
+++ b/lib/classes/MyRealmModel.php
@@ -272,7 +272,7 @@ class MyRealmModel
         $show_semester_name = UserConfig::get($GLOBALS['user']->id)->SHOWSEM_ENABLE;
         $sem_courses = [];
 
-        $param_array = 'name seminar_id visible veranstaltungsnummer start_time duration_time status visible ';
+        $param_array = 'name seminar_id visible veranstaltungsnummer status visible ';
         $param_array .= 'chdate admission_binding modules admission_prelim';
 
         // filtering courses
@@ -826,7 +826,7 @@ class MyRealmModel
             )
         );
 
-        $data_fields = 'name seminar_id visible veranstaltungsnummer start_time duration_time status visible '
+        $data_fields = 'name seminar_id visible veranstaltungsnummer status visible '
                      . 'chdate admission_binding admission_prelim';
         $studygroup_data = [];
         foreach ($studygroup_memberships as $membership) {
diff --git a/lib/classes/StudipLog.php b/lib/classes/StudipLog.php
index 7a520c98dc37fe2819024638c5deb2600a7bc0d1..34321b14fab6ca6fe289ccbe6f972af74782e6b3 100644
--- a/lib/classes/StudipLog.php
+++ b/lib/classes/StudipLog.php
@@ -196,8 +196,12 @@ class StudipLog
         $result = [];
 
         // search for active seminars
-        $courses = Course::findBySQL("VeranstaltungsNummer LIKE CONCAT('%', :needle, '%')
-                     OR seminare.Name LIKE CONCAT('%', :needle, '%') ORDER BY start_time DESC",
+        $courses = Course::findBySQL(
+            "JOIN semester_courses ON seminare.seminar_id = semester_courses.course_id
+            JOIN semester USING (semester_id)
+            WHERE
+            VeranstaltungsNummer LIKE CONCAT('%', :needle, '%')
+                     OR seminare.Name LIKE CONCAT('%', :needle, '%') ORDER BY semester.beginn DESC",
                 [':needle' => $needle]);
 
         foreach ($courses as $course) {
diff --git a/lib/classes/admission/CourseSet.php b/lib/classes/admission/CourseSet.php
index cf876673671a68823e1d822805307cac2b2ab963..9d9889ef1d9a20f5b399844a61a698330def8084 100644
--- a/lib/classes/admission/CourseSet.php
+++ b/lib/classes/admission/CourseSet.php
@@ -375,6 +375,7 @@ class CourseSet
             LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id
             LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id
             LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id
+            LEFT JOIN `semester_courses` ON s.`seminar_id` = `semester_courses`.`course_id`
             WHERE ci.`institute_id`=?";
         $parameters = [$instituteId];
         if (!$GLOBALS['perm']->have_perm('admin')) {
@@ -390,8 +391,8 @@ class CourseSet
             $parameters[] = $filter['rule_types'];
         }
         if (!empty($filter['semester_id'])) {
-            $query .= " AND s.start_time = ?";
-            $parameters[] = Semester::find($filter['semester_id'])->beginn;
+            $query .= " AND `semester_courses`.`semester_id` = ?";
+            $parameters[] = $filter['semester_id'];
         }
         if (!empty($filter['course_set_chdate'])) {
             $query .= " AND c.chdate > ?";
@@ -415,8 +416,11 @@ class CourseSet
             LEFT JOIN courseset_institute ci ON ci.`set_id`=c.`set_id`
             LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id
             LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id
-            LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id
-            WHERE ci.institute_id IS NULL";
+            LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id ";
+        if (!empty($filter['semester_id'])) {
+            $query .= "JOIN `semester_courses` ON s.`seminar_id` = `semester_courses`.`course_id` ";
+        }
+        $query .= "WHERE ci.institute_id IS NULL ";
         $parameters = [];
         $query .= " AND (c.`private`=0 OR c.`user_id`=?)";
         $parameters[] = $GLOBALS['user']->id;
@@ -429,8 +433,8 @@ class CourseSet
             $parameters[] = $filter['rule_types'];
         }
         if (!empty($filter['semester_id'])) {
-            $query .= " AND s.start_time = ?";
-            $parameters[] = Semester::find($filter['semester_id'])->beginn;
+            $query .= " AND `semester_courses`.`semester_id` = ?";
+            $parameters[] = $filter['semester_id'];
         }
         if (!empty($filter['course_set_chdate'])) {
             $query .= " AND c.chdate > ?";
@@ -1026,15 +1030,23 @@ class CourseSet
             $tpl->set_attribute('institutes', $institutes);
         }
         if (!$short || $this->hasAdmissionRule('LimitedAdmission')) {
-            $courses = Course::findAndMapMany(function($c) {
-                return [
-                    'id' => $c->id,
-                    'name' => $c->getFullName('number-name-semester'),
-                    'visible' => $c->visible
-                ];
-            },
-                array_keys($this->courses),
-                'ORDER BY start_time,VeranstaltungsNummer,Name');
+            $courses = Course::findAndMapBySQL(
+                function($c) {
+                    return [
+                        'id' => $c->id,
+                        'name' => $c->getFullName('number-name-semester'),
+                        'visible' => $c->visible
+                    ];
+                },
+             "LEFT JOIN `semester_courses` ON `seminare`.`seminar_id` = `semester_courses`.`course_id`
+                 LEFT JOIN `semester_data` USING (`semester_id`)
+                 WHERE
+                 `seminare`.`seminar_id` IN ( :course_ids )
+                 'ORDER BY `semester_data`.`beginn`, `VeranstaltungsNummer`, `Name`'",
+                [
+                    'course_ids' => array_keys($this->courses)
+                ]
+            );
             if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) {
                 $courses = array_filter($courses,
                     function ($c) {
diff --git a/lib/classes/coursewizardsteps/BasicDataWizardStep.php b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
index d9e79605c6a0211bd97632ef69d92d45ef41ecc1..e6f5518b6f41df2651047d73c2c8bb7eb8a4c7b4 100644
--- a/lib/classes/coursewizardsteps/BasicDataWizardStep.php
+++ b/lib/classes/coursewizardsteps/BasicDataWizardStep.php
@@ -84,17 +84,17 @@ class BasicDataWizardStep implements CourseWizardStep
                 if ($GLOBALS['perm']->have_perm("admin")) {
                     if (
                         $s->id == $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE
-                        && empty($values['start_time'])
+                        && empty($values['start_semester'])
                         && Request::isXhr()
                     ) {
-                        $values['start_time'] = $s->beginn;
+                        $values['start_semester'] = $s->id;
                     }
                 }
                 $semesters[] = $s;
             }
         }
-        if (empty($values['start_time'])) {
-            $values['start_time'] = Semester::findDefault()->beginn;
+        if (empty($values['start_semester'])) {
+            $values['start_semester'] = Semester::findDefault()->id;
         }
         if (!empty($values['studygroup']) && (!count($typestruct) || empty($values['institute'])) ) {
             $message = sprintf(_('Die Konfiguration der Studiengruppen ist unvollständig. ' .
@@ -107,8 +107,8 @@ class BasicDataWizardStep implements CourseWizardStep
         if (count($semesters) > 0) {
             $tpl->set_attribute('semesters', array_reverse($semesters));
             // If no semester is set, use current as selected default.
-            if (empty($values['start_time'])) {
-                $values['start_time'] = Semester::findCurrent()->beginn;
+            if (empty($values['start_semester'])) {
+                $values['start_semester'] = Semester::findCurrent()->id;
             }
         } else {
             $message = sprintf(_('Veranstaltungen können nur ' .
@@ -414,7 +414,7 @@ class BasicDataWizardStep implements CourseWizardStep
         $course->name = new I18NString($values['name'], $values['name_i18n'] ?? []);
         $course->veranstaltungsnummer = $values['number'] ?? null;
         $course->beschreibung = new I18NString($values['description'], $values['description_i18n'] ?? []);
-        $course->start_semester = Semester::findByTimestamp($values['start_time']);
+        $course->start_semester = Semester::find($values['start_semester']);
         $course->institut_id = $values['institute'];
 
         $semclass = $course->getSemClass();
@@ -426,7 +426,6 @@ class BasicDataWizardStep implements CourseWizardStep
         // Studygroups: access and description.
         if (in_array($values['coursetype'], studygroup_sem_types())) {
             $course->visible = 1;
-            $course->duration_time = -1;
             switch ($values['access']) {
                 case 'all':
                     $course->admission_prelim = 0;
@@ -516,7 +515,7 @@ class BasicDataWizardStep implements CourseWizardStep
     {
         $data = [
             'coursetype' => $course->status,
-            'start_time' => $course->start_time,
+            'start_semester' => $course->start_semester->id ?? '',
             'name' => $course->name,
             'name_i18n' => is_object($course->name) ? $course->name->toArray() : $course->name,
             'number' => $course->veranstaltungsnummer,
diff --git a/lib/classes/coursewizardsteps/LVGroupsWizardStep.php b/lib/classes/coursewizardsteps/LVGroupsWizardStep.php
index c651714cf19cc516cc25412c59c28bcda65e86fc..a901b6cdec4e61730ea9e952659cf09888b85f32 100644
--- a/lib/classes/coursewizardsteps/LVGroupsWizardStep.php
+++ b/lib/classes/coursewizardsteps/LVGroupsWizardStep.php
@@ -33,7 +33,7 @@ class LVGroupsWizardStep implements CourseWizardStep
                 ->classname;
 
         // store start time of semester selected in first step
-        $course_start_time = $values[$step_one_class]['start_time'];
+        $course_start_semester = $values[$step_one_class]['start_semester'];
 
         // We only need our own stored values here.
         $values = $values[__CLASS__] ?? [];
@@ -56,8 +56,8 @@ class LVGroupsWizardStep implements CourseWizardStep
         $selection_details = $values['lvgruppe_selection']['area_details'] ?? null;
 
         if (
-            isset($_SESSION[__CLASS__]['course_start_time'])
-            && $_SESSION[__CLASS__]['course_start_time'] != $course_start_time
+            isset($_SESSION[__CLASS__]['course_start_semester'])
+            && $_SESSION[__CLASS__]['course_start_semester'] != $course_start_semester
         ) {
             // don't store previously opened nodes
             // because we get in trouble if the semester has changed
@@ -66,7 +66,7 @@ class LVGroupsWizardStep implements CourseWizardStep
             $open_nodes = !empty($values['open_lvg_nodes']) ? $values['open_lvg_nodes'] : [];
         }
 
-        $_SESSION[__CLASS__]['course_start_time'] = $course_start_time;
+        $_SESSION[__CLASS__]['course_start_semester'] = $course_start_semester;
 
         $tpl->open_lvg_nodes = $open_nodes;
         $tpl->selection = $selection;
@@ -124,10 +124,10 @@ class LVGroupsWizardStep implements CourseWizardStep
 
         $course = Course::findCurrent();
         if ($course) {
-            $course_start = $course->start_time;
-            $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+            $course_start = $course->start_semester?->beginn ?? 0;
+            $course_end   = $course->end_semester?->ende ?? PHP_INT_MAX;
         } else {
-            $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+            $semester = Semester::find($_SESSION[__CLASS__]['course_start_semester']);
             $course_start = $semester->beginn;
             $course_end = $semester->ende;
         }
@@ -192,10 +192,10 @@ class LVGroupsWizardStep implements CourseWizardStep
 
         $course = Course::findCurrent();
         if ($course) {
-            $course_start = $course->start_time;
-            $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+            $course_start = $course->start_semester?->beginn ?? 0;
+            $course_end   = $course->end_semester?->ende ?? PHP_INT_MAX;
         } else {
-            $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+            $semester = Semester::find($_SESSION[__CLASS__]['course_start_semester']);
             $course_start = $semester->beginn;
             $course_end = $semester->ende;
         }
@@ -243,10 +243,10 @@ class LVGroupsWizardStep implements CourseWizardStep
 
         $course = Course::findCurrent();
         if ($course) {
-            $course_start = $course->start_time;
-            $course_end = ($course->end_time < 0 || is_null($course->end_time)) ? PHP_INT_MAX : $course->end_time;
+            $course_start = $course->start_semester?->beginn ?? 0;
+            $course_end   = $course->end_semester?->ende ?? PHP_INT_MAX;
         } else {
-            $semester = Semester::findByTimestamp($_SESSION[__CLASS__]['course_start_time']);
+            $semester = Semester::find($_SESSION[__CLASS__]['course_start_semester']);
             $course_start = $semester->beginn;
             $course_end = $semester->ende;
         }
diff --git a/lib/classes/globalsearch/GlobalSearchCourses.php b/lib/classes/globalsearch/GlobalSearchCourses.php
index 1379f1a023ee92530f7368f3c033c27cf8d2abc4..cb1fbae79adc27b2e8518ace7bb4bf1a5bc88199 100644
--- a/lib/classes/globalsearch/GlobalSearchCourses.php
+++ b/lib/classes/globalsearch/GlobalSearchCourses.php
@@ -100,10 +100,12 @@ class GlobalSearchCourses extends GlobalSearchModule implements GlobalSearchFull
             }
         }
 
-        $sql = "SELECT SQL_CALC_FOUND_ROWS courses.`Seminar_id`, courses.`start_time`,
+        $sql = "SELECT SQL_CALC_FOUND_ROWS courses.`Seminar_id`,
                        {$language_name} AS `Name`,
                        courses.`VeranstaltungsNummer`, courses.`status`
                 FROM `seminare` AS courses
+                JOIN `semester_courses` ON courses.`seminar_id` = `semester_courses`.`course_id`
+                JOIN `semester` USING (`semester_id`)
                 {$language_join}
                 JOIN `seminar_user` u ON (u.`Seminar_id` = courses.`Seminar_id` AND u.`status` = 'dozent')
                 JOIN `auth_user_md5` a ON (a.`user_id` = u.`user_id`)
@@ -118,7 +120,7 @@ class GlobalSearchCourses extends GlobalSearchModule implements GlobalSearchFull
                 {$seminar_type_condition}
                 {$semester_condition}
                 GROUP BY courses.Seminar_id
-                ORDER BY start_time DESC";
+                ORDER BY `semester`.`beginn` DESC";
 
         if (Config::get()->IMPORTANT_SEMNUMBER) {
             $sql .= ", courses.`VeranstaltungsNummer`";
diff --git a/lib/extern/ExternPageCourseDetails.php b/lib/extern/ExternPageCourseDetails.php
index d9e64d9e275dce3da37729383e38adf66f88362f..05ecd24a39b1325fc991f6a367aea9a781b29a09 100644
--- a/lib/extern/ExternPageCourseDetails.php
+++ b/lib/extern/ExternPageCourseDetails.php
@@ -205,14 +205,16 @@ class ExternPageCourseDetails extends ExternPage
                 if (!$GLOBALS['MVV_MODUL']['STATUS']['values'][$modul->stat]['public']) {
                     return false;
                 }
+                $course_start = $course->start_semester->beginn ?? 0;
+                $course_end   = $course->end_semester->ende ?? PHP_INT_MAX;
                 $modul_start = Semester::find($modul->start)->beginn ?: 0;
                 $modul_end = Semester::find($modul->end)->beginn ?: PHP_INT_MAX;
-                return ($course->start_time <= $modul_end)
+                return ($course_start <= $modul_end)
                     && (
-                        ($course->start_time >= $modul_start)
+                        ($course_start >= $modul_start)
                         || $course->isOpenEnded()
-                        || $course->getEndSemester()->ende <= $modul_end
-                        || $course->getEndSemester()->ende >= $modul_start
+                        || $course_end <= $modul_end
+                        || $course_end >= $modul_start
                     );
             });
             ModuleManagementModelTreeItem::setObjectFilter('StgteilVersion', function ($version) {
diff --git a/lib/functions.php b/lib/functions.php
index 5659aa5ee625e03b8bde2b63d8d4e2b57c5c28e9..97d0e7021540dddcedaba1bdbafa99393947db63 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -652,261 +652,6 @@ function check_ticket($studipticket)
     return Seminar_Session::check_ticket($studipticket);
 }
 
-/**
- * searches
- *
- * @global array $perm
- * @global object $user
- * @global array $_fullname_sql
- *
- * @param string $search_str  optional search-string
- * @param string $search_user optional user to search for
- * @param bool   $show_sem    if true, the seminar is added to the result
- *
- * @return array
- */
-function search_range($search_str = false, $search_user = false, $show_sem = true)
-{
-    global $_fullname_sql;
-
-    // Helper function that obtains the correct name for an entity taking
-    // in account whether the semesters should be displayed or not
-    $formatName = function ($row) use ($show_sem) {
-        $name = $row['Name'];
-        if ($show_sem) {
-            $name = sprintf('%s (%s%s)',
-                            $name,
-                            $row['startsem'],
-                            $row['startsem'] != $row['endsem'] ? ' - ' . $row['endsem'] : '');
-        }
-        return $name;
-    };
-
-    $search_result = [];
-    $show_sem_sql1 = ", s.start_time, (SELECT semester_data.name FROM semester_data WHERE s.start_time >= semester_data.`beginn` AND s.start_time <= semester_data.`ende` LIMIT 1) AS startsem, IF(semester_courses.semester_id IS NULL, '"._("unbegrenzt")."', (SELECT semester_data.name FROM semester_data LEFT JOIN semester_courses USING (semester_id) WHERE semester_courses.course_id = s.Seminar_id ORDER BY semester_data.`beginn` DESC LIMIT 1)) AS endsem ";
-    $show_sem_sql2 = "LEFT JOIN semester_courses ON (semester_courses.course_id = s.Seminar_id) ";
-
-
-    if ($search_str && $GLOBALS['perm']->have_perm('root')) {
-        if ($search_user) {
-            $query = "SELECT user_id, CONCAT({$_fullname_sql['full']}, ' (', username, ')') AS name
-                      FROM auth_user_md5 AS a
-                      LEFT JOIN user_info USING (user_id)
-                      WHERE CONCAT(Vorname, ' ', Nachname, ' ', username) LIKE CONCAT('%', ?, '%')
-                      ORDER BY Nachname, Vorname";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$search_str]);
-            while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-                $search_result[$row['user_id']] = [
-                    'type' => 'user',
-                    'name' => $row['name'],
-                ];
-            }
-        }
-
-        $_hidden = _('(versteckt)');
-        $query = "SELECT Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s
-                  FROM seminare AS s %s
-                  WHERE s.Name LIKE CONCAT('%%', ?, '%%')
-                  GROUP BY s.Seminar_id
-                  ORDER BY start_time DESC, Name";
-        $query = $show_sem
-               ? sprintf($query, $show_sem_sql1, $show_sem_sql2)
-               : sprintf($query, '', '');
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$search_str]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Seminar_id']] = [
-                'type'      => 'sem',
-                'name'      => $formatName($row),
-                'starttime' => $row['start_time'],
-                'startsem'  => $row['startsem'],
-            ];
-        }
-
-        $query = "SELECT Institut_id, Name, IF(Institut_id = fakultaets_id, 'fak', 'inst') AS type
-                  FROM Institute
-                  WHERE Name LIKE CONCAT('%', ?, '%')
-                  ORDER BY Name";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$search_str]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Institut_id']] = [
-                'type' => $row['type'],
-                'name' => $row['Name'],
-            ];
-        }
-    } elseif ($search_str && $GLOBALS['perm']->have_perm('admin')) {
-        $_hidden = _('(versteckt)');
-        $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s
-                  FROM user_inst AS a
-                  JOIN seminare AS s USING (Institut_id) %s
-                  WHERE a.user_id = ? AND a.inst_perms = 'admin' AND s.Name LIKE CONCAT('%%', ?, '%%')
-                  ORDER BY start_time";
-        $query = $show_sem
-               ? sprintf($query, $show_sem_sql1, $show_sem_sql2)
-               : sprintf($query, '', '');
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$GLOBALS['user']->id, $search_str]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Seminar_id']] = [
-                'type'      => 'sem',
-                'name'      => $formatName($row),
-                'starttime' => $row['start_time'],
-                'startsem'  => $row['startsem'],
-            ];
-        }
-
-        $query = "SELECT b.Institut_id, b.Name
-                  FROM user_inst AS a
-                  JOIN Institute AS b USING (Institut_id)
-                  WHERE a.user_id = ? AND a.inst_perms = 'admin'
-                    AND a.institut_id != b.fakultaets_id AND b.Name LIKE CONCAT('%', ?, '%')
-                  ORDER BY Name";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$GLOBALS['user']->id, $search_str]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Institut_id']] = [
-                'type' => 'inst',
-                'name' => $row['Name'],
-            ];
-        }
-        if ($GLOBALS['perm']->is_fak_admin()) {
-            $_hidden = _('(versteckt)');
-            $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s
-                      FROM user_inst AS a
-                      JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id)
-                      JOIN Institute AS c ON (c.fakultaets_id = b.Institut_id AND c.fakultaets_id != c.Institut_id)
-                      JOIN seminare AS s ON (s.Institut_id = c.Institut_id) %s
-                      WHERE a.user_id = ? AND a.inst_perms = 'admin'
-                        AND s.Name LIKE CONCAT('%%', ?, '%%')
-                      ORDER BY start_time DESC, Name";
-            $query = $show_sem
-                   ? sprintf($query, $show_sem_sql1, $show_sem_sql2)
-                   : sprintf($query, '', '');
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$GLOBALS['user']->id, $search_str]);
-            while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-                $search_result[$row['Seminar_id']] = [
-                    'type'      => 'sem',
-                    'name'      => $formatName($row),
-                    'starttime' => $row['start_time'],
-                    'startsem'  => $row['startsem'],
-                ];
-            }
-
-            $query = "SELECT c.Institut_id, c.Name
-                      FROM user_inst AS a
-                      JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id)
-                      JOIN Institute AS c ON (c.fakultaets_id = b.institut_id AND c.fakultaets_id != c.institut_id)
-                      WHERE a.user_id = ? AND a.inst_perms = 'admin'
-                        AND c.Name LIKE CONCAT('%', ?, '%')
-                      ORDER BY Name";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$GLOBALS['user']->id, $search_str]);
-            while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-                $search_result[$row['Institut_id']] = [
-                    'type' => 'inst',
-                    'name' => $row['Name'],
-                ];
-            }
-
-            $query = "SELECT b.Institut_id, b.Name
-                      FROM user_inst AS a
-                      JOIN Institute AS b ON (a.Institut_id = b.Institut_id AND b.Institut_id = b.fakultaets_id)
-                      WHERE a.user_id = ? AND a.inst_perms = 'admin'
-                        AND b.Name LIKE CONCAT('%', ?, '%')
-                      ORDER BY Name";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$GLOBALS['user']->id, $search_str]);
-            while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-                $search_result[$row['Institut_id']] = [
-                    'type' => 'inst',
-                    'name' => $row['Name'],
-                ];
-            }
-        }
-    } elseif ($GLOBALS['perm']->have_perm('tutor') || $GLOBALS['perm']->have_perm('autor')) {
-        // autors my also have news in studygroups with proper rights
-        $_hidden = _('(versteckt)');
-        $query = "SELECT s.Seminar_id, IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name) AS Name %s
-                  FROM seminar_user AS a
-                  JOIN seminare AS s USING (Seminar_id) %s
-                  WHERE a.user_id = ? AND a.status IN ('tutor', 'dozent')
-                  ORDER BY start_time DESC, Name";
-        $query = $show_sem
-               ? sprintf($query, $show_sem_sql1, $show_sem_sql2)
-               : sprintf($query, '', '');
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$GLOBALS['user']->id]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Seminar_id']] = [
-                'type'      => 'sem',
-                'name'      => $formatName($row),
-                'starttime' => $row['start_time'],
-                'startsem'  => $row['startsem'],
-            ];
-        }
-
-        $query = "SELECT Institut_id, b.Name,
-                         IF (Institut_id = fakultaets_id, 'fak', 'inst') AS type
-                  FROM user_inst AS a
-                  JOIN Institute AS b USING (Institut_id)
-                  WHERE a.user_id = ? AND a.inst_perms IN ('dozent','tutor')
-                  ORDER BY Name";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$GLOBALS['user']->id]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Institut_id']] = [
-                'name' => $row['Name'],
-                'type' => $row['type'],
-            ];
-        }
-    }
-
-    if (Config::get()->DEPUTIES_ENABLE) {
-        $_hidden = _('(versteckt)');
-        $_deputy = _('Vertretung');
-        $query = "SELECT s.Seminar_id,
-                         CONCAT(IF(s.visible = 0, CONCAT(s.Name, ' {$_hidden}'), s.Name), ' [{$_deputy}]') AS Name %s
-                  FROM seminare AS s
-                  JOIN deputies AS d ON (s.Seminar_id = d.range_id) %s
-                  WHERE d.user_id = ?
-                  ORDER BY s.start_time DESC, Name";
-        $query = $show_sem
-               ? sprintf($query, $show_sem_sql1, $show_sem_sql2)
-               : sprintf($query, '', '');
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$GLOBALS['user']->id]);
-        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-            $search_result[$row['Seminar_id']] = [
-                'type'      => 'sem',
-                'name'      => $formatName($row),
-                'starttime' => $row['start_time'],
-                'startsem'  => $row['startsem'],
-            ];
-        }
-        if (Deputy::isEditActivated()) {
-            $query = "SELECT a.user_id, a.username, 'user' AS type,
-                             CONCAT({$_fullname_sql['full']}, ' (', username, ')') AS name
-                      FROM auth_user_md5 AS a
-                      JOIN user_info USING (user_id)
-                      JOIN deputies AS d ON (a.user_id = d.range_id)
-                      WHERE d.user_id = ?
-                      ORDER BY name ASC";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([
-                $GLOBALS['user']->id
-            ]);
-            while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
-                $search_result[$row['user_id']] = $row;
-            }
-        }
-    }
-
-    return $search_result ?: null;
-}
-
 /**
  * format_help_url($keyword)
  * returns URL for given help keyword
diff --git a/lib/models/Course.php b/lib/models/Course.php
index 25350dbce0b90ee38ae338fe5849312a883d1170..9607b91248d60359fe5c4be1b343697e9a842f77 100644
--- a/lib/models/Course.php
+++ b/lib/models/Course.php
@@ -25,8 +25,6 @@
  * @property string|null $sonstiges database column
  * @property int $lesezugriff database column
  * @property int $schreibzugriff database column
- * @property int|null $start_time database column
- * @property int|null $duration_time database column
  * @property I18NString|null $art database column
  * @property I18NString|null $teilnehmer database column
  * @property I18NString|null $vorrausetzungen database column
@@ -82,6 +80,16 @@
 
 class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange, Studip\Calendar\Owner
 {
+    /**
+     * @var Semester initial start semester.
+     */
+    protected $initial_start_semester;
+
+    /**
+     * @var Semester initial end semester.
+     */
+    protected $initial_end_semester;
+
     protected static function configure($config = [])
     {
         $config['db_table'] = 'seminare';
@@ -261,7 +269,6 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
 
         $config['default_values']['lesezugriff'] = 1;
         $config['default_values']['schreibzugriff'] = 1;
-        $config['default_values']['duration_time'] = 0;
 
         $config['additional_fields']['teachers'] = [
             'get' => 'getTeachers'
@@ -299,8 +306,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
         $config['i18n_fields']['leistungsnachweis'] = true;
         $config['i18n_fields']['ort'] = true;
 
-        $config['registered_callbacks']['before_update'][] = 'logStore';
-        $config['registered_callbacks']['before_store'][] = 'cbSetStartAndDurationTime';
+        $config['registered_callbacks']['before_store'][] = 'logStore';
         $config['registered_callbacks']['after_create'][] = 'setDefaultTools';
         $config['registered_callbacks']['after_delete'][] = function ($course) {
             CourseAvatar::getAvatar($course->id)->reset();
@@ -367,6 +373,31 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
         parent::configure($config);
     }
 
+    /**
+     * @param string $relation
+     */
+    public function initRelation($relation): void
+    {
+        if ($relation === 'semesters' && $this->relations[$relation] === null) {
+            parent::initRelation($relation);
+            $this->initial_start_semester = $this->getStartSemester();
+            $this->initial_end_semester = $this->getEndSemester();
+        }
+        parent::initRelation($relation);
+    }
+
+    /**
+     * Override of SimpleORMap::cbAfterInitialize for resetting the flags that indicate semester changes.
+     *
+     * @see SimpleORMap::cbAfterInitialize
+     */
+    protected function cbAfterInitialize($cb_type): void
+    {
+        parent::cbAfterInitialize($cb_type);
+        //Reset the flags for the start and end semester:
+        $this->initial_start_semester = null;
+        $this->initial_end_semester = null;
+    }
 
     /**
      * Returns the currently active course or false if none is active.
@@ -411,22 +442,6 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
         });
     }
 
-    public function getEnd_Time()
-    {
-        return $this->duration_time == -1 ? -1 : $this->start_time + $this->duration_time;
-    }
-
-    public function setEnd_Time($value)
-    {
-        if ($value == -1) {
-            $this->duration_time = -1;
-        } elseif ($this->start_time > 0 && $value > $this->start_time) {
-            $this->duration_time = $value - $this->start_time;
-        } else {
-            $this->duration_time = 0;
-        }
-    }
-
     public function _set_semester($field, $value)
     {
         $method = 'set' . ($field === 'start_semester' ? 'StartSemester' : 'EndSemester');
@@ -497,11 +512,11 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
      */
     public function getStartSemester()
     {
-        if (count($this->semesters) > 0) {
-            return $this->semesters->first();
-        } else {
-            return Semester::findCurrent();
+        //this is called by __get() and therefore using magic properties is not always safe
+        if ($this->relations['semesters'] === null) {
+            $this->initRelation('semesters');
         }
+        return $this->relations['semesters']->first() ?? Semester::findCurrent();
     }
 
     /**
@@ -512,9 +527,11 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
      */
     public function getEndSemester()
     {
-        if (count($this->semesters) > 0) {
-            return $this->semesters->last();
+        //this is called by __get() and therefore using magic properties is not always safe
+        if ($this->relations['semesters'] === null) {
+            $this->initRelation('semesters');
         }
+        return $this->relations['semesters']->last();
     }
 
     /**
@@ -2133,86 +2150,66 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
      */
     protected function logStore()
     {
-        if ($this->isFieldDirty('start_time')) {
-            //Log change of start semester:
-            StudipLog::log('SEM_SET_STARTSEMESTER', $this->id, isset($this->start_semester) ? $this->start_semester->name : _('unbegrenzt'));
-            NotificationCenter::postNotification('CourseDidChangeSchedule', $this);
-        }
-        if ($this->isFieldDirty('duration_time')) {
-            StudipLog::log('SEM_SET_ENDSEMESTER', $this->id, $this->getTextualSemester());
-            NotificationCenter::postNotification('CourseDidChangeSchedule', $this);
-        }
-
-        $log = [];
-        if ($this->isFieldDirty('admission_prelim')) {
-            $log[] = $this->admission_prelim ?  _('Neuer Anmeldemodus: Vorläufiger Eintrag') : _('Neuer Anmeldemodus: Direkter Eintrag');
-        }
+        if (!$this->isNew()) {
+            if ($this->initial_start_semester?->id !== $this->start_semester?->id) {
+                //Log change of start semester:
+                StudipLog::log('SEM_SET_STARTSEMESTER', $this->id, isset($this->start_semester) ? $this->start_semester->name : _('unbegrenzt'));
+                NotificationCenter::postNotification('CourseDidChangeSchedule', $this);
+            }
+            if ($this->initial_end_semester?->id !== $this->end_semester?->id) {
+                StudipLog::log('SEM_SET_ENDSEMESTER', $this->id, $this->getTextualSemester());
+                NotificationCenter::postNotification('CourseDidChangeSchedule', $this);
+            }
 
-        if ($this->isFieldDirty('admission_binding')) {
-            $log[] = $this->admission_binding? _('Anmeldung verbindlich') : _('Anmeldung unverbindlich');
-        }
+            $log = [];
+            if ($this->isFieldDirty('admission_prelim')) {
+                $log[] = $this->admission_prelim ? _('Neuer Anmeldemodus: Vorläufiger Eintrag') : _('Neuer Anmeldemodus: Direkter Eintrag');
+            }
 
-        if ($this->isFieldDirty('admission_turnout')) {
-            $log[] = sprintf(_('Neue Teilnehmerzahl: %s'), (int)$this->admission_turnout);
-        }
+            if ($this->isFieldDirty('admission_binding')) {
+                $log[] = $this->admission_binding ? _('Anmeldung verbindlich') : _('Anmeldung unverbindlich');
+            }
 
-        if ($this->isFieldDirty('admission_disable_waitlist')) {
-            $log[] = $this->admission_disable_waitlist ? _('Warteliste aktiviert') : _('Warteliste deaktiviert');
-        }
+            if ($this->isFieldDirty('admission_turnout')) {
+                $log[] = sprintf(_('Neue Teilnehmerzahl: %s'), (int)$this->admission_turnout);
+            }
 
-        if ($this->isFieldDirty('admission_waitlist_max')) {
-            $log[] = sprintf(_('Plätze auf der Warteliste geändert: %u'), (int)$this->admission_waitlist_max);
-        }
+            if ($this->isFieldDirty('admission_disable_waitlist')) {
+                $log[] = $this->admission_disable_waitlist ? _('Warteliste aktiviert') : _('Warteliste deaktiviert');
+            }
 
-        if ($this->isFieldDirty('admission_disable_waitlist_move')) {
-            $log[] = $this->admission_disable_waitlist ? _('Nachrücken aktiviert') : _('Nachrücken deaktiviert');
-        }
+            if ($this->isFieldDirty('admission_waitlist_max')) {
+                $log[] = sprintf(_('Plätze auf der Warteliste geändert: %u'), (int)$this->admission_waitlist_max);
+            }
 
-        if ($this->isFieldDirty('admission_prelim_txt')) {
-            if ($this->admission_prelim_txt) {
-                $log[] = sprintf(_('Neuer Hinweistext bei vorläufigen Eintragungen: %s'), strip_tags(kill_format($this->admission_prelim_txt)));
-            } else {
-                $log[] = _('Hinweistext bei vorläufigen Eintragungen wurde entfert');
+            if ($this->isFieldDirty('admission_disable_waitlist_move')) {
+                $log[] = $this->admission_disable_waitlist ? _('Nachrücken aktiviert') : _('Nachrücken deaktiviert');
             }
-        }
 
-        if (!empty($log)) {
-            StudipLog::log(
-                'SEM_CHANGED_ACCESS',
-                $this->id,
-                null,
-                '',
-                implode(' - ', $log)
-            );
-        }
+            if ($this->isFieldDirty('admission_prelim_txt')) {
+                if ($this->admission_prelim_txt) {
+                    $log[] = sprintf(_('Neuer Hinweistext bei vorläufigen Eintragungen: %s'), strip_tags(kill_format($this->admission_prelim_txt)));
+                } else {
+                    $log[] = _('Hinweistext bei vorläufigen Eintragungen wurde entfert');
+                }
+            }
 
-        if ($this->isFieldDirty('visible')) {
-            StudipLog::log($this->visible ? 'SEM_VISIBLE' : 'SEM_INVISIBLE', $this->id);
-        }
-    }
+            if (!empty($log)) {
+                StudipLog::log(
+                    'SEM_CHANGED_ACCESS',
+                    $this->id,
+                    null,
+                    '',
+                    implode(' - ', $log)
+                );
+            }
 
-    /**
-     * Called directly before storing the object to edit the columns start_time and duration_time
-     * which are both deprecated but are still in use for older plugins.
-     */
-    public function cbSetStartAndDurationTime()
-    {
-        if ($this->isFieldDirty('start_time')) {
-            $this->setStartSemester(Semester::findByTimestamp($this->start_time));
-        }
-        if ($this->isFieldDirty('duration_time')) {
-            $this->setEndSemester($this->duration_time == -1 ? null : Semester::findByTimestamp($this->start_time + $this->duration_time));
-        }
-        if ($this->isOpenEnded()) {
-            $this->start_time = $this->start_time ?: Semester::findCurrent()->beginn ?? time();
-            $this->duration_time = -1;
-        } else {
-            $this->start_time = $this->getStartSemester()->beginn;
-            $this->duration_time = $this->getEndSemester()->beginn - $this->start_time;
+            if ($this->isFieldDirty('visible')) {
+                StudipLog::log($this->visible ? 'SEM_VISIBLE' : 'SEM_INVISIBLE', $this->id);
+            }
         }
     }
 
-
     //StudipItem interface implementation:
 
     public function getItemName($long_format = true)
@@ -2342,7 +2339,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
             "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id)
              WHERE Seminar_id IN (?)
              GROUP BY seminare.Seminar_id
-             ORDER BY semester_courses.semester_id IS NULL DESC, start_time DESC, {$name_sort}",
+             ORDER BY semester_courses.semester_id IS NULL DESC, {$name_sort}",
             [$seminar_ids]
         );
     }
diff --git a/lib/models/Lvgruppe.php b/lib/models/Lvgruppe.php
index 05f3b594507d33a1a513e1a24a09406feac85977..bdd9f0b7a83eca52839f03aaecf6047dc29b018a 100644
--- a/lib/models/Lvgruppe.php
+++ b/lib/models/Lvgruppe.php
@@ -126,14 +126,10 @@ class Lvgruppe extends ModuleManagementModelTreeItem
             $semester = Semester::find($semester_id);
             if ($semester) {
                 $filter_sql = trim($filter_sql) ? $filter_sql . ' AND' : $filter_sql . ' WHERE';
-                $filter_sql .= ' seminare.start_time <= :beginn '
-                        . 'AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id) '
-                        . 'AND (start_sem.beginn <= :ende AND '
-                        . 'IF(ISNULL(end_sem.ende), 1, end_sem.ende >= :beginn)) ';
+                $filter_sql .= ' semester_courses.semester_id = :semester_id '
+                        . 'AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id) ';
                 $params = [
-                    ':semester_id' => $semester->semester_id,
-                    ':beginn' => $semester->beginn,
-                    ':ende' => $semester->ende
+                    ':semester_id' => $semester->semester_id
                 ];
                 $semester_join = 'LEFT JOIN mvv_modul ON mvv_modul.modul_id = mvv_modulteil.modul_id '
                 . 'LEFT JOIN semester_data as start_sem ON start_sem.semester_id = mvv_modul.start '
@@ -197,14 +193,10 @@ class Lvgruppe extends ModuleManagementModelTreeItem
                 }
 
                 $filter_sql = trim($filter_sql) ? $filter_sql  : ' AND';
-                $filter_sql .= ' seminare.start_time <= :beginn '
-                        . 'AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id) '
-                        . 'AND (start_sem.beginn <= :ende AND '
-                        . 'IF(ISNULL(end_sem.ende), 1, end_sem.ende >= :beginn)) ';
+                $filter_sql .= ' semester_courses.semester_id = :semester_id '
+                        . 'AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id) ';
                 $params = [
-                    ':semester_id' => $semester->semester_id,
-                    ':beginn' => $semester->beginn,
-                    ':ende' => $semester->ende
+                    ':semester_id' => $semester->semester_id
                 ];
                 $semester_join = 'LEFT JOIN mvv_modul ON mvv_modul.modul_id = mvv_modulteil.modul_id '
                 . 'LEFT JOIN semester_data as start_sem ON start_sem.semester_id = mvv_modul.start '
@@ -517,48 +509,61 @@ class Lvgruppe extends ModuleManagementModelTreeItem
      *
      * @param bool $only_visible Return only visible courses.
      * @param string $semester_id Return only this semester.
-     * @return array All assigned courses grouped by semesters.
+     * @return Course[] All assigned courses grouped by semesters.
      */
     public function getAllAssignedCourses($only_visible = false, $semester_id = null)
     {
-        $sem_start_times = [];
-
+        $conditions_sql = '';
+        if ($only_visible) {
+            $conditions_sql .= " AND `seminare`.`visible` = '1'";
+        }
         if ($semester_id) {
             $semester = Semester::find($semester_id);
             if (!$semester) {
                 return [];
             }
-            $sem_start_times[$semester->id] = $semester->beginn;
+
+            //Find only courses that lie in that semester.
+            $courses = Course::findBySQL(
+                'LEFT JOIN `semester_courses` sc
+                 ON `seminare`.`seminar_id` = sc.`course_id`
+                 JOIN `mvv_lvgruppe_seminar` mls USING (`seminar_id`)
+                 WHERE
+                 mls.`lvgruppe_id` = :group_id
+                 AND (sc.`semester_id` = :semester_id OR sc.`semester_id` IS NULL)'
+                . $conditions_sql . '
+                ORDER BY `seminare`.`Name`',
+                [
+                    'group_id'    => $this->id,
+                    'semester_id' => $semester->id
+                ]
+            );
+            return [$semester->id => $courses];
         } else {
-            $sem_start_times = SimpleORMapCollection::createFromArray(
-                    Semester::getAll())->toGroupedArray('id', 'beginn');
-            $sem_start_times = array_map(
-                    function ($sem) { return $sem['beginn']; }
-                    , $sem_start_times);
-        }
-        $visible_sql = $only_visible ? ' AND visible = 1' : '';
-        $courses = [];
-        $stmt = DBManager::get()->prepare('SELECT seminar_id, Name, '
-                . 'VeranstaltungsNummer, visible, INTERVAL(start_time,'
-                . join(',', $sem_start_times)
-                . ') AS sem_number, '
-                . 'IF(duration_time=-1,' . count($sem_start_times)
-                . ',INTERVAL(start_time+duration_time,'
-                . join(',', $sem_start_times)
-                . ')) AS sem_number_end FROM seminare '
-                . 'INNER JOIN mvv_lvgruppe_seminar USING(seminar_id) '
-                . 'WHERE lvgruppe_id = ? ' . $visible_sql
-                . ' AND start_time <= ' . end($sem_start_times)
-                . ' ORDER BY sem_number DESC, Name');
-        $stmt->execute([$this->getId()]);
-        $sem_ids = array_keys($sem_start_times);
-        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $course) {
-            if ($course['sem_number'] == 0) $course['sem_number'] = 1;
-            for ($i = $course['sem_number']; $i <= $course['sem_number_end']; $i++) {
-                $courses[$sem_ids[$i-1]][] = $course;
+            //No semester specified. Find courses from all semesters.
+            $courses = Course::findBySQL(
+                'JOIN `mvv_lvgruppe_seminar` mls USING (`seminar_id`)
+                 WHERE
+                 mls.`lvgruppe_id` = :group_id '
+                . $conditions_sql . '
+                ORDER BY `seminare`.`Name`',
+                [
+                    'group_id'    => $this->id
+                ]
+            );
+            $data = [];
+            foreach ($courses as $course) {
+                if (!$course->start_semester) {
+                    //An invalid course that cannot be grouped by a semester.
+                    continue;
+                }
+                if (!array_key_exists($course->start_semester->id, $data)) {
+                    $data[$course->start_semester->id] = [];
+                }
+                $data[$course->start_semester->id][] = $course;
             }
+            return $data;
         }
-        return $courses;
     }
 
     /**