diff --git a/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py b/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
index b19b735b07e76d68ea99ced2bcc57f565bf16da5..700556966058fb2fb0046e8409b65f5f85b1d131 100644
--- a/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
+++ b/SAS/TMSS/services/scheduling/lib/constraints/template_constraints_v1.py
@@ -38,15 +38,19 @@ from . import ScoredSchedulingUnit
 def can_run_within_timewindow(scheduling_unit: models.SchedulingUnitBlueprint, lower_bound: datetime, upper_bound: datetime) -> bool:
     '''determine if the given scheduling_unit can run withing the given timewindow evaluating all constraints from the "constraints" version 1 template'''
     if has_manual_scheduler_constraint(scheduling_unit):
+        logger.info("### SchedulingUnitBlueprint id=%s has manual scheduler constraint and cannot be dynamically scheduled." % (scheduling_unit.id))
         return False
 
     if not can_run_within_timewindow_with_time_constraints(scheduling_unit, lower_bound, upper_bound):
+        logger.info("### SchedulingUnitBlueprint id=%s does not meet time constraints between %s and %s." % (scheduling_unit.id, lower_bound, upper_bound))
         return False
 
     if not can_run_within_timewindow_with_sky_constraints(scheduling_unit, lower_bound, upper_bound):
+        logger.info("### SchedulingUnitBlueprint id=%s does not meet sky constraints between %s and %s." % (scheduling_unit.id, lower_bound, upper_bound))
         return False
 
     if not can_run_within_timewindow_with_daily_constraints(scheduling_unit, lower_bound, upper_bound):
+        logger.info("### SchedulingUnitBlueprint id=%s does not meet daily constraints between %s and %s." % (scheduling_unit.id, lower_bound, upper_bound))
         return False
 
     return True
@@ -74,38 +78,55 @@ def has_manual_scheduler_constraint(scheduling_unit: models.SchedulingUnitBluepr
 def can_run_within_timewindow_with_daily_constraints(scheduling_unit: models.SchedulingUnitBlueprint, lower_bound: datetime, upper_bound: datetime) -> bool:
     '''evaluate the daily contraint'''
     constraints = scheduling_unit.draft.scheduling_constraints_doc
-    if not (constraints['daily']['require_day'] and constraints['daily']['require_night'] and constraints['daily']['avoid_twilight']):
-        # no day/night restrictions, can run any time
-        return True
-
     if constraints['daily']['require_day'] or constraints['daily']['require_night'] or constraints['daily']['avoid_twilight']:
 
-        # compute some timestamps over observation time
-        timesteps = 2 + 3 * (lower_bound - upper_bound).days
-        delta = (upper_bound - lower_bound) / timesteps
-        timestamps = [lower_bound + n * delta for n in range(timesteps + 1)]
+        if (upper_bound - lower_bound).days >= 1:
+            logger.info("### SchedulingUnitBlueprint id=%s has daily constraints, but bounds span %s" % (scheduling_unit.id, (upper_bound - lower_bound)))
+            return False
+
+        if upper_bound < lower_bound:
+            raise ValueError("Provided upper_bound=%s is earlier than provided lower_bound=%s" % (upper_bound, lower_bound))
 
-        # get day/night for all timestamps
         stations = scheduling_unit.requirements_doc['tasks']['Observation']['specifications_doc']['stations']
 
         # check contraint and return false on first failure
         for station in stations:
-            sun_events = timestamps_and_stations_to_sun_rise_and_set(timestamps=timestamps, stations=(station,))[station]
+            # get day/night times for bounds
+            # we could sample in between bounds, but will instead do some checks
+            if constraints['daily']['require_day'] and lower_bound.date() != upper_bound.date():
+                logger.info("### SchedulingUnitBlueprint id=%s cannot meet require_day constraint when starting and ending on different days." % scheduling_unit.id)
+                return False
+            timestamps = [lower_bound, upper_bound]
+            sun_events = timestamps_and_stations_to_sun_rise_and_set(timestamps=tuple(timestamps), stations=(station,))[station]
             if constraints['daily']['require_day']:
-                for i in range(timesteps + 1):
+                for i in range(len(timestamps)):
                     if timestamps[i] < sun_events['day'][i]['start'] or timestamps[i] > sun_events['day'][i]['end']:
+                        logger.info("### %s not between %s and %s" % (timestamps[i], sun_events['day'][i]['start'], sun_events['day'][i]['end']))
+                        logger.info("### SchedulingUnitBlueprint id=%s does not meet require_day constraint at timestamp=%s" % (scheduling_unit.id, timestamps[i]))
                         return False
+                    else:
+                        logger.info("### %s between %s and %s" % (timestamps[i], sun_events['day'][i]['start'], sun_events['day'][i]['end'] ))
 
             if constraints['daily']['require_night']:
-                for i in range(timesteps + 1):
+                if sun_events['night'][0]['start'].date() != sun_events['night'][1]['start'].date():
+                    logger.info("### SchedulingUnitBlueprint id=%s cannot meet require_night constraint when starting and ending in different nights." % scheduling_unit.id)
+                    return False
+                for i in range(len(timestamps)):
                     if timestamps[i] < sun_events['night'][i]['start'] or timestamps[i] > sun_events['night'][i]['end']:
+                        logger.info("### SchedulingUnitBlueprint id=%s does not meet require_night constraint at timestamp=%s" % (scheduling_unit.id, timestamps[i]))
                         return False
 
             if constraints['daily']['avoid_twilight']:
-                for i in range(timesteps + 1):
-                    if timestamps[i] < sun_events['day'][i]['start'] or (timestamps[i] > sun_events['day'][i]['end'] and timestamps[i] < sun_events['night'][i]['start']):  # todo: I guess we have to consider previous night here
-                        return False
+                # Note: the same index for sun_events everywhere is not a typo, but to make sure it's the _same_ night or day for both bounds or obs will span over twilight
+                if not (timestamps[0] > sun_events['day'][0]['start'] and timestamps[0] < sun_events['day'][0]['end'] and
+                        timestamps[1] > sun_events['day'][0]['start'] and timestamps[1] < sun_events['day'][0]['end']) or \
+                       (timestamps[0] > sun_events['night'][0]['start'] and timestamps[0] < sun_events['night'][0]['end'] and
+                        timestamps[1] > sun_events['night'][0]['start'] and timestamps[1] < sun_events['night'][0]['end']):
+                    logger.info("### SchedulingUnitBlueprint id=%s does not meet avoid_twilight constraint." % scheduling_unit.id)
+                    return False
+
 
+    logger.info('### SchedulingUnitBlueprint id=%s meets all daily constraints. Returning True.')
     return True
 
 
@@ -134,6 +155,7 @@ def can_run_within_timewindow_with_time_constraints(scheduling_unit: models.Sche
 
 def can_run_within_timewindow_with_sky_constraints(scheduling_unit: models.SchedulingUnitBlueprint, lower_bound: datetime, upper_bound: datetime) -> bool:
     '''evaluate the time contraint(s)'''
+    logger.info('### can_run_within_timewindow_with_sky_constraints called with lower_bound=%s upper_bound=%s '% (lower_bound, upper_bound))
     constraints = scheduling_unit.draft.scheduling_constraints_doc
     # TODO: TMSS-245 TMSS-250 (and more?), evaluate the constraints in constraints['sky']
     # maybe even split this method into sub methods for the very distinct sky constraints: min_calibrator_elevation, min_target_elevation, transit_offset & min_distance
@@ -143,12 +165,13 @@ def can_run_within_timewindow_with_sky_constraints(scheduling_unit: models.Sched
     angle2 = beam['angle2']
     direction_type = beam['direction_type']
     if "sky" in constraints and 'min_distance' in constraints['sky']:
+        # currently we only check at bounds, we probably want to add some more samples in between later on
         distances = coordinates_and_timestamps_to_separation_from_bodies(angle1=angle1, angle2=angle2, direction_type=direction_type, timestamps=(lower_bound, upper_bound), bodies=tuple(constraints['sky']['min_distance'].keys()))
-        for body, timestamps in distances.items():
+        for body, min_distance in constraints['sky']['min_distance'].items():
+            timestamps = distances[body]
             for timestamp, angle in timestamps.items():
-                min_distance = constraints['sky']['min_distance'][body]
                 if angle.rad < min_distance:
-                    logger.info('Distance=%s from body=%s does not meet min_distance=%s constraint at timestamp=%s' % (angle.rad, body, min_distance, timestamp))
+                    logger.info('### Distance=%s from body=%s does not meet min_distance=%s constraint at timestamp=%s' % (angle.rad, body, min_distance, timestamp))
                     return False
 
     return True
@@ -212,7 +235,6 @@ def get_earliest_possible_start_time(scheduling_unit: models.SchedulingUnitBluep
                         continue
                     start_time_per_station[station] = next_day['start']
                     continue
-            logger.info('#### %s' % start_time_per_station)
             return max(start_time_per_station.values())
     except Exception as e:
         logger.exception(str(e))
diff --git a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
index 2dedb92c2270ad04c9905faef92e79950f0dad37..799f40bfe73c33fdfbf52fede5effd4d2bed92ab 100755
--- a/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
+++ b/SAS/TMSS/services/scheduling/test/t_dynamic_scheduling.py
@@ -271,7 +271,7 @@ class TestDynamicScheduling(unittest.TestCase):
         self.assertGreaterEqual(scheduling_unit_blueprint_high.start_time - scheduling_unit_blueprint_manual.stop_time, DEFAULT_INTER_OBSERVATION_GAP)
 
 
-class TestSchedulingConstraints(unittest.TestCase):
+class TestDailyConstraints(unittest.TestCase):
     '''
     Tests for the constraint checkers used in dynamic scheduling
     '''
@@ -280,14 +280,15 @@ class TestSchedulingConstraints(unittest.TestCase):
         # scheduling unit
         self.obs_duration = 120 * 60
         scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data())
-        scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for contraints tests",
+        scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:],
                                                                                                 scheduling_set=scheduling_set,
                                                                                                 obs_duration=self.obs_duration)
         self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft)
 
         # mock out conversions for speedup and assertable timestamps
+        # earliest_start_time requests timestamp and timestamp+1day
         self.sunrise_data = {
-            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)},{"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
                       "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}],
                       "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}],
                       "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]},
@@ -295,20 +296,53 @@ class TestSchedulingConstraints(unittest.TestCase):
                       "day": [{"start": datetime(2020, 1, 1, 9, 45, 0), "end": datetime(2020, 1, 1, 15, 45, 0)}, {"start": datetime(2020, 1, 2, 9, 45, 0), "end": datetime(2020, 1, 2, 15, 45, 0)}],
                       "sunset": [{"start": datetime(2020, 1, 1, 15, 45, 0), "end": datetime(2020, 1, 1, 17, 45, 0)}, {"start": datetime(2020, 1, 2, 15, 45, 0), "end": datetime(2020, 1, 2, 17, 45, 0)}],
                       "night": [{"start": datetime(2020, 1, 1, 17, 45, 0), "end": datetime(2020, 1, 2, 7, 45, 0)}, {"start": datetime(2020, 1, 2, 17, 45, 0), "end": datetime(2020, 1, 3, 7, 45, 0)}]}}
+
+        # variant for timestamp before sunrise, which returns the previous night
+        self.sunrise_data_early_night = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}],
+                      "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}}
+
+
+        # constraint checker requests lower and upper bound, so we need some variants for various cases
+        self.sunrise_data_early_night_early_night = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}],
+                      "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 1, 7, 30, 0)}]}}
+
+        self.sunrise_data_early_night_late_night = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}],
+                      "night": [{"start": datetime(2019, 12, 31, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}}
+
+        self.sunrise_data_late_night_late_night = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)}],
+                      "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}}
+
+        self.sunrise_data_late_night_early_night_next_day = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}],
+                      "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}]}}
+
+        self.sunrise_data_late_night_late_night_next_day = {
+            'CS001': {"sunrise": [{"start": datetime(2020, 1, 1, 7, 30, 0), "end": datetime(2020, 1, 1, 9, 30, 0)}, {"start": datetime(2020, 1, 2, 7, 30, 0), "end": datetime(2020, 1, 2, 9, 30, 0)}],
+                      "day": [{"start": datetime(2020, 1, 1, 9, 30, 0), "end": datetime(2020, 1, 1, 15, 30, 0)}, {"start": datetime(2020, 1, 2, 9, 30, 0), "end": datetime(2020, 1, 2, 15, 30, 0)}],
+                      "sunset": [{"start": datetime(2020, 1, 1, 15, 30, 0), "end": datetime(2020, 1, 1, 17, 30, 0)},{"start": datetime(2020, 1, 2, 15, 30, 0), "end": datetime(2020, 1, 2, 17, 30, 0)}],
+                      "night": [{"start": datetime(2020, 1, 1, 17, 30, 0), "end": datetime(2020, 1, 2, 7, 30, 0)}, {"start": datetime(2020, 1, 2, 17, 30, 0), "end": datetime(2020, 1, 3, 7, 30, 0)}]}}
+
+
         self.sunrise_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.timestamps_and_stations_to_sun_rise_and_set')
         self.sunrise_mock = self.sunrise_patcher.start()
         self.sunrise_mock.return_value = self.sunrise_data
         self.addCleanup(self.sunrise_patcher.stop)
 
-        self.distance_data = {
-           "sun": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.3rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.35rad")},
-           "moon": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.2rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.25rad")},
-           "jupiter": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.1rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.15rad")}
-        }
-        self.distance_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_and_timestamps_to_separation_from_bodies')
-        self.distance_mock = self.distance_patcher.start()
-        self.distance_mock.return_value = self.distance_data
-        self.addCleanup(self.distance_patcher.stop)
+    # require_day
 
     def test_get_earliest_possible_start_time_with_daytime_constraint_returns_day_start(self):
         self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
@@ -346,13 +380,186 @@ class TestSchedulingConstraints(unittest.TestCase):
         returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
         self.assertEqual(returned_time, self.sunrise_data['CS001']['day'][1]['start'])
 
-    # todo: add tests for can_run_within_timewindow_with_daily_constraints
+    def test_can_run_within_timewindow_with_daytime_constraint_returns_true(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {} # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
+        self.scheduling_unit_blueprint.save()
 
-    # todo: add more daytime checks with 255
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 10, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 15, 0, 0)
+        self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
 
-    # todo: add nighttime checks with 254
+    def test_can_run_within_timewindow_with_daytime_constraint_returns_false_when_not_daytime(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {} # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
+        self.scheduling_unit_blueprint.save()
+
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 20, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 23, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+    def test_can_run_within_timewindow_with_daytime_constraint_returns_false_when_partially_not_daytime(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {} # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_day'] = True
+        self.scheduling_unit_blueprint.save()
+
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 10, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 18, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night
+        lower_bound = datetime(2020, 1, 1, 8, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 10, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+    # require_night
+
+    def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 14, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, self.sunrise_data['CS001']['night'][0]['start'])
+
+    def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_night_start_of_latest_station(self):
+        self.scheduling_unit_blueprint.requirements_doc['tasks']['Observation']['specifications_doc']['stations'] = ['CS001', 'DE601']
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+        timestamp = datetime(2020, 1, 1, 14, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, self.sunrise_data['DE601']['night'][0]['start'])
+
+    def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_timestamp(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+
+        # late night
+        timestamp = datetime(2020, 1, 1, 23, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, timestamp)
+
+        # early night
+        self.sunrise_mock.return_value = self.sunrise_data_early_night
+        timestamp = datetime(2020, 1, 1, 3, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, timestamp)
+
+    def test_get_earliest_possible_start_time_with_nighttime_constraint_returns_next_night_start_when_obs_does_not_fit(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+
+        # early night
+        self.sunrise_mock.return_value = self.sunrise_data_early_night
+        timestamp = datetime(2020, 1, 1, 6, 0, 0)
+        returned_time = get_earliest_possible_start_time(self.scheduling_unit_blueprint, timestamp)
+        self.assertEqual(returned_time, self.sunrise_data_early_night['CS001']['night'][1]['start'])
+
+    def test_can_run_within_timewindow_with_nighttime_constraint_returns_true(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {}  # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+
+        # early night
+        self.sunrise_mock.return_value = self.sunrise_data_early_night_early_night
+        lower_bound = datetime(2020, 1, 1, 1, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 3, 0, 0)
+        self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # late night
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 20, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 23, 0, 0)
+        self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # night-night next day
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day
+        lower_bound = datetime(2020, 1, 1, 23, 0, 0)
+        upper_bound = datetime(2020, 1, 2, 3, 0, 0)
+        self.assertTrue(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+    def test_can_run_within_timewindow_with_nighttime_constraint_returns_false_when_not_nighttime(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {}  # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 10, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 14, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+    def test_can_run_within_timewindow_with_nighttime_constraint_returns_false_when_partially_not_nighttime(self):
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {}  # remove sky constraint
+        self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['daily']['require_night'] = True
+        self.scheduling_unit_blueprint.save()
+
+        # night-day next day
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day
+        lower_bound = datetime(2020, 1, 1, 23, 0, 0)
+        upper_bound = datetime(2020, 1, 2, 10, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # day-night next day
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_early_night_next_day
+        lower_bound = datetime(2020, 1, 1, 14, 0, 0)
+        upper_bound = datetime(2020, 1, 2, 3, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # day-night same day
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night
+        lower_bound = datetime(2020, 1, 1, 14, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 20, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # night-day same day
+        self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night
+        lower_bound = datetime(2020, 1, 1, 3, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 10, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # day-night-day
+        self.sunrise_mock.return_value = self.sunrise_data_late_night_late_night_next_day
+        lower_bound = datetime(2020, 1, 1, 14, 0, 0)
+        upper_bound = datetime(2020, 1, 2, 10, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+        # night-day-night
+        self.sunrise_mock.return_value = self.sunrise_data_early_night_late_night
+        lower_bound = datetime(2020, 1, 1, 3, 0, 0)
+        upper_bound = datetime(2020, 1, 1, 23, 0, 0)
+        self.assertFalse(can_run_within_timewindow(self.scheduling_unit_blueprint, lower_bound, upper_bound))
+
+    # todo: avoid_twilight checks / TMSS-256
+
+
+class TestSkyConstraints(unittest.TestCase):
+    '''
+    Tests for the constraint checkers used in dynamic scheduling
+    '''
+
+    def setUp(self) -> None:
+        # scheduling unit
+        self.obs_duration = 120 * 60
+        scheduling_set = models.SchedulingSet.objects.create(**SchedulingSet_test_data())
+        scheduling_unit_draft = TestDynamicScheduling.create_simple_observation_scheduling_unit("scheduling unit for ...%s" % self._testMethodName[30:],
+                                                                                                scheduling_set=scheduling_set,
+                                                                                                obs_duration=self.obs_duration)
+        self.scheduling_unit_blueprint = create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft)
+
+        # mock out conversions for speedup and assertable timestamps
+        self.distance_data = {
+           "sun": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.3rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.35rad")},
+           "moon": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.2rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.25rad")},
+           "jupiter": {datetime(2020, 1, 1, 10, 0, 0): Angle("0.1rad"), datetime(2020, 1, 1, 12, 0, 0): Angle("0.15rad")}
+        }
+        self.distance_patcher = mock.patch('lofar.sas.tmss.services.scheduling.constraints.template_constraints_v1.coordinates_and_timestamps_to_separation_from_bodies')
+        self.distance_mock = self.distance_patcher.start()
+        self.distance_mock.return_value = self.distance_data
+        self.addCleanup(self.distance_patcher.stop)
 
-    # todo: add twilight checks with 256
+    # min_distance
 
     def test_can_run_within_timewindow_with_min_distance_constraint_returns_true_when_met(self):
         self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['sky']['min_distance'] = {'sun': 0.1, 'moon': 0.1, 'jupiter': 0.1}
diff --git a/SAS/TMSS/src/tmss/tmssapp/conversions.py b/SAS/TMSS/src/tmss/tmssapp/conversions.py
index af5d004637c17f20118bd660e4e761b22fef288a..335b8937493b5c3e26fa9f0a80b798bee31107c0 100644
--- a/SAS/TMSS/src/tmss/tmssapp/conversions.py
+++ b/SAS/TMSS/src/tmss/tmssapp/conversions.py
@@ -30,7 +30,9 @@ SUN_SET_RISE_PRECISION = 30  # n_grid_points; higher is more precise but very co
 @lru_cache(maxsize=256, typed=False)  # does not like lists, so use tuples to allow caching
 def timestamps_and_stations_to_sun_rise_and_set(timestamps: tuple, stations: tuple, angle_to_horizon: Angle=SUN_SET_RISE_ANGLE_TO_HORIZON) -> dict:
     """
-    compute sunrise, sunset, day and night of the given stations at the given timestamps
+    Compute sunrise, sunset, day and night of the given stations at the given timestamps.
+    The day/sunrise/sunset is always on the date of the timestamp.
+    The night is usually the one _starting_ on the date of the time stamp, unless the given timestamp falls before sunrise, in which case it is the night _ending_ on the timestamp date.
     :param timestamps: tuple of datetimes, e.g. (datetime(2020, 1, 1), datetime(2020, 1, 2))
     :param stations: tuple of station names, e.g. ("CS002",)
     :return A dict that maps station names to a nested dict that contains lists of start and end times for sunrise, sunset, etc, on each requested date.
@@ -50,24 +52,27 @@ def timestamps_and_stations_to_sun_rise_and_set(timestamps: tuple, stations: tup
     return_dict = {}
     for station in stations:
         for timestamp in timestamps:
+            # todo: this can probably be made faster by moving the following logic to an own function with single station/timestamp as input and putting the lru_cache on there.
+            #  This also means that we have to strip the time from the datetime. Can this be safely done?
             observer = create_astroplan_observer_for_station(station)
-            sunrise_start = observer.sun_rise_time(time=Time(timestamp), which='previous', n_grid_points=SUN_SET_RISE_PRECISION)
-            if sunrise_start.to_datetime().date() < timestamp.date():
-                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='nearest', n_grid_points=SUN_SET_RISE_PRECISION)
-            if sunrise_start.to_datetime().date() < timestamp.date():
-                sunrise_start = observer.sun_rise_time(time=Time(timestamp), horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
-            sunrise_end = observer.sun_rise_time(time=Time(timestamp), horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunrise_start = observer.sun_rise_time(time=Time(datetime.combine(timestamp.date(), dtime(12,0,0))), which='previous', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunrise_end = observer.sun_rise_time(time=Time(sunrise_start), horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
             sunset_start = observer.sun_set_time(time=sunrise_end, horizon=angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
-            sunset_end = observer.sun_set_time(time=sunrise_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
-            sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+            sunset_end = observer.sun_set_time(time=sunset_start, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
             return_dict.setdefault(station, {}).setdefault("sunrise", []).append({"start": sunrise_start.to_datetime(), "end": sunrise_end.to_datetime()})
             return_dict[station].setdefault("sunset", []).append({"start": sunset_start.to_datetime(), "end": sunset_end.to_datetime()})
             return_dict[station].setdefault("day", []).append({"start": sunrise_end.to_datetime(), "end": sunset_start.to_datetime()})
-            return_dict[station].setdefault("night", []).append({"start": sunset_end.to_datetime(), "end": sunrise_next_start.to_datetime()})
+            if timestamp >= sunrise_start:
+                sunrise_next_start = observer.sun_rise_time(time=sunset_end, horizon=-angle_to_horizon, which='next', n_grid_points=SUN_SET_RISE_PRECISION)
+                return_dict[station].setdefault("night", []).append({"start": sunset_end.to_datetime(), "end": sunrise_next_start.to_datetime()})
+            else:
+                sunset_previous_end = observer.sun_set_time(time=sunrise_start, horizon=-angle_to_horizon, which='previous', n_grid_points=SUN_SET_RISE_PRECISION)
+                return_dict[station].setdefault("night", []).append({"start": sunset_previous_end.to_datetime(), "end": sunrise_start.to_datetime()})
+
     return return_dict
 
 
-# Depending on usage patterns, we should consider refactoring this a little so that we cache on a function with a single timestamp as input. Requests with similar (but not identical) timestamps or bodies currently make no use of cached results for the subset computed in previous requests.
+# todo: Depending on usage patterns, we should consider refactoring this a little so that we cache on a function with a single timestamp as input. Requests with similar (but not identical) timestamps or bodies currently make no use of cached results for the subset computed in previous requests.
 @lru_cache(maxsize=256, typed=False)  # does not like lists, so use tuples to allow caching
 def coordinates_and_timestamps_to_separation_from_bodies(angle1: float, angle2: float, direction_type: str, timestamps: tuple, bodies: tuple) -> dict:
     """
diff --git a/SAS/TMSS/src/tmss/tmssapp/views.py b/SAS/TMSS/src/tmss/tmssapp/views.py
index 3c927861dc1153f3563613e4696b8f7d1f5565f6..928b45a71966142f32f46911a1f77f9bb65e1c6b 100644
--- a/SAS/TMSS/src/tmss/tmssapp/views.py
+++ b/SAS/TMSS/src/tmss/tmssapp/views.py
@@ -167,7 +167,6 @@ def get_sun_rise_and_set(request):
     else:
         stations = tuple(stations.split(','))
 
-    # todo: to improve speed for the frontend, we should probably precompute/cache these and return those (where available), to revisit after constraint table / TMSS-190 is done
     return JsonResponse(timestamps_and_stations_to_sun_rise_and_set(timestamps, stations))
 
 
diff --git a/SAS/TMSS/test/t_conversions.py b/SAS/TMSS/test/t_conversions.py
index f153900312eac5e6ebab6a268c80386892983c26..18865051aecdd7bd80946e68ef80487e58f8b815 100755
--- a/SAS/TMSS/test/t_conversions.py
+++ b/SAS/TMSS/test/t_conversions.py
@@ -165,6 +165,38 @@ class UtilREST(unittest.TestCase):
             response_date = dateutil.parser.parse(r_dict['CS002']['sunrise'][i]['start']).date()
             self.assertEqual(expected_date, response_date)
 
+    def test_util_sun_rise_and_set_returns_correct_date_of_day_sunrise_and_sunset(self):
+        timestamps = ['2020-01-01T02-00-00']
+        r = requests.get(BASE_URL + '/util/sun_rise_and_set?timestamps=%s' % ','.join(timestamps), auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        r_dict = json.loads(r.content.decode('utf-8'))
+
+        # assert day of timestamp matches day of returned values
+        expected_date = dateutil.parser.parse(timestamps[0]).date()
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['sunrise'][0]['start']).date())
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['sunrise'][0]['end']).date())
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['day'][0]['start']).date())
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['day'][0]['end']).date())
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['sunset'][0]['start']).date())
+        self.assertEqual(expected_date, dateutil.parser.parse(r_dict['CS002']['sunset'][0]['end']).date())
+
+    def test_util_sun_rise_and_set_returns_correct_date_of_night(self):
+        timestamps = ['2020-01-01T02-00-00', '2020-01-01T12-00-00']
+        r = requests.get(BASE_URL + '/util/sun_rise_and_set?timestamps=%s' % ','.join(timestamps), auth=AUTH)
+        self.assertEqual(r.status_code, 200)
+        r_dict = json.loads(r.content.decode('utf-8'))
+
+        # assert timestamp before sunrise returns night ending on day of timestamp (last night)
+        expected_date = dateutil.parser.parse(timestamps[0]).date()
+        response_date = dateutil.parser.parse(r_dict['CS002']['night'][0]['end']).date()
+        self.assertEqual(expected_date, response_date)
+
+        # assert timestamp after sunrise returns night starting on day of timestamp (next night)
+        expected_date = dateutil.parser.parse(timestamps[1]).date()
+        response_date = dateutil.parser.parse(r_dict['CS002']['night'][1]['start']).date()
+        self.assertEqual(expected_date, response_date)
+
+
     def test_util_angular_separation_from_bodies_yields_error_when_no_pointing_is_given(self):
         r = requests.get(BASE_URL + '/util/angular_separation_from_bodies', auth=AUTH)