Skip to content

Commit

Permalink
Merge pull request #338 from MTES-MCT/dates-utc
Browse files Browse the repository at this point in the history
fix(dates): store in UTC but display in local french
  • Loading branch information
lwih authored Sep 18, 2024
2 parents 8818fb4 + bbde32e commit 3a2132a
Show file tree
Hide file tree
Showing 191 changed files with 2,131 additions and 1,342 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package fr.gouv.dgampa.rapportnav.config

import graphql.language.StringValue
import graphql.schema.*
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Instant
import java.time.format.DateTimeParseException

/**
* Configuration class to define custom GraphQL scalar types.
*/
@Configuration
class GraphQLConfig {

/**
* Defines a custom GraphQL scalar type for Java's `Instant` class.
*
* @return A `GraphQLScalarType` representing the `Instant` type.
* The scalar handles the serialization and deserialization of
* `Instant` values from ISO-8601 formatted strings or long epoch timestamps.
*/
@Bean
fun instantScalar(): GraphQLScalarType {
return GraphQLScalarType.newScalar()
.name("Instant")
.description("Java Instant scalar")
.coercing(object : Coercing<Instant, String> {

/**
* Serializes an `Instant` object into a string for GraphQL responses.
*
* @param dataFetcherResult The `Instant` object to serialize.
* @return The serialized `Instant` as an ISO-8601 formatted string.
* @throws CoercingSerializeException If the input is not an `Instant` object.
*/
override fun serialize(dataFetcherResult: Any): String {
return (dataFetcherResult as? Instant)?.toString()
?: throw CoercingSerializeException("Expected an Instant object.")
}

/**
* Parses a value (from a variable or direct input) into an `Instant` object.
* Accepts ISO-8601 formatted strings or long epoch timestamps.
*
* @param input The input value to parse.
* @return The parsed `Instant` object.
* @throws CoercingParseValueException If the input is not a valid String or Long,
* or if it cannot be parsed into an `Instant`.
*/
override fun parseValue(input: Any): Instant {
return try {
when (input) {
is String -> Instant.parse(input)
is Long -> Instant.ofEpochMilli(input)
else -> throw CoercingParseValueException("Expected a String or Long")
}
} catch (e: DateTimeParseException) {
throw CoercingParseValueException("Invalid ISO-8601 format", e)
}
}

/**
* Parses a literal value (from a GraphQL query) into an `Instant` object.
* The input must be an ISO-8601 formatted string.
*
* @param input The literal value from the GraphQL query.
* @return The parsed `Instant` object.
* @throws CoercingParseLiteralException If the input is not a valid ISO-8601 formatted string,
* or cannot be parsed into an `Instant`.
*/
override fun parseLiteral(input: Any): Instant {
val value = (input as? StringValue)?.value
?: throw CoercingParseLiteralException("Expected an ISO-8601 formatted String")
return try {
Instant.parse(value)
} catch (e: DateTimeParseException) {
throw CoercingParseLiteralException("Invalid ISO-8601 format", e)
}
}
})
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fr.gouv.dgampa.rapportnav.config

import graphql.schema.GraphQLScalarType
import graphql.schema.idl.RuntimeWiring
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.graphql.execution.RuntimeWiringConfigurer

/**
* Configuration class that integrates custom scalars into the GraphQL schema.
*/
@Configuration
class GraphQLSchemaConfig {

/**
* Registers a custom `GraphQLScalarType`, such as `Instant`, into the GraphQL schema's runtime wiring.
*
* @param instantScalar The custom scalar type, typically an `Instant` scalar, to be registered.
* @return A `RuntimeWiringConfigurer` that adds the scalar to the GraphQL schema wiring.
*/
@Bean
fun runtimeWiringConfigurer(instantScalar: GraphQLScalarType): RuntimeWiringConfigurer {
return RuntimeWiringConfigurer { builder: RuntimeWiring.Builder ->
builder.scalar(instantScalar)
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions.VehicleT
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.*
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.status.ActionStatusType
import fr.gouv.dgampa.rapportnav.domain.utils.AEMUtils
import java.time.ZonedDateTime
import java.time.Instant

data class AEMSovereignProtect(
val nbrOfHourAtSea: Int? = 0, // 7.1
Expand All @@ -15,7 +15,7 @@ data class AEMSovereignProtect(
navActions: List<NavActionEntity>,
envActions: List<ExtendedEnvActionEntity?>,
fishActions: List<ExtendedFishActionEntity?>,
missionEndDateTime: ZonedDateTime?
missionEndDateTime: Instant?
) : this(
nbrOfHourAtSea = getNbrHourAtSea(navActions, missionEndDateTime),
nbrOfRecognizedVessel = getNbOfRecognizedVessel(navActions),
Expand All @@ -26,7 +26,7 @@ data class AEMSovereignProtect(
companion object {
fun getNbrHourAtSea(
navActions: List<NavActionEntity>,
missionEndDateTime: ZonedDateTime?
missionEndDateTime: Instant?
): Int {
val sortedStatusActions =
navActions.filter { it.actionType == ActionType.STATUS }
Expand All @@ -37,7 +37,7 @@ data class AEMSovereignProtect(
sortedStatusActions.windowed(2)
.forEach { (first, second) -> first?.endDateTimeUtc = second?.startDateTimeUtc }

if(sortedStatusActions.isNotEmpty()) sortedStatusActions.last()?.endDateTimeUtc = missionEndDateTime;
if (sortedStatusActions.isNotEmpty()) sortedStatusActions.last()?.endDateTimeUtc = missionEndDateTime;
val statusActions =
anchoredActionEntities(sortedStatusActions) + navigationActionEntities(sortedStatusActions);
return AEMUtils.getDurationInHours(statusActions).toInt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.ActionType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.NavActionEntity
import org.slf4j.LoggerFactory
import java.time.ZonedDateTime
import java.time.Instant

data class AEMTableExport(
val outOfMigrationRescue: AEMOutOfMigrationRescue?, //1.1
Expand All @@ -25,7 +25,7 @@ data class AEMTableExport(

companion object {

fun fromMissionAction(actions: List<MissionActionEntity?>, endDateTimeUtc: ZonedDateTime?): AEMTableExport {
fun fromMissionAction(actions: List<MissionActionEntity?>, endDateTimeUtc: Instant?): AEMTableExport {
val navActions = actions.filterIsInstance<MissionActionEntity.NavAction>().map { it.navAction };
val envActions = actions.filterIsInstance<MissionActionEntity.EnvAction>().map { it.envAction };
val fishActions = actions.filterIsInstance<MissionActionEntity.FishAction>().map { it.fishAction };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.crew.MissionCrewEnt
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.generalInfo.MissionGeneralInfoEntity
import org.locationtech.jts.geom.MultiPolygon
import org.slf4j.LoggerFactory
import java.time.ZonedDateTime
import java.time.Instant
import java.time.format.DateTimeParseException

data class MissionEntity(
Expand All @@ -23,8 +23,8 @@ data class MissionEntity(
val observationsCnsp: String? = null,
val facade: String? = null,
val geom: MultiPolygon? = null,
val startDateTimeUtc: ZonedDateTime,
val endDateTimeUtc: ZonedDateTime? = null,
val startDateTimeUtc: Instant,
val endDateTimeUtc: Instant? = null,
val isDeleted: Boolean,
val isGeometryComputedFromControls: Boolean,
val missionSource: MissionSourceEnum,
Expand Down Expand Up @@ -103,22 +103,22 @@ data class MissionEntity(
}

private fun calculateMissionStatus(
startDateTimeUtc: ZonedDateTime,
endDateTimeUtc: ZonedDateTime? = null,
startDateTimeUtc: Instant,
endDateTimeUtc: Instant? = null,
): MissionStatusEnum {
val compareDate = ZonedDateTime.now()
val compareDate = Instant.now()

if (endDateTimeUtc == null) {
return MissionStatusEnum.UNAVAILABLE
}

if (endDateTimeUtc != null && ZonedDateTime.parse(endDateTimeUtc.toString()).isBefore(compareDate)) {
if (endDateTimeUtc != null && Instant.parse(endDateTimeUtc.toString()).isBefore(compareDate)) {
return MissionStatusEnum.ENDED
}

try {
val startDateTime = ZonedDateTime.parse(startDateTimeUtc.toString())
if (startDateTime.isAfter(compareDate) || startDateTime.isEqual(compareDate)) {
val startDateTime = Instant.parse(startDateTimeUtc.toString())
if (startDateTime.isAfter(compareDate) || startDateTime.equals(compareDate)) {
return MissionStatusEnum.UPCOMING
}
} catch (e: DateTimeParseException) {
Expand All @@ -128,10 +128,10 @@ data class MissionEntity(

if (endDateTimeUtc != null) {
try {
val endDateTime = ZonedDateTime.parse(endDateTimeUtc.toString())
val endDateTime = Instant.parse(endDateTimeUtc.toString())
if (endDateTime.isAfter(compareDate)) {
return MissionStatusEnum.IN_PROGRESS
} else if (endDateTime.isBefore(compareDate) || endDateTime.isEqual(compareDate)) {
} else if (endDateTime.isBefore(compareDate) || endDateTime.equals(compareDate)) {
return MissionStatusEnum.ENDED
}
} catch (e: DateTimeParseException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.env
import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.LegacyControlUnitEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions.EnvActionEntity
import org.locationtech.jts.geom.MultiPolygon
import java.time.ZonedDateTime
import java.time.Instant

data class MissionEntity(
val id: Int? = null,
Expand All @@ -15,8 +15,8 @@ data class MissionEntity(
val observationsCnsp: String? = null,
val facade: String? = null,
val geom: MultiPolygon? = null,
val startDateTimeUtc: ZonedDateTime,
val endDateTimeUtc: ZonedDateTime? = null,
val startDateTimeUtc: Instant,
val endDateTimeUtc: Instant? = null,
var envActions: List<EnvActionEntity>? = listOf(),
val isDeleted: Boolean,
val isGeometryComputedFromControls: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions

import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.ActionCompletionEnum
import org.locationtech.jts.geom.Geometry
import java.time.ZonedDateTime
import java.time.Instant
import java.util.*

data class EnvActionControlEntity(
override val id: UUID,
override val actionStartDateTimeUtc: ZonedDateTime? = null,
override val actionEndDateTimeUtc: ZonedDateTime? = null,
override val actionStartDateTimeUtc: Instant? = null,
override val actionEndDateTimeUtc: Instant? = null,
override val completedBy: String? = null,
override val completion: ActionCompletionEnum? = null,
override val controlPlans: List<EnvActionControlPlanEntity>? = listOf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.ActionCompletionEnum
import org.locationtech.jts.geom.Geometry
import org.n52.jackson.datatype.jts.GeometryDeserializer
import java.time.ZonedDateTime
import java.time.Instant
import java.util.*

@JsonTypeInfo(
Expand All @@ -23,8 +23,8 @@ import java.util.*
abstract class EnvActionEntity(
open val id: UUID,
open val actionType: ActionTypeEnum,
open val actionStartDateTimeUtc: ZonedDateTime? = null,
open val actionEndDateTimeUtc: ZonedDateTime? = null,
open val actionStartDateTimeUtc: Instant? = null,
open val actionEndDateTimeUtc: Instant? = null,
open val department: String? = null,
open val facade: String? = null,
open val completedBy: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions

import java.time.ZonedDateTime
import java.time.Instant
import java.util.*

data class EnvActionNoteEntity(
override val id: UUID,
override val actionStartDateTimeUtc: ZonedDateTime? = null,
override val actionEndDateTimeUtc: ZonedDateTime? = null,
override val actionStartDateTimeUtc: Instant? = null,
override val actionEndDateTimeUtc: Instant? = null,
val observations: String? = null
) : EnvActionEntity(
actionType = ActionTypeEnum.NOTE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions

import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.ActionCompletionEnum
import org.locationtech.jts.geom.Geometry
import java.time.ZonedDateTime
import java.time.Instant
import java.util.*

data class EnvActionSurveillanceEntity(
override val id: UUID,
override val actionStartDateTimeUtc: ZonedDateTime? = null,
override val actionEndDateTimeUtc: ZonedDateTime? = null,
override val actionStartDateTimeUtc: Instant? = null,
override val actionEndDateTimeUtc: Instant? = null,
override val geom: Geometry? = null,
override val facade: String? = null,
override val department: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions

import java.time.ZonedDateTime
import java.time.Instant
import java.util.*

data class PatchedEnvActionEntity(
val id: UUID,
val actionStartDateTimeUtc: ZonedDateTime? = null,
val actionEndDateTimeUtc: ZonedDateTime? = null,
val actionStartDateTimeUtc: Instant? = null,
val actionEndDateTimeUtc: Instant? = null,
val observationsByUnit: String? = null,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package fr.gouv.dgampa.rapportnav.domain.entities.mission.fish

import java.time.ZonedDateTime
import java.time.Instant

/**
@see monitorenv/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/missions/MissionEntity.kt
Expand All @@ -14,8 +14,8 @@ data class Mission(
val observationsCnsp: String? = null,
val facade: String? = null,
val geom: MultiPolygon? = null,
val startDateTimeUtc: ZonedDateTime,
val endDateTimeUtc: ZonedDateTime? = null,
val startDateTimeUtc: Instant,
val endDateTimeUtc: Instant? = null,
val missionSource: MissionSource,
val hasMissionOrder: Boolean? = false,
val isUnderJdp: Boolean? = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions

import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlUnit
import java.time.ZonedDateTime
import com.neovisionaries.i18n.CountryCode
import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlUnit
import java.time.Instant

typealias FishAction = MissionAction

Expand All @@ -18,8 +18,8 @@ data class MissionAction(
val districtCode: String? = null,
val faoAreas: List<String> = listOf(),
val actionType: MissionActionType,
val actionDatetimeUtc: ZonedDateTime,
val actionEndDatetimeUtc: ZonedDateTime? = null,
val actionDatetimeUtc: Instant,
val actionEndDatetimeUtc: Instant? = null,
val emitsVms: ControlCheck? = null,
val emitsAis: ControlCheck? = null,
val flightGoals: List<FlightGoal> = listOf(),
Expand Down
Loading

0 comments on commit 3a2132a

Please sign in to comment.