Skip to content

Commit 163b762

Browse files
committed
Experiment with extensible ChronoUnit instead of fixed enum of CalendarUnits
1 parent 741c6b3 commit 163b762

File tree

5 files changed

+81
-10
lines changed

5 files changed

+81
-10
lines changed

core/commonMain/src/CalendarPeriod.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,38 @@ enum class CalendarUnit {
8989
SECOND,
9090
NANOSECOND
9191
}
92+
93+
94+
enum class TimeComponent {
95+
MONTH,
96+
DAY,
97+
NANOSECOND
98+
}
99+
100+
class ChronoUnit(val scale: Long, val component: TimeComponent) {
101+
init {
102+
require(scale > 0) { "Unit scale must be positive, but was $scale" }
103+
}
104+
constructor(number: Long, unit: ChronoUnit) : this(number * unit.scale, unit.component)
105+
// it seems possible to provide 'times' operation
106+
companion object {
107+
val NANOSECOND = ChronoUnit(1, TimeComponent.NANOSECOND)
108+
val MICROSECOND = ChronoUnit(1000, NANOSECOND)
109+
val MILLISECOND = ChronoUnit(1000, MICROSECOND)
110+
val SECOND = ChronoUnit(1000, MILLISECOND)
111+
val MINUTE = ChronoUnit(60, SECOND)
112+
val HOUR = ChronoUnit(60, MINUTE)
113+
val DAY = ChronoUnit(1, TimeComponent.DAY)
114+
val WEEK = ChronoUnit(7, TimeComponent.DAY)
115+
val MONTH = ChronoUnit(1, TimeComponent.MONTH)
116+
val QUARTER = ChronoUnit(3, MONTH)
117+
val YEAR = ChronoUnit(12, MONTH)
118+
val CENTURY = ChronoUnit(100, YEAR)
119+
}
120+
}
121+
122+
internal fun TimeComponent.toCalendarUnit(): CalendarUnit = when(this) {
123+
TimeComponent.MONTH -> CalendarUnit.MONTH
124+
TimeComponent.DAY -> CalendarUnit.DAY
125+
TimeComponent.NANOSECOND -> CalendarUnit.NANOSECOND
126+
}

core/commonMain/src/Instant.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,15 @@ public expect fun Instant.yearsUntil(other: Instant, zone: TimeZone): Int
4646

4747
public fun Instant.minus(other: Instant, zone: TimeZone): CalendarPeriod = other.periodUntil(this, zone)
4848
public fun Instant.minus(other: Instant, unit: CalendarUnit, zone: TimeZone): Long = other.until(this, unit, zone)
49+
50+
51+
52+
public fun Instant.plus(unit: ChronoUnit, zone: TimeZone): Instant =
53+
plus(unit.scale, unit.component.toCalendarUnit(), zone)
54+
public fun Instant.plus(value: Int, unit: ChronoUnit, zone: TimeZone): Instant =
55+
plus(value * unit.scale, unit.component.toCalendarUnit(), zone)
56+
public fun Instant.plus(value: Long, unit: ChronoUnit, zone: TimeZone): Instant =
57+
plus(value * unit.scale, unit.component.toCalendarUnit(), zone)
58+
59+
public fun Instant.until(other: Instant, unit: ChronoUnit, zone: TimeZone): Long =
60+
until(other, unit.component.toCalendarUnit(), zone) / unit.scale

core/commonMain/src/LocalDate.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,18 @@ public fun LocalDate.until(other: LocalDate, unit: CalendarUnit): Int = when(uni
4141
CalendarUnit.DAY -> daysUntil(other)
4242
CalendarUnit.HOUR, CalendarUnit.MINUTE, CalendarUnit.SECOND, CalendarUnit.NANOSECOND ->
4343
throw UnsupportedOperationException("Only date based units can be used to express difference between LocalDate values.")
44-
}
44+
}
45+
46+
public fun LocalDate.plus(unit: ChronoUnit): LocalDate =
47+
plus(unit.scale, unit.component.toCalendarUnit())
48+
public fun LocalDate.plus(value: Int, unit: ChronoUnit): LocalDate =
49+
plus(value * unit.scale, unit.component.toCalendarUnit())
50+
public fun LocalDate.plus(value: Long, unit: ChronoUnit): LocalDate =
51+
plus(value * unit.scale, unit.component.toCalendarUnit())
52+
53+
public fun LocalDate.until(other: LocalDate, unit: ChronoUnit): Int = when(unit.component) {
54+
TimeComponent.MONTH -> (monthsUntil(other) / unit.scale).toInt()
55+
TimeComponent.DAY -> (daysUntil(other) / unit.scale).toInt()
56+
TimeComponent.NANOSECOND ->
57+
throw UnsupportedOperationException("Only date based units can be used to express difference between LocalDate values.")
58+
}

core/commonTest/src/InstantTest.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,28 @@ class InstantTest {
9393
assertEquals(24, instant1.until(instant2, CalendarUnit.HOUR, zone))
9494
assertEquals(24, instant2.minus(instant1, CalendarUnit.HOUR, zone))
9595

96-
val instant3 = instant1.plus(1, CalendarUnit.DAY, zone)
96+
val instant3 = instant1.plus(ChronoUnit.DAY, zone)
9797
checkComponents(instant3.toLocalDateTime(zone), 2019, 10, 28, 2, 59)
9898
assertEquals(25.hours, instant3 - instant1)
9999
assertEquals(1, instant1.until(instant3, CalendarUnit.DAY, zone))
100100
assertEquals(1, instant1.daysUntil(instant3, zone))
101101
assertEquals(1, instant3.minus(instant1, CalendarUnit.DAY, zone))
102102

103+
val instant4 = instant1.plus(14, ChronoUnit.MONTH, zone)
104+
checkComponents(instant4.toLocalDateTime(zone), 2020, 12, 27, 2, 59)
105+
assertEquals(1, instant1.until(instant4, ChronoUnit.YEAR, zone))
106+
assertEquals(4, instant1.until(instant4, ChronoUnit.QUARTER, zone))
107+
assertEquals(14, instant1.until(instant4, ChronoUnit.MONTH, zone))
108+
assertEquals(61, instant1.until(instant4, ChronoUnit.WEEK, zone))
109+
assertEquals(366 + 31 + 30, instant1.until(instant4, ChronoUnit.DAY, zone))
110+
assertEquals((366 + 31 + 30) * 24 + 1, instant1.until(instant4, ChronoUnit.HOUR, zone))
111+
103112
val period = CalendarPeriod(days = 1, hours = 1)
104-
val instant4 = instant1.plus(period, zone)
105-
checkComponents(instant4.toLocalDateTime(zone), 2019, 10, 28, 3, 59)
106-
assertEquals(period, instant1.periodUntil(instant4, zone))
107-
assertEquals(period, instant4.minus(instant1, zone))
108-
assertEquals(26.hours, instant4.minus(instant1))
113+
val instant5 = instant1.plus(period, zone)
114+
checkComponents(instant5.toLocalDateTime(zone), 2019, 10, 28, 3, 59)
115+
assertEquals(period, instant1.periodUntil(instant5, zone))
116+
assertEquals(period, instant5.minus(instant1, zone))
117+
assertEquals(26.hours, instant5.minus(instant1))
109118
}
110119

111120
@OptIn(ExperimentalTime::class)
@@ -218,7 +227,7 @@ class InstantTest {
218227
/* Based on the ThreeTenBp project.
219228
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
220229
*/
221-
@ExperimentalTime
230+
// @ExperimentalTime
222231
@Test
223232
fun strings() {
224233
assertEquals("0000-01-02T00:00:00Z", LocalDateTime(0, 1, 2, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString())

core/commonTest/src/LocalDateTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ class LocalDateTest {
5454
@Test
5555
fun addComponents() {
5656
val startDate = LocalDate(2016, 2, 29)
57-
checkComponents(startDate.plus(1, CalendarUnit.DAY), 2016, 3, 1)
58-
checkComponents(startDate.plus(1, CalendarUnit.YEAR), 2017, 2, 28)
57+
checkComponents(startDate.plus(1, ChronoUnit.DAY), 2016, 3, 1)
58+
checkComponents(startDate.plus(ChronoUnit.YEAR), 2017, 2, 28)
5959
checkComponents(startDate + CalendarPeriod(years = 4), 2020, 2, 29)
6060

6161
checkComponents(LocalDate.parse("2016-01-31") + CalendarPeriod(months = 1), 2016, 2, 29)
6262

6363
assertFailsWith<UnsupportedOperationException> { startDate + CalendarPeriod(hours = 7) }
6464
assertFailsWith<UnsupportedOperationException> { startDate.plus(7, CalendarUnit.HOUR) }
65+
assertFailsWith<UnsupportedOperationException> { startDate.plus(7, ChronoUnit.MINUTE) }
6566
}
6667

6768
@Test

0 commit comments

Comments
 (0)