From 3b65a0e05120f2f89cb58c9be1c9ebb507bdf954 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 20 Nov 2020 15:25:33 +0300 Subject: [PATCH 01/25] Implement serialization of an Instant --- core/build.gradle.kts | 2 ++ core/common/src/Instant.kt | 1 + core/js/src/Instant.kt | 36 ++++++++++++++++++++++++++++++++++++ core/jvm/src/Instant.kt | 36 ++++++++++++++++++++++++++++++++++++ core/native/src/Instant.kt | 2 ++ 5 files changed, 77 insertions(+) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2c923cd8f..e67c16946 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -5,6 +5,7 @@ import javax.xml.parsers.DocumentBuilderFactory plugins { id("kotlin-multiplatform") + kotlin("plugin.serialization") version "1.4.10" `maven-publish` } @@ -148,6 +149,7 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") } } diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index d414fbac9..bab057dee 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -7,6 +7,7 @@ package kotlinx.datetime import kotlin.time.Duration import kotlin.time.ExperimentalTime +import kotlinx.serialization.Serializable @OptIn(ExperimentalTime::class) public expect class Instant : Comparable { diff --git a/core/js/src/Instant.kt b/core/js/src/Instant.kt index d371808ea..41b9401ec 100644 --- a/core/js/src/Instant.kt +++ b/core/js/src/Instant.kt @@ -14,8 +14,44 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration import kotlinx.datetime.internal.JSJoda.Clock as jtClock import kotlinx.datetime.internal.JSJoda.ChronoUnit +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlin.math.truncate +object InstantSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochSeconds") + element("nanosecondsOfSecond") + } + + override fun deserialize(decoder: Decoder): Instant = + decoder.decodeStructure(descriptor) { + var epochSeconds = 0L + var nanosecondsOfSecond = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochSeconds = decodeLongElement(descriptor, 0) + 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.epochSeconds) + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } + } + +} + +@Serializable(with = InstantSerializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 988742bc3..50185255c 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -6,6 +6,9 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit @@ -13,6 +16,39 @@ import kotlin.time.* import java.time.Instant as jtInstant import java.time.Clock as jtClock +object InstantSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochSeconds") + element("nanosecondsOfSecond") + } + + override fun deserialize(decoder: Decoder): Instant = + decoder.decodeStructure(descriptor) { + var epochSeconds = 0L + var nanosecondsOfSecond = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochSeconds = decodeLongElement(descriptor, 0) + 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.epochSeconds) + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } + } + +} + +@Serializable(with = InstantSerializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index 32b924a31..2f3d63e5b 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -8,6 +8,7 @@ package kotlinx.datetime +import kotlinx.serialization.Serializable import kotlin.math.* import kotlin.time.* @@ -83,6 +84,7 @@ private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second internal expect fun currentTime(): Instant +@Serializable @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(actual val epochSeconds: Long, actual val nanosecondsOfSecond: Int) : Comparable { From 357b1511d70dce62bedd458928e88a67856c3962 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 27 Nov 2020 18:16:58 +0300 Subject: [PATCH 02/25] Implement some serializers in common code --- core/build.gradle.kts | 3 +- core/common/src/Instant.kt | 71 ++++++++++++++++++++++++++++++++++-- core/common/src/LocalDate.kt | 55 ++++++++++++++++++++++++++++ core/js/src/Instant.kt | 36 ------------------ core/jvm/src/Instant.kt | 36 ------------------ core/native/src/Instant.kt | 2 - 6 files changed, 125 insertions(+), 78 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e67c16946..eda819474 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -149,7 +149,8 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + api("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1") } } diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index bab057dee..366074f2f 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -5,9 +5,74 @@ package kotlinx.datetime -import kotlin.time.Duration -import kotlin.time.ExperimentalTime -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.time.* + +object InstantISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Instant = + Instant.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeString(value.toString()) + } + +} + +@OptIn(ExperimentalTime::class) +object InstantDoubleSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.DOUBLE) + + override fun deserialize(decoder: Decoder): Instant { + val secondsSince1970 = decoder.decodeDouble() + return Instant.fromEpochSeconds(0) + secondsSince1970.seconds + } + + override fun serialize(encoder: Encoder, value: Instant) { + val durationFrom1970 = value - Instant.fromEpochSeconds(0) + encoder.encodeDouble(durationFrom1970.inSeconds) + } + +} + +object InstantSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochSeconds") + element("nanosecondsOfSecond") + } + + override fun deserialize(decoder: Decoder): Instant = + decoder.decodeStructure(descriptor) { + var epochSeconds = 0L + var nanosecondsOfSecond = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochSeconds = decodeLongElement(descriptor, 0) + 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.epochSeconds) + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } + } + +} @OptIn(ExperimentalTime::class) public expect class Instant : Comparable { diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 43045a15e..c2fc5003b 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -5,6 +5,61 @@ package kotlinx.datetime +import kotlin.time.* +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object LocalDateISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeString(value.toString()) + } + +} + +object LocalDateSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("year") + element("month") + element("day") + } + + override fun deserialize(decoder: Decoder): LocalDate = + decoder.decodeStructure(descriptor) { + var year = 0 + var month: Short = 0 + var day: Short = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> year = decodeIntElement(descriptor, 0) + 1 -> month = decodeShortElement(descriptor, 1) + 2 -> day = decodeShortElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + LocalDate(year, month.toInt(), day.toInt()) + } + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.year) + encodeShortElement(descriptor, 1, value.monthNumber.toShort()) + encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) + } + } + +} + public expect class LocalDate : Comparable { companion object { /** diff --git a/core/js/src/Instant.kt b/core/js/src/Instant.kt index 41b9401ec..d371808ea 100644 --- a/core/js/src/Instant.kt +++ b/core/js/src/Instant.kt @@ -14,44 +14,8 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration import kotlinx.datetime.internal.JSJoda.Clock as jtClock import kotlinx.datetime.internal.JSJoda.ChronoUnit -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* import kotlin.math.truncate -object InstantSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { - element("epochSeconds") - element("nanosecondsOfSecond") - } - - override fun deserialize(decoder: Decoder): Instant = - decoder.decodeStructure(descriptor) { - var epochSeconds = 0L - var nanosecondsOfSecond = 0 - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochSeconds = decodeLongElement(descriptor, 0) - 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.epochSeconds) - encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) - } - } - -} - -@Serializable(with = InstantSerializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 50185255c..988742bc3 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -6,9 +6,6 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit @@ -16,39 +13,6 @@ import kotlin.time.* import java.time.Instant as jtInstant import java.time.Clock as jtClock -object InstantSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { - element("epochSeconds") - element("nanosecondsOfSecond") - } - - override fun deserialize(decoder: Decoder): Instant = - decoder.decodeStructure(descriptor) { - var epochSeconds = 0L - var nanosecondsOfSecond = 0 - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochSeconds = decodeLongElement(descriptor, 0) - 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.epochSeconds) - encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) - } - } - -} - -@Serializable(with = InstantSerializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index 2f3d63e5b..32b924a31 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -8,7 +8,6 @@ package kotlinx.datetime -import kotlinx.serialization.Serializable import kotlin.math.* import kotlin.time.* @@ -84,7 +83,6 @@ private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second internal expect fun currentTime(): Instant -@Serializable @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(actual val epochSeconds: Long, actual val nanosecondsOfSecond: Int) : Comparable { From 73312a125af85b5ef6b264b7915d71d393e3dc98 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 27 Nov 2020 18:18:09 +0300 Subject: [PATCH 03/25] Add rough measurements of serialization efficiency --- core/build.gradle.kts | 1 + core/jvm/src/SerializationMeasurements.kt | 90 +++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 core/jvm/src/SerializationMeasurements.kt diff --git a/core/build.gradle.kts b/core/build.gradle.kts index eda819474..f6e9bf9ab 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -149,6 +149,7 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") + // this is not `compileOnly` only temporarily, until there is a source set for testing serialization api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") api("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1") } diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt new file mode 100644 index 000000000..ca27d1b55 --- /dev/null +++ b/core/jvm/src/SerializationMeasurements.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +package kotlinx.datetime + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.json.* +import kotlinx.serialization.protobuf.ProtoBuf +import kotlin.system.* + +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets.US_ASCII +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream + +@ExperimentalSerializationApi +inline fun protobufMeasurements(serializer: KSerializer, generator: () -> Array) { + val instants = generator() + var result: ByteArray? = null + val time = measureTimeMillis { + result = ProtoBuf.encodeToByteArray(ArraySerializer(serializer), instants) + } + val time2 = measureTimeMillis { + ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), result!!) + } + println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.size}\tfor $serializer") +} + +@ExperimentalSerializationApi +inline fun jsonMeasurements(serializer: KSerializer, generator: () -> Array) { + val instants = generator() + var result: String? = null + val time = measureTimeMillis { + result = Json.encodeToString(ArraySerializer(serializer), instants) + } + val time2 = measureTimeMillis { + Json.decodeFromString(ArraySerializer(serializer), result!!) + } + println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.toByteArray().size}\tfor $serializer") +} + +@ExperimentalSerializationApi +inline fun gzippedJsonMeasurements(serializer: KSerializer, generator: () -> Array) { + val instants = generator() + var result: ByteArray? = null + val time = measureTimeMillis { + result = gzip(Json.encodeToString(ArraySerializer(serializer), instants)) + } + val time2 = measureTimeMillis { + Json.decodeFromString(ArraySerializer(serializer), ungzip(result!!)) + } + println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.size}\tfor $serializer") +} + +@ExperimentalSerializationApi +inline fun measurements(vararg serializers: KSerializer, crossinline generator: () -> Array) { + println("Protobuf:") + for (serializer in serializers) { + protobufMeasurements(serializer, generator) + } + println("JSON:") + for (serializer in serializers) { + jsonMeasurements(serializer, generator) + } + println("Gzipped JSON:") + for (serializer in serializers) { + gzippedJsonMeasurements(serializer, generator) + } +} + +@OptIn(ExperimentalSerializationApi::class) +fun main() { + measurements(InstantSerializer, InstantISO8601Serializer, InstantDoubleSerializer) { + Array(10_000) { Clock.System.now() } + } + measurements(LocalDateSerializer, LocalDateISO8601Serializer) { + Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC).date } + } +} + +fun gzip(content: String): ByteArray { + val bos = ByteArrayOutputStream() + GZIPOutputStream(bos).bufferedWriter(US_ASCII).use { it.write(content) } + return bos.toByteArray() +} + +fun ungzip(content: ByteArray): String = + GZIPInputStream(content.inputStream()).bufferedReader(US_ASCII).use { it.readText() } From 1881a0dfd7f75c3d6862d49e76343b82ab840d21 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 1 Dec 2020 12:18:36 +0300 Subject: [PATCH 04/25] Add serializers for LocalDate, LocalDateTime, DateTimePeriod Additionally, check gzipped JSON and Protobuf in the "benchmarks". --- core/common/src/DateTimePeriod.kt | 100 ++++++++++++++++- core/common/src/Instant.kt | 18 ---- core/common/src/LocalDate.kt | 2 + core/common/src/LocalDateTime.kt | 70 ++++++++++++ core/common/src/Month.kt | 15 +++ core/js/src/LocalDate.kt | 17 +++ core/js/src/LocalDateTime.kt | 36 +++++++ core/jvm/src/LocalDate.kt | 16 +++ core/jvm/src/LocalDateTime.kt | 37 +++++++ core/jvm/src/SerializationMeasurements.kt | 126 ++++++++++++++++------ core/native/src/LocalDate.kt | 17 +++ core/native/src/LocalDateTime.kt | 38 +++++++ 12 files changed, 439 insertions(+), 53 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 8d454ce97..57ea6b18b 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -8,7 +8,67 @@ package kotlinx.datetime import kotlin.math.* import kotlin.time.Duration import kotlin.time.ExperimentalTime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +object DateTimePeriodSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("years") + element("months") + element("days") + element("hours") + element("minutes") + element("seconds") + element("nanoseconds") + } + + override fun deserialize(decoder: Decoder): DateTimePeriod = + decoder.decodeStructure(descriptor) { + var years = 0 + var months = 0 + var days = 0 + var hours = 0 + var minutes = 0 + var seconds = 0 + var nanoseconds = 0L + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> years = decodeIntElement(descriptor, 0) + 1 -> months = decodeIntElement(descriptor, 1) + 2 -> days = decodeIntElement(descriptor, 2) + 3 -> hours = decodeIntElement(descriptor, 3) + 4 -> minutes = decodeIntElement(descriptor, 4) + 5 -> seconds = decodeIntElement(descriptor, 5) + 6 -> nanoseconds = decodeLongElement(descriptor, 6) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DateTimePeriod(years, months, days, hours, minutes, seconds, nanoseconds) + } + + override fun serialize(encoder: Encoder, value: DateTimePeriod) { + encoder.encodeStructure(descriptor) { + with (value) { + encodeIntElement(descriptor, 0, value.years) + encodeIntElement(descriptor, 1, value.months) + encodeIntElement(descriptor, 2, value.days) + if (hours or minutes or seconds != 0 || nanoseconds != 0) { + encodeIntElement(descriptor, 3, value.hours) + encodeIntElement(descriptor, 4, value.minutes) + encodeIntElement(descriptor, 5, value.seconds) + encodeLongElement(descriptor, 6, value.nanoseconds.toLong()) + } + } + } + } + +} + +@Serializable(with = DateTimePeriodSerializer::class) // TODO: could be error-prone without explicitly named params sealed class DateTimePeriod { internal abstract val totalMonths: Int @@ -233,9 +293,45 @@ sealed class DateTimePeriod { public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this) +object DatePeriodSerializer: KSerializer { + + override val descriptor: SerialDescriptor = DateTimePeriodSerializer.descriptor + + override fun deserialize(decoder: Decoder): DatePeriod = + decoder.decodeStructure(descriptor) { + var years = 0 + var months = 0 + var days = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> years = decodeIntElement(descriptor, 0) + 1 -> months = decodeIntElement(descriptor, 1) + 2 -> days = decodeIntElement(descriptor, 2) + 3 -> require(decodeIntElement(descriptor, 3) == 0) + 4 -> require(decodeIntElement(descriptor, 4) == 0) + 5 -> require(decodeLongElement(descriptor, 5) == 0L) + 6 -> require(decodeLongElement(descriptor, 6) == 0L) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + DatePeriod(years, months, days) + } + + override fun serialize(encoder: Encoder, value: DatePeriod) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.years) + encodeIntElement(descriptor, 1, value.months) + encodeIntElement(descriptor, 2, value.days) + } + } + +} + +@Serializable(with = DatePeriodSerializer::class) class DatePeriod internal constructor( - internal override val totalMonths: Int, - override val days: Int, + internal override val totalMonths: Int, + override val days: Int, ) : DateTimePeriod() { constructor(years: Int = 0, months: Int = 0, days: Int = 0): this(totalMonths(years, months), days) // avoiding excessive computations diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 366074f2f..f9dbdbdd0 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -24,24 +24,6 @@ object InstantISO8601Serializer: KSerializer { } -@OptIn(ExperimentalTime::class) -object InstantDoubleSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Instant", PrimitiveKind.DOUBLE) - - override fun deserialize(decoder: Decoder): Instant { - val secondsSince1970 = decoder.decodeDouble() - return Instant.fromEpochSeconds(0) + secondsSince1970.seconds - } - - override fun serialize(encoder: Encoder, value: Instant) { - val durationFrom1970 = value - Instant.fromEpochSeconds(0) - encoder.encodeDouble(durationFrom1970.inSeconds) - } - -} - object InstantSerializer: KSerializer { override val descriptor: SerialDescriptor = diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index c2fc5003b..14c7f41bd 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -60,6 +60,8 @@ object LocalDateSerializer: KSerializer { } +expect object LocalDateLongSerializer: KSerializer + public expect class LocalDate : Comparable { companion object { /** diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 9b8e84435..cdaad2b96 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -5,7 +5,77 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +object LocalDateTimeISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): LocalDateTime = + LocalDateTime.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeString(value.toString()) + } + +} + +object LocalDateTimeSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("year") + element("month") + element("day") + element("hour") + element("minute") + element("second") + element("nanosecond") + } + + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var year = 0 + var month: Short = 0 + var day: Short = 0 + var hour: Short = 0 + var minute: Short = 0 + var second: Short = 0 + var nanosecond = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> year = decodeIntElement(descriptor, 0) + 1 -> month = decodeShortElement(descriptor, 1) + 2 -> day = decodeShortElement(descriptor, 2) + 3 -> hour = decodeShortElement(descriptor, 3) + 4 -> minute = decodeShortElement(descriptor, 4) + 5 -> second = decodeShortElement(descriptor, 5) + 6 -> nanosecond = decodeIntElement(descriptor, 6) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + LocalDateTime(year, month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt(), nanosecond) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.year) + encodeShortElement(descriptor, 1, value.monthNumber.toShort()) + encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) + encodeShortElement(descriptor, 3, value.hour.toShort()) + encodeShortElement(descriptor, 4, value.minute.toShort()) + encodeShortElement(descriptor, 5, value.second.toShort()) + encodeIntElement(descriptor, 6, value.nanosecond) + } + } + +} + +expect object LocalDateTimeCompactSerializer: KSerializer public expect class LocalDateTime : Comparable { companion object { diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 8ee98566c..964b948a8 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -6,6 +6,21 @@ package kotlinx.datetime import kotlin.native.concurrent.* +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object MonthSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Month", PrimitiveKind.INT) + + override fun deserialize(decoder: Decoder): Month = Month(decoder.decodeInt()) + + override fun serialize(encoder: Encoder, value: Month) { + encoder.encodeInt(value.number) + } +} public expect enum class Month { JANUARY, diff --git a/core/js/src/LocalDate.kt b/core/js/src/LocalDate.kt index b384b86b0..6206a5e00 100644 --- a/core/js/src/LocalDate.kt +++ b/core/js/src/LocalDate.kt @@ -6,8 +6,25 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ChronoUnit +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate(jtLocalDate.ofEpochDay(decoder.decodeLong())) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.value.toEpochDay().toLong()) + } + +} + public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { public actual fun parse(isoString: String): LocalDate = try { diff --git a/core/js/src/LocalDateTime.kt b/core/js/src/LocalDateTime.kt index 02366701e..20f9bfdfc 100644 --- a/core/js/src/LocalDateTime.kt +++ b/core/js/src/LocalDateTime.kt @@ -4,8 +4,44 @@ */ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlinx.datetime.internal.JSJoda.LocalDateTime as jtLocalDateTime +import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate +import kotlinx.datetime.internal.JSJoda.LocalTime as jtLocalTime +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochDay") + element("nanoOfDay") + } + + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay = 0L + var nanoOfDay = 0L + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.value.toEpochDay().toLong()) + encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay().toLong()) + } + } + +} public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index d76f5f576..d4700f73b 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -5,11 +5,27 @@ @file:JvmName("LocalDateJvmKt") package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit import java.time.LocalDate as jtLocalDate +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate(jtLocalDate.ofEpochDay(decoder.decodeLong())) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.value.toEpochDay()) + } + +} public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { diff --git a/core/jvm/src/LocalDateTime.kt b/core/jvm/src/LocalDateTime.kt index 235146b50..294c7be9a 100644 --- a/core/jvm/src/LocalDateTime.kt +++ b/core/jvm/src/LocalDateTime.kt @@ -5,14 +5,51 @@ @file:JvmName("LocalDateTimeJvmKt") package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.LocalDateTime as jtLocalDateTime +import java.time.LocalDate as jtLocalDate +import java.time.LocalTime as jtLocalTime public actual typealias Month = java.time.Month public actual typealias DayOfWeek = java.time.DayOfWeek +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochDay") + element("nanoOfDay") + } + + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay = 0L + var nanoOfDay = 0L + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.value.toEpochDay()) + encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay()) + } + } + +} + public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { public actual constructor(year: Int, monthNumber: Int, dayOfMonth: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt index ca27d1b55..24bc57662 100644 --- a/core/jvm/src/SerializationMeasurements.kt +++ b/core/jvm/src/SerializationMeasurements.kt @@ -14,44 +14,94 @@ import java.io.ByteArrayOutputStream import java.nio.charset.StandardCharsets.US_ASCII import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream +import kotlin.random.* + +const val REPETITIONS = 1 @ExperimentalSerializationApi inline fun protobufMeasurements(serializer: KSerializer, generator: () -> Array) { - val instants = generator() - var result: ByteArray? = null - val time = measureTimeMillis { - result = ProtoBuf.encodeToByteArray(ArraySerializer(serializer), instants) - } - val time2 = measureTimeMillis { - ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), result!!) + repeat(REPETITIONS) { + val values = generator() + val result: ByteArray + val time = measureTimeMillis { + result = ProtoBuf.encodeToByteArray(ArraySerializer(serializer), values) + } + val newValues: Array + val time2 = measureTimeMillis { + newValues = ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), result) + } + assert(values.contentEquals(newValues)) + if (it == REPETITIONS - 1) { + println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") + } } - println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.size}\tfor $serializer") } @ExperimentalSerializationApi inline fun jsonMeasurements(serializer: KSerializer, generator: () -> Array) { - val instants = generator() - var result: String? = null - val time = measureTimeMillis { - result = Json.encodeToString(ArraySerializer(serializer), instants) + repeat(REPETITIONS) { + val values = generator() + val result: String + val time = measureTimeMillis { + result = Json.encodeToString(ArraySerializer(serializer), values) + } + val newValues: Array + val time2 = measureTimeMillis { + newValues = Json.decodeFromString(ArraySerializer(serializer), result) + } + assert(values.contentEquals(newValues)) + if (it == REPETITIONS - 1) { + println("time (ser): $time\ttime (des): $time2\tbytes: ${result.toByteArray().size}\tfor $serializer") + } } - val time2 = measureTimeMillis { - Json.decodeFromString(ArraySerializer(serializer), result!!) +} + +@ExperimentalSerializationApi +inline fun gzippedProtobufMeasurements(serializer: KSerializer, generator: () -> Array) { + repeat(REPETITIONS) { + val values = generator() + val result: ByteArray + val time = measureTimeMillis { + val bos = ByteArrayOutputStream() + GZIPOutputStream(bos).use { + it.write(ProtoBuf.encodeToByteArray(ArraySerializer(serializer), values)) + } + result = bos.toByteArray() + } + val newValues: Array + val time2 = measureTimeMillis { + val ungzipped = GZIPInputStream(result.inputStream()).use { it.readBytes() } + newValues = ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), ungzipped) + } + assert(values.contentEquals(newValues)) + if (it == REPETITIONS - 1) { + println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") + } } - println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.toByteArray().size}\tfor $serializer") } @ExperimentalSerializationApi inline fun gzippedJsonMeasurements(serializer: KSerializer, generator: () -> Array) { - val instants = generator() - var result: ByteArray? = null - val time = measureTimeMillis { - result = gzip(Json.encodeToString(ArraySerializer(serializer), instants)) - } - val time2 = measureTimeMillis { - Json.decodeFromString(ArraySerializer(serializer), ungzip(result!!)) + repeat(REPETITIONS) { + val values = generator() + val result: ByteArray + val time = measureTimeMillis { + val bos = ByteArrayOutputStream() + GZIPOutputStream(bos).bufferedWriter(US_ASCII).use { + it.write(Json.encodeToString(ArraySerializer(serializer), values)) + } + result = bos.toByteArray() + } + val newValues: Array + val time2 = measureTimeMillis { + val ungzipped = GZIPInputStream(result.inputStream()).bufferedReader(US_ASCII).use { it.readText() } + newValues = Json.decodeFromString(ArraySerializer(serializer), ungzipped) + } + assert(values.contentEquals(newValues)) + if (it == REPETITIONS - 1) { + println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") + } } - println("time (ser): $time\ttime (des): $time2\tbytes: ${result!!.size}\tfor $serializer") } @ExperimentalSerializationApi @@ -64,6 +114,10 @@ inline fun measurements(vararg serializers: KSerializer, cros for (serializer in serializers) { jsonMeasurements(serializer, generator) } + println("Gzipped Protobuf:") + for (serializer in serializers) { + gzippedProtobufMeasurements(serializer, generator) + } println("Gzipped JSON:") for (serializer in serializers) { gzippedJsonMeasurements(serializer, generator) @@ -72,19 +126,25 @@ inline fun measurements(vararg serializers: KSerializer, cros @OptIn(ExperimentalSerializationApi::class) fun main() { - measurements(InstantSerializer, InstantISO8601Serializer, InstantDoubleSerializer) { + measurements(InstantSerializer, InstantISO8601Serializer) { Array(10_000) { Clock.System.now() } } - measurements(LocalDateSerializer, LocalDateISO8601Serializer) { + measurements(LocalDateSerializer, LocalDateISO8601Serializer, LocalDateLongSerializer) { Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC).date } } -} + measurements(LocalDateTimeSerializer, LocalDateTimeISO8601Serializer, LocalDateTimeCompactSerializer) { + Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC) } + } + measurements(MonthSerializer) { + Array(10_000) { Month(Random.nextInt(1, 13)) } + } -fun gzip(content: String): ByteArray { - val bos = ByteArrayOutputStream() - GZIPOutputStream(bos).bufferedWriter(US_ASCII).use { it.write(content) } - return bos.toByteArray() + val period = DatePeriod(10, 15, 20) + println(Json.encodeToString(period)) + println(Json.encodeToString(period as DateTimePeriod)) + println(Json.encodeToString(period as DateTimePeriod)) + println(Json.decodeFromString(DateTimePeriod.serializer(), """{"years":10,"months":15,"days":20,"hours":0,"minutes":0,"seconds":0,"nanoseconds":0}""")) + println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20,"hours":0,"minutes":0,"seconds":0,"nanoseconds":0}""")) + println(Json.decodeFromString(DateTimePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) + println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) } - -fun ungzip(content: ByteArray): String = - GZIPInputStream(content.inputStream()).bufferedReader(US_ASCII).use { it.readText() } diff --git a/core/native/src/LocalDate.kt b/core/native/src/LocalDate.kt index c73446756..6048d8c62 100644 --- a/core/native/src/LocalDate.kt +++ b/core/native/src/LocalDate.kt @@ -8,8 +8,25 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* import kotlin.math.* +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate.ofEpochDay(decoder.decodeLong().clampToInt()) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.toEpochDay().toLong()) + } + +} + // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5 // org.threeten.bp.format.DateTimeFormatter#ISO_LOCAL_DATE internal val localDateParser: Parser diff --git a/core/native/src/LocalDateTime.kt b/core/native/src/LocalDateTime.kt index b2677392a..9c9d6d948 100644 --- a/core/native/src/LocalDateTime.kt +++ b/core/native/src/LocalDateTime.kt @@ -8,6 +8,44 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochDay") + element("nanoOfDay") + } + + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay = 0L + var nanoOfDay = 0L + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + val date = LocalDate.ofEpochDay(epochDay.clampToInt()) + val time = LocalTime.ofNanoOfDay(nanoOfDay) + LocalDateTime(date, time) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.toEpochDay().toLong()) + encodeLongElement(descriptor, 1, value.time.toNanoOfDay()) + } + } + +} + // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5 // org.threeten.bp.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME internal val localDateTimeParser: Parser From 820516c3fcfb22a737e4ec9a93abc6b0462ec2ce Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 1 Dec 2020 15:04:30 +0300 Subject: [PATCH 05/25] Add serializers for DateTimeUnit --- core/common/src/DateTimeUnit.kt | 63 ++++++++++++----------- core/common/src/Month.kt | 6 +-- core/jvm/src/SerializationMeasurements.kt | 9 ++++ 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index 68a420915..c227ddb88 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -8,50 +8,48 @@ package kotlinx.datetime import kotlin.time.Duration import kotlin.time.ExperimentalTime import kotlin.time.nanoseconds +import kotlinx.serialization.* +@Serializable sealed class DateTimeUnit { abstract operator fun times(scalar: Int): DateTimeUnit + @Serializable + @SerialName("TimeBased") class TimeBased(val nanoseconds: Long) : DateTimeUnit() { - private val unitName: String - private val unitScale: Long + + /* fields without a default value can't be @Transient, so the more natural way of writing this + (setting [unitName] and [unitScale] in init { ... }) won't work. */ + @Transient + private val unitName: String = when { + nanoseconds % 3600_000_000_000 == 0L -> "HOUR" + nanoseconds % 60_000_000_000 == 0L -> "MINUTE" + nanoseconds % 1_000_000_000 == 0L -> "SECOND" + nanoseconds % 1_000_000 == 0L -> "MILLISECOND" + nanoseconds % 1_000 == 0L -> "MICROSECOND" + else -> "NANOSECOND" + } + + @Transient + private val unitScale: Long = when { + nanoseconds % 3600_000_000_000 == 0L -> nanoseconds / 3600_000_000_000 + nanoseconds % 60_000_000_000 == 0L -> nanoseconds / 60_000_000_000 + nanoseconds % 1_000_000_000 == 0L -> nanoseconds / 1_000_000_000 + nanoseconds % 1_000_000 == 0L -> nanoseconds / 1_000_000 + nanoseconds % 1_000 == 0L -> nanoseconds / 1_000 + else -> nanoseconds + } init { require(nanoseconds > 0) { "Unit duration must be positive, but was $nanoseconds ns." } - // find a concise string representation for the unit with this duration - when { - nanoseconds % 3600_000_000_000 == 0L -> { - unitName = "HOUR" - unitScale = nanoseconds / 3600_000_000_000 - } - nanoseconds % 60_000_000_000 == 0L -> { - unitName = "MINUTE" - unitScale = nanoseconds / 60_000_000_000 - } - nanoseconds % 1_000_000_000 == 0L -> { - unitName = "SECOND" - unitScale = nanoseconds / 1_000_000_000 - } - nanoseconds % 1_000_000 == 0L -> { - unitName = "MILLISECOND" - unitScale = nanoseconds / 1_000_000 - } - nanoseconds % 1_000 == 0L -> { - unitName = "MICROSECOND" - unitScale = nanoseconds / 1_000 - } - else -> { - unitName = "NANOSECOND" - unitScale = nanoseconds - } - } } override fun times(scalar: Int): TimeBased = TimeBased(safeMultiply(nanoseconds, scalar.toLong())) @ExperimentalTime - val duration: Duration = nanoseconds.nanoseconds + val duration: Duration + get() = nanoseconds.nanoseconds override fun equals(other: Any?): Boolean = this === other || (other is TimeBased && this.nanoseconds == other.nanoseconds) @@ -61,8 +59,11 @@ sealed class DateTimeUnit { override fun toString(): String = formatToString(unitScale, unitName) } + @Serializable sealed class DateBased : DateTimeUnit() { // TODO: investigate how to move subclasses up to DateTimeUnit scope + @Serializable + @SerialName("DayBased") class DayBased(val days: Int) : DateBased() { init { require(days > 0) { "Unit duration must be positive, but was $days days." } @@ -80,6 +81,8 @@ sealed class DateTimeUnit { else formatToString(days, "DAY") } + @Serializable + @SerialName("MonthBased") class MonthBased(val months: Int) : DateBased() { init { require(months > 0) { "Unit duration must be positive, but was $months months." } diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 964b948a8..aa370677f 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -13,12 +13,12 @@ import kotlinx.serialization.encoding.* object MonthSerializer: KSerializer { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Month", PrimitiveKind.INT) + PrimitiveSerialDescriptor("Month", PrimitiveKind.SHORT) - override fun deserialize(decoder: Decoder): Month = Month(decoder.decodeInt()) + override fun deserialize(decoder: Decoder): Month = Month(decoder.decodeShort().toInt()) override fun serialize(encoder: Encoder, value: Month) { - encoder.encodeInt(value.number) + encoder.encodeShort(value.number.toShort()) } } diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt index 24bc57662..09dddb4a2 100644 --- a/core/jvm/src/SerializationMeasurements.kt +++ b/core/jvm/src/SerializationMeasurements.kt @@ -147,4 +147,13 @@ fun main() { println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20,"hours":0,"minutes":0,"seconds":0,"nanoseconds":0}""")) println(Json.decodeFromString(DateTimePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) + val unit = DateTimeUnit.MICROSECOND * 3 + println(Json.encodeToString(unit)) + println(Json.encodeToString(unit as DateTimeUnit)) + println(Json.decodeFromString(DateTimeUnit.TimeBased.serializer(), """{"nanoseconds":3000}""")) + println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"TimeBased","nanoseconds":3000}""")) + val unit2 = DateTimeUnit.DAY * 2 + println(Json.encodeToString(unit2)) + println(Json.encodeToString(unit2 as DateTimeUnit)) + println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"DayBased","days":2}""")) } From c27e81e83f53116b1514f48359ba40504ae91d9e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 1 Dec 2020 17:21:40 +0300 Subject: [PATCH 06/25] Add error handling to serializers --- core/common/src/DateTimePeriod.kt | 30 ++++++++++++++--------- core/common/src/DateTimeUnit.kt | 3 ++- core/common/src/DayOfWeek.kt | 1 + core/common/src/Instant.kt | 7 ++++-- core/common/src/LocalDate.kt | 11 ++++++--- core/common/src/LocalDateTime.kt | 27 +++++++++++++------- core/common/src/Month.kt | 2 +- core/common/src/math.kt | 3 +++ core/js/src/DayOfWeek.kt | 2 ++ core/js/src/Instant.kt | 2 ++ core/js/src/LocalDate.kt | 1 + core/js/src/LocalDateTime.kt | 8 ++++-- core/jvm/src/Instant.kt | 2 ++ core/jvm/src/LocalDate.kt | 1 + core/jvm/src/LocalDateTime.kt | 10 +++++--- core/jvm/src/SerializationMeasurements.kt | 6 ++++- core/native/src/Instant.kt | 3 +++ core/native/src/LocalDate.kt | 1 + core/native/src/LocalDateTime.kt | 8 ++++-- 19 files changed, 93 insertions(+), 35 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 57ea6b18b..31dd4d4d4 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -16,13 +16,13 @@ object DateTimePeriodSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { - element("years") - element("months") - element("days") - element("hours") - element("minutes") - element("seconds") - element("nanoseconds") + element("years", isOptional = true) + element("months", isOptional = true) + element("days", isOptional = true) + element("hours", isOptional = true) + element("minutes", isOptional = true) + element("seconds", isOptional = true) + element("nanoseconds", isOptional = true) } override fun deserialize(decoder: Decoder): DateTimePeriod = @@ -295,6 +295,14 @@ public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this object DatePeriodSerializer: KSerializer { + private fun unexpectedNonzero(fieldName: String, value: Long) { + if (value != 0L) { + throw SerializationException("expected field '$fieldName' to be zero, but was $value") + } + } + + private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong()) + override val descriptor: SerialDescriptor = DateTimePeriodSerializer.descriptor override fun deserialize(decoder: Decoder): DatePeriod = @@ -307,10 +315,10 @@ object DatePeriodSerializer: KSerializer { 0 -> years = decodeIntElement(descriptor, 0) 1 -> months = decodeIntElement(descriptor, 1) 2 -> days = decodeIntElement(descriptor, 2) - 3 -> require(decodeIntElement(descriptor, 3) == 0) - 4 -> require(decodeIntElement(descriptor, 4) == 0) - 5 -> require(decodeLongElement(descriptor, 5) == 0L) - 6 -> require(decodeLongElement(descriptor, 6) == 0L) + 3 -> unexpectedNonzero("hours", decodeIntElement(descriptor, 3)) + 4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4)) + 5 -> unexpectedNonzero("seconds", decodeLongElement(descriptor, 5)) + 6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6)) CompositeDecoder.DECODE_DONE -> break else -> error("Unexpected index: $index") } diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index c227ddb88..3e2eb04d9 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -20,7 +20,8 @@ sealed class DateTimeUnit { class TimeBased(val nanoseconds: Long) : DateTimeUnit() { /* fields without a default value can't be @Transient, so the more natural way of writing this - (setting [unitName] and [unitScale] in init { ... }) won't work. */ + (setting [unitName] and [unitScale] in init { ... }) won't work: + https://github.com/Kotlin/kotlinx.serialization/issues/1227. */ @Transient private val unitName: String = when { nanoseconds % 3600_000_000_000 == 0L -> "HOUR" diff --git a/core/common/src/DayOfWeek.kt b/core/common/src/DayOfWeek.kt index a93486554..3ef2554a3 100644 --- a/core/common/src/DayOfWeek.kt +++ b/core/common/src/DayOfWeek.kt @@ -5,6 +5,7 @@ package kotlinx.datetime +import kotlinx.serialization.* import kotlin.native.concurrent.* public expect enum class DayOfWeek { diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index f9dbdbdd0..1b73f21a2 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -29,12 +29,13 @@ object InstantSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { element("epochSeconds") - element("nanosecondsOfSecond") + element("nanosecondsOfSecond", isOptional = true) } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): Instant = decoder.decodeStructure(descriptor) { - var epochSeconds = 0L + var epochSeconds: Long? = null var nanosecondsOfSecond = 0 while (true) { when (val index = decodeElementIndex(descriptor)) { @@ -44,6 +45,7 @@ object InstantSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (epochSeconds == null) throw MissingFieldException("epochSeconds") Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) } @@ -57,6 +59,7 @@ object InstantSerializer: KSerializer { } @OptIn(ExperimentalTime::class) +@Serializable(with = InstantISO8601Serializer::class) public expect class Instant : Comparable { /** diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 14c7f41bd..ca483dd50 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -33,11 +33,12 @@ object LocalDateSerializer: KSerializer { element("day") } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): LocalDate = decoder.decodeStructure(descriptor) { - var year = 0 - var month: Short = 0 - var day: Short = 0 + var year: Int? = null + var month: Short? = null + var day: Short? = null while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> year = decodeIntElement(descriptor, 0) @@ -47,6 +48,9 @@ object LocalDateSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (year == null) throw MissingFieldException("year") + if (month == null) throw MissingFieldException("month") + if (day == null) throw MissingFieldException("day") LocalDate(year, month.toInt(), day.toInt()) } @@ -62,6 +66,7 @@ object LocalDateSerializer: KSerializer { expect object LocalDateLongSerializer: KSerializer +@Serializable(with = LocalDateISO8601Serializer::class) public expect class LocalDate : Comparable { companion object { /** diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index cdaad2b96..426c4cec3 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -32,17 +32,18 @@ object LocalDateTimeSerializer: KSerializer { element("day") element("hour") element("minute") - element("second") - element("nanosecond") + element("second", isOptional = true) + element("nanosecond", isOptional = true) } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): LocalDateTime = decoder.decodeStructure(descriptor) { - var year = 0 - var month: Short = 0 - var day: Short = 0 - var hour: Short = 0 - var minute: Short = 0 + var year: Int? = null + var month: Short? = null + var day: Short? = null + var hour: Short? = null + var minute: Short? = null var second: Short = 0 var nanosecond = 0 while (true) { @@ -58,6 +59,11 @@ object LocalDateTimeSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (year == null) throw MissingFieldException("year") + if (month == null) throw MissingFieldException("month") + if (day == null) throw MissingFieldException("day") + if (hour == null) throw MissingFieldException("hour") + if (minute == null) throw MissingFieldException("minute") LocalDateTime(year, month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt(), nanosecond) } @@ -68,8 +74,10 @@ object LocalDateTimeSerializer: KSerializer { encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) encodeShortElement(descriptor, 3, value.hour.toShort()) encodeShortElement(descriptor, 4, value.minute.toShort()) - encodeShortElement(descriptor, 5, value.second.toShort()) - encodeIntElement(descriptor, 6, value.nanosecond) + if (value.second != 0 || value.nanosecond != 0) { + encodeShortElement(descriptor, 5, value.second.toShort()) + encodeIntElement(descriptor, 6, value.nanosecond) + } } } @@ -77,6 +85,7 @@ object LocalDateTimeSerializer: KSerializer { expect object LocalDateTimeCompactSerializer: KSerializer +@Serializable(with = LocalDateTimeISO8601Serializer::class) public expect class LocalDateTime : Comparable { companion object { diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index aa370677f..9ed30c3ad 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -object MonthSerializer: KSerializer { +object MonthIntSerializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Month", PrimitiveKind.SHORT) diff --git a/core/common/src/math.kt b/core/common/src/math.kt index c164f2ddd..601040e30 100644 --- a/core/common/src/math.kt +++ b/core/common/src/math.kt @@ -133,9 +133,12 @@ internal class DivRemResult(val q: Long, val r: Long) { operator fun component2(): Long = r } +@Suppress("NOTHING_TO_INLINE") private inline fun low(x: Long) = x and 0xffffffff +@Suppress("NOTHING_TO_INLINE") private inline fun high(x: Long) = (x shr 32) and 0xffffffff /** For [bit] in [0; 63], return bit #[bit] of [value], counting from the least significant bit */ +@Suppress("NOTHING_TO_INLINE") private inline fun indexBit(value: Long, bit: Int): Long = (value shr bit and 1) diff --git a/core/js/src/DayOfWeek.kt b/core/js/src/DayOfWeek.kt index 228cdc4f8..dd8d7c483 100644 --- a/core/js/src/DayOfWeek.kt +++ b/core/js/src/DayOfWeek.kt @@ -5,8 +5,10 @@ package kotlinx.datetime +import kotlinx.serialization.* import kotlinx.datetime.internal.JSJoda.DayOfWeek as jsDayOfWeek +@Serializable public actual enum class DayOfWeek { MONDAY, TUESDAY, diff --git a/core/js/src/Instant.kt b/core/js/src/Instant.kt index d371808ea..7d7988026 100644 --- a/core/js/src/Instant.kt +++ b/core/js/src/Instant.kt @@ -14,8 +14,10 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration import kotlinx.datetime.internal.JSJoda.Clock as jtClock import kotlinx.datetime.internal.JSJoda.ChronoUnit +import kotlinx.serialization.Serializable import kotlin.math.truncate +@Serializable(with = InstantISO8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/js/src/LocalDate.kt b/core/js/src/LocalDate.kt index 6206a5e00..5173990d1 100644 --- a/core/js/src/LocalDate.kt +++ b/core/js/src/LocalDate.kt @@ -25,6 +25,7 @@ actual object LocalDateLongSerializer: KSerializer { } +@Serializable(with = LocalDateISO8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { public actual fun parse(isoString: String): LocalDate = try { diff --git a/core/js/src/LocalDateTime.kt b/core/js/src/LocalDateTime.kt index 20f9bfdfc..aef68fa93 100644 --- a/core/js/src/LocalDateTime.kt +++ b/core/js/src/LocalDateTime.kt @@ -19,10 +19,11 @@ actual object LocalDateTimeCompactSerializer: KSerializer { element("nanoOfDay") } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): LocalDateTime = decoder.decodeStructure(descriptor) { - var epochDay = 0L - var nanoOfDay = 0L + var epochDay: Long? = null + var nanoOfDay: Long? = null while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> epochDay = decodeLongElement(descriptor, 0) @@ -31,6 +32,8 @@ actual object LocalDateTimeCompactSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) } @@ -43,6 +46,7 @@ actual object LocalDateTimeCompactSerializer: KSerializer { } +@Serializable(with = LocalDateTimeISO8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { public actual constructor(year: Int, monthNumber: Int, dayOfMonth: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 988742bc3..87e9b86c7 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -6,6 +6,7 @@ package kotlinx.datetime +import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit @@ -13,6 +14,7 @@ import kotlin.time.* import java.time.Instant as jtInstant import java.time.Clock as jtClock +@Serializable(with = InstantISO8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index d4700f73b..f2f2fcbf2 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -27,6 +27,7 @@ actual object LocalDateLongSerializer: KSerializer { } +@Serializable(with = LocalDateISO8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { public actual fun parse(isoString: String): LocalDate = try { diff --git a/core/jvm/src/LocalDateTime.kt b/core/jvm/src/LocalDateTime.kt index 294c7be9a..9ef0f253b 100644 --- a/core/jvm/src/LocalDateTime.kt +++ b/core/jvm/src/LocalDateTime.kt @@ -13,7 +13,7 @@ import java.time.format.DateTimeParseException import java.time.LocalDateTime as jtLocalDateTime import java.time.LocalDate as jtLocalDate import java.time.LocalTime as jtLocalTime - +import kotlinx.serialization.internal.* public actual typealias Month = java.time.Month public actual typealias DayOfWeek = java.time.DayOfWeek @@ -26,10 +26,11 @@ actual object LocalDateTimeCompactSerializer: KSerializer { element("nanoOfDay") } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): LocalDateTime = decoder.decodeStructure(descriptor) { - var epochDay = 0L - var nanoOfDay = 0L + var epochDay: Long? = null + var nanoOfDay: Long? = null while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> epochDay = decodeLongElement(descriptor, 0) @@ -38,6 +39,8 @@ actual object LocalDateTimeCompactSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) } @@ -50,6 +53,7 @@ actual object LocalDateTimeCompactSerializer: KSerializer { } +@Serializable(with = LocalDateTimeISO8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { public actual constructor(year: Int, monthNumber: Int, dayOfMonth: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt index 09dddb4a2..6bb0cc752 100644 --- a/core/jvm/src/SerializationMeasurements.kt +++ b/core/jvm/src/SerializationMeasurements.kt @@ -135,11 +135,13 @@ fun main() { measurements(LocalDateTimeSerializer, LocalDateTimeISO8601Serializer, LocalDateTimeCompactSerializer) { Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC) } } - measurements(MonthSerializer) { + measurements(MonthIntSerializer) { Array(10_000) { Month(Random.nextInt(1, 13)) } } val period = DatePeriod(10, 15, 20) + println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) + println(Json.decodeFromString(DateTimePeriod.serializer(), """{}""")) println(Json.encodeToString(period)) println(Json.encodeToString(period as DateTimePeriod)) println(Json.encodeToString(period as DateTimePeriod)) @@ -156,4 +158,6 @@ fun main() { println(Json.encodeToString(unit2)) println(Json.encodeToString(unit2 as DateTimeUnit)) println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"DayBased","days":2}""")) + + println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) } diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index 32b924a31..cfc64d2b3 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -8,9 +8,11 @@ package kotlinx.datetime +import kotlinx.serialization.* import kotlin.math.* import kotlin.time.* +@Serializable public actual enum class DayOfWeek { MONDAY, TUESDAY, @@ -83,6 +85,7 @@ private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second internal expect fun currentTime(): Instant +@Serializable(with = InstantISO8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(actual val epochSeconds: Long, actual val nanosecondsOfSecond: Int) : Comparable { diff --git a/core/native/src/LocalDate.kt b/core/native/src/LocalDate.kt index 6048d8c62..69de96149 100644 --- a/core/native/src/LocalDate.kt +++ b/core/native/src/LocalDate.kt @@ -51,6 +51,7 @@ internal const val YEAR_MAX = 999_999 private fun isValidYear(year: Int): Boolean = year >= YEAR_MIN && year <= YEAR_MAX +@Serializable(with = LocalDateISO8601Serializer::class) public actual class LocalDate actual constructor(actual val year: Int, actual val monthNumber: Int, actual val dayOfMonth: Int) : Comparable { init { diff --git a/core/native/src/LocalDateTime.kt b/core/native/src/LocalDateTime.kt index 9c9d6d948..656b33b3b 100644 --- a/core/native/src/LocalDateTime.kt +++ b/core/native/src/LocalDateTime.kt @@ -20,10 +20,11 @@ actual object LocalDateTimeCompactSerializer: KSerializer { element("nanoOfDay") } + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` override fun deserialize(decoder: Decoder): LocalDateTime = decoder.decodeStructure(descriptor) { - var epochDay = 0L - var nanoOfDay = 0L + var epochDay: Long? = null + var nanoOfDay: Long? = null while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> epochDay = decodeLongElement(descriptor, 0) @@ -32,6 +33,8 @@ actual object LocalDateTimeCompactSerializer: KSerializer { else -> error("Unexpected index: $index") } } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") val date = LocalDate.ofEpochDay(epochDay.clampToInt()) val time = LocalTime.ofNanoOfDay(nanoOfDay) LocalDateTime(date, time) @@ -56,6 +59,7 @@ internal val localDateTimeParser: Parser LocalDateTime(date, time) } +@Serializable(with = LocalDateTimeISO8601Serializer::class) public actual class LocalDateTime internal constructor( actual val date: LocalDate, internal val time: LocalTime) : Comparable { actual companion object { From ebdf6bff4a4d60476bf3ce2bc0a2a39f4481535d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 2 Dec 2020 11:59:27 +0300 Subject: [PATCH 07/25] Serialize time zones --- core/common/src/DateTimePeriod.kt | 26 +++++++-------- core/common/src/DayOfWeek.kt | 1 - core/common/src/Month.kt | 1 - core/common/src/TimeZone.kt | 39 +++++++++++++++++++++++ core/js/src/DayOfWeek.kt | 2 -- core/js/src/TimeZone.kt | 10 +++++- core/jvm/src/SerializationMeasurements.kt | 10 ++++++ core/jvm/src/TimeZoneJvm.kt | 11 +++++-- core/native/src/Instant.kt | 1 - core/native/src/TimeZone.kt | 3 ++ 10 files changed, 83 insertions(+), 21 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 31dd4d4d4..696f66e0b 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -52,16 +52,14 @@ object DateTimePeriodSerializer: KSerializer { override fun serialize(encoder: Encoder, value: DateTimePeriod) { encoder.encodeStructure(descriptor) { - with (value) { - encodeIntElement(descriptor, 0, value.years) - encodeIntElement(descriptor, 1, value.months) - encodeIntElement(descriptor, 2, value.days) - if (hours or minutes or seconds != 0 || nanoseconds != 0) { - encodeIntElement(descriptor, 3, value.hours) - encodeIntElement(descriptor, 4, value.minutes) - encodeIntElement(descriptor, 5, value.seconds) - encodeLongElement(descriptor, 6, value.nanoseconds.toLong()) - } + with(value) { + if (years != 0) encodeIntElement(descriptor, 0, years) + if (months != 0) encodeIntElement(descriptor, 1, months) + if (days != 0) encodeIntElement(descriptor, 2, days) + if (hours != 0) encodeIntElement(descriptor, 3, hours) + if (minutes != 0) encodeIntElement(descriptor, 4, minutes) + if (seconds != 0) encodeIntElement(descriptor, 5, seconds) + if (nanoseconds != 0) encodeLongElement(descriptor, 6, value.nanoseconds.toLong()) } } } @@ -328,9 +326,11 @@ object DatePeriodSerializer: KSerializer { override fun serialize(encoder: Encoder, value: DatePeriod) { encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, value.years) - encodeIntElement(descriptor, 1, value.months) - encodeIntElement(descriptor, 2, value.days) + with(value) { + if (years != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 0, years) + if (months != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 1, months) + if (days != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 2, days) + } } } diff --git a/core/common/src/DayOfWeek.kt b/core/common/src/DayOfWeek.kt index 3ef2554a3..a93486554 100644 --- a/core/common/src/DayOfWeek.kt +++ b/core/common/src/DayOfWeek.kt @@ -5,7 +5,6 @@ package kotlinx.datetime -import kotlinx.serialization.* import kotlin.native.concurrent.* public expect enum class DayOfWeek { diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 9ed30c3ad..93fffddf5 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -35,7 +35,6 @@ public expect enum class Month { OCTOBER, NOVEMBER, DECEMBER; - // val value: Int // member missing in java.time.Month has to be an extension } diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index e0dfc0e56..a806ddfb7 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -8,6 +8,44 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object TimeZoneSerializer: KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): TimeZone = TimeZone.of(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: TimeZone) { + encoder.encodeString(value.id) + } + +} + +object ZoneOffsetSerializer: KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): ZoneOffset { + val zone = TimeZone.of(decoder.decodeString()) + if (zone is ZoneOffset) { + return zone + } else { + throw SerializationException("Timezone identifier '$zone' does not correspond to a fixed-offset timezone") + } + } + + override fun serialize(encoder: Encoder, value: ZoneOffset) { + encoder.encodeString(value.id) + } + +} + +@Serializable(with = TimeZoneSerializer::class) public expect open class TimeZone { /** * Returns the identifier string of the time zone. @@ -80,6 +118,7 @@ public expect open class TimeZone { public fun LocalDateTime.toInstant(): Instant } +@Serializable(with = ZoneOffsetSerializer::class) public expect class ZoneOffset : TimeZone { val totalSeconds: Int } diff --git a/core/js/src/DayOfWeek.kt b/core/js/src/DayOfWeek.kt index dd8d7c483..228cdc4f8 100644 --- a/core/js/src/DayOfWeek.kt +++ b/core/js/src/DayOfWeek.kt @@ -5,10 +5,8 @@ package kotlinx.datetime -import kotlinx.serialization.* import kotlinx.datetime.internal.JSJoda.DayOfWeek as jsDayOfWeek -@Serializable public actual enum class DayOfWeek { MONDAY, TUESDAY, diff --git a/core/js/src/TimeZone.kt b/core/js/src/TimeZone.kt index 1fa0ce556..24ceae0b4 100644 --- a/core/js/src/TimeZone.kt +++ b/core/js/src/TimeZone.kt @@ -5,8 +5,10 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ZoneId +import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.ZoneOffset as jtZoneOffset +@Serializable(with = TimeZoneSerializer::class) actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { public actual val id: String get() = zoneId.id() @@ -27,7 +29,12 @@ actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { actual val UTC: TimeZone = jtZoneOffset.UTC.let(::TimeZone) actual fun of(zoneId: String): TimeZone = try { - ZoneId.of(zoneId).let(::TimeZone) + val zone = ZoneId.of(zoneId) + if (zone is jtZoneOffset) { + ZoneOffset(zone) + } else { + TimeZone(zone) + } } catch (e: Throwable) { if (e.isJodaDateTimeException()) throw IllegalTimeZoneException(e) throw e @@ -37,6 +44,7 @@ actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { } } +@Serializable(with = ZoneOffsetSerializer::class) public actual class ZoneOffset internal constructor(zoneOffset: jtZoneOffset): TimeZone(zoneOffset) { internal val zoneOffset get() = zoneId as jtZoneOffset diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt index 6bb0cc752..bdf1ee685 100644 --- a/core/jvm/src/SerializationMeasurements.kt +++ b/core/jvm/src/SerializationMeasurements.kt @@ -126,6 +126,13 @@ inline fun measurements(vararg serializers: KSerializer, cros @OptIn(ExperimentalSerializationApi::class) fun main() { + println(TimeZone.of("+2")) + val offset = TimeZone.of("+2") as ZoneOffset + println(Json.encodeToString(offset)) + val offset2 = Json.decodeFromString(ZoneOffsetSerializer, """"+2"""") + require(offset == offset2) + val offset3 = Json.decodeFromString(TimeZoneSerializer, """"+2"""") + require(offset == offset3) measurements(InstantSerializer, InstantISO8601Serializer) { Array(10_000) { Clock.System.now() } } @@ -138,6 +145,9 @@ fun main() { measurements(MonthIntSerializer) { Array(10_000) { Month(Random.nextInt(1, 13)) } } + measurements(TimeZoneSerializer) { + TimeZone.availableZoneIds.map { TimeZone.of(it) }.toTypedArray() + } val period = DatePeriod(10, 15, 20) println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) diff --git a/core/jvm/src/TimeZoneJvm.kt b/core/jvm/src/TimeZoneJvm.kt index 6435083b7..18af40dd8 100644 --- a/core/jvm/src/TimeZoneJvm.kt +++ b/core/jvm/src/TimeZoneJvm.kt @@ -8,10 +8,12 @@ package kotlinx.datetime +import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.ZoneId import java.time.ZoneOffset as jtZoneOffset +@Serializable(with = TimeZoneSerializer::class) actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { public actual val id: String get() = zoneId.id @@ -32,8 +34,12 @@ actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { actual val UTC: TimeZone = jtZoneOffset.UTC.let(::TimeZone) actual fun of(zoneId: String): TimeZone = try { - // TODO: Return ZoneOffset for j.t.ZoneOffset - ZoneId.of(zoneId).let(::TimeZone) + val zone = ZoneId.of(zoneId) + if (zone is jtZoneOffset) { + ZoneOffset(zone) + } else { + TimeZone(zone) + } } catch (e: Exception) { if (e is DateTimeException) throw IllegalTimeZoneException(e) throw e @@ -43,6 +49,7 @@ actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { } } +@Serializable(with = ZoneOffsetSerializer::class) public actual class ZoneOffset internal constructor(zoneOffset: jtZoneOffset): TimeZone(zoneOffset) { internal val zoneOffset get() = zoneId as jtZoneOffset diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index cfc64d2b3..6f184ad49 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -12,7 +12,6 @@ import kotlinx.serialization.* import kotlin.math.* import kotlin.time.* -@Serializable public actual enum class DayOfWeek { MONDAY, TUESDAY, diff --git a/core/native/src/TimeZone.kt b/core/native/src/TimeZone.kt index cb37ada2f..3e0206726 100644 --- a/core/native/src/TimeZone.kt +++ b/core/native/src/TimeZone.kt @@ -10,7 +10,9 @@ package kotlinx.datetime import kotlin.math.abs import kotlin.native.concurrent.* +import kotlinx.serialization.Serializable +@Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor(internal val value: TimeZoneImpl) { actual companion object { @@ -79,6 +81,7 @@ public actual open class TimeZone internal constructor(internal val value: TimeZ @ThreadLocal private var zoneOffsetCache: MutableMap = mutableMapOf() +@Serializable(with = ZoneOffsetSerializer::class) public actual class ZoneOffset internal constructor(internal val offset: ZoneOffsetImpl) : TimeZone(offset) { actual val totalSeconds get() = offset.totalSeconds From 2eaac88f6ba8a6f47d874916bacd7d03b58e5bbc Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 4 Dec 2020 17:18:34 +0300 Subject: [PATCH 08/25] Rename serializers --- core/common/src/DateTimePeriod.kt | 16 ++++++++-------- core/common/src/DateTimeUnit.kt | 4 +--- core/common/src/Instant.kt | 2 +- core/common/src/LocalDate.kt | 2 +- core/common/src/LocalDateTime.kt | 2 +- core/jvm/src/SerializationMeasurements.kt | 16 +++++++++++----- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 696f66e0b..46c13385d 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -object DateTimePeriodSerializer: KSerializer { +object DateTimePeriodComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { @@ -66,7 +66,7 @@ object DateTimePeriodSerializer: KSerializer { } -@Serializable(with = DateTimePeriodSerializer::class) +@Serializable(with = DateTimePeriodComponentSerializer::class) // TODO: could be error-prone without explicitly named params sealed class DateTimePeriod { internal abstract val totalMonths: Int @@ -291,7 +291,7 @@ sealed class DateTimePeriod { public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this) -object DatePeriodSerializer: KSerializer { +object DatePeriodComponentSerializer: KSerializer { private fun unexpectedNonzero(fieldName: String, value: Long) { if (value != 0L) { @@ -301,7 +301,7 @@ object DatePeriodSerializer: KSerializer { private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong()) - override val descriptor: SerialDescriptor = DateTimePeriodSerializer.descriptor + override val descriptor: SerialDescriptor = DateTimePeriodComponentSerializer.descriptor override fun deserialize(decoder: Decoder): DatePeriod = decoder.decodeStructure(descriptor) { @@ -327,16 +327,16 @@ object DatePeriodSerializer: KSerializer { override fun serialize(encoder: Encoder, value: DatePeriod) { encoder.encodeStructure(descriptor) { with(value) { - if (years != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 0, years) - if (months != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 1, months) - if (days != 0) encodeIntElement(DateTimePeriodSerializer.descriptor, 2, days) + if (years != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 0, years) + if (months != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 1, months) + if (days != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 2, days) } } } } -@Serializable(with = DatePeriodSerializer::class) +@Serializable(with = DatePeriodComponentSerializer::class) class DatePeriod internal constructor( internal override val totalMonths: Int, override val days: Int, diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index 3e2eb04d9..312319f91 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -5,10 +5,8 @@ package kotlinx.datetime -import kotlin.time.Duration -import kotlin.time.ExperimentalTime -import kotlin.time.nanoseconds import kotlinx.serialization.* +import kotlin.time.* @Serializable sealed class DateTimeUnit { diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 1b73f21a2..154a190bc 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -24,7 +24,7 @@ object InstantISO8601Serializer: KSerializer { } -object InstantSerializer: KSerializer { +object InstantComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index ca483dd50..d31dd6988 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -24,7 +24,7 @@ object LocalDateISO8601Serializer: KSerializer { } -object LocalDateSerializer: KSerializer { +object LocalDateComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 426c4cec3..a8ea94142 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -23,7 +23,7 @@ object LocalDateTimeISO8601Serializer: KSerializer { } -object LocalDateTimeSerializer: KSerializer { +object LocalDateTimeComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Instant") { diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt index bdf1ee685..1e564429b 100644 --- a/core/jvm/src/SerializationMeasurements.kt +++ b/core/jvm/src/SerializationMeasurements.kt @@ -15,8 +15,9 @@ import java.nio.charset.StandardCharsets.US_ASCII import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream import kotlin.random.* +import kotlin.time.* -const val REPETITIONS = 1 +const val REPETITIONS = 100 @ExperimentalSerializationApi inline fun protobufMeasurements(serializer: KSerializer, generator: () -> Array) { @@ -133,13 +134,14 @@ fun main() { require(offset == offset2) val offset3 = Json.decodeFromString(TimeZoneSerializer, """"+2"""") require(offset == offset3) - measurements(InstantSerializer, InstantISO8601Serializer) { + */ + measurements(InstantComponentSerializer, InstantISO8601Serializer) { Array(10_000) { Clock.System.now() } } - measurements(LocalDateSerializer, LocalDateISO8601Serializer, LocalDateLongSerializer) { + measurements(LocalDateComponentSerializer, LocalDateISO8601Serializer, LocalDateLongSerializer) { Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC).date } } - measurements(LocalDateTimeSerializer, LocalDateTimeISO8601Serializer, LocalDateTimeCompactSerializer) { + measurements(LocalDateTimeComponentSerializer, LocalDateTimeISO8601Serializer, LocalDateTimeCompactSerializer) { Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC) } } measurements(MonthIntSerializer) { @@ -149,6 +151,8 @@ fun main() { TimeZone.availableZoneIds.map { TimeZone.of(it) }.toTypedArray() } + + /* val period = DatePeriod(10, 15, 20) println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) println(Json.decodeFromString(DateTimePeriod.serializer(), """{}""")) @@ -169,5 +173,7 @@ fun main() { println(Json.encodeToString(unit2 as DateTimeUnit)) println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"DayBased","days":2}""")) - println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) + // println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) + + */ } From 04522962fb9bb12685ca9a316c3768f3dc37c724 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 9 Dec 2020 11:54:59 +0300 Subject: [PATCH 09/25] Implement handwritten serializers for DateTimeUnit --- core/build.gradle.kts | 4 +- core/common/src/DateTimeUnit.kt | 188 +++++++++++++++++++++- core/jvm/src/SerializationMeasurements.kt | 179 -------------------- 3 files changed, 182 insertions(+), 189 deletions(-) delete mode 100644 core/jvm/src/SerializationMeasurements.kt diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f6e9bf9ab..edfee5736 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -150,8 +150,8 @@ kotlin { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") // this is not `compileOnly` only temporarily, until there is a source set for testing serialization - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") - api("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1") } } diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index 312319f91..fae87eceb 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -6,15 +6,18 @@ package kotlinx.datetime import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* +import kotlin.reflect.* import kotlin.time.* -@Serializable +@Serializable(with = DateTimeUnitSerializer::class) sealed class DateTimeUnit { abstract operator fun times(scalar: Int): DateTimeUnit - @Serializable - @SerialName("TimeBased") + @Serializable(with = TimeBasedSerializer::class) class TimeBased(val nanoseconds: Long) : DateTimeUnit() { /* fields without a default value can't be @Transient, so the more natural way of writing this @@ -58,11 +61,10 @@ sealed class DateTimeUnit { override fun toString(): String = formatToString(unitScale, unitName) } - @Serializable + @Serializable(with = DateBasedSerializer::class) sealed class DateBased : DateTimeUnit() { // TODO: investigate how to move subclasses up to DateTimeUnit scope - @Serializable - @SerialName("DayBased") + @Serializable(with = DayBasedSerializer::class) class DayBased(val days: Int) : DateBased() { init { require(days > 0) { "Unit duration must be positive, but was $days days." } @@ -80,8 +82,7 @@ sealed class DateTimeUnit { else formatToString(days, "DAY") } - @Serializable - @SerialName("MonthBased") + @Serializable(with = MonthBasedSerializer::class) class MonthBased(val months: Int) : DateBased() { init { require(months > 0) { "Unit duration must be positive, but was $months months." } @@ -121,3 +122,174 @@ sealed class DateTimeUnit { val CENTURY = YEAR * 100 } } + +object TimeBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TimeBased") { + element("nanoseconds") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.TimeBased) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.nanoseconds); + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.TimeBased { + var seen = false + var nanoseconds = 0L + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + nanoseconds = decodeLongElement(descriptor, 0) + seen = true + } else { + while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + nanoseconds = decodeLongElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("nanoseconds") + return DateTimeUnit.TimeBased(nanoseconds) + } +} + +object DayBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") { + element("days") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.DayBased) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.days); + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.DayBased { + var seen = false + var days = 0 + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + days = decodeIntElement(descriptor, 0) + seen = true + } else { + while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + days = decodeIntElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("days") + return DateTimeUnit.DateBased.DayBased(days) + } +} + +object MonthBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") { + element("months") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.MonthBased) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.months); + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.MonthBased { + var seen = false + var months = 0 + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + months = decodeIntElement(descriptor, 0) + seen = true + } else { + while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + months = decodeIntElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("months") + return DateTimeUnit.DateBased.MonthBased(months) + } +} + +@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") +object DateBasedSerializer: AbstractPolymorphicSerializer() { + + private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased", + DateTimeUnit.DateBased::class, + arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class), + arrayOf(DayBasedSerializer, MonthBasedSerializer)) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): + DeserializationStrategy? = + impl.findPolymorphicSerializerOrNull(decoder, klassName) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit.DateBased): + SerializationStrategy? = + impl.findPolymorphicSerializerOrNull(encoder, value) + + @InternalSerializationApi + override val baseClass: KClass + get() = DateTimeUnit.DateBased::class + + @InternalSerializationApi + override val descriptor: SerialDescriptor + get() = impl.descriptor + +} + +@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") +object DateTimeUnitSerializer: AbstractPolymorphicSerializer() { + + private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit", + DateTimeUnit::class, + arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class, DateTimeUnit.TimeBased::class), + arrayOf(DayBasedSerializer, MonthBasedSerializer, TimeBasedSerializer)) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy? = + impl.findPolymorphicSerializerOrNull(decoder, klassName) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit): SerializationStrategy? = + impl.findPolymorphicSerializerOrNull(encoder, value) + + @InternalSerializationApi + override val baseClass: KClass + get() = DateTimeUnit::class + + @InternalSerializationApi + override val descriptor: SerialDescriptor + get() = impl.descriptor + +} diff --git a/core/jvm/src/SerializationMeasurements.kt b/core/jvm/src/SerializationMeasurements.kt deleted file mode 100644 index 1e564429b..000000000 --- a/core/jvm/src/SerializationMeasurements.kt +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -package kotlinx.datetime - -import kotlinx.serialization.* -import kotlinx.serialization.builtins.* -import kotlinx.serialization.json.* -import kotlinx.serialization.protobuf.ProtoBuf -import kotlin.system.* - -import java.io.ByteArrayOutputStream -import java.nio.charset.StandardCharsets.US_ASCII -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream -import kotlin.random.* -import kotlin.time.* - -const val REPETITIONS = 100 - -@ExperimentalSerializationApi -inline fun protobufMeasurements(serializer: KSerializer, generator: () -> Array) { - repeat(REPETITIONS) { - val values = generator() - val result: ByteArray - val time = measureTimeMillis { - result = ProtoBuf.encodeToByteArray(ArraySerializer(serializer), values) - } - val newValues: Array - val time2 = measureTimeMillis { - newValues = ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), result) - } - assert(values.contentEquals(newValues)) - if (it == REPETITIONS - 1) { - println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") - } - } -} - -@ExperimentalSerializationApi -inline fun jsonMeasurements(serializer: KSerializer, generator: () -> Array) { - repeat(REPETITIONS) { - val values = generator() - val result: String - val time = measureTimeMillis { - result = Json.encodeToString(ArraySerializer(serializer), values) - } - val newValues: Array - val time2 = measureTimeMillis { - newValues = Json.decodeFromString(ArraySerializer(serializer), result) - } - assert(values.contentEquals(newValues)) - if (it == REPETITIONS - 1) { - println("time (ser): $time\ttime (des): $time2\tbytes: ${result.toByteArray().size}\tfor $serializer") - } - } -} - -@ExperimentalSerializationApi -inline fun gzippedProtobufMeasurements(serializer: KSerializer, generator: () -> Array) { - repeat(REPETITIONS) { - val values = generator() - val result: ByteArray - val time = measureTimeMillis { - val bos = ByteArrayOutputStream() - GZIPOutputStream(bos).use { - it.write(ProtoBuf.encodeToByteArray(ArraySerializer(serializer), values)) - } - result = bos.toByteArray() - } - val newValues: Array - val time2 = measureTimeMillis { - val ungzipped = GZIPInputStream(result.inputStream()).use { it.readBytes() } - newValues = ProtoBuf.decodeFromByteArray(ArraySerializer(serializer), ungzipped) - } - assert(values.contentEquals(newValues)) - if (it == REPETITIONS - 1) { - println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") - } - } -} - -@ExperimentalSerializationApi -inline fun gzippedJsonMeasurements(serializer: KSerializer, generator: () -> Array) { - repeat(REPETITIONS) { - val values = generator() - val result: ByteArray - val time = measureTimeMillis { - val bos = ByteArrayOutputStream() - GZIPOutputStream(bos).bufferedWriter(US_ASCII).use { - it.write(Json.encodeToString(ArraySerializer(serializer), values)) - } - result = bos.toByteArray() - } - val newValues: Array - val time2 = measureTimeMillis { - val ungzipped = GZIPInputStream(result.inputStream()).bufferedReader(US_ASCII).use { it.readText() } - newValues = Json.decodeFromString(ArraySerializer(serializer), ungzipped) - } - assert(values.contentEquals(newValues)) - if (it == REPETITIONS - 1) { - println("time (ser): $time\ttime (des): $time2\tbytes: ${result.size}\tfor $serializer") - } - } -} - -@ExperimentalSerializationApi -inline fun measurements(vararg serializers: KSerializer, crossinline generator: () -> Array) { - println("Protobuf:") - for (serializer in serializers) { - protobufMeasurements(serializer, generator) - } - println("JSON:") - for (serializer in serializers) { - jsonMeasurements(serializer, generator) - } - println("Gzipped Protobuf:") - for (serializer in serializers) { - gzippedProtobufMeasurements(serializer, generator) - } - println("Gzipped JSON:") - for (serializer in serializers) { - gzippedJsonMeasurements(serializer, generator) - } -} - -@OptIn(ExperimentalSerializationApi::class) -fun main() { - println(TimeZone.of("+2")) - val offset = TimeZone.of("+2") as ZoneOffset - println(Json.encodeToString(offset)) - val offset2 = Json.decodeFromString(ZoneOffsetSerializer, """"+2"""") - require(offset == offset2) - val offset3 = Json.decodeFromString(TimeZoneSerializer, """"+2"""") - require(offset == offset3) - */ - measurements(InstantComponentSerializer, InstantISO8601Serializer) { - Array(10_000) { Clock.System.now() } - } - measurements(LocalDateComponentSerializer, LocalDateISO8601Serializer, LocalDateLongSerializer) { - Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC).date } - } - measurements(LocalDateTimeComponentSerializer, LocalDateTimeISO8601Serializer, LocalDateTimeCompactSerializer) { - Array(10_000) { Clock.System.now().toLocalDateTime(TimeZone.UTC) } - } - measurements(MonthIntSerializer) { - Array(10_000) { Month(Random.nextInt(1, 13)) } - } - measurements(TimeZoneSerializer) { - TimeZone.availableZoneIds.map { TimeZone.of(it) }.toTypedArray() - } - - - /* - val period = DatePeriod(10, 15, 20) - println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) - println(Json.decodeFromString(DateTimePeriod.serializer(), """{}""")) - println(Json.encodeToString(period)) - println(Json.encodeToString(period as DateTimePeriod)) - println(Json.encodeToString(period as DateTimePeriod)) - println(Json.decodeFromString(DateTimePeriod.serializer(), """{"years":10,"months":15,"days":20,"hours":0,"minutes":0,"seconds":0,"nanoseconds":0}""")) - println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20,"hours":0,"minutes":0,"seconds":0,"nanoseconds":0}""")) - println(Json.decodeFromString(DateTimePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) - println(Json.decodeFromString(DatePeriod.serializer(), """{"years":10,"months":15,"days":20}""")) - val unit = DateTimeUnit.MICROSECOND * 3 - println(Json.encodeToString(unit)) - println(Json.encodeToString(unit as DateTimeUnit)) - println(Json.decodeFromString(DateTimeUnit.TimeBased.serializer(), """{"nanoseconds":3000}""")) - println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"TimeBased","nanoseconds":3000}""")) - val unit2 = DateTimeUnit.DAY * 2 - println(Json.encodeToString(unit2)) - println(Json.encodeToString(unit2 as DateTimeUnit)) - println(Json.decodeFromString(DateTimeUnit.serializer(), """{"type":"DayBased","days":2}""")) - - // println(ProtoBuf.decodeFromByteArray(DateTimePeriod.serializer(), ProtoBuf.encodeToByteArray(period as DateTimePeriod))) - - */ -} From 038ad25165399a3539338ac5ac35d3d2d2a92f33 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 9 Dec 2020 18:31:39 +0300 Subject: [PATCH 10/25] Add tests for serialization * A new subproject was created just for testing. * DateTimePeriod serialization is not tested yet * Tests that serialize 64-bit numbers fail on nodejs for some reason * Enum classes serialization had to be implemented manually. Though https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/builtin-classes.md#enum-classes claims that enums are automatically serialized, on Kotlin/Native and Kotlin/JS runtime crashes occur if no serializer is passed explicitly. * Some code had to be modified due to a bug in the legacy JS codegen that led to an infinite hanging of deserialization. --- build.gradle.kts | 1 + core/build.gradle.kts | 6 +- core/common/src/DateTimePeriod.kt | 4 +- core/common/src/DateTimeUnit.kt | 12 +-- core/common/src/DayOfWeek.kt | 16 +++ core/common/src/Instant.kt | 8 +- core/common/src/LocalDate.kt | 4 +- core/common/src/LocalDateTime.kt | 8 +- core/common/src/Month.kt | 15 +-- serialization/build.gradle.kts | 102 ++++++++++++++++++ .../test/DateTimePeriodSerializationTest.kt | 9 ++ .../test/DateTimeUnitSerializationTest.kt | 90 ++++++++++++++++ .../common/test/DayOfWeekSerializationTest.kt | 21 ++++ .../common/test/InstantSerializationTest.kt | 54 ++++++++++ .../common/test/LocalDateSerializationTest.kt | 58 ++++++++++ .../test/LocalDateTimeSerializationTest.kt | 68 ++++++++++++ .../common/test/MonthSerializationTest.kt | 21 ++++ .../common/test/TimeZoneSerializationTest.kt | 35 ++++++ serialization/js/test/JsJodaTimeZoneModule.kt | 12 +++ settings.gradle | 2 + 20 files changed, 519 insertions(+), 27 deletions(-) create mode 100644 serialization/build.gradle.kts create mode 100644 serialization/common/test/DateTimePeriodSerializationTest.kt create mode 100644 serialization/common/test/DateTimeUnitSerializationTest.kt create mode 100644 serialization/common/test/DayOfWeekSerializationTest.kt create mode 100644 serialization/common/test/InstantSerializationTest.kt create mode 100644 serialization/common/test/LocalDateSerializationTest.kt create mode 100644 serialization/common/test/LocalDateTimeSerializationTest.kt create mode 100644 serialization/common/test/MonthSerializationTest.kt create mode 100644 serialization/common/test/TimeZoneSerializationTest.kt create mode 100644 serialization/js/test/JsJodaTimeZoneModule.kt diff --git a/build.gradle.kts b/build.gradle.kts index 57157244a..29cdd2676 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,7 @@ buildscript { plugins { id("kotlinx.team.infra") version "0.3.0-dev-64" + kotlin("plugin.serialization") version "1.4.10" } infra { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index edfee5736..d75a9d913 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -5,7 +5,7 @@ import javax.xml.parsers.DocumentBuilderFactory plugins { id("kotlin-multiplatform") - kotlin("plugin.serialization") version "1.4.10" + kotlin("plugin.serialization") `maven-publish` } @@ -149,9 +149,7 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") - // this is not `compileOnly` only temporarily, until there is a source set for testing serialization - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1") } } diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 46c13385d..4ada1e75a 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -308,7 +308,7 @@ object DatePeriodComponentSerializer: KSerializer { var years = 0 var months = 0 var days = 0 - while (true) { + loop@while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> years = decodeIntElement(descriptor, 0) 1 -> months = decodeIntElement(descriptor, 1) @@ -317,7 +317,7 @@ object DatePeriodComponentSerializer: KSerializer { 4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4)) 5 -> unexpectedNonzero("seconds", decodeLongElement(descriptor, 5)) 6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6)) - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> error("Unexpected index: $index") } } diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index fae87eceb..856e16609 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -145,13 +145,13 @@ object TimeBasedSerializer: KSerializer { nanoseconds = decodeLongElement(descriptor, 0) seen = true } else { - while (true) { + loop@while (true) { when (val elementIndex: Int = decodeElementIndex(descriptor)) { 0 -> { nanoseconds = decodeLongElement(descriptor, 0) seen = true } - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> throw UnknownFieldException(elementIndex) } } @@ -184,13 +184,13 @@ object DayBasedSerializer: KSerializer { days = decodeIntElement(descriptor, 0) seen = true } else { - while (true) { + loop@while (true) { when (val elementIndex: Int = decodeElementIndex(descriptor)) { 0 -> { days = decodeIntElement(descriptor, 0) seen = true } - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> throw UnknownFieldException(elementIndex) } } @@ -223,13 +223,13 @@ object MonthBasedSerializer: KSerializer { months = decodeIntElement(descriptor, 0) seen = true } else { - while (true) { + loop@while (true) { when (val elementIndex: Int = decodeElementIndex(descriptor)) { 0 -> { months = decodeIntElement(descriptor, 0) seen = true } - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> throw UnknownFieldException(elementIndex) } } diff --git a/core/common/src/DayOfWeek.kt b/core/common/src/DayOfWeek.kt index a93486554..e8a062fa9 100644 --- a/core/common/src/DayOfWeek.kt +++ b/core/common/src/DayOfWeek.kt @@ -5,8 +5,24 @@ package kotlinx.datetime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* import kotlin.native.concurrent.* +@Suppress("INVISIBLE_MEMBER") +object DayOfWeekSerializer: KSerializer { + private val impl = EnumSerializer("Month", DayOfWeek.values()) + + override val descriptor: SerialDescriptor + get() = impl.descriptor + + override fun deserialize(decoder: Decoder): DayOfWeek = impl.deserialize(decoder) + + override fun serialize(encoder: Encoder, value: DayOfWeek) = impl.serialize(encoder, value) +} + public expect enum class DayOfWeek { MONDAY, TUESDAY, diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 154a190bc..eb8754575 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -37,11 +37,11 @@ object InstantComponentSerializer: KSerializer { decoder.decodeStructure(descriptor) { var epochSeconds: Long? = null var nanosecondsOfSecond = 0 - while (true) { + loop@while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> epochSeconds = decodeLongElement(descriptor, 0) 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> error("Unexpected index: $index") } } @@ -52,7 +52,9 @@ object InstantComponentSerializer: KSerializer { override fun serialize(encoder: Encoder, value: Instant) { encoder.encodeStructure(descriptor) { encodeLongElement(descriptor, 0, value.epochSeconds) - encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + if (value.nanosecondsOfSecond != 0) { + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } } } diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index d31dd6988..03dd17f0f 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -39,12 +39,12 @@ object LocalDateComponentSerializer: KSerializer { var year: Int? = null var month: Short? = null var day: Short? = null - while (true) { + loop@while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> year = decodeIntElement(descriptor, 0) 1 -> month = decodeShortElement(descriptor, 1) 2 -> day = decodeShortElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> error("Unexpected index: $index") } } diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index a8ea94142..eb00bad97 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -46,7 +46,7 @@ object LocalDateTimeComponentSerializer: KSerializer { var minute: Short? = null var second: Short = 0 var nanosecond = 0 - while (true) { + loop@while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> year = decodeIntElement(descriptor, 0) 1 -> month = decodeShortElement(descriptor, 1) @@ -55,7 +55,7 @@ object LocalDateTimeComponentSerializer: KSerializer { 4 -> minute = decodeShortElement(descriptor, 4) 5 -> second = decodeShortElement(descriptor, 5) 6 -> nanosecond = decodeIntElement(descriptor, 6) - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> error("Unexpected index: $index") } } @@ -76,7 +76,9 @@ object LocalDateTimeComponentSerializer: KSerializer { encodeShortElement(descriptor, 4, value.minute.toShort()) if (value.second != 0 || value.nanosecond != 0) { encodeShortElement(descriptor, 5, value.second.toShort()) - encodeIntElement(descriptor, 6, value.nanosecond) + if (value.nanosecond != 0) { + encodeIntElement(descriptor, 6, value.nanosecond) + } } } } diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 93fffddf5..6ba7b604b 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -9,17 +9,18 @@ import kotlin.native.concurrent.* import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* -object MonthIntSerializer: KSerializer { +@Suppress("INVISIBLE_MEMBER") +object MonthSerializer: KSerializer { + private val impl = EnumSerializer("Month", Month.values()) - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Month", PrimitiveKind.SHORT) + override val descriptor: SerialDescriptor + get() = impl.descriptor - override fun deserialize(decoder: Decoder): Month = Month(decoder.decodeShort().toInt()) + override fun deserialize(decoder: Decoder): Month = impl.deserialize(decoder) - override fun serialize(encoder: Encoder, value: Month) { - encoder.encodeShort(value.number.toShort()) - } + override fun serialize(encoder: Encoder, value: Month) = impl.serialize(encoder, value) } public expect enum class Month { diff --git a/serialization/build.gradle.kts b/serialization/build.gradle.kts new file mode 100644 index 000000000..0a5f9eb4b --- /dev/null +++ b/serialization/build.gradle.kts @@ -0,0 +1,102 @@ +import java.util.Locale + +plugins { + id("kotlin-multiplatform") + kotlin("plugin.serialization") + `maven-publish` +} + +val JDK_8: String by project + +kotlin { + infra { + target("linuxX64") + target("mingwX64") + target("macosX64") + target("iosX64") + target("iosArm64") + target("iosArm32") + target("watchosArm32") + target("watchosArm64") + target("watchosX86") + target("tvosArm64") + target("tvosX64") + } + + jvm { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) + } + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + jdkHome = JDK_8 + } + } + + } + + js { + nodejs { + } + compilations.all { + kotlinOptions { + sourceMap = true + moduleKind = "umd" + metaInfo = true + } + } + } + + sourceSets.all { + val suffixIndex = name.indexOfLast { it.isUpperCase() } + val targetName = name.substring(0, suffixIndex) + val suffix = name.substring(suffixIndex).toLowerCase(Locale.ROOT).takeIf { it != "main" } + kotlin.srcDir("$targetName/${suffix ?: "src"}") + resources.srcDir("$targetName/${suffix?.let { it + "Resources "} ?: "resources"}") + languageSettings.apply { + useExperimentalAnnotation("kotlin.Experimental") + } + } + + targets.withType { + compilations["test"].kotlinOptions { + freeCompilerArgs += listOf("-trw") + } + } + + sourceSets { + commonMain { + dependencies { + api(project(":kotlinx-datetime")) + api("org.jetbrains.kotlin:kotlin-stdlib-common") + } + } + + commonTest { + dependencies { + api("org.jetbrains.kotlin:kotlin-test-common") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + api("org.jetbrains.kotlin:kotlin-test-annotations-common") + } + } + + val jvmMain by getting + val jvmTest by getting { + dependencies { + api("org.jetbrains.kotlin:kotlin-test-junit") + } + } + + val jsMain by getting + val jsTest by getting { + dependencies { + api("org.jetbrains.kotlin:kotlin-test-js") + implementation(npm("@js-joda/timezone", "2.3.0")) + } + } + + val nativeMain by getting + val nativeTest by getting + } +} diff --git a/serialization/common/test/DateTimePeriodSerializationTest.kt b/serialization/common/test/DateTimePeriodSerializationTest.kt new file mode 100644 index 000000000..adbc2ffa5 --- /dev/null +++ b/serialization/common/test/DateTimePeriodSerializationTest.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +class DateTimePeriodSerializationTest { +} \ No newline at end of file diff --git a/serialization/common/test/DateTimeUnitSerializationTest.kt b/serialization/common/test/DateTimeUnitSerializationTest.kt new file mode 100644 index 000000000..b0c907c5e --- /dev/null +++ b/serialization/common/test/DateTimeUnitSerializationTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.json.* +import kotlin.random.* +import kotlin.test.* + +class DateTimeUnitSerializationTest { + @Test + fun timeBasedSerialization() { + repeat(100) { + val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) + val unit = DateTimeUnit.TimeBased(nanoseconds) + val json = "{\"nanoseconds\":$nanoseconds}" + assertEquals(json, Json.encodeToString(TimeBasedSerializer, unit)) + assertEquals(unit, Json.decodeFromString(TimeBasedSerializer, json)) + } + } + + @Test + fun dayBasedSerialization() { + repeat(100) { + val days = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.DayBased(days) + val json = "{\"days\":$days}" + assertEquals(json, Json.encodeToString(DayBasedSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DayBasedSerializer, json)) + } + } + + @Test + fun monthBasedSerialization() { + repeat(100) { + val months = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.MonthBased(months) + val json = "{\"months\":$months}" + assertEquals(json, Json.encodeToString(MonthBasedSerializer, unit)) + assertEquals(unit, Json.decodeFromString(MonthBasedSerializer, json)) + } + } + + @Test + fun dateBasedSerialization() { + repeat(100) { + val days = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.DayBased(days) + val json = "{\"type\":\"DayBased\",\"days\":$days}" + assertEquals(json, Json.encodeToString(DateBasedSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateBasedSerializer, json)) + } + repeat(100) { + val months = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.MonthBased(months) + val json = "{\"type\":\"MonthBased\",\"months\":$months}" + assertEquals(json, Json.encodeToString(DateBasedSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateBasedSerializer, json)) + } + } + + @Test + fun serialization() { + repeat(100) { + val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) + val unit = DateTimeUnit.TimeBased(nanoseconds) + val json = "{\"type\":\"TimeBased\",\"nanoseconds\":$nanoseconds}" + assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + } + repeat(100) { + val days = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.DayBased(days) + val json = "{\"type\":\"DayBased\",\"days\":$days}" + assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + } + repeat(100) { + val months = Random.nextInt(1, Int.MAX_VALUE) + val unit = DateTimeUnit.DateBased.MonthBased(months) + val json = "{\"type\":\"MonthBased\",\"months\":$months}" + assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + } + } + +} \ No newline at end of file diff --git a/serialization/common/test/DayOfWeekSerializationTest.kt b/serialization/common/test/DayOfWeekSerializationTest.kt new file mode 100644 index 000000000..922285b0d --- /dev/null +++ b/serialization/common/test/DayOfWeekSerializationTest.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class DayOfWeekSerializationTest { + @Test + fun serialization() { + for (dayOfWeek in DayOfWeek.values()) { + val json = "\"${dayOfWeek.name}\"" + assertEquals(json, Json.encodeToString(DayOfWeekSerializer, dayOfWeek)) + assertEquals(dayOfWeek, Json.decodeFromString(DayOfWeekSerializer, json)) + } + } +} \ No newline at end of file diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/InstantSerializationTest.kt new file mode 100644 index 000000000..1aa93c473 --- /dev/null +++ b/serialization/common/test/InstantSerializationTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class InstantSerializationTest { + + @Test + fun iso8601Serialization() { + for ((instant, json) in listOf( + Pair(Instant.fromEpochSeconds(1607505416, 124000), + "\"2020-12-09T09:16:56.000124Z\""), + Pair(Instant.fromEpochSeconds(-1607505416, -124000), + "\"1919-01-23T14:43:03.999876Z\""), + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "\"2001-04-19T04:25:21.123456789Z\""), + Pair(Instant.fromEpochSeconds(987654321, 0), + "\"2001-04-19T04:25:21Z\""), + )) { + assertEquals(json, Json.encodeToString(InstantISO8601Serializer, instant)) + assertEquals(instant, Json.decodeFromString(InstantISO8601Serializer, json)) + } + } + + @Test + fun componentSerialization() { + for ((instant, json) in listOf( + Pair(Instant.fromEpochSeconds(1607505416, 124000), + "{\"epochSeconds\":1607505416,\"nanosecondsOfSecond\":124000}"), + Pair(Instant.fromEpochSeconds(-1607505416, -124000), + "{\"epochSeconds\":-1607505417,\"nanosecondsOfSecond\":999876000}"), + Pair(Instant.fromEpochSeconds(987654321, 123456789), + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":123456789}"), + Pair(Instant.fromEpochSeconds(987654321, 0), + "{\"epochSeconds\":987654321}"), + )) { + assertEquals(json, Json.encodeToString(InstantComponentSerializer, instant)) + assertEquals(instant, Json.decodeFromString(InstantComponentSerializer, json)) + } + // check that having a `"nanosecondsOfSecond": 0` field doesn't break deserialization + assertEquals(Instant.fromEpochSeconds(987654321, 0), + Json.decodeFromString(InstantComponentSerializer, + "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}")) + // "epochSeconds" should always be present + assertFailsWith { Json.decodeFromString(InstantComponentSerializer, "{}") } + assertFailsWith { Json.decodeFromString(InstantComponentSerializer, "{\"nanosecondsOfSecond\":3}") } + } +} \ No newline at end of file diff --git a/serialization/common/test/LocalDateSerializationTest.kt b/serialization/common/test/LocalDateSerializationTest.kt new file mode 100644 index 000000000..1aa8b3daa --- /dev/null +++ b/serialization/common/test/LocalDateSerializationTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class LocalDateSerializationTest { + @Test + fun iso8601Serialization() { + for ((localDate, json) in listOf( + Pair(LocalDate(2020, 12, 9), "\"2020-12-09\""), + Pair(LocalDate(-2020, 1, 1), "\"-2020-01-01\""), + Pair(LocalDate(2019, 10, 1), "\"2019-10-01\""), + )) { + assertEquals(json, Json.encodeToString(LocalDateISO8601Serializer, localDate)) + assertEquals(localDate, Json.decodeFromString(LocalDateISO8601Serializer, json)) + } + } + + @Test + fun componentSerialization() { + for ((localDate, json) in listOf( + Pair(LocalDate(2020, 12, 9), "{\"year\":2020,\"month\":12,\"day\":9}"), + Pair(LocalDate(-2020, 1, 1), "{\"year\":-2020,\"month\":1,\"day\":1}"), + Pair(LocalDate(2019, 10, 1), "{\"year\":2019,\"month\":10,\"day\":1}"), + )) { + assertEquals(json, Json.encodeToString(LocalDateComponentSerializer, localDate)) + assertEquals(localDate, Json.decodeFromString(LocalDateComponentSerializer, json)) + } + // all components must be present + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{}") + } + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":3,\"month\":12}") + } + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":3,\"day\":12}") + } + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{\"month\":3,\"day\":12}") + } + // invalid values must fail to construct + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":1000000000000,\"month\":3,\"day\":12}") + } + assertFailsWith { + Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":2020,\"month\":30,\"day\":12}") + } + } + +} diff --git a/serialization/common/test/LocalDateTimeSerializationTest.kt b/serialization/common/test/LocalDateTimeSerializationTest.kt new file mode 100644 index 000000000..9327bc705 --- /dev/null +++ b/serialization/common/test/LocalDateTimeSerializationTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class LocalDateTimeSerializationTest { + @Test + fun iso8601Serialization() { + for ((localDateTime, json) in listOf( + Pair(LocalDateTime(2008, 7, 5, 2, 1), "\"2008-07-05T02:01\""), + Pair(LocalDateTime(2007, 12, 31, 23, 59, 1), "\"2007-12-31T23:59:01\""), + Pair(LocalDateTime(999, 12, 31, 23, 59, 59, 990000000), "\"0999-12-31T23:59:59.990\""), + Pair(LocalDateTime(-1, 1, 2, 23, 59, 59, 999990000), "\"-0001-01-02T23:59:59.999990\""), + Pair(LocalDateTime(-2008, 1, 2, 23, 59, 59, 999999990), "\"-2008-01-02T23:59:59.999999990\""), + )) { + assertEquals(json, Json.encodeToString(LocalDateTimeISO8601Serializer, localDateTime)) + assertEquals(localDateTime, Json.decodeFromString(LocalDateTimeISO8601Serializer, json)) + } + } + + @Test + fun componentSerialization() { + for ((localDateTime, json) in listOf( + Pair(LocalDateTime(2008, 7, 5, 2, 1), "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1}"), + Pair(LocalDateTime(2007, 12, 31, 23, 59, 1), + "{\"year\":2007,\"month\":12,\"day\":31,\"hour\":23,\"minute\":59,\"second\":1}"), + Pair(LocalDateTime(999, 12, 31, 23, 59, 59, 990000000), + "{\"year\":999,\"month\":12,\"day\":31,\"hour\":23,\"minute\":59,\"second\":59,\"nanosecond\":990000000}"), + Pair(LocalDateTime(-1, 1, 2, 23, 59, 59, 999990000), + "{\"year\":-1,\"month\":1,\"day\":2,\"hour\":23,\"minute\":59,\"second\":59,\"nanosecond\":999990000}"), + Pair(LocalDateTime(-2008, 1, 2, 23, 59, 59, 999999990), + "{\"year\":-2008,\"month\":1,\"day\":2,\"hour\":23,\"minute\":59,\"second\":59,\"nanosecond\":999999990}"), + Pair(LocalDateTime(-2008, 1, 2, 23, 59, 0, 1), + "{\"year\":-2008,\"month\":1,\"day\":2,\"hour\":23,\"minute\":59,\"second\":0,\"nanosecond\":1}"), + )) { + assertEquals(json, Json.encodeToString(LocalDateTimeComponentSerializer, localDateTime)) + assertEquals(localDateTime, Json.decodeFromString(LocalDateTimeComponentSerializer, json)) + } + // adding omitted values shouldn't break deserialization + assertEquals(LocalDateTime(2008, 7, 5, 2, 1), + Json.decodeFromString(LocalDateTimeComponentSerializer, + "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"second\":0}" + )) + assertEquals(LocalDateTime(2008, 7, 5, 2, 1), + Json.decodeFromString(LocalDateTimeComponentSerializer, + "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"nanosecond\":0}" + )) + assertEquals(LocalDateTime(2008, 7, 5, 2, 1), + Json.decodeFromString(LocalDateTimeComponentSerializer, + "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"second\":0,\"nanosecond\":0}" + )) + // invalid values must fail to construct + assertFailsWith { + Json.decodeFromString(LocalDateTimeComponentSerializer, + "{\"year\":1000000000000,\"month\":3,\"day\":12,\"hour\":10,\"minute\":2}") + } + assertFailsWith { + Json.decodeFromString(LocalDateTimeComponentSerializer, + "{\"year\":2020,\"month\":30,\"day\":12,\"hour\":10,\"minute\":2}") + } + } +} diff --git a/serialization/common/test/MonthSerializationTest.kt b/serialization/common/test/MonthSerializationTest.kt new file mode 100644 index 000000000..7a37fa9e7 --- /dev/null +++ b/serialization/common/test/MonthSerializationTest.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class MonthSerializationTest { + @Test + fun serialization() { + for (month in Month.values()) { + val json = "\"${month.name}\"" + assertEquals(json, Json.encodeToString(MonthSerializer, month)) + assertEquals(month, Json.decodeFromString(MonthSerializer, json)) + } + } +} \ No newline at end of file diff --git a/serialization/common/test/TimeZoneSerializationTest.kt b/serialization/common/test/TimeZoneSerializationTest.kt new file mode 100644 index 000000000..7dd555e67 --- /dev/null +++ b/serialization/common/test/TimeZoneSerializationTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.serialization.json.* +import kotlin.test.* + +class TimeZoneSerializationTest { + + @Test + fun zoneOffsetSerialization() { + val offset2h = TimeZone.of("+02:00") as ZoneOffset + assertEquals("\"+02:00\"", Json.encodeToString(ZoneOffsetSerializer, offset2h)) + assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+02:00\"")) + assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+02\"")) + assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+2\"")) + assertFailsWith { + Json.decodeFromString(ZoneOffsetSerializer, "\"Europe/Berlin\"") + } + } + + @Test + fun serialization() { + for (zoneId in listOf("Europe/Berlin", "+02:00")) { + val zone = TimeZone.of(zoneId) + val json = "\"$zoneId\"" + assertEquals(json, Json.encodeToString(TimeZoneSerializer, zone)) + assertEquals(zone, Json.decodeFromString(TimeZoneSerializer, json)) + } + } +} \ No newline at end of file diff --git a/serialization/js/test/JsJodaTimeZoneModule.kt b/serialization/js/test/JsJodaTimeZoneModule.kt new file mode 100644 index 000000000..f4805e10d --- /dev/null +++ b/serialization/js/test/JsJodaTimeZoneModule.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +@JsModule("@js-joda/timezone") +@JsNonModule +external object JsJodaTimeZoneModule + +private val jsJodaTz = JsJodaTimeZoneModule diff --git a/settings.gradle b/settings.gradle index 891045b26..28218e565 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,3 +13,5 @@ rootProject.name = 'Kotlin-DateTime-library' include ':core' project(":core").name='kotlinx-datetime' +include ':serialization' +project(":serialization").name='kotlinx-datetime-serialization' From 1459c5c5e13d32c6d6b47163a22e631d33d97d01 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 10 Dec 2020 14:28:35 +0300 Subject: [PATCH 11/25] ISO8601 serializer for DateTimePeriod --- core/common/src/DateTimePeriod.kt | 40 ++++++++- core/common/src/DateTimeUnit.kt | 52 ++++++----- .../test/DateTimePeriodSerializationTest.kt | 88 ++++++++++++++++++- .../test/DateTimeUnitSerializationTest.kt | 4 +- 4 files changed, 154 insertions(+), 30 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 4ada1e75a..a8077ed1f 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -34,7 +34,7 @@ object DateTimePeriodComponentSerializer: KSerializer { var minutes = 0 var seconds = 0 var nanoseconds = 0L - while (true) { + loop@while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> years = decodeIntElement(descriptor, 0) 1 -> months = decodeIntElement(descriptor, 1) @@ -43,7 +43,7 @@ object DateTimePeriodComponentSerializer: KSerializer { 4 -> minutes = decodeIntElement(descriptor, 4) 5 -> seconds = decodeIntElement(descriptor, 5) 6 -> nanoseconds = decodeLongElement(descriptor, 6) - CompositeDecoder.DECODE_DONE -> break + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 else -> error("Unexpected index: $index") } } @@ -66,7 +66,21 @@ object DateTimePeriodComponentSerializer: KSerializer { } -@Serializable(with = DateTimePeriodComponentSerializer::class) +object DateTimePeriodISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): DateTimePeriod = + DateTimePeriod.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: DateTimePeriod) { + encoder.encodeString(value.toString()) + } + +} + +@Serializable(with = DateTimePeriodISO8601Serializer::class) // TODO: could be error-prone without explicitly named params sealed class DateTimePeriod { internal abstract val totalMonths: Int @@ -336,7 +350,25 @@ object DatePeriodComponentSerializer: KSerializer { } -@Serializable(with = DatePeriodComponentSerializer::class) +object DatePeriodISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING) + + // TODO: consider whether should fail when parsing "P1YT0H0M0.0S" + override fun deserialize(decoder: Decoder): DatePeriod = + when (val period = DateTimePeriod.parse(decoder.decodeString())) { + is DatePeriod -> period + else -> throw IllegalArgumentException("$period is not a date-based period") + } + + override fun serialize(encoder: Encoder, value: DatePeriod) { + encoder.encodeString(value.toString()) + } + +} + +@Serializable(with = DatePeriodISO8601Serializer::class) class DatePeriod internal constructor( internal override val totalMonths: Int, override val days: Int, diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index 856e16609..40fc5ff47 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -19,32 +19,38 @@ sealed class DateTimeUnit { @Serializable(with = TimeBasedSerializer::class) class TimeBased(val nanoseconds: Long) : DateTimeUnit() { - - /* fields without a default value can't be @Transient, so the more natural way of writing this - (setting [unitName] and [unitScale] in init { ... }) won't work: - https://github.com/Kotlin/kotlinx.serialization/issues/1227. */ - @Transient - private val unitName: String = when { - nanoseconds % 3600_000_000_000 == 0L -> "HOUR" - nanoseconds % 60_000_000_000 == 0L -> "MINUTE" - nanoseconds % 1_000_000_000 == 0L -> "SECOND" - nanoseconds % 1_000_000 == 0L -> "MILLISECOND" - nanoseconds % 1_000 == 0L -> "MICROSECOND" - else -> "NANOSECOND" - } - - @Transient - private val unitScale: Long = when { - nanoseconds % 3600_000_000_000 == 0L -> nanoseconds / 3600_000_000_000 - nanoseconds % 60_000_000_000 == 0L -> nanoseconds / 60_000_000_000 - nanoseconds % 1_000_000_000 == 0L -> nanoseconds / 1_000_000_000 - nanoseconds % 1_000_000 == 0L -> nanoseconds / 1_000_000 - nanoseconds % 1_000 == 0L -> nanoseconds / 1_000 - else -> nanoseconds - } + private val unitName: String + private val unitScale: Long init { require(nanoseconds > 0) { "Unit duration must be positive, but was $nanoseconds ns." } + // find a concise string representation for the unit with this duration + when { + nanoseconds % 3600_000_000_000 == 0L -> { + unitName = "HOUR" + unitScale = nanoseconds / 3600_000_000_000 + } + nanoseconds % 60_000_000_000 == 0L -> { + unitName = "MINUTE" + unitScale = nanoseconds / 60_000_000_000 + } + nanoseconds % 1_000_000_000 == 0L -> { + unitName = "SECOND" + unitScale = nanoseconds / 1_000_000_000 + } + nanoseconds % 1_000_000 == 0L -> { + unitName = "MILLISECOND" + unitScale = nanoseconds / 1_000_000 + } + nanoseconds % 1_000 == 0L -> { + unitName = "MICROSECOND" + unitScale = nanoseconds / 1_000 + } + else -> { + unitName = "NANOSECOND" + unitScale = nanoseconds + } + } } override fun times(scalar: Int): TimeBased = TimeBased(safeMultiply(nanoseconds, scalar.toLong())) diff --git a/serialization/common/test/DateTimePeriodSerializationTest.kt b/serialization/common/test/DateTimePeriodSerializationTest.kt index adbc2ffa5..aaeba44fc 100644 --- a/serialization/common/test/DateTimePeriodSerializationTest.kt +++ b/serialization/common/test/DateTimePeriodSerializationTest.kt @@ -5,5 +5,91 @@ package kotlinx.datetime.serialization.test +import kotlinx.datetime.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.test.* + class DateTimePeriodSerializationTest { -} \ No newline at end of file + + @Test + fun datePeriodISO8601Serialization() { + for ((period, json) in listOf( + Pair(DatePeriod(1, 2, 3), "\"P1Y2M3D\""), + Pair(DatePeriod(years = 1), "\"P1Y\""), + Pair(DatePeriod(years = 1, months = 1), "\"P1Y1M\""), + Pair(DatePeriod(months = 11), "\"P11M\""), + Pair(DatePeriod(months = 14), "\"P1Y2M\""), + Pair(DatePeriod(months = 10, days = 5), "\"P10M5D\""), + Pair(DatePeriod(years = 1, days = 40), "\"P1Y40D\""), + )) { + assertEquals(json, Json.encodeToString(DatePeriodISO8601Serializer, period)) + assertEquals(period, Json.decodeFromString(DatePeriodISO8601Serializer, json)) + assertEquals(json, Json.encodeToString(DateTimePeriodISO8601Serializer, period)) + assertEquals(period, Json.decodeFromString(DateTimePeriodISO8601Serializer, json) as DatePeriod) + } + // time-based keys should not be considered unknown here + assertFailsWith { + Json { ignoreUnknownKeys = true }.decodeFromString(DatePeriodISO8601Serializer, "\"P3DT1H\"") + } + // presence of time-based keys should not be a problem if the values are 0 + Json.decodeFromString(DatePeriodISO8601Serializer, "\"P3DT0H\"") + } + + @Test + fun datePeriodComponentSerialization() { + for ((period, json) in listOf( + Pair(DatePeriod(1, 2, 3), "{\"years\":1,\"months\":2,\"days\":3}"), + Pair(DatePeriod(years = 1), "{\"years\":1}"), + Pair(DatePeriod(years = 1, months = 1), "{\"years\":1,\"months\":1}"), + Pair(DatePeriod(months = 11), "{\"months\":11}"), + Pair(DatePeriod(months = 14), "{\"years\":1,\"months\":2}"), + Pair(DatePeriod(months = 10, days = 5), "{\"months\":10,\"days\":5}"), + Pair(DatePeriod(years = 1, days = 40), "{\"years\":1,\"days\":40}"), + )) { + assertEquals(json, Json.encodeToString(DatePeriodComponentSerializer, period)) + assertEquals(period, Json.decodeFromString(DatePeriodComponentSerializer, json)) + assertEquals(json, Json.encodeToString(DateTimePeriodComponentSerializer, period)) + assertEquals(period, Json.decodeFromString(DateTimePeriodComponentSerializer, json) as DatePeriod) + } + // time-based keys should not be considered unknown here + assertFailsWith { + Json { ignoreUnknownKeys = true }.decodeFromString(DatePeriodComponentSerializer, "{\"hours\":3}") + } + // presence of time-based keys should not be a problem if the values are 0 + Json.decodeFromString(DatePeriodComponentSerializer, "{\"hours\":0}") + } + + @Test + fun dateTimePeriodISO8601Serialization() { + for ((period, json) in listOf( + Pair(DateTimePeriod(), "\"P0D\""), + Pair(DateTimePeriod(hours = 1), "\"PT1H\""), + Pair(DateTimePeriod(days = 1, hours = -1), "\"P1DT-1H\""), + Pair(DateTimePeriod(days = -1, hours = -1), "\"-P1DT1H\""), + Pair(DateTimePeriod(months = -1), "\"-P1M\""), + Pair(DateTimePeriod(years = -1, months = -2, days = -3, hours = -4, minutes = -5, seconds = 0, nanoseconds = 500_000_000), + "\"-P1Y2M3DT4H4M59.500000000S\""), + )) { + assertEquals(json, Json.encodeToString(DateTimePeriodISO8601Serializer, period)) + assertEquals(period, Json.decodeFromString(DateTimePeriodISO8601Serializer, json)) + } + } + + @Test + fun dateTimePeriodComponentSerialization() { + for ((period, json) in listOf( + Pair(DateTimePeriod(), "{}"), + Pair(DateTimePeriod(hours = 1), "{\"hours\":1}"), + Pair(DateTimePeriod(days = 1, hours = -1), "{\"days\":1,\"hours\":-1}"), + Pair(DateTimePeriod(days = -1, hours = -1), "{\"days\":-1,\"hours\":-1}"), + Pair(DateTimePeriod(months = -1), "{\"months\":-1}"), + Pair(DateTimePeriod(years = -1, months = -2, days = -3, hours = -4, minutes = -5, seconds = 0, nanoseconds = 500_000_000), + "{\"years\":-1,\"months\":-2,\"days\":-3,\"hours\":-4,\"minutes\":-4,\"seconds\":-59,\"nanoseconds\":-500000000}"), + )) { + assertEquals(json, Json.encodeToString(DateTimePeriodComponentSerializer, period)) + assertEquals(period, Json.decodeFromString(DateTimePeriodComponentSerializer, json)) + } + } + +} diff --git a/serialization/common/test/DateTimeUnitSerializationTest.kt b/serialization/common/test/DateTimeUnitSerializationTest.kt index b0c907c5e..26171a75b 100644 --- a/serialization/common/test/DateTimeUnitSerializationTest.kt +++ b/serialization/common/test/DateTimeUnitSerializationTest.kt @@ -16,7 +16,7 @@ class DateTimeUnitSerializationTest { repeat(100) { val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) val unit = DateTimeUnit.TimeBased(nanoseconds) - val json = "{\"nanoseconds\":$nanoseconds}" + val json = "{\"nanoseconds\":${nanoseconds.toString()}}" // https://youtrack.jetbrains.com/issue/KT-39891 assertEquals(json, Json.encodeToString(TimeBasedSerializer, unit)) assertEquals(unit, Json.decodeFromString(TimeBasedSerializer, json)) } @@ -67,7 +67,7 @@ class DateTimeUnitSerializationTest { repeat(100) { val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) val unit = DateTimeUnit.TimeBased(nanoseconds) - val json = "{\"type\":\"TimeBased\",\"nanoseconds\":$nanoseconds}" + val json = "{\"type\":\"TimeBased\",\"nanoseconds\":${nanoseconds.toString()}}" // https://youtrack.jetbrains.com/issue/KT-39891 assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) } From d486f9ae4f8c8b680c5d7f12ae84b7357e917cb6 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 10 Dec 2020 15:04:45 +0300 Subject: [PATCH 12/25] Fix serial descriptor names --- core/common/src/DateTimePeriod.kt | 13 +++++++++++-- core/common/src/LocalDate.kt | 4 ++-- core/common/src/LocalDateTime.kt | 4 ++-- core/common/src/TimeZone.kt | 2 +- core/js/src/LocalDateTime.kt | 2 +- core/jvm/src/LocalDateTime.kt | 2 +- core/native/src/LocalDateTime.kt | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index a8077ed1f..d5873071e 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.encoding.* object DateTimePeriodComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("DateTimePeriod") { element("years", isOptional = true) element("months", isOptional = true) element("days", isOptional = true) @@ -315,7 +315,16 @@ object DatePeriodComponentSerializer: KSerializer { private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong()) - override val descriptor: SerialDescriptor = DateTimePeriodComponentSerializer.descriptor + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("DatePeriod") { + element("years", isOptional = true) + element("months", isOptional = true) + element("days", isOptional = true) + element("hours", isOptional = true) + element("minutes", isOptional = true) + element("seconds", isOptional = true) + element("nanoseconds", isOptional = true) + } override fun deserialize(decoder: Decoder): DatePeriod = decoder.decodeStructure(descriptor) { diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 03dd17f0f..4dfa040bf 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -13,7 +13,7 @@ import kotlinx.serialization.encoding.* object LocalDateISO8601Serializer: KSerializer { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): LocalDate = LocalDate.parse(decoder.decodeString()) @@ -27,7 +27,7 @@ object LocalDateISO8601Serializer: KSerializer { object LocalDateComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("LocalDate") { element("year") element("month") element("day") diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index eb00bad97..7fc3f459c 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.encoding.* object LocalDateTimeISO8601Serializer: KSerializer { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): LocalDateTime = LocalDateTime.parse(decoder.decodeString()) @@ -26,7 +26,7 @@ object LocalDateTimeISO8601Serializer: KSerializer { object LocalDateTimeComponentSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("LocalDateTime") { element("year") element("month") element("day") diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index a806ddfb7..3860f1ab0 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -28,7 +28,7 @@ object TimeZoneSerializer: KSerializer { object ZoneOffsetSerializer: KSerializer { override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) + get() = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): ZoneOffset { val zone = TimeZone.of(decoder.decodeString()) diff --git a/core/js/src/LocalDateTime.kt b/core/js/src/LocalDateTime.kt index aef68fa93..9eecbf90b 100644 --- a/core/js/src/LocalDateTime.kt +++ b/core/js/src/LocalDateTime.kt @@ -14,7 +14,7 @@ import kotlinx.datetime.internal.JSJoda.LocalTime as jtLocalTime actual object LocalDateTimeCompactSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("LocalDateTime") { element("epochDay") element("nanoOfDay") } diff --git a/core/jvm/src/LocalDateTime.kt b/core/jvm/src/LocalDateTime.kt index 9ef0f253b..77732375b 100644 --- a/core/jvm/src/LocalDateTime.kt +++ b/core/jvm/src/LocalDateTime.kt @@ -21,7 +21,7 @@ public actual typealias DayOfWeek = java.time.DayOfWeek actual object LocalDateTimeCompactSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("LocalDateTime") { element("epochDay") element("nanoOfDay") } diff --git a/core/native/src/LocalDateTime.kt b/core/native/src/LocalDateTime.kt index 656b33b3b..3f0d4d927 100644 --- a/core/native/src/LocalDateTime.kt +++ b/core/native/src/LocalDateTime.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.encoding.* actual object LocalDateTimeCompactSerializer: KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { + buildClassSerialDescriptor("LocalDateTime") { element("epochDay") element("nanoOfDay") } From 0518207a79e8fa961cc380bdd421c287ff94de78 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 1 Mar 2021 17:45:51 +0300 Subject: [PATCH 13/25] Only depend on serialization with compileOnly from `core` --- core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d75a9d913..7f4a73010 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -149,7 +149,7 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1") } } From 6acf8181037b7df229507c1f5e1a57850389a3ce Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 10 Mar 2021 11:30:04 +0300 Subject: [PATCH 14/25] Move serializers to a separate package --- core/common/src/DateTimePeriod.kt | 146 +------------- core/common/src/DateTimeUnit.kt | 178 +---------------- core/common/src/DayOfWeek.kt | 16 -- core/common/src/Instant.kt | 55 +----- core/common/src/LocalDate.kt | 61 +----- core/common/src/LocalDateTime.kt | 83 +------- core/common/src/Month.kt | 16 -- core/common/src/TimeZone.kt | 39 +--- .../serializers/DateTimePeriodSerializers.kt | 153 ++++++++++++++ .../serializers/DateTimeUnitSerializers.kt | 186 ++++++++++++++++++ .../src/serializers/DayOfWeekSerializers.kt | 24 +++ .../src/serializers/InstantSerializers.kt | 61 ++++++ .../src/serializers/LocalDateSerializers.kt | 67 +++++++ .../serializers/LocalDateTimeSerializers.kt | 90 +++++++++ .../src/serializers/MonthSerializers.kt | 24 +++ .../src/serializers/TimeZoneSerializers.kt | 45 +++++ core/js/src/Instant.kt | 1 + core/js/src/LocalDate.kt | 15 +- core/js/src/LocalDateTime.kt | 42 +--- core/js/src/TimeZone.kt | 2 + .../src/serializers/LocalDateSerializers.kt | 25 +++ .../serializers/LocalDateTimeSerializers.kt | 48 +++++ core/jvm/src/Instant.kt | 1 + core/jvm/src/LocalDate.kt | 19 +- core/jvm/src/LocalDateTime.kt | 43 +--- core/jvm/src/TimeZoneJvm.kt | 2 + .../src/serializers/LocalDateSerializers.kt | 25 +++ .../serializers/LocalDateTimeSerializers.kt | 48 +++++ core/native/src/Instant.kt | 3 +- core/native/src/LocalDate.kt | 19 +- core/native/src/LocalDateTime.kt | 42 +--- core/native/src/TimeZone.kt | 2 + .../src/serializers/LocalDateSerializers.kt | 26 +++ .../serializers/LocalDateTimeSerializers.kt | 51 +++++ .../test/DateTimePeriodSerializationTest.kt | 1 + .../test/DateTimeUnitSerializationTest.kt | 1 + .../common/test/DayOfWeekSerializationTest.kt | 1 + .../common/test/InstantSerializationTest.kt | 1 + .../common/test/LocalDateSerializationTest.kt | 1 + .../test/LocalDateTimeSerializationTest.kt | 1 + .../common/test/MonthSerializationTest.kt | 1 + .../common/test/TimeZoneSerializationTest.kt | 1 + 42 files changed, 916 insertions(+), 750 deletions(-) create mode 100644 core/common/src/serializers/DateTimePeriodSerializers.kt create mode 100644 core/common/src/serializers/DateTimeUnitSerializers.kt create mode 100644 core/common/src/serializers/DayOfWeekSerializers.kt create mode 100644 core/common/src/serializers/InstantSerializers.kt create mode 100644 core/common/src/serializers/LocalDateSerializers.kt create mode 100644 core/common/src/serializers/LocalDateTimeSerializers.kt create mode 100644 core/common/src/serializers/MonthSerializers.kt create mode 100644 core/common/src/serializers/TimeZoneSerializers.kt create mode 100644 core/js/src/serializers/LocalDateSerializers.kt create mode 100644 core/js/src/serializers/LocalDateTimeSerializers.kt create mode 100644 core/jvm/src/serializers/LocalDateSerializers.kt create mode 100644 core/jvm/src/serializers/LocalDateTimeSerializers.kt create mode 100644 core/native/src/serializers/LocalDateSerializers.kt create mode 100644 core/native/src/serializers/LocalDateTimeSerializers.kt diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index d5873071e..e905df950 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -5,80 +5,12 @@ package kotlinx.datetime +import kotlinx.datetime.serializers.DatePeriodISO8601Serializer +import kotlinx.datetime.serializers.DateTimePeriodISO8601Serializer import kotlin.math.* import kotlin.time.Duration import kotlin.time.ExperimentalTime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -object DateTimePeriodComponentSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("DateTimePeriod") { - element("years", isOptional = true) - element("months", isOptional = true) - element("days", isOptional = true) - element("hours", isOptional = true) - element("minutes", isOptional = true) - element("seconds", isOptional = true) - element("nanoseconds", isOptional = true) - } - - override fun deserialize(decoder: Decoder): DateTimePeriod = - decoder.decodeStructure(descriptor) { - var years = 0 - var months = 0 - var days = 0 - var hours = 0 - var minutes = 0 - var seconds = 0 - var nanoseconds = 0L - loop@while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> years = decodeIntElement(descriptor, 0) - 1 -> months = decodeIntElement(descriptor, 1) - 2 -> days = decodeIntElement(descriptor, 2) - 3 -> hours = decodeIntElement(descriptor, 3) - 4 -> minutes = decodeIntElement(descriptor, 4) - 5 -> seconds = decodeIntElement(descriptor, 5) - 6 -> nanoseconds = decodeLongElement(descriptor, 6) - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") - } - } - DateTimePeriod(years, months, days, hours, minutes, seconds, nanoseconds) - } - - override fun serialize(encoder: Encoder, value: DateTimePeriod) { - encoder.encodeStructure(descriptor) { - with(value) { - if (years != 0) encodeIntElement(descriptor, 0, years) - if (months != 0) encodeIntElement(descriptor, 1, months) - if (days != 0) encodeIntElement(descriptor, 2, days) - if (hours != 0) encodeIntElement(descriptor, 3, hours) - if (minutes != 0) encodeIntElement(descriptor, 4, minutes) - if (seconds != 0) encodeIntElement(descriptor, 5, seconds) - if (nanoseconds != 0) encodeLongElement(descriptor, 6, value.nanoseconds.toLong()) - } - } - } - -} - -object DateTimePeriodISO8601Serializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): DateTimePeriod = - DateTimePeriod.parse(decoder.decodeString()) - - override fun serialize(encoder: Encoder, value: DateTimePeriod) { - encoder.encodeString(value.toString()) - } - -} +import kotlinx.serialization.Serializable @Serializable(with = DateTimePeriodISO8601Serializer::class) // TODO: could be error-prone without explicitly named params @@ -305,78 +237,6 @@ sealed class DateTimePeriod { public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this) -object DatePeriodComponentSerializer: KSerializer { - - private fun unexpectedNonzero(fieldName: String, value: Long) { - if (value != 0L) { - throw SerializationException("expected field '$fieldName' to be zero, but was $value") - } - } - - private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong()) - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("DatePeriod") { - element("years", isOptional = true) - element("months", isOptional = true) - element("days", isOptional = true) - element("hours", isOptional = true) - element("minutes", isOptional = true) - element("seconds", isOptional = true) - element("nanoseconds", isOptional = true) - } - - override fun deserialize(decoder: Decoder): DatePeriod = - decoder.decodeStructure(descriptor) { - var years = 0 - var months = 0 - var days = 0 - loop@while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> years = decodeIntElement(descriptor, 0) - 1 -> months = decodeIntElement(descriptor, 1) - 2 -> days = decodeIntElement(descriptor, 2) - 3 -> unexpectedNonzero("hours", decodeIntElement(descriptor, 3)) - 4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4)) - 5 -> unexpectedNonzero("seconds", decodeLongElement(descriptor, 5)) - 6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6)) - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") - } - } - DatePeriod(years, months, days) - } - - override fun serialize(encoder: Encoder, value: DatePeriod) { - encoder.encodeStructure(descriptor) { - with(value) { - if (years != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 0, years) - if (months != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 1, months) - if (days != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 2, days) - } - } - } - -} - -object DatePeriodISO8601Serializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING) - - // TODO: consider whether should fail when parsing "P1YT0H0M0.0S" - override fun deserialize(decoder: Decoder): DatePeriod = - when (val period = DateTimePeriod.parse(decoder.decodeString())) { - is DatePeriod -> period - else -> throw IllegalArgumentException("$period is not a date-based period") - } - - override fun serialize(encoder: Encoder, value: DatePeriod) { - encoder.encodeString(value.toString()) - } - -} - @Serializable(with = DatePeriodISO8601Serializer::class) class DatePeriod internal constructor( internal override val totalMonths: Int, diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index 40fc5ff47..b66d8d934 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -5,11 +5,8 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.internal.* -import kotlin.reflect.* +import kotlinx.datetime.serializers.* +import kotlinx.serialization.Serializable import kotlin.time.* @Serializable(with = DateTimeUnitSerializer::class) @@ -128,174 +125,3 @@ sealed class DateTimeUnit { val CENTURY = YEAR * 100 } } - -object TimeBasedSerializer: KSerializer { - - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TimeBased") { - element("nanoseconds") - } - - override fun serialize(encoder: Encoder, value: DateTimeUnit.TimeBased) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.nanoseconds); - } - } - - @ExperimentalSerializationApi - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): DateTimeUnit.TimeBased { - var seen = false - var nanoseconds = 0L - decoder.decodeStructure(descriptor) { - if (decodeSequentially()) { - nanoseconds = decodeLongElement(descriptor, 0) - seen = true - } else { - loop@while (true) { - when (val elementIndex: Int = decodeElementIndex(descriptor)) { - 0 -> { - nanoseconds = decodeLongElement(descriptor, 0) - seen = true - } - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> throw UnknownFieldException(elementIndex) - } - } - } - } - if (!seen) throw MissingFieldException("nanoseconds") - return DateTimeUnit.TimeBased(nanoseconds) - } -} - -object DayBasedSerializer: KSerializer { - - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") { - element("days") - } - - override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.DayBased) { - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, value.days); - } - } - - @ExperimentalSerializationApi - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.DayBased { - var seen = false - var days = 0 - decoder.decodeStructure(descriptor) { - if (decodeSequentially()) { - days = decodeIntElement(descriptor, 0) - seen = true - } else { - loop@while (true) { - when (val elementIndex: Int = decodeElementIndex(descriptor)) { - 0 -> { - days = decodeIntElement(descriptor, 0) - seen = true - } - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> throw UnknownFieldException(elementIndex) - } - } - } - } - if (!seen) throw MissingFieldException("days") - return DateTimeUnit.DateBased.DayBased(days) - } -} - -object MonthBasedSerializer: KSerializer { - - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") { - element("months") - } - - override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.MonthBased) { - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, value.months); - } - } - - @ExperimentalSerializationApi - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.MonthBased { - var seen = false - var months = 0 - decoder.decodeStructure(descriptor) { - if (decodeSequentially()) { - months = decodeIntElement(descriptor, 0) - seen = true - } else { - loop@while (true) { - when (val elementIndex: Int = decodeElementIndex(descriptor)) { - 0 -> { - months = decodeIntElement(descriptor, 0) - seen = true - } - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> throw UnknownFieldException(elementIndex) - } - } - } - } - if (!seen) throw MissingFieldException("months") - return DateTimeUnit.DateBased.MonthBased(months) - } -} - -@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") -object DateBasedSerializer: AbstractPolymorphicSerializer() { - - private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased", - DateTimeUnit.DateBased::class, - arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class), - arrayOf(DayBasedSerializer, MonthBasedSerializer)) - - @InternalSerializationApi - override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): - DeserializationStrategy? = - impl.findPolymorphicSerializerOrNull(decoder, klassName) - - @InternalSerializationApi - override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit.DateBased): - SerializationStrategy? = - impl.findPolymorphicSerializerOrNull(encoder, value) - - @InternalSerializationApi - override val baseClass: KClass - get() = DateTimeUnit.DateBased::class - - @InternalSerializationApi - override val descriptor: SerialDescriptor - get() = impl.descriptor - -} - -@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") -object DateTimeUnitSerializer: AbstractPolymorphicSerializer() { - - private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit", - DateTimeUnit::class, - arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class, DateTimeUnit.TimeBased::class), - arrayOf(DayBasedSerializer, MonthBasedSerializer, TimeBasedSerializer)) - - @InternalSerializationApi - override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy? = - impl.findPolymorphicSerializerOrNull(decoder, klassName) - - @InternalSerializationApi - override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit): SerializationStrategy? = - impl.findPolymorphicSerializerOrNull(encoder, value) - - @InternalSerializationApi - override val baseClass: KClass - get() = DateTimeUnit::class - - @InternalSerializationApi - override val descriptor: SerialDescriptor - get() = impl.descriptor - -} diff --git a/core/common/src/DayOfWeek.kt b/core/common/src/DayOfWeek.kt index e8a062fa9..a93486554 100644 --- a/core/common/src/DayOfWeek.kt +++ b/core/common/src/DayOfWeek.kt @@ -5,24 +5,8 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.internal.* import kotlin.native.concurrent.* -@Suppress("INVISIBLE_MEMBER") -object DayOfWeekSerializer: KSerializer { - private val impl = EnumSerializer("Month", DayOfWeek.values()) - - override val descriptor: SerialDescriptor - get() = impl.descriptor - - override fun deserialize(decoder: Decoder): DayOfWeek = impl.deserialize(decoder) - - override fun serialize(encoder: Encoder, value: DayOfWeek) = impl.serialize(encoder, value) -} - public expect enum class DayOfWeek { MONDAY, TUESDAY, diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index eb8754575..0bfee0e63 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -5,61 +5,10 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.serialization.Serializable import kotlin.time.* -object InstantISO8601Serializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): Instant = - Instant.parse(decoder.decodeString()) - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeString(value.toString()) - } - -} - -object InstantComponentSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Instant") { - element("epochSeconds") - element("nanosecondsOfSecond", isOptional = true) - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): Instant = - decoder.decodeStructure(descriptor) { - var epochSeconds: Long? = null - var nanosecondsOfSecond = 0 - loop@while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochSeconds = decodeLongElement(descriptor, 0) - 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") - } - } - if (epochSeconds == null) throw MissingFieldException("epochSeconds") - Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.epochSeconds) - if (value.nanosecondsOfSecond != 0) { - encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) - } - } - } - -} - @OptIn(ExperimentalTime::class) @Serializable(with = InstantISO8601Serializer::class) public expect class Instant : Comparable { diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 4dfa040bf..b2b47e16a 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -5,66 +5,9 @@ package kotlinx.datetime +import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.serialization.Serializable import kotlin.time.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -object LocalDateISO8601Serializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate.parse(decoder.decodeString()) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeString(value.toString()) - } - -} - -object LocalDateComponentSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDate") { - element("year") - element("month") - element("day") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDate = - decoder.decodeStructure(descriptor) { - var year: Int? = null - var month: Short? = null - var day: Short? = null - loop@while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> year = decodeIntElement(descriptor, 0) - 1 -> month = decodeShortElement(descriptor, 1) - 2 -> day = decodeShortElement(descriptor, 2) - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") - } - } - if (year == null) throw MissingFieldException("year") - if (month == null) throw MissingFieldException("month") - if (day == null) throw MissingFieldException("day") - LocalDate(year, month.toInt(), day.toInt()) - } - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, value.year) - encodeShortElement(descriptor, 1, value.monthNumber.toShort()) - encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) - } - } - -} - -expect object LocalDateLongSerializer: KSerializer @Serializable(with = LocalDateISO8601Serializer::class) public expect class LocalDate : Comparable { diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 7fc3f459c..cce535fbb 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -5,87 +5,8 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -object LocalDateTimeISO8601Serializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): LocalDateTime = - LocalDateTime.parse(decoder.decodeString()) - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeString(value.toString()) - } - -} - -object LocalDateTimeComponentSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("year") - element("month") - element("day") - element("hour") - element("minute") - element("second", isOptional = true) - element("nanosecond", isOptional = true) - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var year: Int? = null - var month: Short? = null - var day: Short? = null - var hour: Short? = null - var minute: Short? = null - var second: Short = 0 - var nanosecond = 0 - loop@while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> year = decodeIntElement(descriptor, 0) - 1 -> month = decodeShortElement(descriptor, 1) - 2 -> day = decodeShortElement(descriptor, 2) - 3 -> hour = decodeShortElement(descriptor, 3) - 4 -> minute = decodeShortElement(descriptor, 4) - 5 -> second = decodeShortElement(descriptor, 5) - 6 -> nanosecond = decodeIntElement(descriptor, 6) - CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") - } - } - if (year == null) throw MissingFieldException("year") - if (month == null) throw MissingFieldException("month") - if (day == null) throw MissingFieldException("day") - if (hour == null) throw MissingFieldException("hour") - if (minute == null) throw MissingFieldException("minute") - LocalDateTime(year, month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt(), nanosecond) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeIntElement(descriptor, 0, value.year) - encodeShortElement(descriptor, 1, value.monthNumber.toShort()) - encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) - encodeShortElement(descriptor, 3, value.hour.toShort()) - encodeShortElement(descriptor, 4, value.minute.toShort()) - if (value.second != 0 || value.nanosecond != 0) { - encodeShortElement(descriptor, 5, value.second.toShort()) - if (value.nanosecond != 0) { - encodeIntElement(descriptor, 6, value.nanosecond) - } - } - } - } - -} - -expect object LocalDateTimeCompactSerializer: KSerializer +import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.serialization.Serializable @Serializable(with = LocalDateTimeISO8601Serializer::class) public expect class LocalDateTime : Comparable { diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 6ba7b604b..7db91a5ba 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -6,22 +6,6 @@ package kotlinx.datetime import kotlin.native.concurrent.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.internal.* - -@Suppress("INVISIBLE_MEMBER") -object MonthSerializer: KSerializer { - private val impl = EnumSerializer("Month", Month.values()) - - override val descriptor: SerialDescriptor - get() = impl.descriptor - - override fun deserialize(decoder: Decoder): Month = impl.deserialize(decoder) - - override fun serialize(encoder: Encoder, value: Month) = impl.serialize(encoder, value) -} public expect enum class Month { JANUARY, diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index 3860f1ab0..11e0d4144 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -8,42 +8,9 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -object TimeZoneSerializer: KSerializer { - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): TimeZone = TimeZone.of(decoder.decodeString()) - - override fun serialize(encoder: Encoder, value: TimeZone) { - encoder.encodeString(value.id) - } - -} - -object ZoneOffsetSerializer: KSerializer { - - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): ZoneOffset { - val zone = TimeZone.of(decoder.decodeString()) - if (zone is ZoneOffset) { - return zone - } else { - throw SerializationException("Timezone identifier '$zone' does not correspond to a fixed-offset timezone") - } - } - - override fun serialize(encoder: Encoder, value: ZoneOffset) { - encoder.encodeString(value.id) - } - -} +import kotlinx.datetime.serializers.TimeZoneSerializer +import kotlinx.datetime.serializers.ZoneOffsetSerializer +import kotlinx.serialization.Serializable @Serializable(with = TimeZoneSerializer::class) public expect open class TimeZone { diff --git a/core/common/src/serializers/DateTimePeriodSerializers.kt b/core/common/src/serializers/DateTimePeriodSerializers.kt new file mode 100644 index 000000000..aa63c0bbc --- /dev/null +++ b/core/common/src/serializers/DateTimePeriodSerializers.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.DatePeriod +import kotlinx.datetime.DateTimePeriod +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object DateTimePeriodComponentSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("DateTimePeriod") { + element("years", isOptional = true) + element("months", isOptional = true) + element("days", isOptional = true) + element("hours", isOptional = true) + element("minutes", isOptional = true) + element("seconds", isOptional = true) + element("nanoseconds", isOptional = true) + } + + override fun deserialize(decoder: Decoder): DateTimePeriod = + decoder.decodeStructure(descriptor) { + var years = 0 + var months = 0 + var days = 0 + var hours = 0 + var minutes = 0 + var seconds = 0 + var nanoseconds = 0L + loop@while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> years = decodeIntElement(descriptor, 0) + 1 -> months = decodeIntElement(descriptor, 1) + 2 -> days = decodeIntElement(descriptor, 2) + 3 -> hours = decodeIntElement(descriptor, 3) + 4 -> minutes = decodeIntElement(descriptor, 4) + 5 -> seconds = decodeIntElement(descriptor, 5) + 6 -> nanoseconds = decodeLongElement(descriptor, 6) + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> error("Unexpected index: $index") + } + } + DateTimePeriod(years, months, days, hours, minutes, seconds, nanoseconds) + } + + override fun serialize(encoder: Encoder, value: DateTimePeriod) { + encoder.encodeStructure(descriptor) { + with(value) { + if (years != 0) encodeIntElement(descriptor, 0, years) + if (months != 0) encodeIntElement(descriptor, 1, months) + if (days != 0) encodeIntElement(descriptor, 2, days) + if (hours != 0) encodeIntElement(descriptor, 3, hours) + if (minutes != 0) encodeIntElement(descriptor, 4, minutes) + if (seconds != 0) encodeIntElement(descriptor, 5, seconds) + if (nanoseconds != 0) encodeLongElement(descriptor, 6, value.nanoseconds.toLong()) + } + } + } + +} + +object DateTimePeriodISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): DateTimePeriod = + DateTimePeriod.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: DateTimePeriod) { + encoder.encodeString(value.toString()) + } + +} + +object DatePeriodComponentSerializer: KSerializer { + + private fun unexpectedNonzero(fieldName: String, value: Long) { + if (value != 0L) { + throw SerializationException("expected field '$fieldName' to be zero, but was $value") + } + } + + private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong()) + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("DatePeriod") { + element("years", isOptional = true) + element("months", isOptional = true) + element("days", isOptional = true) + element("hours", isOptional = true) + element("minutes", isOptional = true) + element("seconds", isOptional = true) + element("nanoseconds", isOptional = true) + } + + override fun deserialize(decoder: Decoder): DatePeriod = + decoder.decodeStructure(descriptor) { + var years = 0 + var months = 0 + var days = 0 + loop@while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> years = decodeIntElement(descriptor, 0) + 1 -> months = decodeIntElement(descriptor, 1) + 2 -> days = decodeIntElement(descriptor, 2) + 3 -> unexpectedNonzero("hours", decodeIntElement(descriptor, 3)) + 4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4)) + 5 -> unexpectedNonzero("seconds", decodeLongElement(descriptor, 5)) + 6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6)) + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> error("Unexpected index: $index") + } + } + DatePeriod(years, months, days) + } + + override fun serialize(encoder: Encoder, value: DatePeriod) { + encoder.encodeStructure(descriptor) { + with(value) { + if (years != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 0, years) + if (months != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 1, months) + if (days != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 2, days) + } + } + } + +} + +object DatePeriodISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING) + + // TODO: consider whether should fail when parsing "P1YT0H0M0.0S" + override fun deserialize(decoder: Decoder): DatePeriod = + when (val period = DateTimePeriod.parse(decoder.decodeString())) { + is DatePeriod -> period + else -> throw IllegalArgumentException("$period is not a date-based period") + } + + override fun serialize(encoder: Encoder, value: DatePeriod) { + encoder.encodeString(value.toString()) + } + +} \ No newline at end of file diff --git a/core/common/src/serializers/DateTimeUnitSerializers.kt b/core/common/src/serializers/DateTimeUnitSerializers.kt new file mode 100644 index 000000000..8ff05ce69 --- /dev/null +++ b/core/common/src/serializers/DateTimeUnitSerializers.kt @@ -0,0 +1,186 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.DateTimeUnit +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.AbstractPolymorphicSerializer +import kotlin.reflect.KClass + +object TimeBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TimeBased") { + element("nanoseconds") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.TimeBased) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.nanoseconds) + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.TimeBased { + var seen = false + var nanoseconds = 0L + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + nanoseconds = decodeLongElement(descriptor, 0) + seen = true + } else { + loop@while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + nanoseconds = decodeLongElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("nanoseconds") + return DateTimeUnit.TimeBased(nanoseconds) + } +} + +object DayBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") { + element("days") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.DayBased) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.days) + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.DayBased { + var seen = false + var days = 0 + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + days = decodeIntElement(descriptor, 0) + seen = true + } else { + loop@while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + days = decodeIntElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("days") + return DateTimeUnit.DateBased.DayBased(days) + } +} + +object MonthBasedSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") { + element("months") + } + + override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.MonthBased) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.months) + } + } + + @ExperimentalSerializationApi + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.MonthBased { + var seen = false + var months = 0 + decoder.decodeStructure(descriptor) { + if (decodeSequentially()) { + months = decodeIntElement(descriptor, 0) + seen = true + } else { + loop@while (true) { + when (val elementIndex: Int = decodeElementIndex(descriptor)) { + 0 -> { + months = decodeIntElement(descriptor, 0) + seen = true + } + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> throw UnknownFieldException(elementIndex) + } + } + } + } + if (!seen) throw MissingFieldException("months") + return DateTimeUnit.DateBased.MonthBased(months) + } +} + +@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") +object DateBasedSerializer: AbstractPolymorphicSerializer() { + + private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased", + DateTimeUnit.DateBased::class, + arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class), + arrayOf(DayBasedSerializer, MonthBasedSerializer)) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): + DeserializationStrategy? = + impl.findPolymorphicSerializerOrNull(decoder, klassName) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit.DateBased): + SerializationStrategy? = + impl.findPolymorphicSerializerOrNull(encoder, value) + + @InternalSerializationApi + override val baseClass: KClass + get() = DateTimeUnit.DateBased::class + + @InternalSerializationApi + override val descriptor: SerialDescriptor + get() = impl.descriptor + +} + +@Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") +object DateTimeUnitSerializer: AbstractPolymorphicSerializer() { + + private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit", + DateTimeUnit::class, + arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class, DateTimeUnit.TimeBased::class), + arrayOf(DayBasedSerializer, MonthBasedSerializer, TimeBasedSerializer)) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy? = + impl.findPolymorphicSerializerOrNull(decoder, klassName) + + @InternalSerializationApi + override fun findPolymorphicSerializerOrNull(encoder: Encoder, value: DateTimeUnit): SerializationStrategy? = + impl.findPolymorphicSerializerOrNull(encoder, value) + + @InternalSerializationApi + override val baseClass: KClass + get() = DateTimeUnit::class + + @InternalSerializationApi + override val descriptor: SerialDescriptor + get() = impl.descriptor + +} diff --git a/core/common/src/serializers/DayOfWeekSerializers.kt b/core/common/src/serializers/DayOfWeekSerializers.kt new file mode 100644 index 000000000..b1c13183b --- /dev/null +++ b/core/common/src/serializers/DayOfWeekSerializers.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.DayOfWeek +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* + +@Suppress("INVISIBLE_MEMBER") +object DayOfWeekSerializer: KSerializer { + private val impl = EnumSerializer("Month", DayOfWeek.values()) + + override val descriptor: SerialDescriptor + get() = impl.descriptor + + override fun deserialize(decoder: Decoder): DayOfWeek = impl.deserialize(decoder) + + override fun serialize(encoder: Encoder, value: DayOfWeek) = impl.serialize(encoder, value) +} diff --git a/core/common/src/serializers/InstantSerializers.kt b/core/common/src/serializers/InstantSerializers.kt new file mode 100644 index 000000000..244ea1d65 --- /dev/null +++ b/core/common/src/serializers/InstantSerializers.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.Instant +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object InstantISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Instant = + Instant.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeString(value.toString()) + } + +} + +object InstantComponentSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("Instant") { + element("epochSeconds") + element("nanosecondsOfSecond", isOptional = true) + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): Instant = + decoder.decodeStructure(descriptor) { + var epochSeconds: Long? = null + var nanosecondsOfSecond = 0 + loop@while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochSeconds = decodeLongElement(descriptor, 0) + 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> error("Unexpected index: $index") + } + } + if (epochSeconds == null) throw MissingFieldException("epochSeconds") + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.epochSeconds) + if (value.nanosecondsOfSecond != 0) { + encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) + } + } + } + +} diff --git a/core/common/src/serializers/LocalDateSerializers.kt b/core/common/src/serializers/LocalDateSerializers.kt new file mode 100644 index 000000000..4887a6b67 --- /dev/null +++ b/core/common/src/serializers/LocalDateSerializers.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object LocalDateISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeString(value.toString()) + } + +} + +object LocalDateComponentSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("LocalDate") { + element("year") + element("month") + element("day") + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): LocalDate = + decoder.decodeStructure(descriptor) { + var year: Int? = null + var month: Short? = null + var day: Short? = null + loop@while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> year = decodeIntElement(descriptor, 0) + 1 -> month = decodeShortElement(descriptor, 1) + 2 -> day = decodeShortElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> error("Unexpected index: $index") + } + } + if (year == null) throw MissingFieldException("year") + if (month == null) throw MissingFieldException("month") + if (day == null) throw MissingFieldException("day") + LocalDate(year, month.toInt(), day.toInt()) + } + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.year) + encodeShortElement(descriptor, 1, value.monthNumber.toShort()) + encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) + } + } + +} + +expect object LocalDateLongSerializer: KSerializer diff --git a/core/common/src/serializers/LocalDateTimeSerializers.kt b/core/common/src/serializers/LocalDateTimeSerializers.kt new file mode 100644 index 000000000..cee26a4b4 --- /dev/null +++ b/core/common/src/serializers/LocalDateTimeSerializers.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object LocalDateTimeISO8601Serializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): LocalDateTime = + LocalDateTime.parse(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeString(value.toString()) + } + +} + +object LocalDateTimeComponentSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("LocalDateTime") { + element("year") + element("month") + element("day") + element("hour") + element("minute") + element("second", isOptional = true) + element("nanosecond", isOptional = true) + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var year: Int? = null + var month: Short? = null + var day: Short? = null + var hour: Short? = null + var minute: Short? = null + var second: Short = 0 + var nanosecond = 0 + loop@while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> year = decodeIntElement(descriptor, 0) + 1 -> month = decodeShortElement(descriptor, 1) + 2 -> day = decodeShortElement(descriptor, 2) + 3 -> hour = decodeShortElement(descriptor, 3) + 4 -> minute = decodeShortElement(descriptor, 4) + 5 -> second = decodeShortElement(descriptor, 5) + 6 -> nanosecond = decodeIntElement(descriptor, 6) + CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 + else -> error("Unexpected index: $index") + } + } + if (year == null) throw MissingFieldException("year") + if (month == null) throw MissingFieldException("month") + if (day == null) throw MissingFieldException("day") + if (hour == null) throw MissingFieldException("hour") + if (minute == null) throw MissingFieldException("minute") + LocalDateTime(year, month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt(), nanosecond) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.year) + encodeShortElement(descriptor, 1, value.monthNumber.toShort()) + encodeShortElement(descriptor, 2, value.dayOfMonth.toShort()) + encodeShortElement(descriptor, 3, value.hour.toShort()) + encodeShortElement(descriptor, 4, value.minute.toShort()) + if (value.second != 0 || value.nanosecond != 0) { + encodeShortElement(descriptor, 5, value.second.toShort()) + if (value.nanosecond != 0) { + encodeIntElement(descriptor, 6, value.nanosecond) + } + } + } + } + +} + +expect object LocalDateTimeCompactSerializer: KSerializer + diff --git a/core/common/src/serializers/MonthSerializers.kt b/core/common/src/serializers/MonthSerializers.kt new file mode 100644 index 000000000..d0aaae85a --- /dev/null +++ b/core/common/src/serializers/MonthSerializers.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.Month +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* + +@Suppress("INVISIBLE_MEMBER") +object MonthSerializer: KSerializer { + private val impl = EnumSerializer("Month", Month.values()) + + override val descriptor: SerialDescriptor + get() = impl.descriptor + + override fun deserialize(decoder: Decoder): Month = impl.deserialize(decoder) + + override fun serialize(encoder: Encoder, value: Month) = impl.serialize(encoder, value) +} diff --git a/core/common/src/serializers/TimeZoneSerializers.kt b/core/common/src/serializers/TimeZoneSerializers.kt new file mode 100644 index 000000000..327864651 --- /dev/null +++ b/core/common/src/serializers/TimeZoneSerializers.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.TimeZone +import kotlinx.datetime.ZoneOffset +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +object TimeZoneSerializer: KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): TimeZone = TimeZone.of(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: TimeZone) { + encoder.encodeString(value.id) + } + +} + +object ZoneOffsetSerializer: KSerializer { + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): ZoneOffset { + val zone = TimeZone.of(decoder.decodeString()) + if (zone is ZoneOffset) { + return zone + } else { + throw SerializationException("Timezone identifier '$zone' does not correspond to a fixed-offset timezone") + } + } + + override fun serialize(encoder: Encoder, value: ZoneOffset) { + encoder.encodeString(value.id) + } + +} diff --git a/core/js/src/Instant.kt b/core/js/src/Instant.kt index 7d7988026..28ffdad2a 100644 --- a/core/js/src/Instant.kt +++ b/core/js/src/Instant.kt @@ -14,6 +14,7 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration import kotlinx.datetime.internal.JSJoda.Clock as jtClock import kotlinx.datetime.internal.JSJoda.ChronoUnit +import kotlinx.datetime.serializers.InstantISO8601Serializer import kotlinx.serialization.Serializable import kotlin.math.truncate diff --git a/core/js/src/LocalDate.kt b/core/js/src/LocalDate.kt index 5173990d1..0bb823669 100644 --- a/core/js/src/LocalDate.kt +++ b/core/js/src/LocalDate.kt @@ -6,25 +6,12 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ChronoUnit +import kotlinx.datetime.serializers.LocalDateISO8601Serializer import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate(jtLocalDate.ofEpochDay(decoder.decodeLong())) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.value.toEpochDay().toLong()) - } - -} - @Serializable(with = LocalDateISO8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { diff --git a/core/js/src/LocalDateTime.kt b/core/js/src/LocalDateTime.kt index 9eecbf90b..f45833430 100644 --- a/core/js/src/LocalDateTime.kt +++ b/core/js/src/LocalDateTime.kt @@ -4,47 +4,9 @@ */ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.LocalDateTime as jtLocalDateTime -import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate -import kotlinx.datetime.internal.JSJoda.LocalTime as jtLocalTime - -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.value.toEpochDay().toLong()) - encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay().toLong()) - } - } - -} @Serializable(with = LocalDateTimeISO8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { diff --git a/core/js/src/TimeZone.kt b/core/js/src/TimeZone.kt index 24ceae0b4..a37daebca 100644 --- a/core/js/src/TimeZone.kt +++ b/core/js/src/TimeZone.kt @@ -5,6 +5,8 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ZoneId +import kotlinx.datetime.serializers.TimeZoneSerializer +import kotlinx.datetime.serializers.ZoneOffsetSerializer import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.ZoneOffset as jtZoneOffset diff --git a/core/js/src/serializers/LocalDateSerializers.kt b/core/js/src/serializers/LocalDateSerializers.kt new file mode 100644 index 000000000..a3abd461c --- /dev/null +++ b/core/js/src/serializers/LocalDateSerializers.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate(kotlinx.datetime.internal.JSJoda.LocalDate.ofEpochDay(decoder.decodeLong())) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.value.toEpochDay().toLong()) + } + +} diff --git a/core/js/src/serializers/LocalDateTimeSerializers.kt b/core/js/src/serializers/LocalDateTimeSerializers.kt new file mode 100644 index 000000000..5ca63c935 --- /dev/null +++ b/core/js/src/serializers/LocalDateTimeSerializers.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.internal.JSJoda.LocalDate +import kotlinx.datetime.internal.JSJoda.LocalTime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("LocalDateTime") { + element("epochDay") + element("nanoOfDay") + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay: Long? = null + var nanoOfDay: Long? = null + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") + LocalDateTime(kotlinx.datetime.internal.JSJoda.LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay))) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.value.toEpochDay().toLong()) + encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay().toLong()) + } + } + +} diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 87e9b86c7..226252421 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -6,6 +6,7 @@ package kotlinx.datetime +import kotlinx.datetime.serializers.InstantISO8601Serializer import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index f2f2fcbf2..56e78467c 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -5,28 +5,13 @@ @file:JvmName("LocalDateJvmKt") package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit import java.time.LocalDate as jtLocalDate -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate(jtLocalDate.ofEpochDay(decoder.decodeLong())) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.value.toEpochDay()) - } - -} - @Serializable(with = LocalDateISO8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { diff --git a/core/jvm/src/LocalDateTime.kt b/core/jvm/src/LocalDateTime.kt index 77732375b..9305e2c8e 100644 --- a/core/jvm/src/LocalDateTime.kt +++ b/core/jvm/src/LocalDateTime.kt @@ -5,54 +5,15 @@ @file:JvmName("LocalDateTimeJvmKt") package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.LocalDateTime as jtLocalDateTime -import java.time.LocalDate as jtLocalDate -import java.time.LocalTime as jtLocalTime -import kotlinx.serialization.internal.* public actual typealias Month = java.time.Month public actual typealias DayOfWeek = java.time.DayOfWeek -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - LocalDateTime(jtLocalDateTime.of(jtLocalDate.ofEpochDay(epochDay), jtLocalTime.ofNanoOfDay(nanoOfDay))) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.value.toEpochDay()) - encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay()) - } - } - -} - @Serializable(with = LocalDateTimeISO8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { diff --git a/core/jvm/src/TimeZoneJvm.kt b/core/jvm/src/TimeZoneJvm.kt index 18af40dd8..21eeb0183 100644 --- a/core/jvm/src/TimeZoneJvm.kt +++ b/core/jvm/src/TimeZoneJvm.kt @@ -8,6 +8,8 @@ package kotlinx.datetime +import kotlinx.datetime.serializers.TimeZoneSerializer +import kotlinx.datetime.serializers.ZoneOffsetSerializer import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.ZoneId diff --git a/core/jvm/src/serializers/LocalDateSerializers.kt b/core/jvm/src/serializers/LocalDateSerializers.kt new file mode 100644 index 000000000..50827fb25 --- /dev/null +++ b/core/jvm/src/serializers/LocalDateSerializers.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate(java.time.LocalDate.ofEpochDay(decoder.decodeLong())) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.value.toEpochDay()) + } + +} diff --git a/core/jvm/src/serializers/LocalDateTimeSerializers.kt b/core/jvm/src/serializers/LocalDateTimeSerializers.kt new file mode 100644 index 000000000..c8bebf578 --- /dev/null +++ b/core/jvm/src/serializers/LocalDateTimeSerializers.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import java.time.LocalDate +import java.time.LocalTime + +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("LocalDateTime") { + element("epochDay") + element("nanoOfDay") + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay: Long? = null + var nanoOfDay: Long? = null + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") + LocalDateTime(java.time.LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay))) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.value.toEpochDay()) + encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay()) + } + } + +} diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index 6f184ad49..75e387be1 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -8,7 +8,8 @@ package kotlinx.datetime -import kotlinx.serialization.* +import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.serialization.Serializable import kotlin.math.* import kotlin.time.* diff --git a/core/native/src/LocalDate.kt b/core/native/src/LocalDate.kt index 69de96149..e4ec7f716 100644 --- a/core/native/src/LocalDate.kt +++ b/core/native/src/LocalDate.kt @@ -8,25 +8,10 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.serialization.Serializable import kotlin.math.* -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate.ofEpochDay(decoder.decodeLong().clampToInt()) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.toEpochDay().toLong()) - } - -} - // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5 // org.threeten.bp.format.DateTimeFormatter#ISO_LOCAL_DATE internal val localDateParser: Parser diff --git a/core/native/src/LocalDateTime.kt b/core/native/src/LocalDateTime.kt index 3f0d4d927..64987f36a 100644 --- a/core/native/src/LocalDateTime.kt +++ b/core/native/src/LocalDateTime.kt @@ -8,46 +8,8 @@ package kotlinx.datetime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - val date = LocalDate.ofEpochDay(epochDay.clampToInt()) - val time = LocalTime.ofNanoOfDay(nanoOfDay) - LocalDateTime(date, time) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.toEpochDay().toLong()) - encodeLongElement(descriptor, 1, value.time.toNanoOfDay()) - } - } - -} +import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.serialization.Serializable // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5 // org.threeten.bp.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME diff --git a/core/native/src/TimeZone.kt b/core/native/src/TimeZone.kt index 3e0206726..3c81f7118 100644 --- a/core/native/src/TimeZone.kt +++ b/core/native/src/TimeZone.kt @@ -8,6 +8,8 @@ package kotlinx.datetime +import kotlinx.datetime.serializers.TimeZoneSerializer +import kotlinx.datetime.serializers.ZoneOffsetSerializer import kotlin.math.abs import kotlin.native.concurrent.* import kotlinx.serialization.Serializable diff --git a/core/native/src/serializers/LocalDateSerializers.kt b/core/native/src/serializers/LocalDateSerializers.kt new file mode 100644 index 000000000..33b6db791 --- /dev/null +++ b/core/native/src/serializers/LocalDateSerializers.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.clampToInt +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateLongSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): LocalDate = + LocalDate.ofEpochDay(decoder.decodeLong().clampToInt()) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeLong(value.toEpochDay().toLong()) + } + +} diff --git a/core/native/src/serializers/LocalDateTimeSerializers.kt b/core/native/src/serializers/LocalDateTimeSerializers.kt new file mode 100644 index 000000000..bd3ae5ac4 --- /dev/null +++ b/core/native/src/serializers/LocalDateTimeSerializers.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serializers + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.datetime.clampToInt +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +actual object LocalDateTimeCompactSerializer: KSerializer { + + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("LocalDateTime") { + element("epochDay") + element("nanoOfDay") + } + + @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` + override fun deserialize(decoder: Decoder): LocalDateTime = + decoder.decodeStructure(descriptor) { + var epochDay: Long? = null + var nanoOfDay: Long? = null + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> epochDay = decodeLongElement(descriptor, 0) + 1 -> nanoOfDay = decodeLongElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + if (epochDay == null) throw MissingFieldException("epochDay") + if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") + val date = LocalDate.ofEpochDay(epochDay.clampToInt()) + val time = LocalTime.ofNanoOfDay(nanoOfDay) + LocalDateTime(date, time) + } + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.date.toEpochDay().toLong()) + encodeLongElement(descriptor, 1, value.time.toNanoOfDay()) + } + } + +} diff --git a/serialization/common/test/DateTimePeriodSerializationTest.kt b/serialization/common/test/DateTimePeriodSerializationTest.kt index aaeba44fc..3e4610a75 100644 --- a/serialization/common/test/DateTimePeriodSerializationTest.kt +++ b/serialization/common/test/DateTimePeriodSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/DateTimeUnitSerializationTest.kt b/serialization/common/test/DateTimeUnitSerializationTest.kt index 26171a75b..526549b2b 100644 --- a/serialization/common/test/DateTimeUnitSerializationTest.kt +++ b/serialization/common/test/DateTimeUnitSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.json.* import kotlin.random.* import kotlin.test.* diff --git a/serialization/common/test/DayOfWeekSerializationTest.kt b/serialization/common/test/DayOfWeekSerializationTest.kt index 922285b0d..0ae8abc9a 100644 --- a/serialization/common/test/DayOfWeekSerializationTest.kt +++ b/serialization/common/test/DayOfWeekSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/InstantSerializationTest.kt index 1aa93c473..507ab2ece 100644 --- a/serialization/common/test/InstantSerializationTest.kt +++ b/serialization/common/test/InstantSerializationTest.kt @@ -5,6 +5,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/LocalDateSerializationTest.kt b/serialization/common/test/LocalDateSerializationTest.kt index 1aa8b3daa..981d20356 100644 --- a/serialization/common/test/LocalDateSerializationTest.kt +++ b/serialization/common/test/LocalDateSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/LocalDateTimeSerializationTest.kt b/serialization/common/test/LocalDateTimeSerializationTest.kt index 9327bc705..c6e772926 100644 --- a/serialization/common/test/LocalDateTimeSerializationTest.kt +++ b/serialization/common/test/LocalDateTimeSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/MonthSerializationTest.kt b/serialization/common/test/MonthSerializationTest.kt index 7a37fa9e7..cdf1d6cb5 100644 --- a/serialization/common/test/MonthSerializationTest.kt +++ b/serialization/common/test/MonthSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.json.* import kotlin.test.* diff --git a/serialization/common/test/TimeZoneSerializationTest.kt b/serialization/common/test/TimeZoneSerializationTest.kt index 7dd555e67..029ad9077 100644 --- a/serialization/common/test/TimeZoneSerializationTest.kt +++ b/serialization/common/test/TimeZoneSerializationTest.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* +import kotlinx.datetime.serializers.* import kotlinx.serialization.json.* import kotlin.test.* From 756af5ce645534bdb19516ddbcde97ddfe29e787 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 11 Mar 2021 16:32:08 +0300 Subject: [PATCH 15/25] Cleanup --- core/common/src/LocalDate.kt | 1 - core/js/src/LocalDate.kt | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index b2b47e16a..36ab51fd7 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -7,7 +7,6 @@ package kotlinx.datetime import kotlinx.datetime.serializers.LocalDateISO8601Serializer import kotlinx.serialization.Serializable -import kotlin.time.* @Serializable(with = LocalDateISO8601Serializer::class) public expect class LocalDate : Comparable { diff --git a/core/js/src/LocalDate.kt b/core/js/src/LocalDate.kt index 0bb823669..de3250037 100644 --- a/core/js/src/LocalDate.kt +++ b/core/js/src/LocalDate.kt @@ -7,9 +7,7 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ChronoUnit import kotlinx.datetime.serializers.LocalDateISO8601Serializer -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate @Serializable(with = LocalDateISO8601Serializer::class) From 2c35bb4337ff6f4ee85bd18cc63c59cf23155fae Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 12 Mar 2021 15:24:30 +0300 Subject: [PATCH 16/25] Fixes --- build.gradle.kts | 4 ++-- core/build.gradle.kts | 3 ++- .../src/serializers/DateTimePeriodSerializers.kt | 12 ++++++------ core/common/src/serializers/InstantSerializers.kt | 2 +- core/common/src/serializers/LocalDateSerializers.kt | 2 +- .../src/serializers/LocalDateTimeSerializers.kt | 2 +- core/common/src/serializers/TimeZoneSerializers.kt | 6 ++---- core/js/src/serializers/LocalDateTimeSerializers.kt | 2 +- core/jvm/src/serializers/LocalDateTimeSerializers.kt | 2 +- core/native/src/LocalDate.kt | 5 ++++- core/native/src/serializers/LocalDateSerializers.kt | 12 +++++++++--- .../src/serializers/LocalDateTimeSerializers.kt | 6 ++---- gradle.properties | 2 ++ 13 files changed, 34 insertions(+), 26 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 29cdd2676..441973702 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,13 @@ buildscript { mavenCentral() } dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30") } } plugins { id("kotlinx.team.infra") version "0.3.0-dev-64" - kotlin("plugin.serialization") version "1.4.10" + kotlin("plugin.serialization") version "1.4.30" } infra { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 7f4a73010..606d9e28c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -19,6 +19,7 @@ base { //val JDK_6: String by project val JDK_8: String by project +val serializationVersion: String by project kotlin { infra { @@ -149,7 +150,7 @@ kotlin { commonMain { dependencies { api("org.jetbrains.kotlin:kotlin-stdlib-common") - compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") } } diff --git a/core/common/src/serializers/DateTimePeriodSerializers.kt b/core/common/src/serializers/DateTimePeriodSerializers.kt index aa63c0bbc..177e52cf6 100644 --- a/core/common/src/serializers/DateTimePeriodSerializers.kt +++ b/core/common/src/serializers/DateTimePeriodSerializers.kt @@ -44,7 +44,7 @@ object DateTimePeriodComponentSerializer: KSerializer { 5 -> seconds = decodeIntElement(descriptor, 5) 6 -> nanoseconds = decodeLongElement(descriptor, 6) CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } DateTimePeriod(years, months, days, hours, minutes, seconds, nanoseconds) @@ -84,7 +84,7 @@ object DatePeriodComponentSerializer: KSerializer { private fun unexpectedNonzero(fieldName: String, value: Long) { if (value != 0L) { - throw SerializationException("expected field '$fieldName' to be zero, but was $value") + throw SerializationException("DatePeriod should have non-date components be zero, but got $value in '$fieldName'") } } @@ -97,7 +97,7 @@ object DatePeriodComponentSerializer: KSerializer { element("days", isOptional = true) element("hours", isOptional = true) element("minutes", isOptional = true) - element("seconds", isOptional = true) + element("seconds", isOptional = true) element("nanoseconds", isOptional = true) } @@ -113,10 +113,10 @@ object DatePeriodComponentSerializer: KSerializer { 2 -> days = decodeIntElement(descriptor, 2) 3 -> unexpectedNonzero("hours", decodeIntElement(descriptor, 3)) 4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4)) - 5 -> unexpectedNonzero("seconds", decodeLongElement(descriptor, 5)) + 5 -> unexpectedNonzero("seconds", decodeIntElement(descriptor, 5)) 6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6)) CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } DatePeriod(years, months, days) @@ -143,7 +143,7 @@ object DatePeriodISO8601Serializer: KSerializer { override fun deserialize(decoder: Decoder): DatePeriod = when (val period = DateTimePeriod.parse(decoder.decodeString())) { is DatePeriod -> period - else -> throw IllegalArgumentException("$period is not a date-based period") + else -> throw SerializationException("$period is not a date-based period") } override fun serialize(encoder: Encoder, value: DatePeriod) { diff --git a/core/common/src/serializers/InstantSerializers.kt b/core/common/src/serializers/InstantSerializers.kt index 244ea1d65..00161a57f 100644 --- a/core/common/src/serializers/InstantSerializers.kt +++ b/core/common/src/serializers/InstantSerializers.kt @@ -42,7 +42,7 @@ object InstantComponentSerializer: KSerializer { 0 -> epochSeconds = decodeLongElement(descriptor, 0) 1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (epochSeconds == null) throw MissingFieldException("epochSeconds") diff --git a/core/common/src/serializers/LocalDateSerializers.kt b/core/common/src/serializers/LocalDateSerializers.kt index 4887a6b67..511a71a4b 100644 --- a/core/common/src/serializers/LocalDateSerializers.kt +++ b/core/common/src/serializers/LocalDateSerializers.kt @@ -45,7 +45,7 @@ object LocalDateComponentSerializer: KSerializer { 1 -> month = decodeShortElement(descriptor, 1) 2 -> day = decodeShortElement(descriptor, 2) CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (year == null) throw MissingFieldException("year") diff --git a/core/common/src/serializers/LocalDateTimeSerializers.kt b/core/common/src/serializers/LocalDateTimeSerializers.kt index cee26a4b4..ae3c6498a 100644 --- a/core/common/src/serializers/LocalDateTimeSerializers.kt +++ b/core/common/src/serializers/LocalDateTimeSerializers.kt @@ -57,7 +57,7 @@ object LocalDateTimeComponentSerializer: KSerializer { 5 -> second = decodeShortElement(descriptor, 5) 6 -> nanosecond = decodeIntElement(descriptor, 6) CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (year == null) throw MissingFieldException("year") diff --git a/core/common/src/serializers/TimeZoneSerializers.kt b/core/common/src/serializers/TimeZoneSerializers.kt index 327864651..f34cd1fbc 100644 --- a/core/common/src/serializers/TimeZoneSerializers.kt +++ b/core/common/src/serializers/TimeZoneSerializers.kt @@ -13,8 +13,7 @@ import kotlinx.serialization.encoding.* object TimeZoneSerializer: KSerializer { - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("TimeZone", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): TimeZone = TimeZone.of(decoder.decodeString()) @@ -26,8 +25,7 @@ object TimeZoneSerializer: KSerializer { object ZoneOffsetSerializer: KSerializer { - override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING) + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): ZoneOffset { val zone = TimeZone.of(decoder.decodeString()) diff --git a/core/js/src/serializers/LocalDateTimeSerializers.kt b/core/js/src/serializers/LocalDateTimeSerializers.kt index 5ca63c935..e0d303a6f 100644 --- a/core/js/src/serializers/LocalDateTimeSerializers.kt +++ b/core/js/src/serializers/LocalDateTimeSerializers.kt @@ -30,7 +30,7 @@ actual object LocalDateTimeCompactSerializer: KSerializer { 0 -> epochDay = decodeLongElement(descriptor, 0) 1 -> nanoOfDay = decodeLongElement(descriptor, 1) CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (epochDay == null) throw MissingFieldException("epochDay") diff --git a/core/jvm/src/serializers/LocalDateTimeSerializers.kt b/core/jvm/src/serializers/LocalDateTimeSerializers.kt index c8bebf578..c49afbcdf 100644 --- a/core/jvm/src/serializers/LocalDateTimeSerializers.kt +++ b/core/jvm/src/serializers/LocalDateTimeSerializers.kt @@ -30,7 +30,7 @@ actual object LocalDateTimeCompactSerializer: KSerializer { 0 -> epochDay = decodeLongElement(descriptor, 0) 1 -> nanoOfDay = decodeLongElement(descriptor, 1) CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (epochDay == null) throw MissingFieldException("epochDay") diff --git a/core/native/src/LocalDate.kt b/core/native/src/LocalDate.kt index e4ec7f716..298eceb08 100644 --- a/core/native/src/LocalDate.kt +++ b/core/native/src/LocalDate.kt @@ -66,7 +66,7 @@ public actual class LocalDate actual constructor(actual val year: Int, actual va internal fun ofEpochDay(epochDay: Int): LocalDate { // LocalDate(-999999, 1, 1).toEpochDay(), LocalDate(999999, 12, 31).toEpochDay() // Unidiomatic code due to https://github.com/Kotlin/kotlinx-datetime/issues/5 - require(epochDay >= -365961662 && epochDay <= 364522971) { + require(epochDay >= MIN_EPOCH_DAY && epochDay <= MAX_EPOCH_DAY) { "Invalid date: boundaries of LocalDate exceeded" } var zeroDay = epochDay + DAYS_0000_TO_1970 @@ -100,6 +100,9 @@ public actual class LocalDate actual constructor(actual val year: Int, actual va internal actual val MIN = LocalDate(YEAR_MIN, 1, 1) internal actual val MAX = LocalDate(YEAR_MAX, 12, 31) + + internal const val MIN_EPOCH_DAY = -365961662 + internal const val MAX_EPOCH_DAY = 364522971 } // org.threeten.bp.LocalDate#toEpochDay diff --git a/core/native/src/serializers/LocalDateSerializers.kt b/core/native/src/serializers/LocalDateSerializers.kt index 33b6db791..c6b5c32bb 100644 --- a/core/native/src/serializers/LocalDateSerializers.kt +++ b/core/native/src/serializers/LocalDateSerializers.kt @@ -6,7 +6,6 @@ package kotlinx.datetime.serializers import kotlinx.datetime.LocalDate -import kotlinx.datetime.clampToInt import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* @@ -16,11 +15,18 @@ actual object LocalDateLongSerializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate.ofEpochDay(decoder.decodeLong().clampToInt()) + override fun deserialize(decoder: Decoder): LocalDate = dateFromLongEpochDays(decoder.decodeLong()) override fun serialize(encoder: Encoder, value: LocalDate) { encoder.encodeLong(value.toEpochDay().toLong()) } + internal inline fun dateFromLongEpochDays(epochDays: Long): LocalDate = + if (epochDays <= LocalDate.MAX_EPOCH_DAY.toLong() && epochDays >= LocalDate.MIN_EPOCH_DAY.toLong()) { + LocalDate.ofEpochDay(epochDays.toInt()) + } else { + throw SerializationException( + "The passed value exceeds the platform-specific boundaries of days representable in LocalDate") + } + } diff --git a/core/native/src/serializers/LocalDateTimeSerializers.kt b/core/native/src/serializers/LocalDateTimeSerializers.kt index bd3ae5ac4..1781be1c0 100644 --- a/core/native/src/serializers/LocalDateTimeSerializers.kt +++ b/core/native/src/serializers/LocalDateTimeSerializers.kt @@ -5,10 +5,8 @@ package kotlinx.datetime.serializers -import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime -import kotlinx.datetime.clampToInt import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* @@ -31,12 +29,12 @@ actual object LocalDateTimeCompactSerializer: KSerializer { 0 -> epochDay = decodeLongElement(descriptor, 0) 1 -> nanoOfDay = decodeLongElement(descriptor, 1) CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") + else -> throw SerializationException("Unexpected index: $index") } } if (epochDay == null) throw MissingFieldException("epochDay") if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - val date = LocalDate.ofEpochDay(epochDay.clampToInt()) + val date = LocalDateLongSerializer.dateFromLongEpochDays(epochDay) val time = LocalTime.ofNanoOfDay(nanoOfDay) LocalDateTime(date, time) } diff --git a/gradle.properties b/gradle.properties index 7dd4c0c46..c8230af1d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,8 @@ group=org.jetbrains.kotlinx version=0.2.0 versionSuffix=SNAPSHOT +serializationVersion=1.1.0 + kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.mpp.enableCompatibilityMetadataVariant=true kotlin.js.compiler=both From af231f77bea343c17fdbb66b9d85485bf90ff203 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 12 Mar 2021 15:34:06 +0300 Subject: [PATCH 17/25] Remove the serializers that were decided against --- .../src/serializers/LocalDateSerializers.kt | 4 +- .../serializers/LocalDateTimeSerializers.kt | 5 +- .../src/serializers/LocalDateSerializers.kt | 25 ---------- .../serializers/LocalDateTimeSerializers.kt | 48 ------------------ .../src/serializers/LocalDateSerializers.kt | 25 ---------- .../serializers/LocalDateTimeSerializers.kt | 48 ------------------ .../src/serializers/LocalDateSerializers.kt | 32 ------------ .../serializers/LocalDateTimeSerializers.kt | 49 ------------------- 8 files changed, 2 insertions(+), 234 deletions(-) delete mode 100644 core/js/src/serializers/LocalDateSerializers.kt delete mode 100644 core/js/src/serializers/LocalDateTimeSerializers.kt delete mode 100644 core/jvm/src/serializers/LocalDateSerializers.kt delete mode 100644 core/jvm/src/serializers/LocalDateTimeSerializers.kt delete mode 100644 core/native/src/serializers/LocalDateSerializers.kt delete mode 100644 core/native/src/serializers/LocalDateTimeSerializers.kt diff --git a/core/common/src/serializers/LocalDateSerializers.kt b/core/common/src/serializers/LocalDateSerializers.kt index 511a71a4b..13d86b4a8 100644 --- a/core/common/src/serializers/LocalDateSerializers.kt +++ b/core/common/src/serializers/LocalDateSerializers.kt @@ -62,6 +62,4 @@ object LocalDateComponentSerializer: KSerializer { } } -} - -expect object LocalDateLongSerializer: KSerializer +} \ No newline at end of file diff --git a/core/common/src/serializers/LocalDateTimeSerializers.kt b/core/common/src/serializers/LocalDateTimeSerializers.kt index ae3c6498a..19e0aadd9 100644 --- a/core/common/src/serializers/LocalDateTimeSerializers.kt +++ b/core/common/src/serializers/LocalDateTimeSerializers.kt @@ -84,7 +84,4 @@ object LocalDateTimeComponentSerializer: KSerializer { } } -} - -expect object LocalDateTimeCompactSerializer: KSerializer - +} \ No newline at end of file diff --git a/core/js/src/serializers/LocalDateSerializers.kt b/core/js/src/serializers/LocalDateSerializers.kt deleted file mode 100644 index a3abd461c..000000000 --- a/core/js/src/serializers/LocalDateSerializers.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDate -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate(kotlinx.datetime.internal.JSJoda.LocalDate.ofEpochDay(decoder.decodeLong())) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.value.toEpochDay().toLong()) - } - -} diff --git a/core/js/src/serializers/LocalDateTimeSerializers.kt b/core/js/src/serializers/LocalDateTimeSerializers.kt deleted file mode 100644 index e0d303a6f..000000000 --- a/core/js/src/serializers/LocalDateTimeSerializers.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.internal.JSJoda.LocalDate -import kotlinx.datetime.internal.JSJoda.LocalTime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> throw SerializationException("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - LocalDateTime(kotlinx.datetime.internal.JSJoda.LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay))) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.value.toEpochDay().toLong()) - encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay().toLong()) - } - } - -} diff --git a/core/jvm/src/serializers/LocalDateSerializers.kt b/core/jvm/src/serializers/LocalDateSerializers.kt deleted file mode 100644 index 50827fb25..000000000 --- a/core/jvm/src/serializers/LocalDateSerializers.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDate -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = - LocalDate(java.time.LocalDate.ofEpochDay(decoder.decodeLong())) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.value.toEpochDay()) - } - -} diff --git a/core/jvm/src/serializers/LocalDateTimeSerializers.kt b/core/jvm/src/serializers/LocalDateTimeSerializers.kt deleted file mode 100644 index c49afbcdf..000000000 --- a/core/jvm/src/serializers/LocalDateTimeSerializers.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import java.time.LocalDate -import java.time.LocalTime - -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> throw SerializationException("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - LocalDateTime(java.time.LocalDateTime.of(LocalDate.ofEpochDay(epochDay), LocalTime.ofNanoOfDay(nanoOfDay))) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.value.toEpochDay()) - encodeLongElement(descriptor, 1, value.value.toLocalTime().toNanoOfDay()) - } - } - -} diff --git a/core/native/src/serializers/LocalDateSerializers.kt b/core/native/src/serializers/LocalDateSerializers.kt deleted file mode 100644 index c6b5c32bb..000000000 --- a/core/native/src/serializers/LocalDateSerializers.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDate -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateLongSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG) - - override fun deserialize(decoder: Decoder): LocalDate = dateFromLongEpochDays(decoder.decodeLong()) - - override fun serialize(encoder: Encoder, value: LocalDate) { - encoder.encodeLong(value.toEpochDay().toLong()) - } - - internal inline fun dateFromLongEpochDays(epochDays: Long): LocalDate = - if (epochDays <= LocalDate.MAX_EPOCH_DAY.toLong() && epochDays >= LocalDate.MIN_EPOCH_DAY.toLong()) { - LocalDate.ofEpochDay(epochDays.toInt()) - } else { - throw SerializationException( - "The passed value exceeds the platform-specific boundaries of days representable in LocalDate") - } - -} diff --git a/core/native/src/serializers/LocalDateTimeSerializers.kt b/core/native/src/serializers/LocalDateTimeSerializers.kt deleted file mode 100644 index 1781be1c0..000000000 --- a/core/native/src/serializers/LocalDateTimeSerializers.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.serializers - -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.LocalTime -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -actual object LocalDateTimeCompactSerializer: KSerializer { - - override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("LocalDateTime") { - element("epochDay") - element("nanoOfDay") - } - - @Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException` - override fun deserialize(decoder: Decoder): LocalDateTime = - decoder.decodeStructure(descriptor) { - var epochDay: Long? = null - var nanoOfDay: Long? = null - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> epochDay = decodeLongElement(descriptor, 0) - 1 -> nanoOfDay = decodeLongElement(descriptor, 1) - CompositeDecoder.DECODE_DONE -> break - else -> throw SerializationException("Unexpected index: $index") - } - } - if (epochDay == null) throw MissingFieldException("epochDay") - if (nanoOfDay == null) throw MissingFieldException("nanoOfDay") - val date = LocalDateLongSerializer.dateFromLongEpochDays(epochDay) - val time = LocalTime.ofNanoOfDay(nanoOfDay) - LocalDateTime(date, time) - } - - override fun serialize(encoder: Encoder, value: LocalDateTime) { - encoder.encodeStructure(descriptor) { - encodeLongElement(descriptor, 0, value.date.toEpochDay().toLong()) - encodeLongElement(descriptor, 1, value.time.toNanoOfDay()) - } - } - -} From d2cb1eb027f47830095152088bc2fdbc17c38731 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 24 Mar 2021 14:04:01 +0300 Subject: [PATCH 18/25] Disable publishing for the serialization subproject --- serialization/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/serialization/build.gradle.kts b/serialization/build.gradle.kts index 0a5f9eb4b..740264731 100644 --- a/serialization/build.gradle.kts +++ b/serialization/build.gradle.kts @@ -3,7 +3,6 @@ import java.util.Locale plugins { id("kotlin-multiplatform") kotlin("plugin.serialization") - `maven-publish` } val JDK_8: String by project From c5c8ad2ef7e7ca28e7b6f9d28ea52b81e6f9d6c3 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 24 Mar 2021 14:04:47 +0300 Subject: [PATCH 19/25] Rename DateTimeUnit serializers --- core/common/src/DateTimeUnit.kt | 8 ++++---- .../serializers/DateTimeUnitSerializers.kt | 12 +++++------ .../test/DateTimeUnitSerializationTest.kt | 20 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/common/src/DateTimeUnit.kt b/core/common/src/DateTimeUnit.kt index b66d8d934..06a851ed8 100644 --- a/core/common/src/DateTimeUnit.kt +++ b/core/common/src/DateTimeUnit.kt @@ -14,7 +14,7 @@ sealed class DateTimeUnit { abstract operator fun times(scalar: Int): DateTimeUnit - @Serializable(with = TimeBasedSerializer::class) + @Serializable(with = TimeBasedDateTimeUnitSerializer::class) class TimeBased(val nanoseconds: Long) : DateTimeUnit() { private val unitName: String private val unitScale: Long @@ -64,10 +64,10 @@ sealed class DateTimeUnit { override fun toString(): String = formatToString(unitScale, unitName) } - @Serializable(with = DateBasedSerializer::class) + @Serializable(with = DateBasedDateTimeUnitSerializer::class) sealed class DateBased : DateTimeUnit() { // TODO: investigate how to move subclasses up to DateTimeUnit scope - @Serializable(with = DayBasedSerializer::class) + @Serializable(with = DayBasedDateTimeUnitSerializer::class) class DayBased(val days: Int) : DateBased() { init { require(days > 0) { "Unit duration must be positive, but was $days days." } @@ -85,7 +85,7 @@ sealed class DateTimeUnit { else formatToString(days, "DAY") } - @Serializable(with = MonthBasedSerializer::class) + @Serializable(with = MonthBasedDateTimeUnitSerializer::class) class MonthBased(val months: Int) : DateBased() { init { require(months > 0) { "Unit duration must be positive, but was $months months." } diff --git a/core/common/src/serializers/DateTimeUnitSerializers.kt b/core/common/src/serializers/DateTimeUnitSerializers.kt index 8ff05ce69..449402691 100644 --- a/core/common/src/serializers/DateTimeUnitSerializers.kt +++ b/core/common/src/serializers/DateTimeUnitSerializers.kt @@ -14,7 +14,7 @@ import kotlinx.serialization.encoding.* import kotlinx.serialization.internal.AbstractPolymorphicSerializer import kotlin.reflect.KClass -object TimeBasedSerializer: KSerializer { +object TimeBasedDateTimeUnitSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TimeBased") { element("nanoseconds") @@ -53,7 +53,7 @@ object TimeBasedSerializer: KSerializer { } } -object DayBasedSerializer: KSerializer { +object DayBasedDateTimeUnitSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") { element("days") @@ -92,7 +92,7 @@ object DayBasedSerializer: KSerializer { } } -object MonthBasedSerializer: KSerializer { +object MonthBasedDateTimeUnitSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") { element("months") @@ -132,12 +132,12 @@ object MonthBasedSerializer: KSerializer { } @Suppress("EXPERIMENTAL_API_USAGE_ERROR", "INVISIBLE_MEMBER") -object DateBasedSerializer: AbstractPolymorphicSerializer() { +object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer() { private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased", DateTimeUnit.DateBased::class, arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class), - arrayOf(DayBasedSerializer, MonthBasedSerializer)) + arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer)) @InternalSerializationApi override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): @@ -165,7 +165,7 @@ object DateTimeUnitSerializer: AbstractPolymorphicSerializer() { private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit", DateTimeUnit::class, arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class, DateTimeUnit.TimeBased::class), - arrayOf(DayBasedSerializer, MonthBasedSerializer, TimeBasedSerializer)) + arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer, TimeBasedDateTimeUnitSerializer)) @InternalSerializationApi override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy? = diff --git a/serialization/common/test/DateTimeUnitSerializationTest.kt b/serialization/common/test/DateTimeUnitSerializationTest.kt index 526549b2b..b9ccc7c8f 100644 --- a/serialization/common/test/DateTimeUnitSerializationTest.kt +++ b/serialization/common/test/DateTimeUnitSerializationTest.kt @@ -18,8 +18,8 @@ class DateTimeUnitSerializationTest { val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) val unit = DateTimeUnit.TimeBased(nanoseconds) val json = "{\"nanoseconds\":${nanoseconds.toString()}}" // https://youtrack.jetbrains.com/issue/KT-39891 - assertEquals(json, Json.encodeToString(TimeBasedSerializer, unit)) - assertEquals(unit, Json.decodeFromString(TimeBasedSerializer, json)) + assertEquals(json, Json.encodeToString(TimeBasedDateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(TimeBasedDateTimeUnitSerializer, json)) } } @@ -29,8 +29,8 @@ class DateTimeUnitSerializationTest { val days = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.DayBased(days) val json = "{\"days\":$days}" - assertEquals(json, Json.encodeToString(DayBasedSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DayBasedSerializer, json)) + assertEquals(json, Json.encodeToString(DayBasedDateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DayBasedDateTimeUnitSerializer, json)) } } @@ -40,8 +40,8 @@ class DateTimeUnitSerializationTest { val months = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.MonthBased(months) val json = "{\"months\":$months}" - assertEquals(json, Json.encodeToString(MonthBasedSerializer, unit)) - assertEquals(unit, Json.decodeFromString(MonthBasedSerializer, json)) + assertEquals(json, Json.encodeToString(MonthBasedDateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(MonthBasedDateTimeUnitSerializer, json)) } } @@ -51,15 +51,15 @@ class DateTimeUnitSerializationTest { val days = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.DayBased(days) val json = "{\"type\":\"DayBased\",\"days\":$days}" - assertEquals(json, Json.encodeToString(DateBasedSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateBasedSerializer, json)) + assertEquals(json, Json.encodeToString(DateBasedDateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateBasedDateTimeUnitSerializer, json)) } repeat(100) { val months = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.MonthBased(months) val json = "{\"type\":\"MonthBased\",\"months\":$months}" - assertEquals(json, Json.encodeToString(DateBasedSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateBasedSerializer, json)) + assertEquals(json, Json.encodeToString(DateBasedDateTimeUnitSerializer, unit)) + assertEquals(unit, Json.decodeFromString(DateBasedDateTimeUnitSerializer, json)) } } From 9ff7e03f8303fee2dd5194f412f754578630d5e1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 24 Mar 2021 15:29:09 +0300 Subject: [PATCH 20/25] Test which serializers are the default ones --- .../test/DateTimePeriodSerializationTest.kt | 77 +++++++++++------ .../test/DateTimeUnitSerializationTest.kt | 83 +++++++++++++------ .../common/test/DayOfWeekSerializationTest.kt | 2 +- .../common/test/InstantSerializationTest.kt | 36 +++++--- .../common/test/LocalDateSerializationTest.kt | 42 ++++++---- .../test/LocalDateTimeSerializationTest.kt | 42 +++++++--- .../common/test/MonthSerializationTest.kt | 2 +- .../common/test/TimeZoneSerializationTest.kt | 38 ++++++--- 8 files changed, 221 insertions(+), 101 deletions(-) diff --git a/serialization/common/test/DateTimePeriodSerializationTest.kt b/serialization/common/test/DateTimePeriodSerializationTest.kt index 3e4610a75..c3b21442f 100644 --- a/serialization/common/test/DateTimePeriodSerializationTest.kt +++ b/serialization/common/test/DateTimePeriodSerializationTest.kt @@ -13,8 +13,10 @@ import kotlin.test.* class DateTimePeriodSerializationTest { - @Test - fun datePeriodISO8601Serialization() { + private fun datePeriodISO8601Serialization( + datePeriodSerializer: KSerializer, + dateTimePeriodSerializer: KSerializer + ) { for ((period, json) in listOf( Pair(DatePeriod(1, 2, 3), "\"P1Y2M3D\""), Pair(DatePeriod(years = 1), "\"P1Y\""), @@ -24,21 +26,23 @@ class DateTimePeriodSerializationTest { Pair(DatePeriod(months = 10, days = 5), "\"P10M5D\""), Pair(DatePeriod(years = 1, days = 40), "\"P1Y40D\""), )) { - assertEquals(json, Json.encodeToString(DatePeriodISO8601Serializer, period)) - assertEquals(period, Json.decodeFromString(DatePeriodISO8601Serializer, json)) - assertEquals(json, Json.encodeToString(DateTimePeriodISO8601Serializer, period)) - assertEquals(period, Json.decodeFromString(DateTimePeriodISO8601Serializer, json) as DatePeriod) + assertEquals(json, Json.encodeToString(datePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(datePeriodSerializer, json)) + assertEquals(json, Json.encodeToString(dateTimePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(dateTimePeriodSerializer, json) as DatePeriod) } // time-based keys should not be considered unknown here assertFailsWith { - Json { ignoreUnknownKeys = true }.decodeFromString(DatePeriodISO8601Serializer, "\"P3DT1H\"") + Json { ignoreUnknownKeys = true }.decodeFromString(datePeriodSerializer, "\"P3DT1H\"") } // presence of time-based keys should not be a problem if the values are 0 - Json.decodeFromString(DatePeriodISO8601Serializer, "\"P3DT0H\"") + Json.decodeFromString(datePeriodSerializer, "\"P3DT0H\"") } - @Test - fun datePeriodComponentSerialization() { + private fun datePeriodComponentSerialization( + datePeriodSerializer: KSerializer, + dateTimePeriodSerializer: KSerializer + ) { for ((period, json) in listOf( Pair(DatePeriod(1, 2, 3), "{\"years\":1,\"months\":2,\"days\":3}"), Pair(DatePeriod(years = 1), "{\"years\":1}"), @@ -48,21 +52,20 @@ class DateTimePeriodSerializationTest { Pair(DatePeriod(months = 10, days = 5), "{\"months\":10,\"days\":5}"), Pair(DatePeriod(years = 1, days = 40), "{\"years\":1,\"days\":40}"), )) { - assertEquals(json, Json.encodeToString(DatePeriodComponentSerializer, period)) - assertEquals(period, Json.decodeFromString(DatePeriodComponentSerializer, json)) - assertEquals(json, Json.encodeToString(DateTimePeriodComponentSerializer, period)) - assertEquals(period, Json.decodeFromString(DateTimePeriodComponentSerializer, json) as DatePeriod) + assertEquals(json, Json.encodeToString(datePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(datePeriodSerializer, json)) + assertEquals(json, Json.encodeToString(dateTimePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(dateTimePeriodSerializer, json) as DatePeriod) } // time-based keys should not be considered unknown here assertFailsWith { - Json { ignoreUnknownKeys = true }.decodeFromString(DatePeriodComponentSerializer, "{\"hours\":3}") + Json { ignoreUnknownKeys = true }.decodeFromString(datePeriodSerializer, "{\"hours\":3}") } // presence of time-based keys should not be a problem if the values are 0 - Json.decodeFromString(DatePeriodComponentSerializer, "{\"hours\":0}") + Json.decodeFromString(datePeriodSerializer, "{\"hours\":0}") } - @Test - fun dateTimePeriodISO8601Serialization() { + private fun dateTimePeriodISO8601Serialization(dateTimePeriodSerializer: KSerializer) { for ((period, json) in listOf( Pair(DateTimePeriod(), "\"P0D\""), Pair(DateTimePeriod(hours = 1), "\"PT1H\""), @@ -72,13 +75,12 @@ class DateTimePeriodSerializationTest { Pair(DateTimePeriod(years = -1, months = -2, days = -3, hours = -4, minutes = -5, seconds = 0, nanoseconds = 500_000_000), "\"-P1Y2M3DT4H4M59.500000000S\""), )) { - assertEquals(json, Json.encodeToString(DateTimePeriodISO8601Serializer, period)) - assertEquals(period, Json.decodeFromString(DateTimePeriodISO8601Serializer, json)) + assertEquals(json, Json.encodeToString(dateTimePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(dateTimePeriodSerializer, json)) } } - @Test - fun dateTimePeriodComponentSerialization() { + private fun dateTimePeriodComponentSerialization(dateTimePeriodSerializer: KSerializer) { for ((period, json) in listOf( Pair(DateTimePeriod(), "{}"), Pair(DateTimePeriod(hours = 1), "{\"hours\":1}"), @@ -88,9 +90,36 @@ class DateTimePeriodSerializationTest { Pair(DateTimePeriod(years = -1, months = -2, days = -3, hours = -4, minutes = -5, seconds = 0, nanoseconds = 500_000_000), "{\"years\":-1,\"months\":-2,\"days\":-3,\"hours\":-4,\"minutes\":-4,\"seconds\":-59,\"nanoseconds\":-500000000}"), )) { - assertEquals(json, Json.encodeToString(DateTimePeriodComponentSerializer, period)) - assertEquals(period, Json.decodeFromString(DateTimePeriodComponentSerializer, json)) + assertEquals(json, Json.encodeToString(dateTimePeriodSerializer, period)) + assertEquals(period, Json.decodeFromString(dateTimePeriodSerializer, json)) } } + @Test + fun testDatePeriodISO8601Serialization() { + datePeriodISO8601Serialization(DatePeriodISO8601Serializer, DateTimePeriodISO8601Serializer) + } + + @Test + fun testDatePeriodComponentSerialization() { + datePeriodComponentSerialization(DatePeriodComponentSerializer, DateTimePeriodComponentSerializer) + } + + @Test + fun testDateTimePeriodISO8601Serialization() { + dateTimePeriodISO8601Serialization(DateTimePeriodISO8601Serializer) + } + + @Test + fun testDateTimePeriodComponentSerialization() { + dateTimePeriodComponentSerialization(DateTimePeriodComponentSerializer) + } + + @Test + fun testDefaultSerializers() { + // Check that they behave the same as the ISO-8601 serializers + dateTimePeriodISO8601Serialization(Json.serializersModule.serializer()) + datePeriodISO8601Serialization(Json.serializersModule.serializer(), Json.serializersModule.serializer()) + } + } diff --git a/serialization/common/test/DateTimeUnitSerializationTest.kt b/serialization/common/test/DateTimeUnitSerializationTest.kt index b9ccc7c8f..f63fcab87 100644 --- a/serialization/common/test/DateTimeUnitSerializationTest.kt +++ b/serialization/common/test/DateTimeUnitSerializationTest.kt @@ -7,85 +7,116 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* import kotlinx.datetime.serializers.* +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.* +import kotlinx.serialization.serializer import kotlin.random.* import kotlin.test.* class DateTimeUnitSerializationTest { - @Test - fun timeBasedSerialization() { + private fun timeBasedSerialization(serializer: KSerializer) { repeat(100) { val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) val unit = DateTimeUnit.TimeBased(nanoseconds) val json = "{\"nanoseconds\":${nanoseconds.toString()}}" // https://youtrack.jetbrains.com/issue/KT-39891 - assertEquals(json, Json.encodeToString(TimeBasedDateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(TimeBasedDateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } } - @Test - fun dayBasedSerialization() { + private fun dayBasedSerialization(serializer: KSerializer) { repeat(100) { val days = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.DayBased(days) val json = "{\"days\":$days}" - assertEquals(json, Json.encodeToString(DayBasedDateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DayBasedDateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } } - @Test - fun monthBasedSerialization() { + private fun monthBasedSerialization(serializer: KSerializer) { repeat(100) { val months = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.MonthBased(months) val json = "{\"months\":$months}" - assertEquals(json, Json.encodeToString(MonthBasedDateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(MonthBasedDateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } } - @Test - fun dateBasedSerialization() { + private fun dateBasedSerialization(serializer: KSerializer) { repeat(100) { val days = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.DayBased(days) val json = "{\"type\":\"DayBased\",\"days\":$days}" - assertEquals(json, Json.encodeToString(DateBasedDateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateBasedDateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } repeat(100) { val months = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.MonthBased(months) val json = "{\"type\":\"MonthBased\",\"months\":$months}" - assertEquals(json, Json.encodeToString(DateBasedDateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateBasedDateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } } - @Test - fun serialization() { + private fun serialization(serializer: KSerializer) { repeat(100) { val nanoseconds = Random.nextLong(1, Long.MAX_VALUE) val unit = DateTimeUnit.TimeBased(nanoseconds) val json = "{\"type\":\"TimeBased\",\"nanoseconds\":${nanoseconds.toString()}}" // https://youtrack.jetbrains.com/issue/KT-39891 - assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } repeat(100) { val days = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.DayBased(days) val json = "{\"type\":\"DayBased\",\"days\":$days}" - assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } repeat(100) { val months = Random.nextInt(1, Int.MAX_VALUE) val unit = DateTimeUnit.DateBased.MonthBased(months) val json = "{\"type\":\"MonthBased\",\"months\":$months}" - assertEquals(json, Json.encodeToString(DateTimeUnitSerializer, unit)) - assertEquals(unit, Json.decodeFromString(DateTimeUnitSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, unit)) + assertEquals(unit, Json.decodeFromString(serializer, json)) } } + @Test + fun testTimeBasedUnitSerialization() { + timeBasedSerialization(TimeBasedDateTimeUnitSerializer) + } + + @Test + fun testDayBasedSerialization() { + dayBasedSerialization(DayBasedDateTimeUnitSerializer) + } + + @Test + fun testMonthBasedSerialization() { + monthBasedSerialization(MonthBasedDateTimeUnitSerializer) + } + + @Test + fun testDateBasedSerialization() { + dateBasedSerialization(DateBasedDateTimeUnitSerializer) + } + + @Test + fun testSerialization() { + serialization(DateTimeUnitSerializer) + } + + @Test + fun testDefaultSerializers() { + monthBasedSerialization(Json.serializersModule.serializer()) + timeBasedSerialization(Json.serializersModule.serializer()) + dayBasedSerialization(Json.serializersModule.serializer()) + dateBasedSerialization(Json.serializersModule.serializer()) + serialization(Json.serializersModule.serializer()) + } + } \ No newline at end of file diff --git a/serialization/common/test/DayOfWeekSerializationTest.kt b/serialization/common/test/DayOfWeekSerializationTest.kt index 0ae8abc9a..18470976b 100644 --- a/serialization/common/test/DayOfWeekSerializationTest.kt +++ b/serialization/common/test/DayOfWeekSerializationTest.kt @@ -12,7 +12,7 @@ import kotlin.test.* class DayOfWeekSerializationTest { @Test - fun serialization() { + fun testSerialization() { for (dayOfWeek in DayOfWeek.values()) { val json = "\"${dayOfWeek.name}\"" assertEquals(json, Json.encodeToString(DayOfWeekSerializer, dayOfWeek)) diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/InstantSerializationTest.kt index 507ab2ece..624ef8e5b 100644 --- a/serialization/common/test/InstantSerializationTest.kt +++ b/serialization/common/test/InstantSerializationTest.kt @@ -12,8 +12,7 @@ import kotlin.test.* class InstantSerializationTest { - @Test - fun iso8601Serialization() { + private fun iso8601Serialization(serializer: KSerializer) { for ((instant, json) in listOf( Pair(Instant.fromEpochSeconds(1607505416, 124000), "\"2020-12-09T09:16:56.000124Z\""), @@ -24,13 +23,12 @@ class InstantSerializationTest { Pair(Instant.fromEpochSeconds(987654321, 0), "\"2001-04-19T04:25:21Z\""), )) { - assertEquals(json, Json.encodeToString(InstantISO8601Serializer, instant)) - assertEquals(instant, Json.decodeFromString(InstantISO8601Serializer, json)) + assertEquals(json, Json.encodeToString(serializer, instant)) + assertEquals(instant, Json.decodeFromString(serializer, json)) } } - @Test - fun componentSerialization() { + private fun componentSerialization(serializer: KSerializer) { for ((instant, json) in listOf( Pair(Instant.fromEpochSeconds(1607505416, 124000), "{\"epochSeconds\":1607505416,\"nanosecondsOfSecond\":124000}"), @@ -41,15 +39,31 @@ class InstantSerializationTest { Pair(Instant.fromEpochSeconds(987654321, 0), "{\"epochSeconds\":987654321}"), )) { - assertEquals(json, Json.encodeToString(InstantComponentSerializer, instant)) - assertEquals(instant, Json.decodeFromString(InstantComponentSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, instant)) + assertEquals(instant, Json.decodeFromString(serializer, json)) } // check that having a `"nanosecondsOfSecond": 0` field doesn't break deserialization assertEquals(Instant.fromEpochSeconds(987654321, 0), - Json.decodeFromString(InstantComponentSerializer, + Json.decodeFromString(serializer, "{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}")) // "epochSeconds" should always be present - assertFailsWith { Json.decodeFromString(InstantComponentSerializer, "{}") } - assertFailsWith { Json.decodeFromString(InstantComponentSerializer, "{\"nanosecondsOfSecond\":3}") } + assertFailsWith { Json.decodeFromString(serializer, "{}") } + assertFailsWith { Json.decodeFromString(serializer, "{\"nanosecondsOfSecond\":3}") } + } + + @Test + fun testIso8601Serialization() { + iso8601Serialization(InstantISO8601Serializer) + } + + @Test + fun testComponentSerialization() { + componentSerialization(InstantComponentSerializer) + } + + @Test + fun testDefaultSerializers() { + // should be the same as the ISO-8601 + iso8601Serialization(Json.serializersModule.serializer()) } } \ No newline at end of file diff --git a/serialization/common/test/LocalDateSerializationTest.kt b/serialization/common/test/LocalDateSerializationTest.kt index 981d20356..504ed2c16 100644 --- a/serialization/common/test/LocalDateSerializationTest.kt +++ b/serialization/common/test/LocalDateSerializationTest.kt @@ -12,48 +12,62 @@ import kotlinx.serialization.json.* import kotlin.test.* class LocalDateSerializationTest { - @Test - fun iso8601Serialization() { + private fun iso8601Serialization(serializer: KSerializer) { for ((localDate, json) in listOf( Pair(LocalDate(2020, 12, 9), "\"2020-12-09\""), Pair(LocalDate(-2020, 1, 1), "\"-2020-01-01\""), Pair(LocalDate(2019, 10, 1), "\"2019-10-01\""), )) { - assertEquals(json, Json.encodeToString(LocalDateISO8601Serializer, localDate)) - assertEquals(localDate, Json.decodeFromString(LocalDateISO8601Serializer, json)) + assertEquals(json, Json.encodeToString(serializer, localDate)) + assertEquals(localDate, Json.decodeFromString(serializer, json)) } } - @Test - fun componentSerialization() { + private fun componentSerialization(serializer: KSerializer) { for ((localDate, json) in listOf( Pair(LocalDate(2020, 12, 9), "{\"year\":2020,\"month\":12,\"day\":9}"), Pair(LocalDate(-2020, 1, 1), "{\"year\":-2020,\"month\":1,\"day\":1}"), Pair(LocalDate(2019, 10, 1), "{\"year\":2019,\"month\":10,\"day\":1}"), )) { - assertEquals(json, Json.encodeToString(LocalDateComponentSerializer, localDate)) - assertEquals(localDate, Json.decodeFromString(LocalDateComponentSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, localDate)) + assertEquals(localDate, Json.decodeFromString(serializer, json)) } // all components must be present assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{}") + Json.decodeFromString(serializer, "{}") } assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":3,\"month\":12}") + Json.decodeFromString(serializer, "{\"year\":3,\"month\":12}") } assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":3,\"day\":12}") + Json.decodeFromString(serializer, "{\"year\":3,\"day\":12}") } assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{\"month\":3,\"day\":12}") + Json.decodeFromString(serializer, "{\"month\":3,\"day\":12}") } // invalid values must fail to construct assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":1000000000000,\"month\":3,\"day\":12}") + Json.decodeFromString(serializer, "{\"year\":1000000000000,\"month\":3,\"day\":12}") } assertFailsWith { - Json.decodeFromString(LocalDateComponentSerializer, "{\"year\":2020,\"month\":30,\"day\":12}") + Json.decodeFromString(serializer, "{\"year\":2020,\"month\":30,\"day\":12}") } } + @Test + fun testIso8601Serialization() { + iso8601Serialization(LocalDateISO8601Serializer) + } + + @Test + fun testComponentSerialization() { + componentSerialization(LocalDateComponentSerializer) + } + + @Test + fun testDefaultSerializers() { + // should be the same as the ISO-8601 + iso8601Serialization(Json.serializersModule.serializer()) + } + } diff --git a/serialization/common/test/LocalDateTimeSerializationTest.kt b/serialization/common/test/LocalDateTimeSerializationTest.kt index c6e772926..c8245da81 100644 --- a/serialization/common/test/LocalDateTimeSerializationTest.kt +++ b/serialization/common/test/LocalDateTimeSerializationTest.kt @@ -7,12 +7,13 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* import kotlinx.datetime.serializers.* +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.* +import kotlinx.serialization.serializer import kotlin.test.* class LocalDateTimeSerializationTest { - @Test - fun iso8601Serialization() { + private fun iso8601Serialization(serializer: KSerializer) { for ((localDateTime, json) in listOf( Pair(LocalDateTime(2008, 7, 5, 2, 1), "\"2008-07-05T02:01\""), Pair(LocalDateTime(2007, 12, 31, 23, 59, 1), "\"2007-12-31T23:59:01\""), @@ -20,13 +21,12 @@ class LocalDateTimeSerializationTest { Pair(LocalDateTime(-1, 1, 2, 23, 59, 59, 999990000), "\"-0001-01-02T23:59:59.999990\""), Pair(LocalDateTime(-2008, 1, 2, 23, 59, 59, 999999990), "\"-2008-01-02T23:59:59.999999990\""), )) { - assertEquals(json, Json.encodeToString(LocalDateTimeISO8601Serializer, localDateTime)) - assertEquals(localDateTime, Json.decodeFromString(LocalDateTimeISO8601Serializer, json)) + assertEquals(json, Json.encodeToString(serializer, localDateTime)) + assertEquals(localDateTime, Json.decodeFromString(serializer, json)) } } - @Test - fun componentSerialization() { + private fun componentSerialization(serializer: KSerializer) { for ((localDateTime, json) in listOf( Pair(LocalDateTime(2008, 7, 5, 2, 1), "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1}"), Pair(LocalDateTime(2007, 12, 31, 23, 59, 1), @@ -40,30 +40,46 @@ class LocalDateTimeSerializationTest { Pair(LocalDateTime(-2008, 1, 2, 23, 59, 0, 1), "{\"year\":-2008,\"month\":1,\"day\":2,\"hour\":23,\"minute\":59,\"second\":0,\"nanosecond\":1}"), )) { - assertEquals(json, Json.encodeToString(LocalDateTimeComponentSerializer, localDateTime)) - assertEquals(localDateTime, Json.decodeFromString(LocalDateTimeComponentSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, localDateTime)) + assertEquals(localDateTime, Json.decodeFromString(serializer, json)) } // adding omitted values shouldn't break deserialization assertEquals(LocalDateTime(2008, 7, 5, 2, 1), - Json.decodeFromString(LocalDateTimeComponentSerializer, + Json.decodeFromString(serializer, "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"second\":0}" )) assertEquals(LocalDateTime(2008, 7, 5, 2, 1), - Json.decodeFromString(LocalDateTimeComponentSerializer, + Json.decodeFromString(serializer, "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"nanosecond\":0}" )) assertEquals(LocalDateTime(2008, 7, 5, 2, 1), - Json.decodeFromString(LocalDateTimeComponentSerializer, + Json.decodeFromString(serializer, "{\"year\":2008,\"month\":7,\"day\":5,\"hour\":2,\"minute\":1,\"second\":0,\"nanosecond\":0}" )) // invalid values must fail to construct assertFailsWith { - Json.decodeFromString(LocalDateTimeComponentSerializer, + Json.decodeFromString(serializer, "{\"year\":1000000000000,\"month\":3,\"day\":12,\"hour\":10,\"minute\":2}") } assertFailsWith { - Json.decodeFromString(LocalDateTimeComponentSerializer, + Json.decodeFromString(serializer, "{\"year\":2020,\"month\":30,\"day\":12,\"hour\":10,\"minute\":2}") } } + + @Test + fun testIso8601Serialization() { + iso8601Serialization(LocalDateTimeISO8601Serializer) + } + + @Test + fun testComponentSerialization() { + componentSerialization(LocalDateTimeComponentSerializer) + } + + @Test + fun testDefaultSerializers() { + // should be the same as the ISO-8601 + iso8601Serialization(Json.serializersModule.serializer()) + } } diff --git a/serialization/common/test/MonthSerializationTest.kt b/serialization/common/test/MonthSerializationTest.kt index cdf1d6cb5..8d8879b72 100644 --- a/serialization/common/test/MonthSerializationTest.kt +++ b/serialization/common/test/MonthSerializationTest.kt @@ -12,7 +12,7 @@ import kotlin.test.* class MonthSerializationTest { @Test - fun serialization() { + fun testSerialization() { for (month in Month.values()) { val json = "\"${month.name}\"" assertEquals(json, Json.encodeToString(MonthSerializer, month)) diff --git a/serialization/common/test/TimeZoneSerializationTest.kt b/serialization/common/test/TimeZoneSerializationTest.kt index 029ad9077..9cb0f5058 100644 --- a/serialization/common/test/TimeZoneSerializationTest.kt +++ b/serialization/common/test/TimeZoneSerializationTest.kt @@ -7,30 +7,46 @@ package kotlinx.datetime.serialization.test import kotlinx.datetime.* import kotlinx.datetime.serializers.* +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.* +import kotlinx.serialization.serializer import kotlin.test.* class TimeZoneSerializationTest { - @Test - fun zoneOffsetSerialization() { + private fun zoneOffsetSerialization(serializer: KSerializer) { val offset2h = TimeZone.of("+02:00") as ZoneOffset - assertEquals("\"+02:00\"", Json.encodeToString(ZoneOffsetSerializer, offset2h)) - assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+02:00\"")) - assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+02\"")) - assertEquals(offset2h, Json.decodeFromString(ZoneOffsetSerializer, "\"+2\"")) + assertEquals("\"+02:00\"", Json.encodeToString(serializer, offset2h)) + assertEquals(offset2h, Json.decodeFromString(serializer, "\"+02:00\"")) + assertEquals(offset2h, Json.decodeFromString(serializer, "\"+02\"")) + assertEquals(offset2h, Json.decodeFromString(serializer, "\"+2\"")) assertFailsWith { - Json.decodeFromString(ZoneOffsetSerializer, "\"Europe/Berlin\"") + Json.decodeFromString(serializer, "\"Europe/Berlin\"") } } - @Test - fun serialization() { + private fun serialization(serializer: KSerializer) { for (zoneId in listOf("Europe/Berlin", "+02:00")) { val zone = TimeZone.of(zoneId) val json = "\"$zoneId\"" - assertEquals(json, Json.encodeToString(TimeZoneSerializer, zone)) - assertEquals(zone, Json.decodeFromString(TimeZoneSerializer, json)) + assertEquals(json, Json.encodeToString(serializer, zone)) + assertEquals(zone, Json.decodeFromString(serializer, json)) } } + + @Test + fun testZoneOffsetSerialization() { + zoneOffsetSerialization(ZoneOffsetSerializer) + } + + @Test + fun testSerialization() { + serialization(TimeZoneSerializer) + } + + @Test + fun testDefaultSerializers() { + zoneOffsetSerialization(Json.serializersModule.serializer()) + serialization(Json.serializersModule.serializer()) + } } \ No newline at end of file From b3ce0fecbd5d9d509ad22304a834829e1904614a Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 24 Mar 2021 15:50:45 +0300 Subject: [PATCH 21/25] Add a (failing) test for contextual serializers usage --- .../test/ContextualSerializationTest.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 serialization/common/test/ContextualSerializationTest.kt diff --git a/serialization/common/test/ContextualSerializationTest.kt b/serialization/common/test/ContextualSerializationTest.kt new file mode 100644 index 000000000..ac2e80e98 --- /dev/null +++ b/serialization/common/test/ContextualSerializationTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.serialization.test + +import kotlinx.datetime.* +import kotlinx.datetime.serializers.* +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContextualSerializationTest { + + @Serializable + data class Dummy( + @Contextual val instant: Instant, + @Contextual val date: LocalDate, + @Contextual val dateTime: LocalDateTime, + @Contextual val datePeriod: DatePeriod, + @Contextual val dateTimePeriod: DateTimePeriod, + ) + + private val module = SerializersModule { + contextual(InstantComponentSerializer) + contextual(LocalDateComponentSerializer) + contextual(LocalDateTimeComponentSerializer) + contextual(DatePeriodComponentSerializer) + contextual(DateTimePeriodComponentSerializer) + } + + private val format = Json { serializersModule = module } + + @Test + fun testContextualSerialization() { + val dummyValue = Dummy( + Instant.parse("2021-03-24T01:29:30.123456789Z"), + LocalDate.parse("2020-01-02"), + LocalDateTime.parse("2020-01-03T12:59:58.010203045"), + DatePeriod.parse("P20Y-2M-3D"), + DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"), + ) + val json = """{"instant":{"epochSeconds":1616549370,"nanosecondsOfSecond":123456789},""" + + """"date":{"year":2020,"month":1,"day":2},""" + + """"dateTime":{"year":2020,"month":1,"day":3,"hour":12,"minute":59,"second":58,"nanosecond":10203045},""" + + """"datePeriod":{"years":19,"months":10,"days":-3},""" + + """"dateTimePeriod":{"years":-49,"months":-11,"days":2,"hours":-3,"minutes":-4,"seconds":-5,"nanoseconds":-12300000}}""" + assertEquals("""{"years":-49,"months":-11,"days":2,"hours":-3,"minutes":-4,"seconds":-5,"nanoseconds":-12300000}""", + Json.encodeToString(DateTimePeriodComponentSerializer, DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"))) + assertEquals(dummyValue, format.decodeFromString(json)) + assertEquals(json, format.encodeToString(dummyValue)) // fails + } +} \ No newline at end of file From 31d8fa33a3e6de3b3cd7cb918312a6ea3d1e8b69 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 25 Mar 2021 17:46:53 +0300 Subject: [PATCH 22/25] Fix the failing test and add a test for default serializers --- serialization/build.gradle.kts | 3 +- ...erializationTest.kt => IntegrationTest.kt} | 34 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) rename serialization/common/test/{ContextualSerializationTest.kt => IntegrationTest.kt} (66%) diff --git a/serialization/build.gradle.kts b/serialization/build.gradle.kts index 740264731..432f00a8a 100644 --- a/serialization/build.gradle.kts +++ b/serialization/build.gradle.kts @@ -6,6 +6,7 @@ plugins { } val JDK_8: String by project +val serializationVersion: String by project kotlin { infra { @@ -75,7 +76,7 @@ kotlin { commonTest { dependencies { api("org.jetbrains.kotlin:kotlin-test-common") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") api("org.jetbrains.kotlin:kotlin-test-annotations-common") } } diff --git a/serialization/common/test/ContextualSerializationTest.kt b/serialization/common/test/IntegrationTest.kt similarity index 66% rename from serialization/common/test/ContextualSerializationTest.kt rename to serialization/common/test/IntegrationTest.kt index ac2e80e98..3f271c761 100644 --- a/serialization/common/test/ContextualSerializationTest.kt +++ b/serialization/common/test/IntegrationTest.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.modules.contextual import kotlin.test.Test import kotlin.test.assertEquals -class ContextualSerializationTest { +class IntegrationTest { @Serializable data class Dummy( @@ -52,9 +52,35 @@ class ContextualSerializationTest { """"dateTime":{"year":2020,"month":1,"day":3,"hour":12,"minute":59,"second":58,"nanosecond":10203045},""" + """"datePeriod":{"years":19,"months":10,"days":-3},""" + """"dateTimePeriod":{"years":-49,"months":-11,"days":2,"hours":-3,"minutes":-4,"seconds":-5,"nanoseconds":-12300000}}""" - assertEquals("""{"years":-49,"months":-11,"days":2,"hours":-3,"minutes":-4,"seconds":-5,"nanoseconds":-12300000}""", - Json.encodeToString(DateTimePeriodComponentSerializer, DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"))) assertEquals(dummyValue, format.decodeFromString(json)) - assertEquals(json, format.encodeToString(dummyValue)) // fails + assertEquals(json, format.encodeToString(dummyValue)) + } + + @Serializable + data class Dummy2( + val instant: Instant, + val date: LocalDate, + val dateTime: LocalDateTime, + val datePeriod: DatePeriod, + val dateTimePeriod: DateTimePeriod, + ) + + @Test + fun testDefaultSerialization() { + val dummyValue = Dummy2( + Instant.parse("2021-03-24T01:29:30.123456789Z"), + LocalDate.parse("2020-01-02"), + LocalDateTime.parse("2020-01-03T12:59:58.010203045"), + DatePeriod.parse("P20Y-2M-3D"), + DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"), + ) + val json = "{\"instant\":\"2021-03-24T01:29:30.123456789Z\"," + + "\"date\":\"2020-01-02\"," + + "\"dateTime\":\"2020-01-03T12:59:58.010203045\"," + + "\"datePeriod\":\"P19Y10M-3D\"," + + "\"dateTimePeriod\":\"P-49Y-11M2DT-3H-4M-5.012300000S\"" + + "}" + assertEquals(dummyValue, Json.decodeFromString(json)) + assertEquals(json, Json.encodeToString(dummyValue)) } } \ No newline at end of file From 8170830f509ade13ddc969d1cc92eb3de824eee4 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 26 Mar 2021 10:07:19 +0300 Subject: [PATCH 23/25] ISO -> Iso in serializer names --- core/common/src/DateTimePeriod.kt | 8 ++++---- core/common/src/Instant.kt | 4 ++-- core/common/src/LocalDate.kt | 4 ++-- core/common/src/LocalDateTime.kt | 4 ++-- .../src/serializers/DateTimePeriodSerializers.kt | 4 ++-- .../common/src/serializers/InstantSerializers.kt | 2 +- .../src/serializers/LocalDateSerializers.kt | 2 +- .../src/serializers/LocalDateTimeSerializers.kt | 2 +- core/js/src/Instant.kt | 4 ++-- core/js/src/LocalDate.kt | 4 ++-- core/js/src/LocalDateTime.kt | 4 ++-- core/jvm/src/Instant.kt | 4 ++-- core/jvm/src/LocalDate.kt | 4 ++-- core/jvm/src/LocalDateTime.kt | 4 ++-- core/native/src/Instant.kt | 4 ++-- core/native/src/LocalDate.kt | 4 ++-- core/native/src/LocalDateTime.kt | 4 ++-- .../test/DateTimePeriodSerializationTest.kt | 16 ++++++++-------- .../common/test/InstantSerializationTest.kt | 2 +- .../common/test/LocalDateSerializationTest.kt | 2 +- .../test/LocalDateTimeSerializationTest.kt | 2 +- 21 files changed, 44 insertions(+), 44 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index e905df950..6fe754d17 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -5,14 +5,14 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.DatePeriodISO8601Serializer -import kotlinx.datetime.serializers.DateTimePeriodISO8601Serializer +import kotlinx.datetime.serializers.DatePeriodIso8601Serializer +import kotlinx.datetime.serializers.DateTimePeriodIso8601Serializer import kotlin.math.* import kotlin.time.Duration import kotlin.time.ExperimentalTime import kotlinx.serialization.Serializable -@Serializable(with = DateTimePeriodISO8601Serializer::class) +@Serializable(with = DateTimePeriodIso8601Serializer::class) // TODO: could be error-prone without explicitly named params sealed class DateTimePeriod { internal abstract val totalMonths: Int @@ -237,7 +237,7 @@ sealed class DateTimePeriod { public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this) -@Serializable(with = DatePeriodISO8601Serializer::class) +@Serializable(with = DatePeriodIso8601Serializer::class) class DatePeriod internal constructor( internal override val totalMonths: Int, override val days: Int, diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 0bfee0e63..01bfaa0d8 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -5,12 +5,12 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.datetime.serializers.InstantIso8601Serializer import kotlinx.serialization.Serializable import kotlin.time.* @OptIn(ExperimentalTime::class) -@Serializable(with = InstantISO8601Serializer::class) +@Serializable(with = InstantIso8601Serializer::class) public expect class Instant : Comparable { /** diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 36ab51fd7..40c89ec7f 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -5,10 +5,10 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.datetime.serializers.LocalDateIso8601Serializer import kotlinx.serialization.Serializable -@Serializable(with = LocalDateISO8601Serializer::class) +@Serializable(with = LocalDateIso8601Serializer::class) public expect class LocalDate : Comparable { companion object { /** diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index cce535fbb..773710262 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -5,10 +5,10 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer import kotlinx.serialization.Serializable -@Serializable(with = LocalDateTimeISO8601Serializer::class) +@Serializable(with = LocalDateTimeIso8601Serializer::class) public expect class LocalDateTime : Comparable { companion object { diff --git a/core/common/src/serializers/DateTimePeriodSerializers.kt b/core/common/src/serializers/DateTimePeriodSerializers.kt index 177e52cf6..48efebf00 100644 --- a/core/common/src/serializers/DateTimePeriodSerializers.kt +++ b/core/common/src/serializers/DateTimePeriodSerializers.kt @@ -66,7 +66,7 @@ object DateTimePeriodComponentSerializer: KSerializer { } -object DateTimePeriodISO8601Serializer: KSerializer { +object DateTimePeriodIso8601Serializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING) @@ -134,7 +134,7 @@ object DatePeriodComponentSerializer: KSerializer { } -object DatePeriodISO8601Serializer: KSerializer { +object DatePeriodIso8601Serializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING) diff --git a/core/common/src/serializers/InstantSerializers.kt b/core/common/src/serializers/InstantSerializers.kt index 00161a57f..3464353ff 100644 --- a/core/common/src/serializers/InstantSerializers.kt +++ b/core/common/src/serializers/InstantSerializers.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -object InstantISO8601Serializer: KSerializer { +object InstantIso8601Serializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) diff --git a/core/common/src/serializers/LocalDateSerializers.kt b/core/common/src/serializers/LocalDateSerializers.kt index 13d86b4a8..39d6fedee 100644 --- a/core/common/src/serializers/LocalDateSerializers.kt +++ b/core/common/src/serializers/LocalDateSerializers.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -object LocalDateISO8601Serializer: KSerializer { +object LocalDateIso8601Serializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) diff --git a/core/common/src/serializers/LocalDateTimeSerializers.kt b/core/common/src/serializers/LocalDateTimeSerializers.kt index 19e0aadd9..aa2576ca5 100644 --- a/core/common/src/serializers/LocalDateTimeSerializers.kt +++ b/core/common/src/serializers/LocalDateTimeSerializers.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* -object LocalDateTimeISO8601Serializer: KSerializer { +object LocalDateTimeIso8601Serializer: KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) diff --git a/core/js/src/Instant.kt b/core/js/src/Instant.kt index 28ffdad2a..35f2e0f3c 100644 --- a/core/js/src/Instant.kt +++ b/core/js/src/Instant.kt @@ -14,11 +14,11 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration import kotlinx.datetime.internal.JSJoda.Clock as jtClock import kotlinx.datetime.internal.JSJoda.ChronoUnit -import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.datetime.serializers.InstantIso8601Serializer import kotlinx.serialization.Serializable import kotlin.math.truncate -@Serializable(with = InstantISO8601Serializer::class) +@Serializable(with = InstantIso8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/js/src/LocalDate.kt b/core/js/src/LocalDate.kt index de3250037..94b5ebe6f 100644 --- a/core/js/src/LocalDate.kt +++ b/core/js/src/LocalDate.kt @@ -6,11 +6,11 @@ package kotlinx.datetime import kotlinx.datetime.internal.JSJoda.ChronoUnit -import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.datetime.serializers.LocalDateIso8601Serializer import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.LocalDate as jtLocalDate -@Serializable(with = LocalDateISO8601Serializer::class) +@Serializable(with = LocalDateIso8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { public actual fun parse(isoString: String): LocalDate = try { diff --git a/core/js/src/LocalDateTime.kt b/core/js/src/LocalDateTime.kt index f45833430..3f99d21f5 100644 --- a/core/js/src/LocalDateTime.kt +++ b/core/js/src/LocalDateTime.kt @@ -4,11 +4,11 @@ */ package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer import kotlinx.serialization.Serializable import kotlinx.datetime.internal.JSJoda.LocalDateTime as jtLocalDateTime -@Serializable(with = LocalDateTimeISO8601Serializer::class) +@Serializable(with = LocalDateTimeIso8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { public actual constructor(year: Int, monthNumber: Int, dayOfMonth: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 226252421..79cf7554d 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -6,7 +6,7 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.datetime.serializers.InstantIso8601Serializer import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException @@ -15,7 +15,7 @@ import kotlin.time.* import java.time.Instant as jtInstant import java.time.Clock as jtClock -@Serializable(with = InstantISO8601Serializer::class) +@Serializable(with = InstantIso8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index 56e78467c..d3708ae54 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -5,14 +5,14 @@ @file:JvmName("LocalDateJvmKt") package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.datetime.serializers.LocalDateIso8601Serializer import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit import java.time.LocalDate as jtLocalDate -@Serializable(with = LocalDateISO8601Serializer::class) +@Serializable(with = LocalDateIso8601Serializer::class) public actual class LocalDate internal constructor(internal val value: jtLocalDate) : Comparable { actual companion object { public actual fun parse(isoString: String): LocalDate = try { diff --git a/core/jvm/src/LocalDateTime.kt b/core/jvm/src/LocalDateTime.kt index 9305e2c8e..4d4e38d3c 100644 --- a/core/jvm/src/LocalDateTime.kt +++ b/core/jvm/src/LocalDateTime.kt @@ -5,7 +5,7 @@ @file:JvmName("LocalDateTimeJvmKt") package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.format.DateTimeParseException @@ -14,7 +14,7 @@ import java.time.LocalDateTime as jtLocalDateTime public actual typealias Month = java.time.Month public actual typealias DayOfWeek = java.time.DayOfWeek -@Serializable(with = LocalDateTimeISO8601Serializer::class) +@Serializable(with = LocalDateTimeIso8601Serializer::class) public actual class LocalDateTime internal constructor(internal val value: jtLocalDateTime) : Comparable { public actual constructor(year: Int, monthNumber: Int, dayOfMonth: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/native/src/Instant.kt b/core/native/src/Instant.kt index 75e387be1..74f3baac8 100644 --- a/core/native/src/Instant.kt +++ b/core/native/src/Instant.kt @@ -8,7 +8,7 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.InstantISO8601Serializer +import kotlinx.datetime.serializers.InstantIso8601Serializer import kotlinx.serialization.Serializable import kotlin.math.* import kotlin.time.* @@ -85,7 +85,7 @@ private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second internal expect fun currentTime(): Instant -@Serializable(with = InstantISO8601Serializer::class) +@Serializable(with = InstantIso8601Serializer::class) @OptIn(ExperimentalTime::class) public actual class Instant internal constructor(actual val epochSeconds: Long, actual val nanosecondsOfSecond: Int) : Comparable { diff --git a/core/native/src/LocalDate.kt b/core/native/src/LocalDate.kt index 298eceb08..e3b13d064 100644 --- a/core/native/src/LocalDate.kt +++ b/core/native/src/LocalDate.kt @@ -8,7 +8,7 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateISO8601Serializer +import kotlinx.datetime.serializers.LocalDateIso8601Serializer import kotlinx.serialization.Serializable import kotlin.math.* @@ -36,7 +36,7 @@ internal const val YEAR_MAX = 999_999 private fun isValidYear(year: Int): Boolean = year >= YEAR_MIN && year <= YEAR_MAX -@Serializable(with = LocalDateISO8601Serializer::class) +@Serializable(with = LocalDateIso8601Serializer::class) public actual class LocalDate actual constructor(actual val year: Int, actual val monthNumber: Int, actual val dayOfMonth: Int) : Comparable { init { diff --git a/core/native/src/LocalDateTime.kt b/core/native/src/LocalDateTime.kt index 64987f36a..c79a146b5 100644 --- a/core/native/src/LocalDateTime.kt +++ b/core/native/src/LocalDateTime.kt @@ -8,7 +8,7 @@ package kotlinx.datetime -import kotlinx.datetime.serializers.LocalDateTimeISO8601Serializer +import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer import kotlinx.serialization.Serializable // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5 @@ -21,7 +21,7 @@ internal val localDateTimeParser: Parser LocalDateTime(date, time) } -@Serializable(with = LocalDateTimeISO8601Serializer::class) +@Serializable(with = LocalDateTimeIso8601Serializer::class) public actual class LocalDateTime internal constructor( actual val date: LocalDate, internal val time: LocalTime) : Comparable { actual companion object { diff --git a/serialization/common/test/DateTimePeriodSerializationTest.kt b/serialization/common/test/DateTimePeriodSerializationTest.kt index c3b21442f..b70407e71 100644 --- a/serialization/common/test/DateTimePeriodSerializationTest.kt +++ b/serialization/common/test/DateTimePeriodSerializationTest.kt @@ -13,7 +13,7 @@ import kotlin.test.* class DateTimePeriodSerializationTest { - private fun datePeriodISO8601Serialization( + private fun datePeriodIso8601Serialization( datePeriodSerializer: KSerializer, dateTimePeriodSerializer: KSerializer ) { @@ -65,7 +65,7 @@ class DateTimePeriodSerializationTest { Json.decodeFromString(datePeriodSerializer, "{\"hours\":0}") } - private fun dateTimePeriodISO8601Serialization(dateTimePeriodSerializer: KSerializer) { + private fun dateTimePeriodIso8601Serialization(dateTimePeriodSerializer: KSerializer) { for ((period, json) in listOf( Pair(DateTimePeriod(), "\"P0D\""), Pair(DateTimePeriod(hours = 1), "\"PT1H\""), @@ -96,8 +96,8 @@ class DateTimePeriodSerializationTest { } @Test - fun testDatePeriodISO8601Serialization() { - datePeriodISO8601Serialization(DatePeriodISO8601Serializer, DateTimePeriodISO8601Serializer) + fun testDatePeriodIso8601Serialization() { + datePeriodIso8601Serialization(DatePeriodIso8601Serializer, DateTimePeriodIso8601Serializer) } @Test @@ -106,8 +106,8 @@ class DateTimePeriodSerializationTest { } @Test - fun testDateTimePeriodISO8601Serialization() { - dateTimePeriodISO8601Serialization(DateTimePeriodISO8601Serializer) + fun testDateTimePeriodIso8601Serialization() { + dateTimePeriodIso8601Serialization(DateTimePeriodIso8601Serializer) } @Test @@ -118,8 +118,8 @@ class DateTimePeriodSerializationTest { @Test fun testDefaultSerializers() { // Check that they behave the same as the ISO-8601 serializers - dateTimePeriodISO8601Serialization(Json.serializersModule.serializer()) - datePeriodISO8601Serialization(Json.serializersModule.serializer(), Json.serializersModule.serializer()) + dateTimePeriodIso8601Serialization(Json.serializersModule.serializer()) + datePeriodIso8601Serialization(Json.serializersModule.serializer(), Json.serializersModule.serializer()) } } diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/InstantSerializationTest.kt index 624ef8e5b..ffb92cd8b 100644 --- a/serialization/common/test/InstantSerializationTest.kt +++ b/serialization/common/test/InstantSerializationTest.kt @@ -53,7 +53,7 @@ class InstantSerializationTest { @Test fun testIso8601Serialization() { - iso8601Serialization(InstantISO8601Serializer) + iso8601Serialization(InstantIso8601Serializer) } @Test diff --git a/serialization/common/test/LocalDateSerializationTest.kt b/serialization/common/test/LocalDateSerializationTest.kt index 504ed2c16..1dd953352 100644 --- a/serialization/common/test/LocalDateSerializationTest.kt +++ b/serialization/common/test/LocalDateSerializationTest.kt @@ -56,7 +56,7 @@ class LocalDateSerializationTest { @Test fun testIso8601Serialization() { - iso8601Serialization(LocalDateISO8601Serializer) + iso8601Serialization(LocalDateIso8601Serializer) } @Test diff --git a/serialization/common/test/LocalDateTimeSerializationTest.kt b/serialization/common/test/LocalDateTimeSerializationTest.kt index c8245da81..f01254d8a 100644 --- a/serialization/common/test/LocalDateTimeSerializationTest.kt +++ b/serialization/common/test/LocalDateTimeSerializationTest.kt @@ -69,7 +69,7 @@ class LocalDateTimeSerializationTest { @Test fun testIso8601Serialization() { - iso8601Serialization(LocalDateTimeISO8601Serializer) + iso8601Serialization(LocalDateTimeIso8601Serializer) } @Test From b4b29dabee4b338217ae62dca094fb540ca9f09f Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 26 Mar 2021 16:23:28 +0300 Subject: [PATCH 24/25] Add a test for manual setting of serializers --- serialization/common/test/IntegrationTest.kt | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/serialization/common/test/IntegrationTest.kt b/serialization/common/test/IntegrationTest.kt index 3f271c761..0452a10fd 100644 --- a/serialization/common/test/IntegrationTest.kt +++ b/serialization/common/test/IntegrationTest.kt @@ -26,6 +26,8 @@ class IntegrationTest { @Contextual val dateTime: LocalDateTime, @Contextual val datePeriod: DatePeriod, @Contextual val dateTimePeriod: DateTimePeriod, + // @Contextual val dayOfWeek: DayOfWeek, // doesn't compile on Native + // @Contextual val month: Month, ) private val module = SerializersModule { @@ -34,6 +36,8 @@ class IntegrationTest { contextual(LocalDateTimeComponentSerializer) contextual(DatePeriodComponentSerializer) contextual(DateTimePeriodComponentSerializer) + contextual(DayOfWeekSerializer) + contextual(MonthSerializer) } private val format = Json { serializersModule = module } @@ -46,6 +50,8 @@ class IntegrationTest { LocalDateTime.parse("2020-01-03T12:59:58.010203045"), DatePeriod.parse("P20Y-2M-3D"), DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"), + // DayOfWeek.MONDAY, // doesn't compile on Native + // Month.DECEMBER, ) val json = """{"instant":{"epochSeconds":1616549370,"nanosecondsOfSecond":123456789},""" + """"date":{"year":2020,"month":1,"day":2},""" + @@ -63,6 +69,8 @@ class IntegrationTest { val dateTime: LocalDateTime, val datePeriod: DatePeriod, val dateTimePeriod: DateTimePeriod, + // val dayOfWeek: DayOfWeek, + // val month: Month, ) @Test @@ -73,6 +81,8 @@ class IntegrationTest { LocalDateTime.parse("2020-01-03T12:59:58.010203045"), DatePeriod.parse("P20Y-2M-3D"), DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"), + // DayOfWeek.MONDAY, + // Month.DECEMBER, ) val json = "{\"instant\":\"2021-03-24T01:29:30.123456789Z\"," + "\"date\":\"2020-01-02\"," + @@ -83,4 +93,38 @@ class IntegrationTest { assertEquals(dummyValue, Json.decodeFromString(json)) assertEquals(json, Json.encodeToString(dummyValue)) } + + @Serializable + data class Dummy3( + @Serializable(with = InstantComponentSerializer::class) val instant: Instant, + @Serializable(with = LocalDateComponentSerializer::class) val date: LocalDate, + @Serializable(with = LocalDateTimeComponentSerializer::class) val dateTime: LocalDateTime, + @Serializable(with = DatePeriodComponentSerializer::class) val datePeriod: DatePeriod, + @Serializable(with = DateTimePeriodComponentSerializer::class) val dateTimePeriod: DateTimePeriod, + @Serializable(with = DayOfWeekSerializer::class) val dayOfWeek: DayOfWeek, + @Serializable(with = MonthSerializer::class) val month: Month, + ) + + @Test + fun testExplicitSerializerSpecification() { + val dummyValue = Dummy3( + Instant.parse("2021-03-24T01:29:30.123456789Z"), + LocalDate.parse("2020-01-02"), + LocalDateTime.parse("2020-01-03T12:59:58.010203045"), + DatePeriod.parse("P20Y-2M-3D"), + DateTimePeriod.parse("-P50Y-1M-2DT3H4M5.0123S"), + DayOfWeek.MONDAY, + Month.DECEMBER, + ) + val json = """{"instant":{"epochSeconds":1616549370,"nanosecondsOfSecond":123456789},""" + + """"date":{"year":2020,"month":1,"day":2},""" + + """"dateTime":{"year":2020,"month":1,"day":3,"hour":12,"minute":59,"second":58,"nanosecond":10203045},""" + + """"datePeriod":{"years":19,"months":10,"days":-3},""" + + """"dateTimePeriod":{"years":-49,"months":-11,"days":2,"hours":-3,"minutes":-4,"seconds":-5,"nanoseconds":-12300000},""" + + """"dayOfWeek":"MONDAY",""" + + """"month":"DECEMBER"""" + + """}""" + assertEquals(dummyValue, format.decodeFromString(json)) + assertEquals(json, format.encodeToString(dummyValue)) + } } \ No newline at end of file From f15d28cd4d0477e8569ec61f9469432a8f8a5fcc Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 29 Mar 2021 10:09:20 +0300 Subject: [PATCH 25/25] Remove unrelated changes to formatting --- core/common/src/DateTimePeriod.kt | 4 ++-- core/common/src/Month.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 6fe754d17..1d76d19a3 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -239,8 +239,8 @@ public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this @Serializable(with = DatePeriodIso8601Serializer::class) class DatePeriod internal constructor( - internal override val totalMonths: Int, - override val days: Int, + internal override val totalMonths: Int, + override val days: Int, ) : DateTimePeriod() { constructor(years: Int = 0, months: Int = 0, days: Int = 0): this(totalMonths(years, months), days) // avoiding excessive computations diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 7db91a5ba..8ee98566c 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -20,6 +20,7 @@ public expect enum class Month { OCTOBER, NOVEMBER, DECEMBER; + // val value: Int // member missing in java.time.Month has to be an extension }