A multiplatform Kotlin library for working with date and time, specifically the iOS x64 target variant
—
Range and progression support for date types, enabling iteration, membership testing, and range operations with step control. The library provides specialized range types for LocalDate and YearMonth with comprehensive progression capabilities.
Range implementation for LocalDate values, supporting inclusive ranges and iteration.
/**
* Range of LocalDate values with inclusive bounds
* Extends LocalDateProgression with range semantics
*/
class LocalDateRange : LocalDateProgression, ClosedRange<LocalDate> {
/** Lower bound of the range (inclusive) */
override val start: LocalDate
/** Upper bound of the range (inclusive) */
override val endInclusive: LocalDate
/**
* Check if the range contains no elements
* @returns true if start > endInclusive
*/
override fun isEmpty(): Boolean
companion object {
/** Empty range constant */
val EMPTY: LocalDateRange
}
}Creation:
/**
* Create inclusive range from start to end
* @param that End date (inclusive)
* @returns LocalDateRange from this to that
*/
infix fun LocalDate.rangeTo(that: LocalDate): LocalDateRange
/**
* Create exclusive range from start to end
* @param that End date (exclusive)
* @returns LocalDateRange from this to that-1
*/
infix fun LocalDate.rangeUntil(that: LocalDate): LocalDateRangeUsage Examples:
import kotlinx.datetime.*
val start = LocalDate(2023, 12, 1)
val end = LocalDate(2023, 12, 31)
// Create ranges
val inclusiveRange = start..end // December 1-31, 2023
val exclusiveRange = start.rangeUntil(end) // December 1-30, 2023
val usingRangeTo = start.rangeTo(end) // Same as start..end
// Properties
println("Start: ${inclusiveRange.start}") // 2023-12-01
println("End: ${inclusiveRange.endInclusive}") // 2023-12-31
println("Empty: ${inclusiveRange.isEmpty()}") // false
// Empty range
val emptyRange = LocalDateRange.EMPTY
println("Empty: ${emptyRange.isEmpty()}") // true
// Membership testing
val christmas = LocalDate(2023, 12, 25)
val newYear = LocalDate(2024, 1, 1)
println("Christmas in range: ${christmas in inclusiveRange}") // true
println("New Year in range: ${newYear in inclusiveRange}") // false
// Iteration
println("First week of December:")
for (date in start..LocalDate(2023, 12, 7)) {
println(" $date (${date.dayOfWeek})")
}Progression implementation that supports step-based iteration over LocalDate values.
/**
* Progression of LocalDate values with configurable step
* Supports forward and backward iteration with date-based steps
*/
open class LocalDateProgression : Iterable<LocalDate> {
/** First date in the progression */
val first: LocalDate
/** Last date that would be included (may not be reached if step doesn't align) */
val last: LocalDate
/** Number of elements in the progression */
val size: Int
/**
* Create reversed progression
* @returns LocalDateProgression in opposite direction
*/
fun reversed(): LocalDateProgression
/**
* Change the step of the progression
* @param value Step amount
* @param unit Date-based unit for the step
* @returns New progression with specified step
*/
fun step(value: Int, unit: DateTimeUnit.DayBased): LocalDateProgression
fun step(value: Long, unit: DateTimeUnit.DayBased): LocalDateProgression
/**
* Get a random element from the progression
* @param random Random number generator
* @returns Random LocalDate from the progression
*/
fun random(random: Random = Random.Default): LocalDate
/**
* Check if a date is contained in this progression
* @param value Date to check
* @returns true if the date is part of this progression
*/
operator fun contains(value: LocalDate): Boolean
/**
* Iterator for stepping through the progression
* @returns Iterator over LocalDate values
*/
override fun iterator(): Iterator<LocalDate>
}Creation Functions:
/**
* Create descending progression from this date to that date
* @param that End date
* @returns LocalDateProgression stepping backward by days
*/
infix fun LocalDate.downTo(that: LocalDate): LocalDateProgressionUsage Examples:
import kotlinx.datetime.*
val start = LocalDate(2023, 12, 1)
val end = LocalDate(2023, 12, 31)
// Basic progression (daily steps)
val dailyProgression = start..end
println("Size: ${dailyProgression.size}") // 31 days
// Reverse progression
val reverseProgression = dailyProgression.reversed()
println("Reverse first: ${reverseProgression.first}") // 2023-12-31
println("Reverse last: ${reverseProgression.last}") // 2023-12-01
// Descending progression
val countdown = end.downTo(start)
println("Countdown from ${countdown.first} to ${countdown.last}")
// Weekly progression (every 7 days)
val weeklyProgression = (start..end).step(7, DateTimeUnit.DAY)
println("Weekly progression:")
for (date in weeklyProgression) {
println(" $date") // Every Sunday in December 2023
}
// Bi-weekly progression (every 14 days)
val biWeeklyProgression = (start..end).step(14, DateTimeUnit.DAY)
// Random selection
val randomDate = dailyProgression.random()
println("Random date in December: $randomDate")
// Membership testing
println("Christmas in progression: ${LocalDate(2023, 12, 25) in dailyProgression}") // true
// Custom step with different units (if extending to month-based)
val monthlyDates = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 1)
// Note: Monthly stepping would require DateTimeUnit.MonthBased support in actual implementationRange implementation for YearMonth values, supporting month-based ranges.
/**
* Range of YearMonth values with inclusive bounds
* Extends YearMonthProgression with range semantics
*/
class YearMonthRange : YearMonthProgression, ClosedRange<YearMonth> {
/** Lower bound of the range (inclusive) */
override val start: YearMonth
/** Upper bound of the range (inclusive) */
override val endInclusive: YearMonth
/**
* Check if the range contains no elements
* @returns true if start > endInclusive
*/
override fun isEmpty(): Boolean
companion object {
/** Empty range constant */
val EMPTY: YearMonthRange
}
}Creation:
/**
* Create inclusive range from start to end
* @param that End YearMonth (inclusive)
* @returns YearMonthRange from this to that
*/
infix fun YearMonth.rangeTo(that: YearMonth): YearMonthRange
/**
* Create exclusive range from start to end
* @param that End YearMonth (exclusive)
* @returns YearMonthRange from this to that-1month
*/
infix fun YearMonth.rangeUntil(that: YearMonth): YearMonthRangeUsage Examples:
import kotlinx.datetime.*
val startMonth = YearMonth(2023, 1) // January 2023
val endMonth = YearMonth(2023, 12) // December 2023
// Create ranges
val yearRange = startMonth..endMonth // All of 2023
val firstHalf = startMonth.rangeUntil(YearMonth(2023, 7)) // Jan-June 2023
// Properties
println("Start: ${yearRange.start}") // 2023-01
println("End: ${yearRange.endInclusive}") // 2023-12
println("Size: ${yearRange.size}") // 12
// Membership testing
val summer = YearMonth(2023, 7)
val nextYear = YearMonth(2024, 1)
println("July in range: ${summer in yearRange}") // true
println("Next year in range: ${nextYear in yearRange}") // false
// Iteration
println("First quarter:")
for (month in YearMonth(2023, 1)..YearMonth(2023, 3)) {
println(" $month - ${month.numberOfDays} days")
}Progression implementation for YearMonth values with month-based stepping.
/**
* Progression of YearMonth values with configurable step
* Supports forward and backward iteration with month-based steps
*/
open class YearMonthProgression : Iterable<YearMonth> {
/** First month in the progression */
val first: YearMonth
/** Last month that would be included */
val last: YearMonth
/** Number of elements in the progression */
val size: Int
/**
* Create reversed progression
* @returns YearMonthProgression in opposite direction
*/
fun reversed(): YearMonthProgression
/**
* Change the step of the progression
* @param value Step amount
* @param unit Month-based unit for the step
* @returns New progression with specified step
*/
fun step(value: Int, unit: DateTimeUnit.MonthBased): YearMonthProgression
fun step(value: Long, unit: DateTimeUnit.MonthBased): YearMonthProgression
/**
* Get a random element from the progression
* @param random Random number generator
* @returns Random YearMonth from the progression
*/
fun random(random: Random = Random.Default): YearMonth
/**
* Check if a YearMonth is contained in this progression
* @param value YearMonth to check
* @returns true if the YearMonth is part of this progression
*/
operator fun contains(value: YearMonth): Boolean
/**
* Iterator for stepping through the progression
* @returns Iterator over YearMonth values
*/
override fun iterator(): Iterator<YearMonth>
}Creation Functions:
/**
* Create descending progression from this month to that month
* @param that End month
* @returns YearMonthProgression stepping backward by months
*/
infix fun YearMonth.downTo(that: YearMonth): YearMonthProgressionUsage Examples:
import kotlinx.datetime.*
val startMonth = YearMonth(2023, 1)
val endMonth = YearMonth(2025, 12)
// Basic progression (monthly steps)
val monthlyProgression = startMonth..endMonth
println("Total months: ${monthlyProgression.size}") // 36 months
// Reverse progression
val reverseMonths = monthlyProgression.reversed()
println("Reverse: ${reverseMonths.first} to ${reverseMonths.last}")
// Descending progression
val countdown = endMonth.downTo(startMonth)
// Quarterly progression (every 3 months)
val quarterlyProgression = (startMonth..endMonth).step(3, DateTimeUnit.MONTH)
println("Quarterly progression:")
for (month in quarterlyProgression) {
println(" $month (Q${(month.month.number - 1) / 3 + 1} ${month.year})")
}
// Yearly progression (every 12 months)
val yearlyProgression = (startMonth..endMonth).step(12, DateTimeUnit.MONTH)
println("Yearly progression:")
for (month in yearlyProgression) {
println(" $month") // January of each year
}
// Semi-annual progression (every 6 months)
val semiAnnualProgression = (startMonth..endMonth).step(6, DateTimeUnit.MONTH)
// Random selection
val randomMonth = monthlyProgression.random()
println("Random month: $randomMonth")import kotlinx.datetime.*
// Utility functions for working with date ranges
fun LocalDateRange.weekends(): List<LocalDate> {
return this.filter { it.dayOfWeek == DayOfWeek.SATURDAY || it.dayOfWeek == DayOfWeek.SUNDAY }
}
fun LocalDateRange.weekdays(): List<LocalDate> {
return this.filter { it.dayOfWeek !in listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) }
}
fun LocalDateRange.monthBoundaries(): List<LocalDate> {
return this.filter { it.day == 1 || it == it.yearMonth.lastDay }
}
// Usage
val december = LocalDate(2023, 12, 1)..LocalDate(2023, 12, 31)
val weekendDays = december.weekends()
println("Weekend days in December: ${weekendDays.size}")
val workDays = december.weekdays()
println("Work days in December: ${workDays.size}")
// Month boundaries across a longer range
val yearRange = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 31)
val monthBounds = yearRange.monthBoundaries()
println("Month boundaries in 2023: ${monthBounds.size}") // 24 (first and last day of each month)import kotlinx.datetime.*
// Extension function for business day progression (Mon-Fri only)
fun LocalDate.businessDaysTo(endDate: LocalDate): Sequence<LocalDate> {
return generateSequence(this) { current ->
val next = current.plus(1, DateTimeUnit.DAY)
if (next <= endDate) {
// Skip to next weekday if next is weekend
when (next.dayOfWeek) {
DayOfWeek.SATURDAY -> next.plus(2, DateTimeUnit.DAY)
DayOfWeek.SUNDAY -> next.plus(1, DateTimeUnit.DAY)
else -> next
}
} else null
}.takeWhile { it <= endDate }
}
// Usage
val startDate = LocalDate(2023, 12, 1) // Friday
val endDate = LocalDate(2023, 12, 15) // Friday
val businessDays = startDate.businessDaysTo(endDate).toList()
println("Business days:")
businessDays.forEach { date ->
println(" $date (${date.dayOfWeek})")
}
// Extension for nth day of month progression
fun YearMonth.nthDayProgression(dayOfMonth: Int, months: Int): Sequence<LocalDate> {
return generateSequence(0) { it + 1 }
.take(months)
.map { this.plus(it, DateTimeUnit.MONTH) }
.mapNotNull { yearMonth ->
try {
LocalDate(yearMonth.year, yearMonth.month, dayOfMonth)
} catch (e: Exception) {
// Handle cases where day doesn't exist (e.g., Feb 30)
null
}
}
}
// Usage: Every 15th of the month for a year
val base = YearMonth(2023, 1)
val fifteenthOfMonths = base.nthDayProgression(15, 12).toList()
println("15th of each month in 2023:")
fifteenthOfMonths.forEach { println(" $it") }import kotlinx.datetime.*
// Extension functions for range operations
fun LocalDateRange.intersect(other: LocalDateRange): LocalDateRange? {
val start = maxOf(this.start, other.start)
val end = minOf(this.endInclusive, other.endInclusive)
return if (start <= end) start..end else null
}
fun LocalDateRange.union(other: LocalDateRange): LocalDateRange? {
// Only works for overlapping or adjacent ranges
val gap = when {
this.endInclusive < other.start -> other.start.minus(this.endInclusive.plus(1, DateTimeUnit.DAY))
other.endInclusive < this.start -> this.start.minus(other.endInclusive.plus(1, DateTimeUnit.DAY))
else -> DatePeriod() // Overlapping or adjacent
}
return if (gap.days <= 1) { // Adjacent or overlapping
minOf(this.start, other.start)..maxOf(this.endInclusive, other.endInclusive)
} else null
}
fun LocalDateRange.subtract(other: LocalDateRange): List<LocalDateRange> {
val intersection = this.intersect(other) ?: return listOf(this)
val ranges = mutableListOf<LocalDateRange>()
// Before intersection
if (this.start < intersection.start) {
ranges.add(this.start.rangeUntil(intersection.start))
}
// After intersection
if (intersection.endInclusive < this.endInclusive) {
ranges.add(intersection.endInclusive.plus(1, DateTimeUnit.DAY)..this.endInclusive)
}
return ranges
}
// Usage
val range1 = LocalDate(2023, 12, 1)..LocalDate(2023, 12, 15) // Dec 1-15
val range2 = LocalDate(2023, 12, 10)..LocalDate(2023, 12, 25) // Dec 10-25
val intersection = range1.intersect(range2)
println("Intersection: $intersection") // Dec 10-15
val union = range1.union(range2)
println("Union: $union") // Dec 1-25
val subtraction = range1.subtract(range2)
println("Range1 - Range2: $subtraction") // [Dec 1-9]import kotlinx.datetime.*
// For large ranges, prefer sequences over lists to avoid memory issues
fun LocalDateRange.asSequence(): Sequence<LocalDate> {
return generateSequence(this.start) { current ->
val next = current.plus(1, DateTimeUnit.DAY)
if (next <= this.endInclusive) next else null
}
}
// Efficient counting without iteration
fun LocalDateRange.count(): Int {
return if (isEmpty()) 0 else this.start.daysUntil(this.endInclusive) + 1
}
// Efficient nth element access
fun LocalDateRange.elementAt(index: Int): LocalDate? {
return if (index in 0 until count()) {
this.start.plus(index, DateTimeUnit.DAY)
} else null
}
// Usage for large ranges
val largeRange = LocalDate(2020, 1, 1)..LocalDate(2030, 12, 31) // 11 years
println("Large range size: ${largeRange.count()}") // ~4000 days
// Use sequence for memory-efficient processing
val weekendsCount = largeRange.asSequence()
.filter { it.dayOfWeek in listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) }
.count()
println("Weekends in large range: $weekendsCount")
// Direct access without iteration
val midpoint = largeRange.elementAt(largeRange.count() / 2)
println("Midpoint date: $midpoint")import kotlinx.datetime.*
// Safe range creation with validation
fun createSafeRange(start: LocalDate, end: LocalDate): LocalDateRange? {
return if (start <= end) {
start..end
} else {
null // Invalid range
}
}
// Range bounds checking
fun LocalDateRange.isValidRange(): Boolean {
return !isEmpty()
}
fun LocalDateRange.clampToRange(bounds: LocalDateRange): LocalDateRange? {
val clampedStart = maxOf(this.start, bounds.start)
val clampedEnd = minOf(this.endInclusive, bounds.endInclusive)
return if (clampedStart <= clampedEnd) {
clampedStart..clampedEnd
} else {
null // No overlap
}
}
// Usage
val validRange = createSafeRange(
LocalDate(2023, 12, 1),
LocalDate(2023, 12, 31)
) // Valid range
val invalidRange = createSafeRange(
LocalDate(2023, 12, 31),
LocalDate(2023, 12, 1)
) // null - invalid
// Bounds checking
val bounds = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 31) // Year 2023
val testRange = LocalDate(2022, 6, 1)..LocalDate(2024, 6, 1) // Spans multiple years
val clamped = testRange.clampToRange(bounds)
println("Clamped range: $clamped") // 2023-01-01..2023-12-31Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-datetime-iosx64