From 56d75c096cbab642b456d6d01cd59f1dabaec55b Mon Sep 17 00:00:00 2001 From: nicoti8m Date: Thu, 2 Oct 2025 15:47:12 +0200 Subject: [PATCH 1/2] Fix LocalDateTimeConverter to correctly parse time zone offsets --- .../converter/LocalDateTimeTypeConverter.kt | 61 +++++++++- .../ojp/data/dto/request/ServiceRequestDto.kt | 6 +- .../lir/LocationInformationRequestDto.kt | 4 +- .../data/dto/request/tir/PlaceContextDto.kt | 6 +- .../data/dto/request/tir/TripRequestDto.kt | 4 +- .../dto/request/trr/TripRefineRequestDto.kt | 4 +- .../data/dto/response/ServiceDeliveryDto.kt | 18 +-- .../LocationInformationDeliveryDto.kt | 4 +- .../dto/response/delivery/TripDeliveryDto.kt | 4 +- .../delivery/TripRefineDeliveryDto.kt | 4 +- .../dto/response/tir/leg/ServiceTimeDto.kt | 6 +- .../response/tir/situations/PtSituationDto.kt | 4 +- .../tir/situations/PublishingActionsDto.kt | 4 +- .../tir/situations/ValidityPeriodDto.kt | 6 +- .../data/dto/response/tir/trips/TripDto.kt | 6 +- .../opentransportdata/ojp/di/NetworkModule.kt | 7 +- .../LocalDateTimeTypeConverterTest.kt | 112 +++++++++++------- 17 files changed, 165 insertions(+), 95 deletions(-) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverter.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverter.kt index 80efdf9..ba35adb 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverter.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverter.kt @@ -6,23 +6,72 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import timber.log.Timber +import java.time.Instant import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.ZonedDateTime import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException /** * Created by Michael Ruppen on 05.07.2024 */ -object LocalDateTimeSerializer : KSerializer { - private val formatter = DateTimeFormatter.ISO_DATE_TIME +class LocalDateTimeSerializer( + private val zone: ZoneId +) : KSerializer { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) + PrimitiveSerialDescriptor("LocalDateTimeAsInstant", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeString(value.format(formatter)) + val instant = value.atZone(zone).toInstant() + encoder.encodeString(DateTimeFormatter.ISO_INSTANT.format(instant)) } override fun deserialize(decoder: Decoder): LocalDateTime { - return LocalDateTime.parse(decoder.decodeString(), formatter) + val text = decoder.decodeString() + return parseToLocalDateTime(text, zone) } -} \ No newline at end of file + + companion object { + private val ZONED = DateTimeFormatter.ISO_ZONED_DATE_TIME + private val OFFSET = DateTimeFormatter.ISO_OFFSET_DATE_TIME + private val INSTANT = DateTimeFormatter.ISO_INSTANT + private val LOCAL = DateTimeFormatter.ISO_LOCAL_DATE_TIME + + fun parseToLocalDateTime(text: String, zone: ZoneId): LocalDateTime { + try { + return ZonedDateTime.parse(text, ZONED) + .withZoneSameInstant(zone) + .toLocalDateTime() + } catch (exception: DateTimeParseException) { + Timber.e("Could not parse ZONED $exception") + } + + try { + return OffsetDateTime.parse(text, OFFSET) + .atZoneSameInstant(zone) + .toLocalDateTime() + } catch (exception: DateTimeParseException) { + Timber.e("Could not parse OFFSET $exception") + } + + try { + return INSTANT.parse(text, Instant::from) + .atZone(zone) + .toLocalDateTime() + } catch (exception: Exception) { + Timber.e("Could not parse INSTANT $exception") + } + + try { + return LocalDateTime.parse(text, LOCAL) + } catch (exception: Exception) { + Timber.e("Could not parse LOCAL $exception") + } + return LocalDateTime.now() + } + } +} diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/ServiceRequestDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/ServiceRequestDto.kt index a5fb2a5..8dd79d2 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/ServiceRequestDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/ServiceRequestDto.kt @@ -3,10 +3,10 @@ package ch.opentransportdata.ojp.data.dto.request import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.request.lir.LocationInformationRequestDto -import ch.opentransportdata.ojp.data.dto.request.trr.TripRefineRequestDto import ch.opentransportdata.ojp.data.dto.request.tir.TripRequestDto +import ch.opentransportdata.ojp.data.dto.request.trr.TripRefineRequestDto +import kotlinx.serialization.Contextual import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName import java.time.LocalDateTime @@ -24,7 +24,7 @@ internal data class ServiceRequestDto( @XmlElement(true) @XmlSerialName("RequestTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @kotlinx.serialization.Serializable(with = LocalDateTimeSerializer::class) + @Contextual val requestTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/lir/LocationInformationRequestDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/lir/LocationInformationRequestDto.kt index cfdb0ce..a82bd95 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/lir/LocationInformationRequestDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/lir/LocationInformationRequestDto.kt @@ -3,7 +3,7 @@ package ch.opentransportdata.ojp.data.dto.request.lir import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer +import kotlinx.serialization.Contextual import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName import java.time.LocalDateTime @@ -18,7 +18,7 @@ internal data class LocationInformationRequestDto( @XmlElement(true) @XmlSerialName("RequestTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @kotlinx.serialization.Serializable(with = LocalDateTimeSerializer::class) + @Contextual val requestTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/PlaceContextDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/PlaceContextDto.kt index 9347c84..1c6a19b 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/PlaceContextDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/PlaceContextDto.kt @@ -1,10 +1,10 @@ package ch.opentransportdata.ojp.data.dto.request.tir import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName -import kotlinx.serialization.Serializable import java.time.LocalDateTime /** @@ -18,6 +18,6 @@ internal data class PlaceContextDto( @XmlElement(true) @XmlSerialName("DepArrTime", OJP_NAME_SPACE, "") - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val departureArrivalTime: LocalDateTime? = null ) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/TripRequestDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/TripRequestDto.kt index c762c1d..ed787f7 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/TripRequestDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/tir/TripRequestDto.kt @@ -3,7 +3,7 @@ package ch.opentransportdata.ojp.data.dto.request.tir import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer +import kotlinx.serialization.Contextual import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName import java.time.LocalDateTime @@ -17,7 +17,7 @@ internal data class TripRequestDto( @XmlElement(true) @XmlSerialName("RequestTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @kotlinx.serialization.Serializable(with = LocalDateTimeSerializer::class) + @Contextual val requestTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/trr/TripRefineRequestDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/trr/TripRefineRequestDto.kt index 52fe7a7..baa7044 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/trr/TripRefineRequestDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/request/trr/TripRefineRequestDto.kt @@ -3,8 +3,8 @@ package ch.opentransportdata.ojp.data.dto.request.trr import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.tir.TripResultDto +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -20,7 +20,7 @@ internal data class TripRefineRequestDto( @XmlElement(true) @XmlSerialName("RequestTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val requestTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/ServiceDeliveryDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/ServiceDeliveryDto.kt index 9c20c57..ceec1a5 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/ServiceDeliveryDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/ServiceDeliveryDto.kt @@ -3,11 +3,11 @@ package ch.opentransportdata.ojp.data.dto.response import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.delivery.AbstractDeliveryDto import ch.opentransportdata.ojp.data.dto.response.delivery.LocationInformationDeliveryDto import ch.opentransportdata.ojp.data.dto.response.delivery.TripDeliveryDto import ch.opentransportdata.ojp.data.dto.response.delivery.TripRefineDeliveryDto +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -21,16 +21,13 @@ import java.time.LocalDateTime internal data class ServiceDeliveryDto( @XmlElement(true) @XmlSerialName("ResponseTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val responseTimestamp: LocalDateTime, @XmlElement(true) @XmlSerialName("ProducerRef", SIRI_NAME_SPACE, SIRI_PREFIX) val producerRef: String? = null, - // Polymorphic child: tag name determines the concrete delivery subtype. - // You can either rely on @XmlSerialName on the subtypes (with a polymorphic SerializersModule), - // or explicitly list the element names here via @XmlPolyChildren: @XmlElement(true) @XmlSerialName("OJPLocationInformationDelivery", OJP_NAME_SPACE, "") val ojpLocationInformationDelivery: LocationInformationDeliveryDto? = null, @@ -42,18 +39,7 @@ internal data class ServiceDeliveryDto( @XmlElement(true) @XmlSerialName("OJPTripRefineDelivery", OJP_NAME_SPACE, "") val ojpTripRefineDelivery: TripRefineDeliveryDto? = null, -// @XmlElement(true) -// @XmlPolyChildren( -// value = [ -// // childSerialName[=[prefix:]localName] -// "OJPLocationInformationDelivery=ojp:OJPLocationInformationDelivery", -// "OJPTripDelivery=ojp:OJPTripDelivery", -// "OJPTripRefineDelivery=ojp:OJPTripRefineDelivery" -// ] -// ) -// val ojpDelivery: AbstractDeliveryDto ) { - // Optional: keep a convenience getter so the rest of your code hardly changes val ojpDelivery: AbstractDeliveryDto? get() = ojpLocationInformationDelivery ?: ojpTripDelivery diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/LocationInformationDeliveryDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/LocationInformationDeliveryDto.kt index 649c500..cbfa234 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/LocationInformationDeliveryDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/LocationInformationDeliveryDto.kt @@ -3,8 +3,8 @@ package ch.opentransportdata.ojp.data.dto.response.delivery import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.PlaceResultDto +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -18,7 +18,7 @@ import java.time.LocalDateTime internal data class LocationInformationDeliveryDto( @XmlElement(true) @XmlSerialName("ResponseTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual override val responseTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripDeliveryDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripDeliveryDto.kt index 22cfac8..60556a7 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripDeliveryDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripDeliveryDto.kt @@ -3,9 +3,9 @@ package ch.opentransportdata.ojp.data.dto.response.delivery import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.tir.TripResponseContextDto import ch.opentransportdata.ojp.data.dto.response.tir.TripResultDto +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -20,7 +20,7 @@ import java.time.LocalDateTime data class TripDeliveryDto( @XmlElement(true) @XmlSerialName("ResponseTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual override val responseTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripRefineDeliveryDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripRefineDeliveryDto.kt index 067fbf7..732f72b 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripRefineDeliveryDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/delivery/TripRefineDeliveryDto.kt @@ -3,9 +3,9 @@ package ch.opentransportdata.ojp.data.dto.response.delivery import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.tir.TripResponseContextDto import ch.opentransportdata.ojp.data.dto.response.tir.TripResultDto +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -21,7 +21,7 @@ import java.time.LocalDateTime data class TripRefineDeliveryDto( @XmlElement(true) @XmlSerialName("ResponseTimestamp", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual override val responseTimestamp: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/leg/ServiceTimeDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/leg/ServiceTimeDto.kt index 2b8f76a..2fbe0a7 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/leg/ServiceTimeDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/leg/ServiceTimeDto.kt @@ -2,8 +2,8 @@ package ch.opentransportdata.ojp.data.dto.response.tir.leg import android.os.Parcelable import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -18,12 +18,12 @@ import java.time.LocalDateTime data class ServiceTimeDto( @XmlElement(true) @XmlSerialName("TimetabledTime", OJP_NAME_SPACE, "") - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val timetabledTime: LocalDateTime, @XmlElement(true) @XmlSerialName("EstimatedTime", OJP_NAME_SPACE, "") - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val estimatedTime: LocalDateTime? = null ) : Parcelable { diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PtSituationDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PtSituationDto.kt index 05341b3..3f0cf39 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PtSituationDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PtSituationDto.kt @@ -4,9 +4,9 @@ import android.os.Parcelable import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.domain.model.ScopeType import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Contextual import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName import java.time.LocalDateTime @@ -20,7 +20,7 @@ import java.time.LocalDateTime data class PtSituationDto( @XmlElement(true) @XmlSerialName("CreationTime", SIRI_NAME_SPACE, SIRI_PREFIX) - @kotlinx.serialization.Serializable(with = LocalDateTimeSerializer::class) + @Contextual val creationTime: LocalDateTime? = null, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PublishingActionsDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PublishingActionsDto.kt index ca6042b..f14efa5 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PublishingActionsDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/PublishingActionsDto.kt @@ -3,8 +3,8 @@ package ch.opentransportdata.ojp.data.dto.response.tir.situations import android.os.Parcelable import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -43,7 +43,7 @@ data class PublishingActionDto( data class PassengerInformationActionDto( @XmlElement(true) @XmlSerialName("RecordedAtTime", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val recordedAtTime: LocalDateTime? = null, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/ValidityPeriodDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/ValidityPeriodDto.kt index b445752..c99cef8 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/ValidityPeriodDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/situations/ValidityPeriodDto.kt @@ -3,8 +3,8 @@ package ch.opentransportdata.ojp.data.dto.response.tir.situations import android.os.Parcelable import ch.opentransportdata.ojp.data.dto.SIRI_NAME_SPACE import ch.opentransportdata.ojp.data.dto.SIRI_PREFIX -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -19,11 +19,11 @@ import java.time.LocalDateTime data class ValidityPeriodDto( @XmlElement(true) @XmlSerialName("StartTime", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val startTime: LocalDateTime, @XmlElement(true) @XmlSerialName("EndTime", SIRI_NAME_SPACE, SIRI_PREFIX) - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val endTime: LocalDateTime ) : Parcelable \ No newline at end of file diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/trips/TripDto.kt b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/trips/TripDto.kt index 8240855..efd5c68 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/trips/TripDto.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/data/dto/response/tir/trips/TripDto.kt @@ -3,7 +3,6 @@ package ch.opentransportdata.ojp.data.dto.response.tir.trips import android.os.Parcelable import ch.opentransportdata.ojp.data.dto.OJP_NAME_SPACE import ch.opentransportdata.ojp.data.dto.converter.DurationSerializer -import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.PlacesDto import ch.opentransportdata.ojp.data.dto.response.tir.LegDto import ch.opentransportdata.ojp.data.dto.response.tir.leg.ContinuousLegDto @@ -13,6 +12,7 @@ import ch.opentransportdata.ojp.data.dto.response.tir.minimalCopy import ch.opentransportdata.ojp.data.dto.response.tir.replaceWithParentRef import ch.opentransportdata.ojp.data.dto.response.tir.situations.PtSituationDto import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -38,12 +38,12 @@ data class TripDto( @XmlElement(true) @XmlSerialName("StartTime", OJP_NAME_SPACE, "") - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val startTime: LocalDateTime, @XmlElement(true) @XmlSerialName("EndTime", OJP_NAME_SPACE, "") - @Serializable(with = LocalDateTimeSerializer::class) + @Contextual val endTime: LocalDateTime, @XmlElement(true) diff --git a/sdk/src/main/java/ch/opentransportdata/ojp/di/NetworkModule.kt b/sdk/src/main/java/ch/opentransportdata/ojp/di/NetworkModule.kt index 6b5e882..dd61c60 100644 --- a/sdk/src/main/java/ch/opentransportdata/ojp/di/NetworkModule.kt +++ b/sdk/src/main/java/ch/opentransportdata/ojp/di/NetworkModule.kt @@ -2,6 +2,7 @@ package ch.opentransportdata.ojp.di import ch.opentransportdata.ojp.BuildConfig import ch.opentransportdata.ojp.data.dto.converter.FareClassSerializer +import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.converter.XmlUtilConverterFactory import ch.opentransportdata.ojp.data.remote.OjpService import ch.opentransportdata.ojp.di.interceptor.TokenInterceptor @@ -16,13 +17,14 @@ import okhttp3.logging.HttpLoggingInterceptor import org.koin.core.qualifier.named import org.koin.dsl.module import retrofit2.Retrofit +import java.time.LocalDateTime import java.util.concurrent.TimeUnit /** * Created by Michael Ruppen on 08.04.2024 */ internal val networkModule = module { - single { provideXml() } + single { provideXml(get()) } single { provideLoggingInterceptor() } single(named("ojpHttpClient")) { provideOkHttpClient(get(), get()) } single(named("ojpRetrofit")) { provideRetrofit(get(named("ojpHttpClient")), get(), get()) } @@ -62,8 +64,9 @@ internal fun provideRetrofit( } @OptIn(ExperimentalXmlUtilApi::class) -internal fun provideXml(): XML = XML( +internal fun provideXml(initializer: Initializer): XML = XML( serializersModule = SerializersModule { + contextual(LocalDateTime::class, LocalDateTimeSerializer(initializer.defaultTimeZone)) contextual(FareClass::class, FareClassSerializer) } ) { diff --git a/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverterTest.kt b/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverterTest.kt index 13cbdda..5b79eda 100644 --- a/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverterTest.kt +++ b/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/LocalDateTimeTypeConverterTest.kt @@ -1,66 +1,98 @@ package ch.opentransportdata.ojp.data.dto.converter import assertk.assertThat -import assertk.assertions.contains import assertk.assertions.isEqualTo -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString -import nl.adaptivity.xmlutil.serialization.XML -import nl.adaptivity.xmlutil.serialization.XmlElement -import nl.adaptivity.xmlutil.serialization.XmlSerialName +import assertk.assertions.isNotEqualTo +import kotlinx.serialization.json.Json +import org.junit.Before import org.junit.Test import java.time.LocalDateTime +import java.time.ZoneId class LocalDateTimeSerializerXmlUtilTest { - private val xml = XML {} + private val zone = ZoneId.of("Europe/Zurich") + private lateinit var serializer: LocalDateTimeSerializer - @Serializable - @XmlSerialName("Root", "", "") - private data class Root( - @XmlElement(true) - @XmlSerialName("When", "", "") - @Serializable(with = LocalDateTimeSerializer::class) - val whenLocal: LocalDateTime - ) + @Before + fun setUp() { + serializer = LocalDateTimeSerializer(zone) + } + + @Test + fun `Date time string has format +2,00 is parsed correctly to given timeZone EuropeZurich`() { + // GIVEN + val timeString = "2024-09-04T13:29:24.1819927+02:00" + + // ACTION + val result = LocalDateTimeSerializer.parseToLocalDateTime(timeString, zone) + + // ASSERTION + assertThat(result).isNotEqualTo(LocalDateTime.now(zone)) + assertThat(result.hour).isEqualTo(13) + } @Test - fun `parses ISO local datetime without offset`() { - val s = "2024-09-04T13:29:24.1819927" - val result = xml.decodeFromString("$s") - assertThat(result.whenLocal).isEqualTo(LocalDateTime.parse(s)) + fun `Date time string has format +1,00 is parsed correctly to given timeZone EuropeZurich`() { + // GIVEN + val timeString = "2024-09-04T13:29:24.1819927+01:00" + + // ACTION + val result = LocalDateTimeSerializer.parseToLocalDateTime(timeString, zone) + + // ASSERTION + assertThat(result).isNotEqualTo(LocalDateTime.now(zone)) + assertThat(result.hour).isEqualTo(14) } @Test - fun `parses value with offset and ignores the offset`() { - val s = "2024-09-04T13:29:24.1819927+02:00" - val result = xml.decodeFromString("$s") - assertThat(result.whenLocal).isEqualTo( - LocalDateTime.parse("2024-09-04T13:29:24.1819927") - ) + fun `Date time string has format +0,00 is parsed correctly to given timeZone EuropeZurich`() { + // GIVEN + val timeString = "2024-09-04T13:29:24.1819927+00:00" + + // ACTION + val result = LocalDateTimeSerializer.parseToLocalDateTime(timeString, zone) + + // ASSERTION + assertThat(result).isNotEqualTo(LocalDateTime.now(zone)) + assertThat(result.hour).isEqualTo(15) } @Test - fun `parses Zulu time and ignores the Z`() { - val s = "2024-09-04T07:40:00Z" - val result = xml.decodeFromString("$s") - assertThat(result.whenLocal).isEqualTo( - LocalDateTime.parse("2024-09-04T07:40:00") - ) + fun `Bare local date time is treated as target-zone local time`() { + // GIVEN + val timeString = "2024-09-04T13:29:24.1819927" + + // ACTION + val result = LocalDateTimeSerializer.parseToLocalDateTime(timeString, zone) + + // ASSERTION + assertThat(result).isEqualTo(LocalDateTime.parse(timeString)) } @Test - fun `serializes to ISO local datetime without zone`() { - val value = LocalDateTime.parse("2024-09-05T15:02:49.258568") - val xmlOut = xml.encodeToString(Root.serializer(), Root(value)) - assertThat(xmlOut).contains("2024-09-05T15:02:49.258568") + fun `Date time string as zulu time is parsed correctly to given timeZone EuropeZurich`() { + // GIVEN + val timeString = "2024-09-04T07:40:00Z" + + // ACTION + val result = LocalDateTimeSerializer.parseToLocalDateTime(timeString, zone) + + // ASSERTION + assertThat(result).isNotEqualTo(LocalDateTime.now(zone)) + assertThat(result.hour).isEqualTo(9) } @Test - fun `round-trip local datetime`() { - val original = LocalDateTime.parse("2024-12-01T08:09:10.123456789") - val encoded = xml.encodeToString(Root.serializer(), Root(original)) - val decoded = xml.decodeFromString(encoded) - assertThat(decoded.whenLocal).isEqualTo(original) + fun `LocalDateTime to string parsed correctly to zulu time`() { + // GIVEN should be identical + val timeString = "2024-09-05T15:02:49.258568Z" + + // ACTION + val decoded = Json.decodeFromString(serializer, "\"$timeString\"") + val reEncoded = Json.encodeToString(serializer, decoded).trim('"') + + // ASSERTION + assertThat(reEncoded).isEqualTo(timeString) } } \ No newline at end of file From c92d093fdb261d6fb886cdbd221f5653860e0a6d Mon Sep 17 00:00:00 2001 From: nicoti8m Date: Thu, 2 Oct 2025 16:25:26 +0200 Subject: [PATCH 2/2] Add new converter to test cases --- .../test/java/ch/opentransportdata/ojp/OjpSdkTest.kt | 3 +++ .../data/dto/converter/DurationTypeConverterTest.kt | 12 +++++++++++- .../usecase/TripDeliveryHashCalculationTest.kt | 5 ++++- .../ojp/domain/usecase/TripRefinementTest.kt | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/sdk/src/test/java/ch/opentransportdata/ojp/OjpSdkTest.kt b/sdk/src/test/java/ch/opentransportdata/ojp/OjpSdkTest.kt index 324d799..a499bdb 100644 --- a/sdk/src/test/java/ch/opentransportdata/ojp/OjpSdkTest.kt +++ b/sdk/src/test/java/ch/opentransportdata/ojp/OjpSdkTest.kt @@ -6,6 +6,7 @@ import assertk.assertions.isNotEmpty import assertk.assertions.isNotNull import ch.opentransportdata.ojp.data.dto.OjpDto import ch.opentransportdata.ojp.data.dto.converter.FareClassSerializer +import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.domain.model.FareClass import ch.opentransportdata.ojp.domain.model.LanguageCode import ch.opentransportdata.ojp.domain.model.LocationInformationParams @@ -24,6 +25,7 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import java.io.File +import java.time.LocalDateTime import java.time.ZoneId class OjpSdkXmlUtilTest { @@ -38,6 +40,7 @@ class OjpSdkXmlUtilTest { @OptIn(ExperimentalXmlUtilApi::class) private fun xmlWithPtMode(): XML = XML( serializersModule = SerializersModule { + contextual(LocalDateTime::class, LocalDateTimeSerializer(initializer.defaultTimeZone)) contextual(FareClass::class, FareClassSerializer) } ) { diff --git a/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/DurationTypeConverterTest.kt b/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/DurationTypeConverterTest.kt index 63ab54d..05a6191 100644 --- a/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/DurationTypeConverterTest.kt +++ b/sdk/src/test/java/ch/opentransportdata/ojp/data/dto/converter/DurationTypeConverterTest.kt @@ -5,6 +5,7 @@ import assertk.assertions.contains import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import ch.opentransportdata.ojp.data.dto.response.tir.LegDto +import ch.opentransportdata.ojp.domain.usecase.Initializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -19,9 +20,12 @@ import nl.adaptivity.xmlutil.ExperimentalXmlUtilApi import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XmlConfig import nl.adaptivity.xmlutil.serialization.XmlElement +import org.junit.Before import org.junit.Test import java.io.File import java.time.Duration +import java.time.LocalDateTime +import java.time.ZoneId class DurationSerializationXmlUtilTest { @@ -35,11 +39,17 @@ class DurationSerializationXmlUtilTest { override fun serialize(encoder: Encoder, value: Duration) = encoder.encodeString(value.toString()) } + private val initializer = Initializer() + + @Before + fun setUp() { + initializer.defaultTimeZone = ZoneId.of("Europe/Zurich") + } @OptIn(ExperimentalXmlUtilApi::class) private fun xml(): XML = XML( serializersModule = SerializersModule { - // Only needed if your LegDto does NOT annotate the field with a serializer. + contextual(LocalDateTime::class, LocalDateTimeSerializer(initializer.defaultTimeZone)) contextual(Duration::class, DurationIsoSerializer) } ) { diff --git a/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripDeliveryHashCalculationTest.kt b/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripDeliveryHashCalculationTest.kt index b74b3b5..5672e3f 100644 --- a/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripDeliveryHashCalculationTest.kt +++ b/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripDeliveryHashCalculationTest.kt @@ -4,6 +4,7 @@ import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import ch.opentransportdata.ojp.data.dto.converter.FareClassSerializer +import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.response.delivery.TripDeliveryDto import ch.opentransportdata.ojp.data.dto.response.tir.TripResultDto import ch.opentransportdata.ojp.data.dto.response.tir.trips.TripDto @@ -16,6 +17,7 @@ import nl.adaptivity.xmlutil.serialization.XmlConfig import org.junit.Before import org.junit.Test import java.io.File +import java.time.LocalDateTime import java.time.ZoneId /** @@ -34,6 +36,7 @@ internal class TripDeliveryHashCalculationTest { @OptIn(ExperimentalXmlUtilApi::class) private fun xml(): XML = XML( serializersModule = SerializersModule { + contextual(LocalDateTime::class, LocalDateTimeSerializer(initializer.defaultTimeZone)) contextual(FareClass::class, FareClassSerializer) } ) { @@ -91,7 +94,7 @@ internal class TripDeliveryHashCalculationTest { // ASSERTION assertThat(result).isNotNull() assertThat(result.tripResults?.size).isEqualTo(13) - assertThat(filteredList?.size).isEqualTo(10) + assertThat(filteredList?.size).isEqualTo(9) } diff --git a/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripRefinementTest.kt b/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripRefinementTest.kt index 74210fe..a058596 100644 --- a/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripRefinementTest.kt +++ b/sdk/src/test/java/ch/opentransportdata/ojp/domain/usecase/TripRefinementTest.kt @@ -10,6 +10,7 @@ import ch.opentransportdata.ojp.BuildConfig import ch.opentransportdata.ojp.OjpSdk import ch.opentransportdata.ojp.data.dto.OjpDto import ch.opentransportdata.ojp.data.dto.converter.FareClassSerializer +import ch.opentransportdata.ojp.data.dto.converter.LocalDateTimeSerializer import ch.opentransportdata.ojp.data.dto.request.tir.PlaceReferenceDto import ch.opentransportdata.ojp.data.dto.response.GeoPositionDto import ch.opentransportdata.ojp.data.dto.response.NameDto @@ -52,6 +53,7 @@ class TripRefinementXmlUtilTest { @OptIn(ExperimentalXmlUtilApi::class) private fun xml(): XML = XML( serializersModule = SerializersModule { + contextual(LocalDateTime::class, LocalDateTimeSerializer(initializer.defaultTimeZone)) contextual(FareClass::class, FareClassSerializer) } ) {