A multiplatform Kotlin library for working with date and time, specifically the iOS x64 target variant
—
Date and time arithmetic operations using units, periods, and duration-based calculations. The library provides flexible arithmetic capabilities for both precise time-based operations and calendar-aware date operations.
Sealed class hierarchy representing units for measuring time intervals, supporting both precise time-based units and calendar-based units.
/**
* Units for measuring time intervals
* Base sealed class for all time measurement units
*/
sealed class DateTimeUnit {
/**
* Multiply this unit by a scalar value
* @param scalar Multiplier for the unit
* @returns New DateTimeUnit representing the scaled unit
*/
abstract fun times(scalar: Int): DateTimeUnit
}Time-Based Units:
/**
* Time-based unit with precise nanosecond duration
* Represents units that have a fixed duration in nanoseconds
*/
class TimeBased(val nanoseconds: Long) : DateTimeUnit() {
override fun times(scalar: Int): TimeBased
}
// Predefined time-based constants
object DateTimeUnit {
val NANOSECOND: TimeBased // 1 nanosecond
val MICROSECOND: TimeBased // 1,000 nanoseconds
val MILLISECOND: TimeBased // 1,000,000 nanoseconds
val SECOND: TimeBased // 1,000,000,000 nanoseconds
val MINUTE: TimeBased // 60 seconds
val HOUR: TimeBased // 60 minutes
}Date-Based Units:
/**
* Date-based unit (abstract base for calendar units)
* Represents units that vary in duration based on calendar context
*/
sealed class DateBased : DateTimeUnit()
/**
* Day-based unit representing a number of calendar days
* Duration varies based on DST transitions and leap seconds
*/
class DayBased(val days: Int) : DateBased() {
override fun times(scalar: Int): DayBased
}
/**
* Month-based unit representing a number of months
* Duration varies significantly based on month length and year
*/
class MonthBased(val months: Int) : DateBased() {
override fun times(scalar: Int): MonthBased
}
// Predefined date-based constants
object DateTimeUnit {
val DAY: DayBased // 1 calendar day
val WEEK: DayBased // 7 calendar days
val MONTH: MonthBased // 1 calendar month
val QUARTER: MonthBased // 3 calendar months
val YEAR: MonthBased // 12 calendar months
val CENTURY: MonthBased // 1200 calendar months
}Usage Examples:
import kotlinx.datetime.*
import kotlin.time.Clock
val now = Clock.System.now()
// Time-based arithmetic (precise)
val oneHourLater = now.plus(1, DateTimeUnit.HOUR, TimeZone.UTC)
val thirtyMinutes = now.plus(30, DateTimeUnit.MINUTE, TimeZone.UTC)
val fiveSeconds = now.plus(5, DateTimeUnit.SECOND, TimeZone.UTC)
// Custom time units
val twoHours = DateTimeUnit.HOUR.times(2)
val halfHour = DateTimeUnit.MINUTE.times(30)
// Date-based arithmetic (calendar-aware)
val tomorrow = now.plus(1, DateTimeUnit.DAY, TimeZone.of("America/New_York"))
val nextWeek = now.plus(1, DateTimeUnit.WEEK, TimeZone.UTC)
val nextMonth = now.plus(1, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())Represents a combination of date and time components for complex arithmetic operations.
/**
* Combination of date and time components
* Represents a period with separate year/month/day/hour/minute/second/nanosecond parts
*/
sealed class DateTimePeriod {
/** Whole years */
abstract val years: Int
/** Months not forming whole years (-11..11) */
abstract val months: Int
/** Calendar days (can exceed month boundaries) */
abstract val days: Int
/** Whole hours (can exceed day boundaries) */
abstract val hours: Int
/** Minutes not forming whole hours (-59..59) */
abstract val minutes: Int
/** Seconds not forming whole minutes (-59..59) */
abstract val seconds: Int
/** Nanoseconds not forming whole seconds */
abstract val nanoseconds: Int
}Factory Functions:
/**
* Create a DateTimePeriod with specified components
* @param years Number of years (default 0)
* @param months Number of months (default 0)
* @param days Number of days (default 0)
* @param hours Number of hours (default 0)
* @param minutes Number of minutes (default 0)
* @param seconds Number of seconds (default 0)
* @param nanoseconds Number of nanoseconds (default 0)
* @returns DateTimePeriod with the specified components
*/
fun DateTimePeriod(
years: Int = 0,
months: Int = 0,
days: Int = 0,
hours: Int = 0,
minutes: Int = 0,
seconds: Int = 0,
nanoseconds: Long = 0
): DateTimePeriod
/**
* Parse DateTimePeriod from ISO 8601 duration string
* @param text ISO 8601 duration string (e.g., "P1Y2M3DT4H5M6.7S")
* @returns Parsed DateTimePeriod
*/
fun DateTimePeriod.Companion.parse(text: String): DateTimePeriod
/**
* Convert kotlin.time.Duration to DateTimePeriod
* @returns DateTimePeriod representing the duration
*/
fun Duration.toDateTimePeriod(): DateTimePeriodFormatting:
/**
* Convert to ISO 8601 duration format
* @returns ISO 8601 duration string (e.g., "P1Y2M3DT4H5M6.789S")
*/
override fun DateTimePeriod.toString(): StringUsage Examples:
import kotlinx.datetime.*
import kotlin.time.*
// Create periods
val complexPeriod = DateTimePeriod(
years = 1,
months = 2,
days = 15,
hours = 8,
minutes = 30,
seconds = 45
)
val shortPeriod = DateTimePeriod(hours = 2, minutes = 30)
// From Duration
val duration = 2.hours + 30.minutes
val period = duration.toDateTimePeriod()
// Parse from ISO 8601
val parsed = DateTimePeriod.parse("P1Y2M15DT8H30M45S")
// Format as ISO 8601
println(complexPeriod.toString()) // "P1Y2M15DT8H30M45S"
// Use in arithmetic
val now = Clock.System.now()
val timeZone = TimeZone.of("Europe/Paris")
val future = now.plus(complexPeriod, timeZone)Specialized period class for date-only arithmetic operations.
/**
* Date-only period (time components are zero)
* Extends DateTimePeriod with only date components
*/
class DatePeriod : DateTimePeriod {
/**
* Create a date period with specified components
* @param years Number of years (default 0)
* @param months Number of months (default 0)
* @param days Number of days (default 0)
*/
constructor(years: Int = 0, months: Int = 0, days: Int = 0)
companion object {
/**
* Parse DatePeriod from ISO 8601 date duration string
* @param text ISO 8601 date duration (e.g., "P1Y2M15D")
* @returns Parsed DatePeriod
*/
fun parse(text: String): DatePeriod
}
}Usage Examples:
import kotlinx.datetime.*
// Create date periods
val oneYear = DatePeriod(years = 1)
val twoMonths = DatePeriod(months = 2)
val fifteenDays = DatePeriod(days = 15)
val combined = DatePeriod(years = 1, months = 6, days = 10)
// Parse from ISO 8601
val parsed = DatePeriod.parse("P1Y6M10D")
// Use with LocalDate
val today = LocalDate(2023, 12, 25)
val nextYear = today.plus(oneYear)
val futureDate = today.plus(combined)
// Use with Instant (requires time zone for calendar arithmetic)
val now = Clock.System.now()
val timeZone = TimeZone.of("America/New_York")
val futureInstant = now.plus(combined, timeZone)Extension functions for performing arithmetic on Instant values.
/**
* Add a DateTimePeriod to an Instant in the specified time zone
* @param period Period to add
* @param timeZone Time zone for calendar calculations
* @returns New Instant representing the result
*/
fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant
/**
* Subtract a DateTimePeriod from an Instant in the specified time zone
* @param period Period to subtract
* @param timeZone Time zone for calendar calculations
* @returns New Instant representing the result
*/
fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant
/**
* Add a value in the specified unit to an Instant
* @param value Amount to add
* @param unit Unit for the arithmetic
* @param timeZone Time zone for calendar-based units
* @returns New Instant representing the result
*/
fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant
fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant
/**
* Subtract a value in the specified unit from an Instant
* @param value Amount to subtract
* @param unit Unit for the arithmetic
* @param timeZone Time zone for calendar-based units
* @returns New Instant representing the result
*/
fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant
fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant/**
* Calculate the difference between two instants in the specified unit
* @param other The other instant
* @param unit Unit for the result
* @param timeZone Time zone for calendar-based calculations
* @returns Difference in the specified unit
*/
fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long
/**
* Calculate the number of whole days between two instants
* @param other The other instant
* @param timeZone Time zone for day calculations
* @returns Number of days
*/
fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int
/**
* Calculate the number of whole months between two instants
* @param other The other instant
* @param timeZone Time zone for month calculations
* @returns Number of months
*/
fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int
/**
* Calculate the number of whole years between two instants
* @param other The other instant
* @param timeZone Time zone for year calculations
* @returns Number of years
*/
fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int
/**
* Calculate the period between two instants
* @param other The other instant
* @param timeZone Time zone for calculations
* @returns DateTimePeriod representing the difference
*/
fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriodUsage Examples:
import kotlinx.datetime.*
import kotlin.time.Clock
val now = Clock.System.now()
val timeZone = TimeZone.of("Europe/London")
// Basic arithmetic with periods
val period = DateTimePeriod(years = 1, months = 6, days = 15, hours = 12)
val future = now.plus(period, timeZone)
val past = now.minus(period, timeZone)
// Arithmetic with units
val tomorrow = now.plus(1, DateTimeUnit.DAY, timeZone)
val nextHour = now.plus(1, DateTimeUnit.HOUR, timeZone)
val nextMonth = now.plus(1, DateTimeUnit.MONTH, timeZone)
// Calculate differences
val startOfYear = LocalDate(2023, 1, 1).atStartOfDayIn(timeZone)
val endOfYear = LocalDate(2023, 12, 31).atTime(23, 59, 59).toInstant(timeZone)
val daysInYear = startOfYear.daysUntil(endOfYear, timeZone)
val monthsInYear = startOfYear.monthsUntil(endOfYear, timeZone)
val fullPeriod = startOfYear.periodUntil(endOfYear, timeZone)
println("Days: $daysInYear") // 364
println("Months: $monthsInYear") // 11
println("Period: $fullPeriod") // P11M30D23H59M59SArithmetic operations specifically for LocalDate values.
/**
* Add a DatePeriod to a LocalDate
* @param period Date period to add
* @returns New LocalDate representing the result
*/
fun LocalDate.plus(period: DatePeriod): LocalDate
/**
* Subtract a DatePeriod from a LocalDate
* @param period Date period to subtract
* @returns New LocalDate representing the result
*/
fun LocalDate.minus(period: DatePeriod): LocalDate
/**
* Add a value in the specified date-based unit
* @param value Amount to add
* @param unit Date-based unit (DayBased or MonthBased)
* @returns New LocalDate representing the result
*/
fun LocalDate.plus(value: Int, unit: DateTimeUnit.DateBased): LocalDate
fun LocalDate.plus(value: Long, unit: DateTimeUnit.DateBased): LocalDate
/**
* Subtract a value in the specified date-based unit
* @param value Amount to subtract
* @param unit Date-based unit (DayBased or MonthBased)
* @returns New LocalDate representing the result
*/
fun LocalDate.minus(value: Int, unit: DateTimeUnit.DateBased): LocalDate
fun LocalDate.minus(value: Long, unit: DateTimeUnit.DateBased): LocalDate
/**
* Calculate difference between dates in the specified unit
* @param other The other date
* @param unit Date-based unit for the result
* @returns Difference in the specified unit
*/
fun LocalDate.until(other: LocalDate, unit: DateTimeUnit.DateBased): Long
/**
* Calculate whole days between dates
* @param other The other date
* @returns Number of days
*/
fun LocalDate.daysUntil(other: LocalDate): Int
/**
* Calculate whole months between dates
* @param other The other date
* @returns Number of months
*/
fun LocalDate.monthsUntil(other: LocalDate): Int
/**
* Calculate whole years between dates
* @param other The other date
* @returns Number of years
*/
fun LocalDate.yearsUntil(other: LocalDate): Int
/**
* Calculate the date period between two dates
* @param other The other date
* @returns DatePeriod representing the difference
*/
fun LocalDate.periodUntil(other: LocalDate): DatePeriodUsage Examples:
import kotlinx.datetime.*
val startDate = LocalDate(2023, 1, 15)
// Add/subtract periods
val period = DatePeriod(years = 1, months = 6, days = 10)
val futureDate = startDate.plus(period) // 2024-07-25
val pastDate = startDate.minus(period) // 2021-07-05
// Add/subtract units
val tomorrow = startDate.plus(1, DateTimeUnit.DAY) // 2023-01-16
val nextWeek = startDate.plus(1, DateTimeUnit.WEEK) // 2023-01-22
val nextMonth = startDate.plus(1, DateTimeUnit.MONTH) // 2023-02-15
val nextYear = startDate.plus(1, DateTimeUnit.YEAR) // 2024-01-15
// Calculate differences
val endDate = LocalDate(2024, 7, 25)
val daysDiff = startDate.daysUntil(endDate) // 556
val monthsDiff = startDate.monthsUntil(endDate) // 18
val yearsDiff = startDate.yearsUntil(endDate) // 1
val fullPeriod = startDate.periodUntil(endDate) // P1Y6M10DSpecialized arithmetic for YearMonth values.
/**
* Add months to a YearMonth
* @param value Number of months to add
* @param unit Must be MonthBased unit
* @returns New YearMonth representing the result
*/
fun YearMonth.plus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth
fun YearMonth.plus(value: Long, unit: DateTimeUnit.MonthBased): YearMonth
/**
* Subtract months from a YearMonth
* @param value Number of months to subtract
* @param unit Must be MonthBased unit
* @returns New YearMonth representing the result
*/
fun YearMonth.minus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth
fun YearMonth.minus(value: Long, unit: DateTimeUnit.MonthBased): YearMonth
/**
* Calculate difference in months
* @param other The other YearMonth
* @param unit MonthBased unit for the result
* @returns Difference in the specified unit
*/
fun YearMonth.until(other: YearMonth, unit: DateTimeUnit.MonthBased): Long
/**
* Calculate whole months between YearMonth values
* @param other The other YearMonth
* @returns Number of months
*/
fun YearMonth.monthsUntil(other: YearMonth): Int
/**
* Calculate whole years between YearMonth values
* @param other The other YearMonth
* @returns Number of years
*/
fun YearMonth.yearsUntil(other: YearMonth): Int
// Convenience functions
fun YearMonth.plusYear(): YearMonth
fun YearMonth.minusYear(): YearMonth
fun YearMonth.plusMonth(): YearMonth
fun YearMonth.minusMonth(): YearMonthUsage Examples:
import kotlinx.datetime.*
val startMonth = YearMonth(2023, 6) // June 2023
// Add/subtract using units
val nextYear = startMonth.plus(1, DateTimeUnit.YEAR) // 2024-06
val nextQuarter = startMonth.plus(1, DateTimeUnit.QUARTER) // 2023-09
val nextMonth = startMonth.plus(1, DateTimeUnit.MONTH) // 2023-07
// Convenience functions
val plusYear = startMonth.plusYear() // 2024-06
val plusMonth = startMonth.plusMonth() // 2023-07
val minusYear = startMonth.minusYear() // 2022-06
// Calculate differences
val endMonth = YearMonth(2025, 12) // December 2025
val monthsDiff = startMonth.monthsUntil(endMonth) // 30
val yearsDiff = startMonth.yearsUntil(endMonth) // 2Arithmetic operations can throw DateTimeArithmeticException in certain scenarios.
/**
* Thrown when datetime arithmetic operations fail
* Typically occurs with invalid date calculations
*/
class DateTimeArithmeticException : RuntimeException {
constructor(message: String)
constructor(message: String, cause: Throwable?)
}Common Error Scenarios:
import kotlinx.datetime.*
try {
// This can throw if the result would be invalid
val leap = LocalDate(2020, 2, 29) // Feb 29 in leap year
val nextYear = leap.plus(1, DateTimeUnit.YEAR) // Would be Feb 29, 2021 (invalid)
} catch (e: DateTimeArithmeticException) {
println("Arithmetic failed: ${e.message}")
}
// Safe alternatives - check before arithmetic
val date = LocalDate(2020, 2, 29)
val targetYear = 2021
// Check if the target date would be valid
val wouldBeValid = try {
LocalDate(targetYear, date.month, date.day)
true
} catch (e: Exception) {
false
}
if (wouldBeValid) {
val result = date.plus(1, DateTimeUnit.YEAR)
} else {
// Handle invalid date case (e.g., use last day of month)
val lastDayOfMonth = YearMonth(targetYear, date.month).lastDay
println("Using last day of month instead: $lastDayOfMonth")
}When performing arithmetic with Instant values, the choice of time zone affects calendar-based calculations during DST transitions.
import kotlinx.datetime.*
val timeZone = TimeZone.of("America/New_York")
// Spring forward example (2023-03-12 02:00 -> 03:00)
val beforeSpring = LocalDateTime(2023, 3, 12, 1, 0).toInstant(timeZone)
// Adding 24 hours vs adding 1 day
val plus24Hours = beforeSpring.plus(24, DateTimeUnit.HOUR, timeZone)
val plus1Day = beforeSpring.plus(1, DateTimeUnit.DAY, timeZone)
val local24Hours = plus24Hours.toLocalDateTime(timeZone) // 2:00 AM (due to DST)
val local1Day = plus1Day.toLocalDateTime(timeZone) // 1:00 AM (same time next day)
println("24 hours later: $local24Hours") // 2023-03-13T02:00
println("1 day later: $local1Day") // 2023-03-13T01:00
// Fall back example (2023-11-05 02:00 -> 01:00)
val beforeFall = LocalDateTime(2023, 11, 5, 1, 0).toInstant(timeZone)
val fallPlus24Hours = beforeFall.plus(24, DateTimeUnit.HOUR, timeZone)
val fallPlus1Day = beforeFall.plus(1, DateTimeUnit.DAY, timeZone)
val fallLocal24Hours = fallPlus24Hours.toLocalDateTime(timeZone) // 12:00 AM (due to DST)
val fallLocal1Day = fallPlus1Day.toLocalDateTime(timeZone) // 1:00 AM (same time next day)Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-datetime-iosx64