diff --git a/ephios/core/views/event.py b/ephios/core/views/event.py index 47e276351..231efb7bc 100644 --- a/ephios/core/views/event.py +++ b/ephios/core/views/event.py @@ -393,8 +393,10 @@ def _build_day_css_context(self, events, shifts): earliest_shift_start = min(shift.start_time for shift in shifts) latest_shift_end = max(shift.end_time for shift in shifts) # calculate timescale based on shortest shift - # to not make things too small, consider a maximum of 4 hours - shortest_shift_duration_in_hours = min(4, shortest_shift_duration.total_seconds() / 3600) + # to not make things too small, consider a maximum of 4 hours and a minimum of 15 minutes + shortest_shift_duration_in_hours = max( + 0.25, min(4, shortest_shift_duration.total_seconds() / 3600) + ) # seconds per em: the shortest shift should be 3600/300 = 12em high time_scaling_factor = int(300 * shortest_shift_duration_in_hours) for shift in shifts: @@ -541,6 +543,7 @@ def setup(self, request, *args, **kwargs): self.object = self.get_object() def form_valid(self, form): + tz = get_current_timezone() occurrences = form.cleaned_data["recurrence"].between( datetime.now() - timedelta(days=1), datetime.now() + timedelta(days=7305), # allow dates up to twenty years in the future @@ -551,7 +554,7 @@ def form_valid(self, form): ) for date in occurrences: event = self.get_object() - start_date = event.get_start_time().date() + start_date = event.get_start_time().astimezone(tz).date() shifts = list(event.shifts.all()) event.pk = None event.save() @@ -579,25 +582,25 @@ def form_valid(self, form): for shift in shifts: shift.pk = None # shifts on following days should have the same offset from the new date - offset = shift.start_time.date() - start_date + offset = shift.start_time.astimezone(tz).date() - start_date # shifts ending on the next day should end on the next day to the new date - end_offset = shift.end_time.date() - shift.start_time.date() - current_tz = get_current_timezone() - shift.end_time = make_aware( - datetime.combine( - date.date() + offset + end_offset, - shift.end_time.astimezone(current_tz).time(), - ) + end_offset = ( + shift.end_time.astimezone(tz).date() - shift.start_time.astimezone(tz).date() ) - shift.meeting_time = make_aware( - datetime.combine( - date.date() + offset, shift.meeting_time.astimezone(current_tz).time() - ) + shift.meeting_time = datetime.combine( + date.date() + offset, + shift.meeting_time.astimezone(tz).time(), + tzinfo=tz, ) - shift.start_time = make_aware( - datetime.combine( - date.date() + offset, shift.start_time.astimezone(current_tz).time() - ) + shift.start_time = datetime.combine( + date.date() + offset, + shift.start_time.astimezone(tz).time(), + tzinfo=tz, + ) + shift.end_time = datetime.combine( + date.date() + offset + end_offset, + shift.end_time.astimezone(tz).time(), + tzinfo=tz, ) shift.event = event shifts_to_create.append(shift) diff --git a/tests/core/test_event_copy.py b/tests/core/test_event_copy.py index 3e5c73471..12a1c8766 100644 --- a/tests/core/test_event_copy.py +++ b/tests/core/test_event_copy.py @@ -1,7 +1,8 @@ -from datetime import datetime, timedelta +from datetime import datetime, time, timedelta import recurrence from django.urls import reverse +from django.utils import timezone from guardian.shortcuts import assign_perm from ephios.core.models import Event, Shift @@ -145,3 +146,33 @@ def test_event_to_next_day_copy(self, django_app, planner, event_to_next_day, gr assert planners in get_groups_with_perms( shift.event, only_with_perms_in=["change_event"] ) + + def test_copy_overnight_event_with_times_close_to_midnight( + self, django_app, planner, event, tz + ): + original_shift = event.shifts.first() + original_shift.start_time = datetime.combine( + original_shift.start_time.date(), time(hour=23, minute=30), tzinfo=tz + ) + original_shift.end_time = datetime.combine( + original_shift.start_time.date() + timedelta(days=1), time(hour=0, minute=30), tzinfo=tz + ) + original_shift.save() + response = django_app.get(reverse("core:event_copy", kwargs={"pk": event.id}), user=planner) + form = response.form + target_starttime = timezone.now() + timedelta(days=14) + recurr = recurrence.Recurrence( + dtstart=target_starttime, + rdates=[], + ) + form["start_date"] = target_starttime.date() + form["recurrence"] = str(recurr) + form.submit() + copied_shift = Shift.objects.exclude(event=event).get() + assert copied_shift.start_time.astimezone(tz).date() == target_starttime.date() + assert copied_shift.start_time.astimezone(tz).time() == original_shift.start_time.time() + assert ( + copied_shift.end_time.astimezone(tz).date() + == (target_starttime + timedelta(days=1)).date() + ) + assert copied_shift.end_time.astimezone(tz).time() == original_shift.end_time.time()