Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,14 +62,27 @@ public ResponseEntity<InterrogationBatchResponse> 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<InterrogationInfo> 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<InterrogationInfo> idsInfo = surveyUnitService.searchInterrogations(collectionInstrumentId, resolvedSinceDate, resolvedEndDate);
InterrogationBatchResponse response = buildInterrogationBatchResponse(idsInfo);
return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -199,15 +201,44 @@ public ResponseEntity<Map<String, List<String>>> getProcessedDataIdsSinceHours(
@PreAuthorize("hasRole('USER_BATCH_GENERIC')")
public ResponseEntity<PagedModel<LunaticJsonRawDataModel>> 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<LunaticJsonRawDataModel> 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<LunaticJsonRawDataModel> 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));
}

Expand Down Expand Up @@ -256,15 +287,41 @@ public ResponseEntity<Void> existsLunaticJsonByInterrogationId(@PathVariable Str
@PreAuthorize("hasRole('USER_BATCH_GENERIC')")
public ResponseEntity<PagedModel<RawResponseModel>> 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<RawResponseModel> rawResponses = rawResponseApiPort.findRawResponseDataByCampaignIdAndDate(campaignId, startDate, endDate, pageable);
log.info("rawResponses for campaign {}, with startDate={} and endDate={} ={}",campaignId, startDate, endDate, rawResponses.getContent().size());
Page<RawResponseModel> 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));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,6 +19,7 @@
import org.springframework.web.bind.annotation.RequestParam;

import java.time.Instant;
import java.time.LocalDateTime;

@Controller
@RequiredArgsConstructor
Expand All @@ -44,20 +46,33 @@ public ResponseEntity<String> 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));
}
Expand All @@ -81,20 +96,33 @@ public ResponseEntity<String> 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -420,13 +422,27 @@ public ResponseEntity<List<SurveyUnitSimplifiedDto>> 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<InterrogationId> interrogationIds)
{
Instant resolvedRecordedBefore = DateTimeUtils.resolveInstant(
recordedBefore,
localRecordedBefore
);
return ResponseEntity.ok(
surveyUnitService.findSimplifiedList(
collectionInstrumentId,
interrogationIds,
recordedBefore
resolvedRecordedBefore
)
);
}
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/fr/insee/genesis/controller/utils/DateTimeUtils.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ void getAllInterrogationIdsByQuestionnaire_date_test() {
interrogationController.getAllInterrogationIdsByQuestionnaire(
TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID,
since,
null
null,
null,null
);

//THEN
Expand Down
Loading
Loading