Skip to content

Commit 93c9150

Browse files
authored
kotlinx-serialization support (#101)
Fixes #37
1 parent d3c63fb commit 93c9150

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1560
-15
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ buildscript {
33
mavenCentral()
44
}
55
dependencies {
6-
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0")
6+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30")
77
}
88
}
99

1010
plugins {
1111
id("kotlinx.team.infra") version "0.3.0-dev-64"
12+
kotlin("plugin.serialization") version "1.4.30"
1213
}
1314

1415
infra {

core/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import javax.xml.parsers.DocumentBuilderFactory
55

66
plugins {
77
id("kotlin-multiplatform")
8+
kotlin("plugin.serialization")
89
`maven-publish`
910
}
1011

@@ -18,6 +19,7 @@ base {
1819

1920
//val JDK_6: String by project
2021
val JDK_8: String by project
22+
val serializationVersion: String by project
2123

2224
kotlin {
2325
infra {
@@ -148,6 +150,7 @@ kotlin {
148150
commonMain {
149151
dependencies {
150152
api("org.jetbrains.kotlin:kotlin-stdlib-common")
153+
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
151154
}
152155
}
153156

core/common/src/DateTimePeriod.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55

66
package kotlinx.datetime
77

8+
import kotlinx.datetime.serializers.DatePeriodIso8601Serializer
9+
import kotlinx.datetime.serializers.DateTimePeriodIso8601Serializer
810
import kotlin.math.*
911
import kotlin.time.Duration
1012
import kotlin.time.ExperimentalTime
13+
import kotlinx.serialization.Serializable
1114

15+
@Serializable(with = DateTimePeriodIso8601Serializer::class)
1216
// TODO: could be error-prone without explicitly named params
1317
sealed class DateTimePeriod {
1418
internal abstract val totalMonths: Int
@@ -233,6 +237,7 @@ sealed class DateTimePeriod {
233237

234238
public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this)
235239

240+
@Serializable(with = DatePeriodIso8601Serializer::class)
236241
class DatePeriod internal constructor(
237242
internal override val totalMonths: Int,
238243
override val days: Int,

core/common/src/DateTimeUnit.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55

66
package kotlinx.datetime
77

8-
import kotlin.time.Duration
9-
import kotlin.time.ExperimentalTime
10-
import kotlin.time.nanoseconds
8+
import kotlinx.datetime.serializers.*
9+
import kotlinx.serialization.Serializable
10+
import kotlin.time.*
1111

12+
@Serializable(with = DateTimeUnitSerializer::class)
1213
sealed class DateTimeUnit {
1314

1415
abstract operator fun times(scalar: Int): DateTimeUnit
1516

17+
@Serializable(with = TimeBasedDateTimeUnitSerializer::class)
1618
class TimeBased(val nanoseconds: Long) : DateTimeUnit() {
1719
private val unitName: String
1820
private val unitScale: Long
@@ -51,7 +53,8 @@ sealed class DateTimeUnit {
5153
override fun times(scalar: Int): TimeBased = TimeBased(safeMultiply(nanoseconds, scalar.toLong()))
5254

5355
@ExperimentalTime
54-
val duration: Duration = nanoseconds.nanoseconds
56+
val duration: Duration
57+
get() = nanoseconds.nanoseconds
5558

5659
override fun equals(other: Any?): Boolean =
5760
this === other || (other is TimeBased && this.nanoseconds == other.nanoseconds)
@@ -61,8 +64,10 @@ sealed class DateTimeUnit {
6164
override fun toString(): String = formatToString(unitScale, unitName)
6265
}
6366

67+
@Serializable(with = DateBasedDateTimeUnitSerializer::class)
6468
sealed class DateBased : DateTimeUnit() {
6569
// TODO: investigate how to move subclasses up to DateTimeUnit scope
70+
@Serializable(with = DayBasedDateTimeUnitSerializer::class)
6671
class DayBased(val days: Int) : DateBased() {
6772
init {
6873
require(days > 0) { "Unit duration must be positive, but was $days days." }
@@ -80,6 +85,7 @@ sealed class DateTimeUnit {
8085
else
8186
formatToString(days, "DAY")
8287
}
88+
@Serializable(with = MonthBasedDateTimeUnitSerializer::class)
8389
class MonthBased(val months: Int) : DateBased() {
8490
init {
8591
require(months > 0) { "Unit duration must be positive, but was $months months." }

core/common/src/Instant.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
package kotlinx.datetime
77

8-
import kotlin.time.Duration
9-
import kotlin.time.ExperimentalTime
8+
import kotlinx.datetime.serializers.InstantIso8601Serializer
9+
import kotlinx.serialization.Serializable
10+
import kotlin.time.*
1011

1112
@OptIn(ExperimentalTime::class)
13+
@Serializable(with = InstantIso8601Serializer::class)
1214
public expect class Instant : Comparable<Instant> {
1315

1416
/**

core/common/src/LocalDate.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
package kotlinx.datetime
77

8+
import kotlinx.datetime.serializers.LocalDateIso8601Serializer
9+
import kotlinx.serialization.Serializable
10+
11+
@Serializable(with = LocalDateIso8601Serializer::class)
812
public expect class LocalDate : Comparable<LocalDate> {
913
companion object {
1014
/**

core/common/src/LocalDateTime.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
package kotlinx.datetime
77

8+
import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer
9+
import kotlinx.serialization.Serializable
810

9-
11+
@Serializable(with = LocalDateTimeIso8601Serializer::class)
1012
public expect class LocalDateTime : Comparable<LocalDateTime> {
1113
companion object {
1214

core/common/src/TimeZone.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
package kotlinx.datetime
1010

11+
import kotlinx.datetime.serializers.TimeZoneSerializer
12+
import kotlinx.datetime.serializers.ZoneOffsetSerializer
13+
import kotlinx.serialization.Serializable
14+
15+
@Serializable(with = TimeZoneSerializer::class)
1116
public expect open class TimeZone {
1217
/**
1318
* Returns the identifier string of the time zone.
@@ -80,6 +85,7 @@ public expect open class TimeZone {
8085
public fun LocalDateTime.toInstant(): Instant
8186
}
8287

88+
@Serializable(with = ZoneOffsetSerializer::class)
8389
public expect class ZoneOffset : TimeZone {
8490
val totalSeconds: Int
8591
}

core/common/src/math.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,12 @@ internal class DivRemResult(val q: Long, val r: Long) {
133133
operator fun component2(): Long = r
134134
}
135135

136+
@Suppress("NOTHING_TO_INLINE")
136137
private inline fun low(x: Long) = x and 0xffffffff
138+
@Suppress("NOTHING_TO_INLINE")
137139
private inline fun high(x: Long) = (x shr 32) and 0xffffffff
138140
/** For [bit] in [0; 63], return bit #[bit] of [value], counting from the least significant bit */
141+
@Suppress("NOTHING_TO_INLINE")
139142
private inline fun indexBit(value: Long, bit: Int): Long = (value shr bit and 1)
140143

141144

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2019-2021 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.datetime.serializers
7+
8+
import kotlinx.datetime.DatePeriod
9+
import kotlinx.datetime.DateTimePeriod
10+
import kotlinx.serialization.KSerializer
11+
import kotlinx.serialization.SerializationException
12+
import kotlinx.serialization.descriptors.*
13+
import kotlinx.serialization.encoding.*
14+
15+
object DateTimePeriodComponentSerializer: KSerializer<DateTimePeriod> {
16+
17+
override val descriptor: SerialDescriptor =
18+
buildClassSerialDescriptor("DateTimePeriod") {
19+
element<Int>("years", isOptional = true)
20+
element<Int>("months", isOptional = true)
21+
element<Int>("days", isOptional = true)
22+
element<Int>("hours", isOptional = true)
23+
element<Int>("minutes", isOptional = true)
24+
element<Int>("seconds", isOptional = true)
25+
element<Long>("nanoseconds", isOptional = true)
26+
}
27+
28+
override fun deserialize(decoder: Decoder): DateTimePeriod =
29+
decoder.decodeStructure(descriptor) {
30+
var years = 0
31+
var months = 0
32+
var days = 0
33+
var hours = 0
34+
var minutes = 0
35+
var seconds = 0
36+
var nanoseconds = 0L
37+
loop@while (true) {
38+
when (val index = decodeElementIndex(descriptor)) {
39+
0 -> years = decodeIntElement(descriptor, 0)
40+
1 -> months = decodeIntElement(descriptor, 1)
41+
2 -> days = decodeIntElement(descriptor, 2)
42+
3 -> hours = decodeIntElement(descriptor, 3)
43+
4 -> minutes = decodeIntElement(descriptor, 4)
44+
5 -> seconds = decodeIntElement(descriptor, 5)
45+
6 -> nanoseconds = decodeLongElement(descriptor, 6)
46+
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
47+
else -> throw SerializationException("Unexpected index: $index")
48+
}
49+
}
50+
DateTimePeriod(years, months, days, hours, minutes, seconds, nanoseconds)
51+
}
52+
53+
override fun serialize(encoder: Encoder, value: DateTimePeriod) {
54+
encoder.encodeStructure(descriptor) {
55+
with(value) {
56+
if (years != 0) encodeIntElement(descriptor, 0, years)
57+
if (months != 0) encodeIntElement(descriptor, 1, months)
58+
if (days != 0) encodeIntElement(descriptor, 2, days)
59+
if (hours != 0) encodeIntElement(descriptor, 3, hours)
60+
if (minutes != 0) encodeIntElement(descriptor, 4, minutes)
61+
if (seconds != 0) encodeIntElement(descriptor, 5, seconds)
62+
if (nanoseconds != 0) encodeLongElement(descriptor, 6, value.nanoseconds.toLong())
63+
}
64+
}
65+
}
66+
67+
}
68+
69+
object DateTimePeriodIso8601Serializer: KSerializer<DateTimePeriod> {
70+
71+
override val descriptor: SerialDescriptor =
72+
PrimitiveSerialDescriptor("DateTimePeriod", PrimitiveKind.STRING)
73+
74+
override fun deserialize(decoder: Decoder): DateTimePeriod =
75+
DateTimePeriod.parse(decoder.decodeString())
76+
77+
override fun serialize(encoder: Encoder, value: DateTimePeriod) {
78+
encoder.encodeString(value.toString())
79+
}
80+
81+
}
82+
83+
object DatePeriodComponentSerializer: KSerializer<DatePeriod> {
84+
85+
private fun unexpectedNonzero(fieldName: String, value: Long) {
86+
if (value != 0L) {
87+
throw SerializationException("DatePeriod should have non-date components be zero, but got $value in '$fieldName'")
88+
}
89+
}
90+
91+
private fun unexpectedNonzero(fieldName: String, value: Int) = unexpectedNonzero(fieldName, value.toLong())
92+
93+
override val descriptor: SerialDescriptor =
94+
buildClassSerialDescriptor("DatePeriod") {
95+
element<Int>("years", isOptional = true)
96+
element<Int>("months", isOptional = true)
97+
element<Int>("days", isOptional = true)
98+
element<Int>("hours", isOptional = true)
99+
element<Int>("minutes", isOptional = true)
100+
element<Int>("seconds", isOptional = true)
101+
element<Long>("nanoseconds", isOptional = true)
102+
}
103+
104+
override fun deserialize(decoder: Decoder): DatePeriod =
105+
decoder.decodeStructure(descriptor) {
106+
var years = 0
107+
var months = 0
108+
var days = 0
109+
loop@while (true) {
110+
when (val index = decodeElementIndex(descriptor)) {
111+
0 -> years = decodeIntElement(descriptor, 0)
112+
1 -> months = decodeIntElement(descriptor, 1)
113+
2 -> days = decodeIntElement(descriptor, 2)
114+
3 -> unexpectedNonzero("hours", decodeIntElement(descriptor, 3))
115+
4 -> unexpectedNonzero("minutes", decodeIntElement(descriptor, 4))
116+
5 -> unexpectedNonzero("seconds", decodeIntElement(descriptor, 5))
117+
6 -> unexpectedNonzero("nanoseconds", decodeLongElement(descriptor, 6))
118+
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262
119+
else -> throw SerializationException("Unexpected index: $index")
120+
}
121+
}
122+
DatePeriod(years, months, days)
123+
}
124+
125+
override fun serialize(encoder: Encoder, value: DatePeriod) {
126+
encoder.encodeStructure(descriptor) {
127+
with(value) {
128+
if (years != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 0, years)
129+
if (months != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 1, months)
130+
if (days != 0) encodeIntElement(DateTimePeriodComponentSerializer.descriptor, 2, days)
131+
}
132+
}
133+
}
134+
135+
}
136+
137+
object DatePeriodIso8601Serializer: KSerializer<DatePeriod> {
138+
139+
override val descriptor: SerialDescriptor =
140+
PrimitiveSerialDescriptor("DatePeriod", PrimitiveKind.STRING)
141+
142+
// TODO: consider whether should fail when parsing "P1YT0H0M0.0S"
143+
override fun deserialize(decoder: Decoder): DatePeriod =
144+
when (val period = DateTimePeriod.parse(decoder.decodeString())) {
145+
is DatePeriod -> period
146+
else -> throw SerializationException("$period is not a date-based period")
147+
}
148+
149+
override fun serialize(encoder: Encoder, value: DatePeriod) {
150+
encoder.encodeString(value.toString())
151+
}
152+
153+
}

0 commit comments

Comments
 (0)