CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlinx--kotlinx-datetime-iosx64

A multiplatform Kotlin library for working with date and time, specifically the iOS x64 target variant

Pending
Overview
Eval results
Files

serialization.mddocs/

Serialization Support

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.

Capabilities

Default Serializers

Standard serializers that delegate to the toString() and parse() methods of each type, providing automatic ISO 8601 compliance.

Core Type Serializers

/**
 * 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>

Time Zone Serializers

/**
 * 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>

Period and Unit Serializers

/**
 * 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>

Enumeration Serializers

/**
 * 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")

ISO 8601 Serializers

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"}

Component Serializers

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-25

Specialized DateTimeUnit Serializers

Serializers 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 values

Advanced Serialization Patterns

Custom Serialization Context

import 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 }

Conditional Serialization

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)}")

Polymorphic Serialization

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()
}

Database Serialization

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())
    }
}

Migration and Compatibility

Handling Format Changes

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-is

Error Handling and Validation

import 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

docs

arithmetic.md

formatting.md

index.md

instant.md

local-types.md

platform.md

ranges.md

serialization.md

timezones.md

tile.json