From 2c954e0969a61d7a9ff9ef80309221e252fe9509 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Thu, 11 Jun 2026 13:57:05 +0200 Subject: [PATCH 1/4] feat: add local date parameter support --- .../responses/InterrogationController.java | 18 ++++- .../rest/responses/RawResponseController.java | 77 ++++++++++++++++--- .../RawResponseReprocessController.java | 40 ++++++++-- .../rest/responses/ResponseController.java | 18 ++++- .../controller/utils/DateTimeUtils.java | 43 +++++++++++ 5 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 src/main/java/fr/insee/genesis/controller/utils/DateTimeUtils.java diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/InterrogationController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/InterrogationController.java index bd93de3ec..b25eb947a 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/InterrogationController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/InterrogationController.java @@ -2,6 +2,7 @@ import fr.insee.genesis.controller.dto.InterrogationBatchResponse; import fr.insee.genesis.controller.rest.CommonApiResponse; +import fr.insee.genesis.controller.utils.DateTimeUtils; import fr.insee.genesis.domain.model.surveyunit.InterrogationId; import fr.insee.genesis.domain.model.surveyunit.InterrogationInfo; import fr.insee.genesis.domain.ports.api.SurveyUnitApiPort; @@ -61,14 +62,27 @@ public ResponseEntity getAllInterrogationIdsByQuesti ) @RequestParam(value = "since", required = false) Instant since, + @RequestParam(value = "localSinceDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Filter interrogations to those recorded strictly after the given timestamp (Europe/Paris timezone)", + schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localSinceDate, @RequestParam(value = "until", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Parameter( description = "Filter interrogations to those recorded before the given timestamp or at the same time (ISO-8601 UTC format).", schema = @Schema(type = "string", format = "date-time", example = "2026-01-31T23:59:59Z") ) - Instant until) { - List idsInfo = surveyUnitService.searchInterrogations(collectionInstrumentId, since, until); + Instant until, + @RequestParam(value = "localUntilDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Filter interrogations to those recorded before the given timestamp or at the same time (Europe/Paris timezone)", + schema = @Schema(type = "string", format = "date-time", example = "2026-04-02T01:00:00")) + LocalDateTime localUntilDate) { + + Instant resolvedSinceDate = DateTimeUtils.resolveInstant(since, localSinceDate); + Instant resolvedEndDate = DateTimeUtils.resolveInstant(until, localUntilDate); + List idsInfo = surveyUnitService.searchInterrogations(collectionInstrumentId, resolvedSinceDate, resolvedEndDate); InterrogationBatchResponse response = buildInterrogationBatchResponse(idsInfo); return ResponseEntity.ok(response); } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseController.java index b9d1be35d..010097d7b 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseController.java @@ -1,6 +1,7 @@ package fr.insee.genesis.controller.rest.responses; import fr.insee.genesis.controller.dto.rawdata.LunaticJsonRawDataUnprocessedDto; +import fr.insee.genesis.controller.utils.DateTimeUtils; import fr.insee.genesis.domain.model.surveyunit.Mode; import fr.insee.genesis.domain.model.surveyunit.rawdata.DataProcessResult; import fr.insee.genesis.domain.model.surveyunit.rawdata.LunaticJsonRawDataModel; @@ -14,6 +15,7 @@ import fr.insee.modelefiliere.RawResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -199,15 +201,44 @@ public ResponseEntity>> getProcessedDataIdsSinceHours( @PreAuthorize("hasRole('USER_BATCH_GENERIC')") public ResponseEntity> getLunaticJsonRawDataModelFromJsonBody( @PathVariable String campaignId, - @RequestParam(value = "startDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant startDate, - @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant endDate, + @RequestParam(value = "startDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Start date in UTC", example = "2026-02-02T00:00:00Z") + Instant startDate, + @RequestParam(value = "localStartDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract since in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localStartDate, + @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "End date in UTC", example = "2026-02-02T00:00:00Z") + Instant endDate, + @RequestParam(value = "localEndDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract until in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localEndDate, @RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value = "size", defaultValue = "1000") int size ) { - log.info("Try to read raw JSONs for campaign {}, with startDate={} and endDate={} - page={} - size={}", campaignId, startDate, endDate,page,size); + Instant resolvedStartDate = DateTimeUtils.resolveInstant(startDate, localStartDate); + Instant resolvedEndDate = DateTimeUtils.resolveInstant(endDate, localEndDate); + log.info( + "Try to read raw JSONs for campaign {} with startDateUtc={} startDateLocal={} endDateUtc={} endDateLocal={} - page={} - size={}", + campaignId, + resolvedStartDate, + DateTimeUtils.toFranceDateTime(resolvedStartDate), + resolvedEndDate, + DateTimeUtils.toFranceDateTime(resolvedEndDate), + page, + size + ); Pageable pageable = PageRequest.of(page, size); - Page rawResponses = lunaticJsonRawDataApiPort.findRawDataByCampaignIdAndDate(campaignId, startDate, endDate, pageable); - log.info("rawResponses, lunatic-json for campaign {}, with startDate={} and endDate={} ={}", campaignId, startDate, endDate,rawResponses.getContent().size()); + Page rawResponses = lunaticJsonRawDataApiPort.findRawDataByCampaignIdAndDate(campaignId, resolvedStartDate, resolvedEndDate, pageable); + log.info( + "rawResponses, lunatic-json for campaign {} with startDateUtc={} startDateLocal={} endDateUtc={} endDateLocal={} count={}", + campaignId, + resolvedStartDate, + DateTimeUtils.toFranceDateTime(resolvedStartDate), + resolvedEndDate, + DateTimeUtils.toFranceDateTime(resolvedEndDate), + rawResponses.getContent().size() + ); return ResponseEntity.status(HttpStatus.OK).body(new PagedModel<>(rawResponses)); } @@ -256,15 +287,41 @@ public ResponseEntity existsLunaticJsonByInterrogationId(@PathVariable Str @PreAuthorize("hasRole('USER_BATCH_GENERIC')") public ResponseEntity> getRawResponsesFromJsonBody( @PathVariable String campaignId, - @RequestParam(value = "startDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant startDate, - @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant endDate, + @RequestParam(value = "startDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Start date in UTC", example = "2026-02-02T00:00:00Z") + Instant startDate, + @RequestParam(value = "localStartDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract since in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localStartDate, + @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "End date in UTC", example = "2026-04-02T00:00:00Z") + Instant endDate, + @RequestParam(value = "localEndDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract until in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localEndDate, @RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value = "size", defaultValue = "1000") int size ) { - log.info("Try to read raw lunatic JSONs for campaign {}, with startDate={} and endDate={} - page={} - size={}", campaignId, startDate, endDate,page,size); + Instant resolvedStartDate = DateTimeUtils.resolveInstant(startDate, localStartDate); + Instant resolvedEndDate = DateTimeUtils.resolveInstant(endDate, localEndDate); + log.info("Try to read raw JSONs for campaign {} with startDateUtc={} startDateLocal={} endDateUtc={} endDateLocal={} - page={} - size={}", + campaignId, + resolvedStartDate, + DateTimeUtils.toFranceDateTime(resolvedStartDate), + resolvedEndDate, + DateTimeUtils.toFranceDateTime(resolvedEndDate), + page, + size + ); Pageable pageable = PageRequest.of(page, size); - Page rawResponses = rawResponseApiPort.findRawResponseDataByCampaignIdAndDate(campaignId, startDate, endDate, pageable); - log.info("rawResponses for campaign {}, with startDate={} and endDate={} ={}",campaignId, startDate, endDate, rawResponses.getContent().size()); + Page rawResponses = rawResponseApiPort.findRawResponseDataByCampaignIdAndDate(campaignId, resolvedStartDate, resolvedEndDate, pageable); + log.info("rawResponses for campaign {},with startDateUtc={} startDateLocal={} endDateUtc={} endDateLocal={} count={}", + campaignId, + resolvedStartDate, + DateTimeUtils.toFranceDateTime(resolvedStartDate), + resolvedEndDate, + DateTimeUtils.toFranceDateTime(resolvedEndDate), + rawResponses.getContent().size()); return ResponseEntity.status(HttpStatus.OK).body(new PagedModel<>(rawResponses)); } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseReprocessController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseReprocessController.java index 2f9d87250..ce20341ee 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseReprocessController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/RawResponseReprocessController.java @@ -1,5 +1,6 @@ package fr.insee.genesis.controller.rest.responses; +import fr.insee.genesis.controller.utils.DateTimeUtils; import fr.insee.genesis.domain.model.surveyunit.rawdata.DataProcessResult; import fr.insee.genesis.domain.model.surveyunit.rawdata.RawDataModelType; import fr.insee.genesis.domain.ports.api.ReprocessRawResponseApiPort; @@ -18,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestParam; import java.time.Instant; +import java.time.LocalDateTime; @Controller @RequiredArgsConstructor @@ -44,20 +46,33 @@ public ResponseEntity reProcessRawResponsesByCollectionInstrumentId( @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant sinceDate, + @RequestParam(value = "localSinceDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract since in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localSinceDate, + @Parameter( description = "Extract until", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T00:00:00Z") ) @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - Instant endDate + Instant endDate, + + @RequestParam(value = "localEndDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract until in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localEndDate ) throws GenesisException { + Instant resolvedSinceDate = DateTimeUtils.resolveInstant(sinceDate, localSinceDate); + Instant resolvedEndDate = DateTimeUtils.resolveInstant(endDate, localEndDate); + DataProcessResult result = reprocessRawResponseApiPort.reprocessRawResponses( RawDataModelType.FILIERE, collectionInstrumentId, - sinceDate, - endDate); + resolvedSinceDate, + resolvedEndDate); return ResponseEntity.ok(result.message(collectionInstrumentId)); } @@ -81,20 +96,33 @@ public ResponseEntity reProcessJsonRawDataByQuestionnaireId( @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant sinceDate, + @RequestParam(value = "localSinceDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract since in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localSinceDate, + @Parameter( description = "Extract until", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T00:00:00Z") ) @RequestParam(value = "endDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - Instant endDate + Instant endDate, + + @RequestParam(value = "localEndDate", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + @Parameter(description = "Extract until in Europe/Paris timezone", schema = @Schema(type = "string", format = "date-time", example = "2026-02-02T01:00:00")) + LocalDateTime localEndDate ) throws GenesisException { + Instant resolvedSinceDate = DateTimeUtils.resolveInstant(sinceDate, localSinceDate); + Instant resolvedEndDate = DateTimeUtils.resolveInstant(endDate, localEndDate); + DataProcessResult result = reprocessRawResponseApiPort.reprocessRawResponses( RawDataModelType.LEGACY, collectionInstrumentId, - sinceDate, - endDate); + resolvedSinceDate, + resolvedEndDate); return ResponseEntity.ok(result.message(collectionInstrumentId)); } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java index eba3557f4..2735334cf 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java @@ -19,6 +19,7 @@ import fr.insee.genesis.controller.utils.AuthUtils; import fr.insee.genesis.controller.utils.ControllerUtils; import fr.insee.genesis.controller.utils.DataTransformer; +import fr.insee.genesis.controller.utils.DateTimeUtils; import fr.insee.genesis.domain.model.context.DataProcessingContextModel; import fr.insee.genesis.domain.model.surveyunit.InterrogationId; import fr.insee.genesis.domain.model.surveyunit.Mode; @@ -39,6 +40,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -420,13 +422,27 @@ public ResponseEntity> searchResponses( schema = @Schema(type = "string", format = "date-time", example = "2026-01-01T00:00:00Z") ) @RequestParam(value = "recordedBefore", required = false) Instant recordedBefore, + @Parameter( + description = "Filter responses recorded before or at the same time of the given timestamp in Europe/Paris local time", + schema = @Schema( + type = "string", + format = "date-time", + example = "2026-01-01T01:00:00" + ) + ) + @RequestParam(value = "localRecordedBefore", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime localRecordedBefore, @RequestBody List interrogationIds) { + Instant resolvedRecordedBefore = DateTimeUtils.resolveInstant( + recordedBefore, + localRecordedBefore + ); return ResponseEntity.ok( surveyUnitService.findSimplifiedList( collectionInstrumentId, interrogationIds, - recordedBefore + resolvedRecordedBefore ) ); } diff --git a/src/main/java/fr/insee/genesis/controller/utils/DateTimeUtils.java b/src/main/java/fr/insee/genesis/controller/utils/DateTimeUtils.java new file mode 100644 index 000000000..b3a8b73a2 --- /dev/null +++ b/src/main/java/fr/insee/genesis/controller/utils/DateTimeUtils.java @@ -0,0 +1,43 @@ +package fr.insee.genesis.controller.utils; + +import fr.insee.genesis.exceptions.InvalidDateIntervalException; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public final class DateTimeUtils { + + private static final ZoneId FRANCE_ZONE = ZoneId.of("Europe/Paris"); + + private DateTimeUtils() { + } + + public static Instant resolveInstant( + Instant utcDate, + LocalDateTime localDate + ) { + if (utcDate != null && localDate != null) { + throw new InvalidDateIntervalException( + "Use either UTC date or local date, not both" + ); + } + + if (localDate != null) { + return localDate + .atZone(FRANCE_ZONE) + .toInstant(); + } + + return utcDate; + } + + public static ZonedDateTime toFranceDateTime(Instant instant) { + if (instant == null) { + return null; + } + + return instant.atZone(FRANCE_ZONE); + } +} From c06f16ca0511275cb3c2666d9ad0f37b6a4886d5 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Thu, 11 Jun 2026 14:26:20 +0200 Subject: [PATCH 2/4] correct test --- .../controller/rest/responses/InterrogationControllerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/InterrogationControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/InterrogationControllerTest.java index 7f79f0145..f885a79f0 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/InterrogationControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/InterrogationControllerTest.java @@ -67,7 +67,8 @@ void getAllInterrogationIdsByQuestionnaire_date_test() { interrogationController.getAllInterrogationIdsByQuestionnaire( TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID, since, - null + null, + null,null ); //THEN From 1f9d4fa8e5e3bafe40ac49735d3571dc7cde5f7e Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Thu, 11 Jun 2026 15:35:06 +0200 Subject: [PATCH 3/4] correct test --- .../responses/ResponseControllerTest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java index c57fd2d5a..94f3c37ae 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java @@ -38,6 +38,8 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import static org.hamcrest.Matchers.containsString; @@ -453,6 +455,44 @@ void getResponseList_shouldDelegateToService() throws Exception { } } + @Test + @DisplayName("Should convert localRecordedBefore to UTC before calling service") + void getResponseList_withLocalRecordedBefore_shouldConvertToUtc() throws Exception { + LocalDateTime localRecordedBefore = LocalDateTime.of(2026, 1, 1, 1, 0); + Instant expectedUtc = localRecordedBefore + .atZone(ZoneId.of("Europe/Paris")) + .toInstant(); + + when(surveyUnitApiPort.findSimplifiedList( + anyString(), any(), any())) + .thenReturn(List.of()); + + mockMvc.perform(post("/responses/QUEST01") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .param("localRecordedBefore", "2026-01-01T01:00:00") + .content("[{\"interrogationId\":\"INTERRO01\"}]")) + .andExpect(status().isOk()); + + verify(surveyUnitApiPort).findSimplifiedList( + eq("QUEST01"), + any(), + eq(expectedUtc) + ); + } + + @Test + @DisplayName("Should return bad request when both recordedBefore and localRecordedBefore are provided") + void getResponseList_withBothRecordedBeforeParams_shouldReturnBadRequest() throws Exception { + mockMvc.perform(post("/responses/QUEST01") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .param("recordedBefore", "2026-01-01T00:00:00Z") + .param("localRecordedBefore", "2026-01-01T01:00:00") + .content("[{\"interrogationId\":\"INTERRO01\"}]")) + .andExpect(status().isBadRequest()); + } + @Nested @DisplayName("POST /responses/save-edited tests") class SaveEditedVariablesTests { From 450fe9d21ad0af8f56a78907b3cc131f22b921e9 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Wed, 1 Jul 2026 13:26:49 +0200 Subject: [PATCH 4/4] add tests --- .../responses/ResponseControllerTest.java | 3 +- .../controller/utils/DateTimeUtilsTest.java | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/test/java/fr/insee/genesis/controller/utils/DateTimeUtilsTest.java diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java index 94f3c37ae..502cafe4a 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java @@ -39,6 +39,7 @@ import java.time.Instant; import java.time.LocalDateTime; +import java.time.Month; import java.time.ZoneId; import java.util.List; @@ -458,7 +459,7 @@ void getResponseList_shouldDelegateToService() throws Exception { @Test @DisplayName("Should convert localRecordedBefore to UTC before calling service") void getResponseList_withLocalRecordedBefore_shouldConvertToUtc() throws Exception { - LocalDateTime localRecordedBefore = LocalDateTime.of(2026, 1, 1, 1, 0); + LocalDateTime localRecordedBefore = LocalDateTime.of(2026, Month.JANUARY, 1, 1, 0); Instant expectedUtc = localRecordedBefore .atZone(ZoneId.of("Europe/Paris")) .toInstant(); diff --git a/src/test/java/fr/insee/genesis/controller/utils/DateTimeUtilsTest.java b/src/test/java/fr/insee/genesis/controller/utils/DateTimeUtilsTest.java new file mode 100644 index 000000000..f63cd12e0 --- /dev/null +++ b/src/test/java/fr/insee/genesis/controller/utils/DateTimeUtilsTest.java @@ -0,0 +1,59 @@ +package fr.insee.genesis.controller.utils; + +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class DateTimeUtilsTest { + + @Test + void shouldReturnUtcDateWhenUtcDateIsProvided() { + Instant utcDate = Instant.parse("2026-06-10T12:00:00Z"); + + Instant result = DateTimeUtils.resolveInstant(utcDate, null); + + assertEquals(utcDate, result); + } + + @Test + void shouldConvertLocalDateToInstantWhenLocalDateIsProvided() { + LocalDateTime localDate = LocalDateTime.of(2026, Month.JUNE, 10, 14, 0); + + Instant result = DateTimeUtils.resolveInstant(null, localDate); + + assertEquals( + localDate.atZone(ZoneId.of("Europe/Paris")).toInstant(), + result + ); + } + + @Test + void shouldReturnNullWhenNoDateIsProvided() { + Instant result = DateTimeUtils.resolveInstant(null, null); + + assertNull(result); + } + + @Test + void shouldConvertInstantToFranceDateTime() { + Instant instant = Instant.parse("2026-06-10T12:00:00Z"); + + ZonedDateTime result = DateTimeUtils.toFranceDateTime(instant); + + assertEquals( + instant.atZone(ZoneId.of("Europe/Paris")), + result + ); + } + + @Test + void shouldReturnNullWhenInstantIsNull() { + assertNull(DateTimeUtils.toFranceDateTime(null)); + } +} \ No newline at end of file