diff --git a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java index d8c44769f..9613601b2 100644 --- a/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java +++ b/src/main/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTrip.java @@ -28,6 +28,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -670,6 +671,18 @@ public boolean shouldSkipMonitoredTripCheck(boolean persist) throws Exception { return true; } + // For trips that are snoozed, see if they should be unsnoozed first. + if (trip.snoozed) { + if (shouldUnsnoozeTrip()) { + // Clear previous matching itinerary as we want to start afresh. + // The snoozed state will be updated later in the process. + previousMatchingItinerary = null; + } else { + LOG.info("Skipping: Trip is snoozed."); + return true; + } + } + if (isPrevMatchingItineraryNotConcluded()) { // Skip checking the trip the rest of the time that it is active if the trip was deemed not possible for the // next possible time during a previous query to find candidate itinerary matches. @@ -678,12 +691,6 @@ public boolean shouldSkipMonitoredTripCheck(boolean persist) throws Exception { return true; } - // skip checking the trip if it has been snoozed - if (trip.snoozed) { - LOG.info("Skipping: Trip is snoozed."); - return true; - } - matchingItinerary = previousMatchingItinerary; targetZonedDateTime = DateTimeUtils.makeOtpZonedDateTime(previousJourneyState.targetDate, trip.tripTime); } else { @@ -929,4 +936,25 @@ private Locale getOtpUserLocale() { private String getTripUrl() { return String.format("%s%s/%s", OTP_UI_URL, TRIPS_PATH, trip.id); } + + /** + * Whether a trip should be unsnoozed and monitoring should resume. + * @return true if the current time is after the calendar day (on or after midnight) + * after the matching trip start day, false otherwise. + */ + public boolean shouldUnsnoozeTrip() { + ZoneId otpZoneId = DateTimeUtils.getOtpZoneId(); + var midnightAfterLastChecked = ZonedDateTime + .ofInstant( + Instant.ofEpochMilli(previousJourneyState.lastCheckedEpochMillis).plus(1, ChronoUnit.DAYS), + otpZoneId + ) + .withHour(0) + .withMinute(0) + .withSecond(0); + + ZonedDateTime now = DateTimeUtils.nowAsZonedDateTime(otpZoneId); + // Include equal or after midnight as true. + return !now.isBefore(midnightAfterLastChecked); + } } diff --git a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java index 9bc9a3eb6..80d90ff00 100644 --- a/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java +++ b/src/test/java/org/opentripplanner/middleware/tripmonitor/jobs/CheckMonitoredTripTest.java @@ -746,4 +746,65 @@ private void assertCheckMonitoredTrip( assertEquals(expectedAttempts, monitoredTrip.attemptsToGetMatchingItinerary); assertEquals(expectedTripStatus, monitoredTrip.journeyState.tripStatus); } + + @ParameterizedTest + @MethodSource("createCanUnsnoozeTripCases") + void canUnsnoozeTrip(ZonedDateTime lastCheckedTime, ZonedDateTime clockTime, boolean shouldUnsnooze) throws Exception { + MonitoredTrip monitoredTrip = PersistenceTestUtils.createMonitoredTrip( + user.id, + OtpTestUtils.OTP2_DISPATCHER_PLAN_RESPONSE.clone(), + false, + OtpTestUtils.createDefaultJourneyState() + ); + monitoredTrip.id = UUID.randomUUID().toString(); + // Mark trip as snoozed + monitoredTrip.snoozed = true; + // Set monitored days to Tuesday only. + monitoredTrip.monday = false; + monitoredTrip.tuesday = true; + + Persistence.monitoredTrips.create(monitoredTrip); + + // Mock the current time + DateTimeUtils.useFixedClockAt(clockTime); + + // After snoozed trip is over, trip checks on that trip should not be skipped + CheckMonitoredTrip check = new CheckMonitoredTrip(monitoredTrip, this::mockOtpPlanResponse); + + // Add artifacts of prior monitoring (e.g. monitoring was active until a few minutes before trip snooze) + JourneyState journeyState = monitoredTrip.journeyState; + journeyState.targetDate = "2020-06-09"; + journeyState.tripStatus = TripStatus.TRIP_UPCOMING; + // Set last-checked-time + journeyState.lastCheckedEpochMillis = lastCheckedTime.toInstant().toEpochMilli(); + journeyState.matchingItinerary = monitoredTrip.itinerary; + check.previousJourneyState = journeyState; + check.previousMatchingItinerary = monitoredTrip.itinerary; + + assertEquals(shouldUnsnooze, check.shouldUnsnoozeTrip()); + check.shouldSkipMonitoredTripCheck(); + + MonitoredTrip modifiedTrip = Persistence.monitoredTrips.getById(monitoredTrip.id); + assertEquals(!shouldUnsnooze, modifiedTrip.snoozed); + + // Clear the created trip. + PersistenceTestUtils.deleteMonitoredTrip(modifiedTrip); + } + + private static Stream createCanUnsnoozeTripCases() { + // (Trips for these tests start on Tuesday, June 9, 2020 at 8:40am and ends at 8:58am.) + ZonedDateTime tuesday = noonMonday8June2020.withDayOfMonth(9).withHour(0).withMinute(0).withSecond(0); + ZonedDateTime wednesday = tuesday.withDayOfMonth(10); + + return Stream.of( + // Trip snoozed at 8:00am on Tuesday, June 9, 2020, should remain snoozed right after trip ends at 9:00am. + Arguments.of(tuesday.withHour(8), tuesday.withHour(9), false), + // Trip snoozed at 8:00am on Tuesday, June 9, 2020, should unsnooze at 12:00am (midnight) on + // Wednesday, June 10, 2020, but it is too early for the trip to be analyzed again. + Arguments.of(tuesday.withHour(8), wednesday, true), + // Trip snoozed on Monday, June 8, 2020 (a day before the trip starts), should unsnooze at 12:00am (midnight) + // on Tuesday, June 9, 2020. + Arguments.of(noonMonday8June2020, tuesday, true) + ); + } }