A multiplatform Kotlin library for working with date and time, specifically the iOS x64 target variant
—
Comprehensive formatting and parsing system with DSL builders for creating custom date/time formats. The library supports ISO 8601 formats out of the box and provides flexible builders for custom formatting needs.
Core formatting interface that handles both parsing and formatting of date/time values.
/**
* Format for parsing and formatting date/time values
* Sealed interface providing type-safe formatting operations
*/
sealed interface DateTimeFormat<T> {
/**
* Format a value to string
* @param value The value to format
* @returns Formatted string representation
*/
fun format(value: T): String
/**
* Format a value to an appendable
* @param appendable Target to append the formatted value to
* @param value The value to format
* @returns The appendable for chaining
*/
fun formatTo(appendable: Appendable, value: T): Appendable
/**
* Parse a string to the target type
* @param input String to parse
* @returns Parsed value
* @throws DateTimeParseException if parsing fails
*/
fun parse(input: CharSequence): T
/**
* Parse a string to the target type, returning null on failure
* @param input String to parse
* @returns Parsed value or null if parsing fails
*/
fun parseOrNull(input: CharSequence): T?
companion object {
/**
* Generate Kotlin DSL code for recreating a format
* @param format The format to analyze
* @returns Kotlin code string
*/
fun formatAsKotlinBuilderDsl(format: DateTimeFormat<*>): String
}
}Usage Examples:
import kotlinx.datetime.*
// Using predefined formats
val date = LocalDate(2023, 12, 25)
val formatted = date.format(LocalDate.Formats.ISO) // "2023-12-25"
// Parse from string
val parsed = LocalDate.parse("2023-12-25", LocalDate.Formats.ISO)
// Safe parsing
val maybeParsed = LocalDate.Formats.ISO.parseOrNull("invalid-date") // null
// Format to StringBuilder
val buffer = StringBuilder()
LocalDate.Formats.ISO.formatTo(buffer, date)
println(buffer.toString()) // "2023-12-25"Each date/time type provides predefined format constants for common use cases.
object LocalDate.Formats {
/** ISO 8601 extended format: YYYY-MM-DD */
val ISO: DateTimeFormat<LocalDate>
/** ISO 8601 basic format: YYYYMMDD */
val ISO_BASIC: DateTimeFormat<LocalDate>
}object LocalTime.Formats {
/** ISO 8601 extended format: HH:MM:SS[.fff] */
val ISO: DateTimeFormat<LocalTime>
}object LocalDateTime.Formats {
/** ISO 8601 extended format: YYYY-MM-DDTHH:MM:SS[.fff] */
val ISO: DateTimeFormat<LocalDateTime>
}object YearMonth.Formats {
/** ISO 8601 extended format: YYYY-MM */
val ISO: DateTimeFormat<YearMonth>
}object UtcOffset.Formats {
/** ISO 8601 extended format: +HH:MM, +HH:MM:SS, or Z */
val ISO: DateTimeFormat<UtcOffset>
/** ISO 8601 basic format: +HHMM, +HHMMSS, or Z */
val ISO_BASIC: DateTimeFormat<UtcOffset>
/** Four digits format: always ±HHMM, never Z */
val FOUR_DIGITS: DateTimeFormat<UtcOffset>
}Usage Examples:
import kotlinx.datetime.*
val date = LocalDate(2023, 12, 25)
val time = LocalTime(15, 30, 45, 123456789)
val dateTime = LocalDateTime(date, time)
val yearMonth = YearMonth(2023, 12)
val offset = UtcOffset(hours = 5, minutes = 30)
// Format using predefined formats
println(date.format(LocalDate.Formats.ISO)) // "2023-12-25"
println(date.format(LocalDate.Formats.ISO_BASIC)) // "20231225"
println(time.format(LocalTime.Formats.ISO)) // "15:30:45.123456789"
println(dateTime.format(LocalDateTime.Formats.ISO)) // "2023-12-25T15:30:45.123456789"
println(yearMonth.format(YearMonth.Formats.ISO)) // "2023-12"
println(offset.format(UtcOffset.Formats.ISO)) // "+05:30"
println(offset.format(UtcOffset.Formats.ISO_BASIC)) // "+0530"
println(offset.format(UtcOffset.Formats.FOUR_DIGITS)) // "+0530"
// Parse using predefined formats
val parsedDate = LocalDate.parse("2023-12-25", LocalDate.Formats.ISO)
val parsedTime = LocalTime.parse("15:30:45", LocalTime.Formats.ISO)
val parsedOffset = UtcOffset.parse("+05:30", UtcOffset.Formats.ISO)Flexible DSL for creating custom date/time formats with fine-grained control over each component.
/**
* Base interface for all format builders
* Provides common formatting operations
*/
interface DateTimeFormatBuilder {
/**
* Add literal text to the format
* @param value Literal string to include
*/
fun chars(value: String)
/**
* Add a single literal character
* @param value Character to include
*/
fun char(value: Char)
}/**
* Builder for formats that include date components
*/
interface DateTimeFormatBuilder.WithDate : DateTimeFormatBuilder {
/**
* Year component
* @param padding Padding style for the year
*/
fun year(padding: Padding = Padding.ZERO)
/**
* Month as number
* @param padding Padding style (NONE, ZERO, SPACE)
*/
fun monthNumber(padding: Padding = Padding.ZERO)
/**
* Day of month
* @param padding Padding style
*/
fun dayOfMonth(padding: Padding = Padding.ZERO)
/**
* Day of year (1-366)
* @param padding Padding style
*/
fun dayOfYear(padding: Padding = Padding.ZERO)
/**
* Day of week as number (1=Monday, 7=Sunday)
* @param padding Padding style
*/
fun dayOfWeek(padding: Padding = Padding.ZERO)
}/**
* Builder for formats that include time components
*/
interface DateTimeFormatBuilder.WithTime : DateTimeFormatBuilder {
/**
* Hour component (0-23)
* @param padding Padding style
*/
fun hour(padding: Padding = Padding.ZERO)
/**
* Minute component (0-59)
* @param padding Padding style
*/
fun minute(padding: Padding = Padding.ZERO)
/**
* Second component (0-59)
* @param padding Padding style
*/
fun second(padding: Padding = Padding.ZERO)
/**
* Fractional second component
* @param minLength Minimum number of digits
* @param maxLength Maximum number of digits
*/
fun secondFraction(minLength: Int = 0, maxLength: Int = 9)
/**
* AM/PM marker
* @param am String for AM (default "AM")
* @param pm String for PM (default "PM")
*/
fun amPmMarker(am: String = "AM", pm: String = "PM")
}/**
* Builder for formats with both date and time components
*/
interface DateTimeFormatBuilder.WithDateTime :
DateTimeFormatBuilder.WithDate,
DateTimeFormatBuilder.WithTime
/**
* Builder for formats with UTC offset
*/
interface DateTimeFormatBuilder.WithUtcOffset : DateTimeFormatBuilder {
/**
* UTC offset component
* @param format Offset format style
*/
fun offset(format: UtcOffsetFormat)
}
/**
* Builder for YearMonth formats
*/
interface DateTimeFormatBuilder.WithYearMonth : DateTimeFormatBuilder {
fun year(padding: Padding = Padding.ZERO)
fun monthNumber(padding: Padding = Padding.ZERO)
}/**
* Padding style for formatting numeric components
*/
enum class Padding {
/** No padding */
NONE,
/** Pad with zeros */
ZERO,
/** Pad with spaces */
SPACE
}Factory functions for creating custom formats using the DSL.
/**
* Create a LocalDate format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for LocalDate
*/
fun LocalDate.Format(
builder: DateTimeFormatBuilder.WithDate.() -> Unit
): DateTimeFormat<LocalDate>
/**
* Create a LocalTime format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for LocalTime
*/
fun LocalTime.Format(
builder: DateTimeFormatBuilder.WithTime.() -> Unit
): DateTimeFormat<LocalTime>
/**
* Create a LocalDateTime format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for LocalDateTime
*/
fun LocalDateTime.Format(
builder: DateTimeFormatBuilder.WithDateTime.() -> Unit
): DateTimeFormat<LocalDateTime>
/**
* Create a YearMonth format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for YearMonth
*/
fun YearMonth.Format(
builder: DateTimeFormatBuilder.WithYearMonth.() -> Unit
): DateTimeFormat<YearMonth>
/**
* Create a UtcOffset format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for UtcOffset
*/
fun UtcOffset.Format(
builder: DateTimeFormatBuilder.WithUtcOffset.() -> Unit
): DateTimeFormat<UtcOffset>
/**
* Create a DateTimeComponents format using the DSL
* @param builder DSL builder function
* @returns DateTimeFormat for DateTimeComponents
*/
fun DateTimeComponents.Format(
builder: DateTimeFormatBuilder.WithDateTime.() -> Unit
): DateTimeFormat<DateTimeComponents>Usage Examples:
import kotlinx.datetime.*
// Custom LocalDate format: "25/12/2023"
val customDateFormat = LocalDate.Format {
dayOfMonth()
char('/')
monthNumber()
char('/')
year()
}
val date = LocalDate(2023, 12, 25)
println(date.format(customDateFormat)) // "25/12/2023"
// Custom LocalTime format: "3:30:45 PM"
val customTimeFormat = LocalTime.Format {
hour(padding = Padding.NONE) // No leading zero
char(':')
minute()
char(':')
second()
char(' ')
amPmMarker()
}
val time = LocalTime(15, 30, 45)
println(time.format(customTimeFormat)) // "3:30:45 PM"
// Custom LocalDateTime format with milliseconds: "2023-12-25 15:30:45.123"
val customDateTimeFormat = LocalDateTime.Format {
year()
char('-')
monthNumber()
char('-')
dayOfMonth()
char(' ')
hour()
char(':')
minute()
char(':')
second()
char('.')
secondFraction(minLength = 3, maxLength = 3) // Always 3 digits
}
val dateTime = LocalDateTime(2023, 12, 25, 15, 30, 45, 123000000)
println(dateTime.format(customDateTimeFormat)) // "2023-12-25 15:30:45.123"
// Complex format with day of week: "Monday, December 25, 2023"
val verboseFormat = LocalDate.Format {
// Note: This would require additional enum formatting support
monthNumber() // Would need month name support in actual implementation
char(' ')
dayOfMonth()
chars(", ")
year()
}Container class for holding individual date/time components during formatting operations.
/**
* Container for date/time components used in formatting
* Holds individual components that can be formatted independently
*/
class DateTimeComponents {
// Date components
var year: Int?
var month: Int?
var dayOfMonth: Int?
var dayOfYear: Int?
var dayOfWeek: Int?
// Time components
var hour: Int?
var minute: Int?
var second: Int?
var nanosecond: Int?
// Offset component
var offsetSeconds: Int?
/**
* Convert to LocalDate if date components are available
* @returns LocalDate or null if insufficient components
*/
fun toLocalDate(): LocalDate?
/**
* Convert to LocalTime if time components are available
* @returns LocalTime or null if insufficient components
*/
fun toLocalTime(): LocalTime?
/**
* Convert to LocalDateTime if date and time components are available
* @returns LocalDateTime or null if insufficient components
*/
fun toLocalDateTime(): LocalDateTime?
/**
* Convert to UtcOffset if offset component is available
* @returns UtcOffset or null if offset not set
*/
fun toUtcOffset(): UtcOffset?
}Special formatting for Instant values that require offset specification.
/**
* Format an Instant using DateTimeComponents format with specified offset
* @param format DateTimeComponents format to use
* @param offset UTC offset for formatting the instant
* @returns Formatted string
*/
fun Instant.format(format: DateTimeFormat<DateTimeComponents>, offset: UtcOffset): String
/**
* Parse an Instant from string using DateTimeComponents format
* @param input String to parse
* @param format DateTimeComponents format to use
* @returns Parsed Instant
*/
fun Instant.Companion.parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): InstantUsage Examples:
import kotlinx.datetime.*
import kotlin.time.Clock
// Create a custom format for Instant
val instantFormat = DateTimeComponents.Format {
year()
char('-')
monthNumber()
char('-')
dayOfMonth()
char('T')
hour()
char(':')
minute()
char(':')
second()
offset(UtcOffsetFormat.ISO)
}
val now = Clock.System.now()
val offset = UtcOffset(hours = 5, minutes = 30)
// Format instant with offset
val formatted = now.format(instantFormat, offset)
println(formatted) // "2023-12-25T20:30:45+05:30"
// Parse back to instant
val parsed = Instant.parse(formatted, instantFormat)import kotlinx.datetime.*
// Format that shows seconds only when non-zero
val conditionalTimeFormat = LocalTime.Format {
hour()
char(':')
minute()
// In a real implementation, this would need conditional support
// Currently not directly supported, would need custom format implementation
}
// Work around with multiple formats for different cases
val timeWithSeconds = LocalTime.Format {
hour()
char(':')
minute()
char(':')
second()
}
val timeWithoutSeconds = LocalTime.Format {
hour()
char(':')
minute()
}
fun formatTimeConditionally(time: LocalTime): String {
return if (time.second == 0 && time.nanosecond == 0) {
time.format(timeWithoutSeconds)
} else {
time.format(timeWithSeconds)
}
}
val time1 = LocalTime(15, 30) // No seconds
val time2 = LocalTime(15, 30, 45) // Has seconds
println(formatTimeConditionally(time1)) // "15:30"
println(formatTimeConditionally(time2)) // "15:30:45"While the core library focuses on ISO formats, custom localized formats can be created:
import kotlinx.datetime.*
// German date format: DD.MM.YYYY
val germanDateFormat = LocalDate.Format {
dayOfMonth()
char('.')
monthNumber()
char('.')
year()
}
// US date format: MM/DD/YYYY
val usDateFormat = LocalDate.Format {
monthNumber()
char('/')
dayOfMonth()
char('/')
year()
}
val date = LocalDate(2023, 12, 25)
println("German: ${date.format(germanDateFormat)}") // "25.12.2023"
println("US: ${date.format(usDateFormat)}") // "12/25/2023"
// 24-hour vs 12-hour time
val time24Format = LocalTime.Format {
hour()
char(':')
minute()
}
val time12Format = LocalTime.Format {
hour() // Would need 12-hour conversion logic
char(':')
minute()
char(' ')
amPmMarker()
}
val time = LocalTime(15, 30)
println("24-hour: ${time.format(time24Format)}") // "15:30"
// 12-hour would need additional conversion logicimport kotlinx.datetime.*
// ISO week date format (would need additional support)
val isoWeekFormat = LocalDate.Format {
year()
char('-')
chars("W")
// Would need week-of-year support
dayOfWeek()
}
// Ordinal date format: YYYY-DDD
val ordinalFormat = LocalDate.Format {
year()
char('-')
dayOfYear(padding = Padding.ZERO) // Pad to 3 digits
}
val date = LocalDate(2023, 12, 25)
println("Ordinal: ${date.format(ordinalFormat)}") // "2023-359"
// Custom verbose format with literals
val verboseFormat = LocalDate.Format {
chars("Year ")
year()
chars(", Month ")
monthNumber()
chars(", Day ")
dayOfMonth()
}
println("Verbose: ${date.format(verboseFormat)}") // "Year 2023, Month 12, Day 25"Parsing operations can fail and throw exceptions:
/**
* Thrown when date/time parsing fails
*/
class DateTimeParseException : RuntimeException {
constructor(message: String)
constructor(message: String, cause: Throwable?)
}Error Handling Examples:
import kotlinx.datetime.*
val format = LocalDate.Format {
year()
char('-')
monthNumber()
char('-')
dayOfMonth()
}
// Safe parsing
val input = "2023-13-45" // Invalid date
val result = format.parseOrNull(input) // Returns null instead of throwing
if (result == null) {
println("Failed to parse: $input")
} else {
println("Parsed: $result")
}
// Exception-based parsing
try {
val parsed = format.parse(input)
println("Parsed: $parsed")
} catch (e: DateTimeParseException) {
println("Parse error: ${e.message}")
}
// Validate format before parsing
fun safeParse(input: String, format: DateTimeFormat<LocalDate>): LocalDate? {
return try {
format.parse(input)
} catch (e: Exception) {
null
}
}Generate DSL code for existing formats:
import kotlinx.datetime.*
// Generate code for predefined format
val isoCode = DateTimeFormat.formatAsKotlinBuilderDsl(LocalDate.Formats.ISO)
println("ISO format DSL:")
println(isoCode)
// This would output something like:
// LocalDate.Format {
// year()
// char('-')
// monthNumber()
// char('-')
// dayOfMonth()
// }Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-datetime-iosx64