From 506705a7252487e89d3d1094c1087df9a4c82c73 Mon Sep 17 00:00:00 2001 From: Thomas Kurz Date: Thu, 21 Dec 2023 15:35:53 +0100 Subject: [PATCH] 105 store correct start date --- .../data/model/scheduler/RelativeDate.java | 17 +++++++++- .../more/data/repository/StudyRepository.java | 19 +++++++---- .../more/data/schedule/SchedulerUtils.java | 19 ++++++++++- .../more/data/service/CalendarService.java | 7 +++- .../data/schedule/SchedulerUtilsTest.java | 32 +++++++++++++++++++ 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/redlink/more/data/model/scheduler/RelativeDate.java b/src/main/java/io/redlink/more/data/model/scheduler/RelativeDate.java index a83a502..d6e660c 100644 --- a/src/main/java/io/redlink/more/data/model/scheduler/RelativeDate.java +++ b/src/main/java/io/redlink/more/data/model/scheduler/RelativeDate.java @@ -1,18 +1,24 @@ package io.redlink.more.data.model.scheduler; +import java.time.ZoneId; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RelativeDate { - private static Pattern CLOCK = Pattern.compile("(\\d\\d):(\\d\\d)"); + private static Pattern CLOCK = Pattern.compile("(\\d?\\d):(\\d\\d)"); private Duration offset; private String time; + private String timezone; public RelativeDate() { } + public ZoneId getZoneId() { + return timezone != null ? ZoneId.of(timezone) : ZoneId.of("Europe/Berlin"); + } + public int getHours() { return getTimeGroup(1); } @@ -50,4 +56,13 @@ public RelativeDate setTime(String time) { this.time = time; return this; } + + public String getTimezone() { + return timezone; + } + + public RelativeDate setTimezone(String timezone) { + this.timezone = timezone; + return this; + } } diff --git a/src/main/java/io/redlink/more/data/repository/StudyRepository.java b/src/main/java/io/redlink/more/data/repository/StudyRepository.java index 4fe4e67..7e5c415 100644 --- a/src/main/java/io/redlink/more/data/repository/StudyRepository.java +++ b/src/main/java/io/redlink/more/data/repository/StudyRepository.java @@ -9,7 +9,6 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -250,16 +249,24 @@ public Optional createCredentials(String registrationToken, ParticipantC if (apiId != null) { jdbcTemplate.update(SQL_CLEAR_TOKEN, registrationToken); - updateParticipantStatus(routingInfo.studyId(), routingInfo.participantId(), "new", "active"); + updateParticipantStatus(routingInfo.studyId(), routingInfo.studyGroupId().orElse(0), routingInfo.participantId(), "new", "active"); return Optional.of(apiId); } throw new IllegalStateException("Creating API-Credentials failed!"); } - private void updateParticipantStatus(long studyId, int particpantId, String oldStatus, String newStatus) { + private void updateParticipantStatus(long studyId, int groupId, int participantId, String oldStatus, String newStatus) { + Timestamp start = null; + + if ("active".equals(newStatus)) { + start = Timestamp.from( + SchedulerUtils.shiftStartIfObservationAlreadyStarted(Instant.now(), listObservations(studyId, groupId, participantId, true)) + ); + } + namedTemplate.update(SQL_SET_PARTICIPANT_STATUS, - toParameterSource(studyId, particpantId) - .addValue("start", "active".equals(newStatus) ? Timestamp.valueOf(LocalDateTime.now()) : null) + toParameterSource(studyId, participantId) + .addValue("start", start) .addValue("oldStatus", oldStatus) .addValue("newStatus", newStatus) ); @@ -288,7 +295,7 @@ public void clearCredentials(String apiId) { final long studyId = rs.getLong("study_id"); final int participantId = rs.getInt("participant_id"); withdrawConsent(studyId, participantId); - updateParticipantStatus(studyId, participantId, + updateParticipantStatus(studyId, participantId, 0, "active", "abandoned"); } ); diff --git a/src/main/java/io/redlink/more/data/schedule/SchedulerUtils.java b/src/main/java/io/redlink/more/data/schedule/SchedulerUtils.java index b0ad64e..ff67cd9 100644 --- a/src/main/java/io/redlink/more/data/schedule/SchedulerUtils.java +++ b/src/main/java/io/redlink/more/data/schedule/SchedulerUtils.java @@ -13,6 +13,7 @@ import biweekly.util.Frequency; import biweekly.util.Recurrence; import biweekly.util.com.google.ical.compat.javautil.DateIterator; +import io.redlink.more.data.model.Observation; import io.redlink.more.data.model.scheduler.*; import org.apache.commons.lang3.tuple.Pair; @@ -62,7 +63,7 @@ private static Instant shiftStartIfNecessary(Instant start) { } private static Instant toInstant(RelativeDate date, Instant start) { - return ZonedDateTime.ofInstant(start.plus(date.getOffset().getValue() - 1L, date.getOffset().getUnit().toTemporalUnit()), ZoneId.systemDefault()) + return ZonedDateTime.ofInstant(start.plus(date.getOffset().getValue() - 1L, date.getOffset().getUnit().toTemporalUnit()), date.getZoneId()) .withHour(date.getHours()) .withMinute(date.getMinutes()) .withSecond(0) @@ -97,6 +98,22 @@ public static List> parseToObservationSchedules(ScheduleE } } + public static Instant shiftStartIfObservationAlreadyStarted(Instant start, List observations) { + // returns start date, if now event ends before, otherwise start date + 1 day + return observations.stream() + .map(Observation::observationSchedule) + .filter(scheduleEvent -> scheduleEvent.getType().equals(RelativeEvent.TYPE)) + .map(r -> ((RelativeEvent) r).getDtend()) + .filter(relativeDate -> relativeDate.getOffset().getValue() == 1) + .map(relativeDate -> start.atZone(relativeDate.getZoneId()).withHour(relativeDate.getHours()).withMinute(relativeDate.getMinutes()).withSecond(0).withNano(0).toInstant()) + .filter(instant -> { + return instant.isBefore(start.plus(1, ChronoUnit.HOURS)); + }) + .map(instant -> start.atZone(ZoneId.systemDefault()).withHour(0).withMinute(0).plusDays(1).toInstant()) + .findFirst() + .orElse(start); + } + private static long getEventTime(Event event) { return Duration.between(event.getDateStart(), event.getDateEnd()).getSeconds(); } diff --git a/src/main/java/io/redlink/more/data/service/CalendarService.java b/src/main/java/io/redlink/more/data/service/CalendarService.java index 60d85ab..27741d1 100644 --- a/src/main/java/io/redlink/more/data/service/CalendarService.java +++ b/src/main/java/io/redlink/more/data/service/CalendarService.java @@ -17,6 +17,8 @@ import java.util.Optional; import java.util.TimeZone; +import static io.redlink.more.data.schedule.SchedulerUtils.shiftStartIfObservationAlreadyStarted; + @Service public class CalendarService { private final StudyRepository studyRepository; @@ -37,7 +39,10 @@ public Optional getICalendarString(Long studyId) { iCalEvent.setDateEnd(Date.from(study.endDate().atStartOfDay(TimeZone.getDefault().toZoneId()).toInstant()), false); ical.addEvent(iCalEvent); - Instant start = study.plannedStartDate().atStartOfDay(ZoneId.systemDefault()).toInstant(); + final Instant start = shiftStartIfObservationAlreadyStarted( + study.plannedStartDate().atStartOfDay(ZoneId.systemDefault()).toInstant(), + study.observations() + ); StudyDurationInfo info = studyRepository.getStudyDurationInfo(studyId) .orElseThrow(() -> new RuntimeException("Cannot create calendar")); diff --git a/src/test/java/io/redlink/more/data/schedule/SchedulerUtilsTest.java b/src/test/java/io/redlink/more/data/schedule/SchedulerUtilsTest.java index 4717724..9103e86 100644 --- a/src/test/java/io/redlink/more/data/schedule/SchedulerUtilsTest.java +++ b/src/test/java/io/redlink/more/data/schedule/SchedulerUtilsTest.java @@ -8,6 +8,7 @@ */ package io.redlink.more.data.schedule; +import io.redlink.more.data.model.Observation; import io.redlink.more.data.model.scheduler.*; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Assertions; @@ -24,7 +25,10 @@ import java.util.Arrays; import java.util.List; +import static io.redlink.more.data.schedule.SchedulerUtils.shiftStartIfObservationAlreadyStarted; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class SchedulerUtilsTest { @@ -370,4 +374,32 @@ void testRelativeEventWithRecursionLongRun() { Assertions.assertEquals(5, events.size()); } + @Test + @DisplayName("Set real start date") + void testRelativeEventShift() { + Instant start = Instant.parse("2023-12-24T11:00:00.000Z"); + Observation observationDay1At10 = mock(Observation.class); + Observation observationDay1At12 = mock(Observation.class); + Observation observationDay1At13 = mock(Observation.class); + Observation observationDay2At10 = mock(Observation.class); + ScheduleEvent day1At10 = new RelativeEvent().setDtend(new RelativeDate().setTime("10:00").setTimezone("Europe/Berlin").setOffset(new Duration().setValue(1))); + ScheduleEvent day1At12 = new RelativeEvent().setDtend(new RelativeDate().setTime("12:00").setTimezone("Europe/Berlin").setOffset(new Duration().setValue(1))); + ScheduleEvent day1At13 = new RelativeEvent().setDtend(new RelativeDate().setTime("13:00").setTimezone("Europe/Berlin").setOffset(new Duration().setValue(1))); + ScheduleEvent day2At10 = new RelativeEvent().setDtend(new RelativeDate().setTime("10:00").setTimezone("Europe/Berlin").setOffset(new Duration().setValue(2))); + + when(observationDay1At10.observationSchedule()).thenReturn(day1At10); + when(observationDay1At12.observationSchedule()).thenReturn(day1At12); + when(observationDay1At13.observationSchedule()).thenReturn(day1At13); + when(observationDay2At10.observationSchedule()).thenReturn(day2At10); + + Instant s1 = shiftStartIfObservationAlreadyStarted(start, List.of(observationDay1At10, observationDay1At12, observationDay2At10)); + Assertions.assertNotEquals(s1.toEpochMilli(), start.toEpochMilli()); + + Instant s2 = shiftStartIfObservationAlreadyStarted(start, List.of(observationDay1At12, observationDay2At10)); + Assertions.assertNotEquals(s2.toEpochMilli(), start.toEpochMilli()); + + Instant s3 = shiftStartIfObservationAlreadyStarted(start, List.of(observationDay1At13, observationDay2At10)); + Assertions.assertEquals(s3.toEpochMilli(), start.toEpochMilli()); + } + }