Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the serializers using the DateTimeFormat API #415

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions core/common/src/DateTimePeriod.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import kotlinx.datetime.internal.*
import kotlinx.datetime.serializers.DatePeriodIso8601Serializer
import kotlinx.datetime.serializers.DateTimePeriodIso8601Serializer
import kotlinx.datetime.serializers.DatePeriodComponentSerializer
import kotlinx.datetime.serializers.DatePeriodSerializer
import kotlinx.datetime.serializers.DateTimePeriodComponentSerializer
import kotlinx.datetime.serializers.DateTimePeriodSerializer
import kotlin.math.*
import kotlin.time.Duration
import kotlinx.serialization.Serializable
Expand Down Expand Up @@ -63,13 +65,14 @@ import kotlinx.serialization.Serializable
* `DateTimePeriod` can also be returned as the result of instant arithmetic operations (see [Instant.periodUntil]).
*
* Additionally, there are several `kotlinx-serialization` serializers for [DateTimePeriod]:
* - [DateTimePeriodIso8601Serializer] for the ISO 8601 format;
* - The default serializer, delegating to [toString] and [parse].
* - [DateTimePeriodIso8601Serializer] for the ISO 8601 format.
* - [DateTimePeriodComponentSerializer] for an object with components.
*
* @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.construction
* @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.simpleParsingAndFormatting
*/
@Serializable(with = DateTimePeriodIso8601Serializer::class)
@Serializable(with = DateTimePeriodSerializer::class)
// TODO: could be error-prone without explicitly named params
public sealed class DateTimePeriod {
internal abstract val totalMonths: Int
Expand Down Expand Up @@ -426,7 +429,7 @@ public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this
*
* @sample kotlinx.datetime.test.samples.DatePeriodSamples.simpleParsingAndFormatting
*/
@Serializable(with = DatePeriodIso8601Serializer::class)
@Serializable(with = DatePeriodSerializer::class)
public class DatePeriod internal constructor(
internal override val totalMonths: Int,
override val days: Int,
Expand Down
6 changes: 3 additions & 3 deletions core/common/src/Instant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ package kotlinx.datetime

import kotlinx.datetime.format.*
import kotlinx.datetime.internal.*
import kotlinx.datetime.serializers.InstantIso8601Serializer
import kotlinx.datetime.serializers.InstantComponentSerializer
import kotlinx.datetime.serializers.*
import kotlinx.serialization.Serializable
import kotlin.time.*

Expand Down Expand Up @@ -189,12 +188,13 @@ import kotlin.time.*
* ```
*
* Additionally, there are several `kotlinx-serialization` serializers for [Instant]:
* - The default serializer, delegating to [toString] and [parse].
* - [InstantIso8601Serializer] for the ISO 8601 extended format.
* - [InstantComponentSerializer] for an object with components.
*
* @see LocalDateTime for a user-visible representation of moments in time in an unspecified time zone.
*/
@Serializable(with = InstantIso8601Serializer::class)
@Serializable(with = InstantSerializer::class)
public expect class Instant : Comparable<Instant> {

/**
Expand Down
3 changes: 2 additions & 1 deletion core/common/src/LocalDate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import kotlinx.serialization.Serializable
* See sample 4.
*
* Additionally, there are several `kotlinx-serialization` serializers for [LocalDate]:
* - The default serializer, delegating to [toString] and [parse].
* - [LocalDateIso8601Serializer] for the ISO 8601 extended format.
* - [LocalDateComponentSerializer] for an object with components.
*
Expand All @@ -64,7 +65,7 @@ import kotlinx.serialization.Serializable
* @sample kotlinx.datetime.test.samples.LocalDateSamples.simpleParsingAndFormatting
* @sample kotlinx.datetime.test.samples.LocalDateSamples.customFormat
*/
@Serializable(with = LocalDateIso8601Serializer::class)
@Serializable(with = LocalDateSerializer::class)
public expect class LocalDate : Comparable<LocalDate> {
public companion object {
/**
Expand Down
6 changes: 3 additions & 3 deletions core/common/src/LocalDateTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
package kotlinx.datetime

import kotlinx.datetime.format.*
import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer
import kotlinx.datetime.serializers.LocalDateTimeComponentSerializer
import kotlinx.datetime.serializers.*
import kotlinx.serialization.Serializable

/**
Expand Down Expand Up @@ -94,6 +93,7 @@ import kotlinx.serialization.Serializable
* See sample 4.
*
* Additionally, there are several `kotlinx-serialization` serializers for [LocalDateTime]:
* - The default serializer, delegating to [toString] and [parse].
* - [LocalDateTimeIso8601Serializer] for the ISO 8601 extended format.
* - [LocalDateTimeComponentSerializer] for an object with components.
*
Expand All @@ -105,7 +105,7 @@ import kotlinx.serialization.Serializable
* @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.simpleParsingAndFormatting
* @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.customFormat
*/
@Serializable(with = LocalDateTimeIso8601Serializer::class)
@Serializable(with = LocalDateTimeSerializer::class)
public expect class LocalDateTime : Comparable<LocalDateTime> {
public companion object {

Expand Down
6 changes: 3 additions & 3 deletions core/common/src/LocalTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ package kotlinx.datetime

import kotlinx.datetime.LocalDate.Companion.parse
import kotlinx.datetime.format.*
import kotlinx.datetime.serializers.LocalTimeIso8601Serializer
import kotlinx.datetime.serializers.LocalTimeComponentSerializer
import kotlinx.datetime.serializers.*
import kotlinx.serialization.Serializable


Expand Down Expand Up @@ -68,6 +67,7 @@ import kotlinx.serialization.Serializable
* See sample 4.
*
* Additionally, there are several `kotlinx-serialization` serializers for [LocalTime]:
* - The default serializer, delegating to [toString] and [parse].
* - [LocalTimeIso8601Serializer] for the ISO 8601 extended format,
* - [LocalTimeComponentSerializer] for an object with components.
*
Expand All @@ -76,7 +76,7 @@ import kotlinx.serialization.Serializable
* @sample kotlinx.datetime.test.samples.LocalTimeSamples.simpleParsingAndFormatting
* @sample kotlinx.datetime.test.samples.LocalTimeSamples.customFormat
*/
@Serializable(LocalTimeIso8601Serializer::class)
@Serializable(LocalTimeSerializer::class)
public expect class LocalTime : Comparable<LocalTime> {
public companion object {

Expand Down
6 changes: 4 additions & 2 deletions core/common/src/UtcOffset.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package kotlinx.datetime

import kotlinx.datetime.format.*
import kotlinx.datetime.serializers.UtcOffsetSerializer
import kotlinx.datetime.serializers.*
import kotlinx.serialization.Serializable

/**
Expand Down Expand Up @@ -47,12 +47,14 @@ import kotlinx.serialization.Serializable
* [parse] and [UtcOffset.format] both support custom formats created with [Format] or defined in [Formats].
* See sample 3.
*
* To serialize and deserialize [UtcOffset] values with `kotlinx-serialization`, use the [UtcOffsetSerializer].
* To serialize and deserialize [UtcOffset] values with `kotlinx-serialization`, use the default serializer,
* or [UtcOffsetIso8601Serializer] for the ISO 8601 format explicitly.
*
* @sample kotlinx.datetime.test.samples.UtcOffsetSamples.construction
* @sample kotlinx.datetime.test.samples.UtcOffsetSamples.simpleParsingAndFormatting
* @sample kotlinx.datetime.test.samples.UtcOffsetSamples.customFormat
*/
@Suppress("DEPRECATION")
@Serializable(with = UtcOffsetSerializer::class)
public expect class UtcOffset {
/**
Expand Down
20 changes: 16 additions & 4 deletions core/common/src/serializers/DateTimePeriodSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import kotlinx.serialization.encoding.*
public object DateTimePeriodComponentSerializer: KSerializer<DateTimePeriod> {

override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("kotlinx.datetime.DateTimePeriod") {
buildClassSerialDescriptor("kotlinx.datetime.DateTimePeriod components") {
element<Int>("years", isOptional = true)
element<Int>("months", isOptional = true)
element<Int>("days", isOptional = true)
Expand Down Expand Up @@ -81,7 +81,7 @@ public object DateTimePeriodComponentSerializer: KSerializer<DateTimePeriod> {
public object DateTimePeriodIso8601Serializer: KSerializer<DateTimePeriod> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.DateTimePeriod", PrimitiveKind.STRING)
PrimitiveSerialDescriptor("kotlinx.datetime.DateTimePeriod ISO", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): DateTimePeriod =
DateTimePeriod.parse(decoder.decodeString())
Expand Down Expand Up @@ -110,7 +110,7 @@ public object DatePeriodComponentSerializer: KSerializer<DatePeriod> {
private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong())

override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("kotlinx.datetime.DatePeriod") {
buildClassSerialDescriptor("kotlinx.datetime.DatePeriod components") {
element<Int>("years", isOptional = true)
element<Int>("months", isOptional = true)
element<Int>("days", isOptional = true)
Expand Down Expand Up @@ -166,7 +166,7 @@ public object DatePeriodComponentSerializer: KSerializer<DatePeriod> {
public object DatePeriodIso8601Serializer: KSerializer<DatePeriod> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.DatePeriod", PrimitiveKind.STRING)
PrimitiveSerialDescriptor("kotlinx.datetime.DatePeriod ISO", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): DatePeriod =
when (val period = DateTimePeriod.parse(decoder.decodeString())) {
Expand All @@ -179,3 +179,15 @@ public object DatePeriodIso8601Serializer: KSerializer<DatePeriod> {
}

}

@PublishedApi
internal object DateTimePeriodSerializer: KSerializer<DateTimePeriod> by DateTimePeriodIso8601Serializer {
override val descriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.DateTimePeriod", PrimitiveKind.STRING)
}

@PublishedApi
internal object DatePeriodSerializer: KSerializer<DatePeriod> by DatePeriodIso8601Serializer {
override val descriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.DatePeriod", PrimitiveKind.STRING)
}
82 changes: 75 additions & 7 deletions core/common/src/serializers/InstantSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

package kotlinx.datetime.serializers

import kotlinx.datetime.Instant
import kotlinx.datetime.*
import kotlinx.datetime.format.DateTimeComponents
import kotlinx.datetime.format.DateTimeFormat
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
Expand All @@ -15,19 +17,18 @@ import kotlinx.serialization.encoding.*
*
* JSON example: `"2020-12-09T09:16:56.000124Z"`
*
* @see Instant.toString
* @see Instant.parse
* @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET
*/
public object InstantIso8601Serializer : KSerializer<Instant> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.Instant", PrimitiveKind.STRING)
PrimitiveSerialDescriptor("kotlinx.datetime.Instant ISO", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): Instant =
Instant.parse(decoder.decodeString())
Instant.parse(decoder.decodeString(), DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)

override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(value.toString())
encoder.encodeString(value.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET))
}

}
Expand All @@ -40,7 +41,7 @@ public object InstantIso8601Serializer : KSerializer<Instant> {
public object InstantComponentSerializer : KSerializer<Instant> {

override val descriptor: SerialDescriptor =
buildClassSerialDescriptor("kotlinx.datetime.Instant") {
buildClassSerialDescriptor("kotlinx.datetime.Instant components") {
element<Long>("epochSeconds")
element<Long>("nanosecondsOfSecond", isOptional = true)
}
Expand Down Expand Up @@ -75,3 +76,70 @@ public object InstantComponentSerializer : KSerializer<Instant> {
}

}

/**
* An abstract serializer for [Instant] values that uses
* a custom [DateTimeFormat] for serializing to and deserializing.
*
* [format] should be a format that includes enough components to unambiguously define a date, a time, and a UTC offset.
* See [Instant.parse] for details of how deserialization is performed.
*
* [name] is the name of the serializer.
* The [SerialDescriptor.serialName] of the resulting serializer is `kotlinx.datetime.Instant serializer `[name].
* [SerialDescriptor.serialName] must be unique across all serializers in the same serialization context.
* When defining a serializer in a library, it is recommended to use the fully qualified class name in [name]
* to avoid conflicts with serializers defined by other libraries and client code.
*
* When serializing, the [Instant] value is formatted as a string using the specified [format]
* in the [ZERO][UtcOffset.ZERO] UTC offset.
*
* This serializer is abstract and must be subclassed to provide a concrete serializer.
* Example:
* ```
* // serializes LocalDateTime(2008, 6, 30, 11, 5, 30).toInstant(TimeZone.UTC)
* // as the string "Mon, 30 Jun 2008 11:05:30 GMT"
* object Rfc1123InstantSerializer : FormattedInstantSerializer(
* "my.package.RFC1123", DateTimeComponents.Formats.RFC_1123
* )
* ```
*
* Note that [Instant] is [kotlinx.serialization.Serializable] by default,
* so it is not necessary to create custom serializers when the format is not important.
* Additionally, [InstantIso8601Serializer] is provided for the ISO 8601 format.
*/
public abstract class FormattedInstantSerializer(
name: String,
private val format: DateTimeFormat<DateTimeComponents>,
) : KSerializer<Instant> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.Instant serializer $name", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): Instant =
Instant.parse(decoder.decodeString(), format)

override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(value.format(format))
}

@OptIn(ExperimentalSerializationApi::class)
override fun toString(): String = descriptor.serialName
}

/**
* A serializer for [Instant] that uses the default [Instant.toString]/[Instant.parse].
*
* JSON example: `"2020-12-09T09:16:56.000124Z"`
*/
@PublishedApi internal object InstantSerializer : KSerializer<Instant> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.datetime.Instant", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): Instant =
Instant.parse(decoder.decodeString())

override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(value.toString())
}

}
Loading