Skip to content

Commit f526543

Browse files
committed
CSCEXAM-1553 Fix a DST bug with external reservation search dates
1 parent 694115d commit f526543

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

app/controllers/iop/transfer/impl/ExternalCalendarController.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -478,19 +478,16 @@ private Set<CalendarHandler.TimeSlot> getExamSlots(
478478
private LocalDate parseSearchDate(String day, String startDate, String endDate, ExamRoom room)
479479
throws IllegalArgumentException {
480480
int windowSize = calendarHandler.getReservationWindowSize();
481-
int offset =
482-
room != null
483-
? DateTimeZone.forID(room.getLocalTimezone()).getOffset(DateTime.now())
484-
: configReader.getDefaultTimeZone().getOffset(DateTime.now());
485-
LocalDate now = DateTime.now().plusMillis(offset).toLocalDate();
481+
DateTimeZone zone =
482+
room != null ? DateTimeZone.forID(room.getLocalTimezone()) : configReader.getDefaultTimeZone();
483+
LocalDate now = DateTime.now().withZone(zone).toLocalDate();
486484
LocalDate reservationWindowDate = now.plusDays(windowSize);
487485
LocalDate examEndDate = DateTime.parse(endDate, ISODateTimeFormat.dateTimeParser())
488-
.plusMillis(offset)
486+
.withZone(zone)
489487
.toLocalDate();
490488
LocalDate searchEndDate = reservationWindowDate.isBefore(examEndDate) ? reservationWindowDate : examEndDate;
491-
492489
LocalDate examStartDate = DateTime.parse(startDate, ISODateTimeFormat.dateTimeParser())
493-
.plusMillis(offset)
490+
.withZone(zone)
494491
.toLocalDate();
495492
LocalDate searchDate = day.isEmpty() ? now : LocalDate.parse(day, ISODateTimeFormat.dateParser());
496493
searchDate = searchDate.withDayOfWeek(1);

test/controllers/iop/ExternalCalendarInterfaceTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.commons.io.IOUtils;
5050
import org.eclipse.jetty.server.Server;
5151
import org.joda.time.DateTime;
52+
import org.joda.time.DateTimeZone;
5253
import org.joda.time.LocalDate;
5354
import org.joda.time.format.ISODateTimeFormat;
5455
import org.junit.AfterClass;
@@ -328,6 +329,51 @@ public void testProvideSlots() {
328329
}
329330
}
330331

332+
/**
333+
* When the exam period starts April 2nd (in the room's timezone) and the requested week
334+
* includes April 1st, provideSlots must not return slots for April 1st. This guards
335+
* against DST bugs where the wrong offset was used to derive the exam start date.
336+
*/
337+
@Test
338+
public void testProvideSlotsExcludesDayBeforeExamStartAfterDstChange() {
339+
room = DB.find(ExamRoom.class, 1L);
340+
room.setExternalRef(ROOM_REF);
341+
room.setLocalTimezone("Europe/Helsinki");
342+
room.update();
343+
344+
GeneralSettings gs = new GeneralSettings();
345+
gs.setName("reservation_window_size");
346+
gs.setValue("60");
347+
gs.setId(3L);
348+
gs.save();
349+
350+
// Exam period: April 2–5 in room's timezone. Use UTC instants so the URL has no '+' (e.g. +03:00).
351+
// April 2 00:00 Helsinki (EEST) = 2026-04-01T21:00:00Z, April 5 23:59 Helsinki = 2026-04-05T20:59:59Z.
352+
DateTime examStartUtc = new DateTime(2026, 4, 1, 21, 0, 0, DateTimeZone.UTC);
353+
DateTime examEndUtc = new DateTime(2026, 4, 5, 20, 59, 59, DateTimeZone.UTC);
354+
355+
// Request slots for the week containing April 1 (Monday 2026-03-31).
356+
// Without the DST fix, search could start on April 1 and return slots for that day.
357+
String url = String.format(
358+
"/integration/iop/slots?roomId=%s&date=2026-03-31&start=%s&end=%s&duration=%d",
359+
room.getExternalRef(),
360+
ISODateTimeFormat.dateTime().print(examStartUtc),
361+
ISODateTimeFormat.dateTime().print(examEndUtc),
362+
180
363+
);
364+
Result result = get(url);
365+
assertThat(result.status()).isEqualTo(Http.Status.OK);
366+
JsonNode node = Json.parse(contentAsString(result));
367+
ArrayNode slots = (ArrayNode) node;
368+
369+
LocalDate examStartDate = new LocalDate(2026, 4, 2);
370+
for (JsonNode slot : slots) {
371+
String startIso = slot.get("start").asText();
372+
LocalDate slotDate = ISODateTimeFormat.dateTimeParser().parseDateTime(startIso).toLocalDate();
373+
assertThat(slotDate.isBefore(examStartDate)).isFalse();
374+
}
375+
}
376+
331377
@Test
332378
public void testProvideReservation() {
333379
room = DB.find(ExamRoom.class, 1L);

0 commit comments

Comments
 (0)