A multiplatform Kotlin library for working with date and time, specifically the iOS x64 target variant
—
Comprehensive kotlinx.serialization support with multiple serializer types for different use cases. The library provides default serializers, explicit ISO 8601 serializers, component-based serializers, and specialized serializers for different data formats.
Standard serializers that delegate to the toString() and parse() methods of each type, providing automatic ISO 8601 compliance.
/**
* Default serializer for kotlin.time.Instant
* Uses ISO 8601 format via toString/parse delegation
*/
object InstantSerializer : KSerializer<Instant>
/**
* Default serializer for LocalDate
* Uses ISO 8601 extended format (YYYY-MM-DD)
*/
object LocalDateSerializer : KSerializer<LocalDate>
/**
* Default serializer for LocalTime
* Uses ISO 8601 extended format (HH:MM:SS[.fff])
*/
object LocalTimeSerializer : KSerializer<LocalTime>
/**
* Default serializer for LocalDateTime
* Uses ISO 8601 extended format (YYYY-MM-DDTHH:MM:SS[.fff])
*/
object LocalDateTimeSerializer : KSerializer<LocalDateTime>
/**
* Default serializer for YearMonth
* Uses ISO 8601 format (YYYY-MM)
*/
object YearMonthSerializer : KSerializer<YearMonth>
/**
* Default serializer for UtcOffset
* Uses ISO 8601 format (+HH:MM, Z, etc.)
*/
object UtcOffsetSerializer : KSerializer<UtcOffset>/**
* Default serializer for TimeZone
* Serializes the time zone ID as a string
*/
object TimeZoneSerializer : KSerializer<TimeZone>
/**
* Default serializer for FixedOffsetTimeZone
* Serializes as the offset string representation
*/
object FixedOffsetTimeZoneSerializer : KSerializer<FixedOffsetTimeZone>/**
* Default serializer for DateTimePeriod
* Uses ISO 8601 duration format via toString/parse
*/
object DateTimePeriodSerializer : KSerializer<DateTimePeriod>
/**
* Default serializer for DatePeriod
* Uses ISO 8601 date duration format via toString/parse
*/
object DatePeriodSerializer : KSerializer<DatePeriod>
/**
* Default serializer for DateTimeUnit
* Serializes unit representation
*/
object DateTimeUnitSerializer : KSerializer<DateTimeUnit>/**
* Default serializer for Month enum
* Serializes as enum name
*/
object MonthSerializer : KSerializer<Month>
/**
* Default serializer for DayOfWeek enum
* Serializes as enum name
*/
object DayOfWeekSerializer : KSerializer<DayOfWeek>Usage Examples:
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Event(
val name: String,
val date: LocalDate,
val time: LocalTime,
val timeZone: TimeZone,
val duration: DateTimePeriod
)
val event = Event(
name = "Conference",
date = LocalDate(2023, 12, 25),
time = LocalTime(15, 30),
timeZone = TimeZone.of("America/New_York"),
duration = DateTimePeriod(hours = 2, minutes = 30)
)
// Serialize to JSON using default serializers
val json = Json.encodeToString(event)
println(json)
// {"name":"Conference","date":"2023-12-25","time":"15:30:00","timeZone":"America/New_York","duration":"PT2H30M"}
// Deserialize from JSON
val restored = Json.decodeFromString<Event>(json)
println("Restored: $restored")Explicit ISO 8601 serializers that guarantee strict ISO compliance regardless of default toString() implementation.
/**
* Explicit ISO 8601 serializer for kotlin.time.Instant
* Guarantees ISO 8601 format regardless of toString implementation
*/
object InstantIso8601Serializer : KSerializer<Instant>
/**
* Explicit ISO 8601 serializer for LocalDate
* Uses ISO 8601 extended format (YYYY-MM-DD)
*/
object LocalDateIso8601Serializer : KSerializer<LocalDate>
/**
* Explicit ISO 8601 serializer for LocalTime
* Uses ISO 8601 extended format with optional fractions
*/
object LocalTimeIso8601Serializer : KSerializer<LocalTime>
/**
* Explicit ISO 8601 serializer for LocalDateTime
* Uses ISO 8601 extended format
*/
object LocalDateTimeIso8601Serializer : KSerializer<LocalDateTime>
/**
* Explicit ISO 8601 serializer for YearMonth
* Uses ISO 8601 format (YYYY-MM)
*/
object YearMonthIso8601Serializer : KSerializer<YearMonth>
/**
* Explicit ISO 8601 serializer for UtcOffset
* Uses ISO 8601 offset format
*/
object UtcOffsetIso8601Serializer : KSerializer<UtcOffset>
/**
* Explicit ISO 8601 serializer for DateTimePeriod
* Uses ISO 8601 duration format
*/
object DateTimePeriodIso8601Serializer : KSerializer<DateTimePeriod>
/**
* Explicit ISO 8601 serializer for DatePeriod
* Uses ISO 8601 date duration format
*/
object DatePeriodIso8601Serializer : KSerializer<DatePeriod>Usage Examples:
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class StrictEvent(
val name: String,
@Serializable(with = LocalDateIso8601Serializer::class)
val date: LocalDate,
@Serializable(with = LocalTimeIso8601Serializer::class)
val time: LocalTime,
@Serializable(with = DateTimePeriodIso8601Serializer::class)
val duration: DateTimePeriod
)
val event = StrictEvent(
name = "Meeting",
date = LocalDate(2023, 12, 25),
time = LocalTime(15, 30, 45, 123456789),
duration = DateTimePeriod(hours = 1, minutes = 30)
)
val json = Json.encodeToString(event)
println(json)
// {"name":"Meeting","date":"2023-12-25","time":"15:30:45.123456789","duration":"PT1H30M"}Serializers that represent date/time values as JSON objects with individual component fields.
/**
* Component serializer for LocalDate
* Serializes as object: {"year": 2023, "month": 12, "day": 25}
*/
object LocalDateComponentSerializer : KSerializer<LocalDate>
/**
* Component serializer for LocalTime
* Serializes as object: {"hour": 15, "minute": 30, "second": 45, "nanosecond": 123456789}
*/
object LocalTimeComponentSerializer : KSerializer<LocalTime>
/**
* Component serializer for LocalDateTime
* Serializes as object with separate date and time components
*/
object LocalDateTimeComponentSerializer : KSerializer<LocalDateTime>
/**
* Component serializer for YearMonth
* Serializes as object: {"year": 2023, "month": 12}
*/
object YearMonthComponentSerializer : KSerializer<YearMonth>
/**
* Component serializer for DateTimePeriod
* Serializes as object with all period components
*/
object DateTimePeriodComponentSerializer : KSerializer<DateTimePeriod>
/**
* Component serializer for DatePeriod
* Serializes as object: {"years": 1, "months": 6, "days": 15}
*/
object DatePeriodComponentSerializer : KSerializer<DatePeriod>Usage Examples:
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class ComponentEvent(
val name: String,
@Serializable(with = LocalDateComponentSerializer::class)
val date: LocalDate,
@Serializable(with = LocalTimeComponentSerializer::class)
val time: LocalTime,
@Serializable(with = DatePeriodComponentSerializer::class)
val duration: DatePeriod
)
val event = ComponentEvent(
name = "Workshop",
date = LocalDate(2023, 12, 25),
time = LocalTime(15, 30, 45),
duration = DatePeriod(days = 2)
)
val json = Json { prettyPrint = true }.encodeToString(event)
println(json)
/*
{
"name": "Workshop",
"date": {
"year": 2023,
"month": 12,
"day": 25
},
"time": {
"hour": 15,
"minute": 30,
"second": 45,
"nanosecond": 0
},
"duration": {
"years": 0,
"months": 0,
"days": 2
}
}
*/
// Deserialize back to objects
val restored = Json.decodeFromString<ComponentEvent>(json)
println("Restored date: ${restored.date}") // 2023-12-25Serializers for different DateTimeUnit types based on their classification.
/**
* Serializer for TimeBased DateTimeUnit
* Handles units with nanosecond precision
*/
object TimeBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.TimeBased>
/**
* Serializer for DayBased DateTimeUnit
* Handles day and week units
*/
object DayBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.DayBased>
/**
* Serializer for MonthBased DateTimeUnit
* Handles month, quarter, year, and century units
*/
object MonthBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.MonthBased>
/**
* Serializer for DateBased DateTimeUnit (base class)
* Handles all date-based units
*/
object DateBasedDateTimeUnitSerializer : KSerializer<DateTimeUnit.DateBased>Usage Examples:
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class ScheduleRule(
val name: String,
@Serializable(with = TimeBasedDateTimeUnitSerializer::class)
val timeUnit: DateTimeUnit.TimeBased,
@Serializable(with = MonthBasedDateTimeUnitSerializer::class)
val dateUnit: DateTimeUnit.MonthBased,
val interval: Int
)
val rule = ScheduleRule(
name = "Quarterly Review",
timeUnit = DateTimeUnit.HOUR,
dateUnit = DateTimeUnit.QUARTER,
interval = 1
)
val json = Json.encodeToString(rule)
println(json)
// Exact format depends on implementation - could be unit names or nanosecond valuesimport kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
// Custom serializer that includes time zone information
@Serializer(forClass = LocalDateTime::class)
object LocalDateTimeWithZoneSerializer : KSerializer<LocalDateTime> {
override val descriptor = buildClassSerialDescriptor("LocalDateTimeWithZone") {
element<String>("dateTime")
element<String>("timeZone")
}
override fun serialize(encoder: Encoder, value: LocalDateTime) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.toString())
// Would need context to get time zone - this is a conceptual example
encodeStringElement(descriptor, 1, "UTC")
}
}
override fun deserialize(decoder: Decoder): LocalDateTime {
return decoder.decodeStructure(descriptor) {
var dateTime: String? = null
var timeZone: String? = null
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> dateTime = decodeStringElement(descriptor, 0)
1 -> timeZone = decodeStringElement(descriptor, 1)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
LocalDateTime.parse(dateTime!!)
}
}
}
// Using custom module
val customModule = SerializersModule {
contextual(LocalDateTime::class, LocalDateTimeWithZoneSerializer)
}
val json = Json { serializersModule = customModule }import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class FlexibleEvent(
val name: String,
val date: LocalDate,
val time: LocalTime? = null, // Optional time
@Serializable(with = DateTimePeriodSerializer::class)
val duration: DateTimePeriod? = null // Optional duration
) {
// Custom property for all-day events
val isAllDay: Boolean get() = time == null
// Convert to full datetime if time is present
val dateTime: LocalDateTime? get() = time?.let { date.atTime(it) }
}
val allDayEvent = FlexibleEvent(
name = "Holiday",
date = LocalDate(2023, 12, 25)
)
val timedEvent = FlexibleEvent(
name = "Meeting",
date = LocalDate(2023, 12, 25),
time = LocalTime(15, 30),
duration = DateTimePeriod(hours = 2)
)
// Both serialize differently
println("All-day: ${Json.encodeToString(allDayEvent)}")
println("Timed: ${Json.encodeToString(timedEvent)}")import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
sealed class TimeSpan {
@Serializable
@SerialName("duration")
data class DurationSpan(
@Serializable(with = DateTimePeriodSerializer::class)
val period: DateTimePeriod
) : TimeSpan()
@Serializable
@SerialName("range")
data class DateRangeSpan(
val start: LocalDate,
val end: LocalDate
) : TimeSpan()
@Serializable
@SerialName("recurring")
data class RecurringSpan(
val start: LocalDate,
@Serializable(with = DateTimePeriodSerializer::class)
val interval: DateTimePeriod,
val count: Int
) : TimeSpan()
}
@Serializable
data class Task(
val name: String,
val timeSpan: TimeSpan
)
val tasks = listOf(
Task("Quick task", TimeSpan.DurationSpan(DateTimePeriod(hours = 1))),
Task("Project", TimeSpan.DateRangeSpan(
LocalDate(2023, 12, 1),
LocalDate(2023, 12, 31)
)),
Task("Weekly meeting", TimeSpan.RecurringSpan(
LocalDate(2023, 12, 1),
DateTimePeriod(days = 7),
count = 4
))
)
val json = Json { prettyPrint = true }
tasks.forEach { task ->
println("${task.name}:")
println(json.encodeToString(task))
println()
}import kotlinx.datetime.*
import kotlinx.serialization.*
// Example adapter for database storage
class DateTimeSerializationAdapter {
// Convert to database-friendly formats
fun localDateToLong(date: LocalDate): Long = date.toEpochDays()
fun longToLocalDate(epochDays: Long): LocalDate = LocalDate.fromEpochDays(epochDays)
fun instantToLong(instant: Instant): Long = instant.epochSeconds
fun longToInstant(epochSeconds: Long): Instant = Instant.fromEpochSeconds(epochSeconds)
fun timeZoneToString(timeZone: TimeZone): String = timeZone.id
fun stringToTimeZone(id: String): TimeZone = TimeZone.of(id)
fun periodToString(period: DateTimePeriod): String = period.toString()
fun stringToPeriod(str: String): DateTimePeriod = DateTimePeriod.parse(str)
}
// Usage in data classes for database entities
@Serializable
data class DatabaseEvent(
val id: Long,
val name: String,
// Store as epoch days for efficient database operations
@Serializable(with = EpochDaysSerializer::class)
val date: LocalDate,
// Store as epoch seconds
@Serializable(with = EpochSecondsSerializer::class)
val createdAt: Instant,
// Store time zone as string ID
val timeZoneId: String
) {
// Computed property for actual time zone
val timeZone: TimeZone get() = TimeZone.of(timeZoneId)
}
// Custom serializers for database storage
object EpochDaysSerializer : KSerializer<LocalDate> {
override val descriptor = PrimitiveSerialDescriptor("EpochDays", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: LocalDate) {
encoder.encodeLong(value.toEpochDays())
}
override fun deserialize(decoder: Decoder): LocalDate {
return LocalDate.fromEpochDays(decoder.decodeLong())
}
}
object EpochSecondsSerializer : KSerializer<Instant> {
override val descriptor = PrimitiveSerialDescriptor("EpochSeconds", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeLong(value.epochSeconds)
}
override fun deserialize(decoder: Decoder): Instant {
return Instant.fromEpochSeconds(decoder.decodeLong())
}
}import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
// Versioned serialization for backward compatibility
@Serializable
data class EventV1(
val name: String,
val dateString: String // Old format: stored as string
)
@Serializable
data class EventV2(
val name: String,
val date: LocalDate, // New format: proper LocalDate
val version: Int = 2
)
// Migration serializer
object EventMigrationSerializer : KSerializer<EventV2> {
override val descriptor = EventV2.serializer().descriptor
override fun serialize(encoder: Encoder, value: EventV2) {
EventV2.serializer().serialize(encoder, value)
}
override fun deserialize(decoder: Decoder): EventV2 {
val element = decoder.decodeSerializableValue(JsonElement.serializer())
val jsonObject = element.jsonObject
return when {
"version" in jsonObject -> {
// New format
Json.decodeFromJsonElement(EventV2.serializer(), element)
}
"dateString" in jsonObject -> {
// Old format - migrate
val v1 = Json.decodeFromJsonElement(EventV1.serializer(), element)
EventV2(
name = v1.name,
date = LocalDate.parse(v1.dateString) // Convert string to LocalDate
)
}
else -> error("Unknown event format")
}
}
}
// Usage
val oldJson = """{"name":"Meeting","dateString":"2023-12-25"}"""
val newJson = """{"name":"Conference","date":"2023-12-31","version":2}"""
val event1 = Json.decodeFromString(EventMigrationSerializer, oldJson)
val event2 = Json.decodeFromString(EventMigrationSerializer, newJson)
println("Migrated: $event1") // EventV2 with LocalDate
println("Current: $event2") // EventV2 as-isimport kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
// Robust serializer with validation
object ValidatedLocalDateSerializer : KSerializer<LocalDate> {
override val descriptor = PrimitiveSerialDescriptor("ValidatedLocalDate", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDate) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): LocalDate {
val dateString = decoder.decodeString()
return try {
LocalDate.parse(dateString)
} catch (e: Exception) {
// Try alternative formats or provide default
when {
dateString.matches(Regex("""\d{8}""")) -> {
// Handle YYYYMMDD format
val year = dateString.substring(0, 4).toInt()
val month = dateString.substring(4, 6).toInt()
val day = dateString.substring(6, 8).toInt()
LocalDate(year, month, day)
}
else -> throw SerializationException("Invalid date format: $dateString", e)
}
}
}
}
// Safe parsing wrapper
inline fun <reified T> safeJsonDecode(json: String): Result<T> {
return try {
Result.success(Json.decodeFromString<T>(json))
} catch (e: SerializationException) {
Result.failure(e)
}
}
// Usage
val validJson = """{"date":"2023-12-25"}"""
val invalidJson = """{"date":"invalid-date"}"""
val alternativeJson = """{"date":"20231225"}"""
@Serializable
data class DateContainer(@Serializable(with = ValidatedLocalDateSerializer::class) val date: LocalDate)
val result1 = safeJsonDecode<DateContainer>(validJson) // Success
val result2 = safeJsonDecode<DateContainer>(invalidJson) // Failure
val result3 = safeJsonDecode<DateContainer>(alternativeJson) // Success (handled YYYYMMDD)
result1.onSuccess { println("Valid: ${it.date}") }
result2.onFailure { println("Error: ${it.message}") }
result3.onSuccess { println("Alternative: ${it.date}") }Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-datetime-iosx64