Skip to content

Commit 20789e9

Browse files
committed
Implement unified ranges in Native and the JVM
1 parent 797c7e4 commit 20789e9

File tree

15 files changed

+221
-240
lines changed

15 files changed

+221
-240
lines changed

core/common/src/DateTimePeriod.kt

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import kotlinx.serialization.Serializable
7272
@Serializable(with = DateTimePeriodIso8601Serializer::class)
7373
// TODO: could be error-prone without explicitly named params
7474
public sealed class DateTimePeriod {
75-
internal abstract val totalMonths: Int
75+
internal abstract val totalMonths: Long
7676

7777
/**
7878
* The number of calendar days. Can be negative.
@@ -90,14 +90,14 @@ public sealed class DateTimePeriod {
9090
*
9191
* @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.valueNormalization
9292
*/
93-
public val years: Int get() = totalMonths / 12
93+
public val years: Int get() = (totalMonths / 12).toInt()
9494

9595
/**
9696
* The number of months in this period that don't form a whole year, so this value is always in `(-11..11)`.
9797
*
9898
* @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.valueNormalization
9999
*/
100-
public val months: Int get() = totalMonths % 12
100+
public val months: Int get() = (totalMonths % 12).toInt()
101101

102102
/**
103103
* The number of whole hours in this period. Can be negative.
@@ -131,7 +131,7 @@ public sealed class DateTimePeriod {
131131
public open val nanoseconds: Int get() = (totalNanoseconds % NANOS_PER_ONE).toInt()
132132

133133
private fun allNonpositive() =
134-
totalMonths <= 0 && days <= 0 && totalNanoseconds <= 0 && (totalMonths or days != 0 || totalNanoseconds != 0L)
134+
totalMonths <= 0 && days <= 0 && totalNanoseconds <= 0 && (totalMonths or totalNanoseconds != 0L || days != 0)
135135

136136
/**
137137
* Converts this period to the ISO 8601 string representation for durations, for example, `P2M1DT3H`.
@@ -186,7 +186,7 @@ public sealed class DateTimePeriod {
186186
}
187187

188188
override fun hashCode(): Int {
189-
var result = totalMonths
189+
var result = totalMonths.hashCode()
190190
result = 31 * result + days
191191
result = 31 * result + totalNanoseconds.hashCode()
192192
return result
@@ -314,7 +314,7 @@ public sealed class DateTimePeriod {
314314
while (i < text.length && text[i] in '0'..'9') {
315315
try {
316316
number = safeAdd(safeMultiply(number, 10), (text[i] - '0').toLong())
317-
} catch (e: ArithmeticException) {
317+
} catch (_: ArithmeticException) {
318318
parseException("The number is too large", iStart)
319319
}
320320
i += 1
@@ -432,7 +432,7 @@ public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this
432432
*/
433433
@Serializable(with = DatePeriodIso8601Serializer::class)
434434
public class DatePeriod internal constructor(
435-
internal override val totalMonths: Int,
435+
internal override val totalMonths: Long,
436436
override val days: Int,
437437
) : DateTimePeriod() {
438438
/**
@@ -464,7 +464,7 @@ public class DatePeriod internal constructor(
464464

465465
/** The number of nanoseconds in this period. Always equal to zero. */
466466
override val nanoseconds: Int get() = 0
467-
internal override val totalNanoseconds: Long get() = 0
467+
override val totalNanoseconds: Long get() = 0
468468

469469
public companion object {
470470
/**
@@ -494,17 +494,12 @@ public class DatePeriod internal constructor(
494494
public fun String.toDatePeriod(): DatePeriod = DatePeriod.parse(this)
495495

496496
private class DateTimePeriodImpl(
497-
internal override val totalMonths: Int,
497+
override val totalMonths: Long,
498498
override val days: Int,
499-
internal override val totalNanoseconds: Long,
499+
override val totalNanoseconds: Long,
500500
) : DateTimePeriod()
501501

502-
// TODO: these calculations fit in a JS Number. Possible to do an expect/actual here.
503-
private fun totalMonths(years: Int, months: Int): Int =
504-
when (val totalMonths = years.toLong() * 12 + months.toLong()) {
505-
in Int.MIN_VALUE..Int.MAX_VALUE -> totalMonths.toInt()
506-
else -> throw IllegalArgumentException("The total number of months in $years years and $months months overflows an Int")
507-
}
502+
private fun totalMonths(years: Int, months: Int): Long = years.toLong() * 12 + months.toLong()
508503

509504
private fun totalNanoseconds(hours: Int, minutes: Int, seconds: Int, nanoseconds: Long): Long {
510505
val totalMinutes: Long = hours.toLong() * 60 + minutes
@@ -517,12 +512,12 @@ private fun totalNanoseconds(hours: Int, minutes: Int, seconds: Int, nanoseconds
517512
// absolute value at most 2^44 + 2^31 < 2^45
518513
return try {
519514
multiplyAndAdd(totalSeconds, 1_000_000_000, nanoseconds % NANOS_PER_ONE)
520-
} catch (e: ArithmeticException) {
515+
} catch (_: ArithmeticException) {
521516
throw IllegalArgumentException("The total number of nanoseconds in $hours hours, $minutes minutes, $seconds seconds, and $nanoseconds nanoseconds overflows a Long")
522517
}
523518
}
524519

525-
internal fun buildDateTimePeriod(totalMonths: Int = 0, days: Int = 0, totalNanoseconds: Long): DateTimePeriod =
520+
internal fun buildDateTimePeriod(totalMonths: Long = 0, days: Int = 0, totalNanoseconds: Long): DateTimePeriod =
526521
if (totalNanoseconds != 0L)
527522
DateTimePeriodImpl(totalMonths, days, totalNanoseconds)
528523
else

core/common/src/Instant.kt

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ import kotlin.time.*
5656
* ```
5757
*
5858
* For values very far in the past or the future, this conversion may fail.
59-
* The specific range of values that can be converted to [LocalDateTime] is platform-specific, but at least
60-
* [DISTANT_PAST], [DISTANT_FUTURE], and all values between them can be converted to [LocalDateTime].
59+
* The specific range of values that can be converted to [LocalDateTime] is unspecified, but at least
60+
* [DISTANT_PAST], [DISTANT_FUTURE], and all values between them are included in that range.
6161
*
6262
* #### Date or time separately
6363
*
@@ -225,7 +225,8 @@ public expect class Instant : Comparable<Instant> {
225225
*
226226
* Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds.
227227
*
228-
* If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result.
228+
* If the result does not fit in [Long],
229+
* returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result.
229230
*
230231
* @see fromEpochMilliseconds
231232
* @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds
@@ -238,7 +239,7 @@ public expect class Instant : Comparable<Instant> {
238239
* If the [duration] is positive, the returned instant is later than this instant.
239240
* If the [duration] is negative, the returned instant is earlier than this instant.
240241
*
241-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
242+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
242243
*
243244
* **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based.
244245
* Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based
@@ -255,7 +256,7 @@ public expect class Instant : Comparable<Instant> {
255256
* If the [duration] is positive, the returned instant is earlier than this instant.
256257
* If the [duration] is negative, the returned instant is later than this instant.
257258
*
258-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
259+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
259260
*
260261
* **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based.
261262
* Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based
@@ -319,8 +320,7 @@ public expect class Instant : Comparable<Instant> {
319320
/**
320321
* Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`.
321322
*
322-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
323-
* In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented.
323+
* Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant].
324324
*
325325
* Note that [Instant] also supports nanosecond precision via [fromEpochSeconds].
326326
*
@@ -333,7 +333,7 @@ public expect class Instant : Comparable<Instant> {
333333
* Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z`
334334
* and the [nanosecondAdjustment] number of nanoseconds from the whole second.
335335
*
336-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
336+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
337337
* In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented.
338338
*
339339
* [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision.
@@ -348,7 +348,7 @@ public expect class Instant : Comparable<Instant> {
348348
* Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z`
349349
* and the [nanosecondAdjustment] number of nanoseconds from the whole second.
350350
*
351-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
351+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
352352
* In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented.
353353
*
354354
* [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision.
@@ -389,7 +389,7 @@ public expect class Instant : Comparable<Instant> {
389389
* An instant value that is far in the past.
390390
*
391391
* All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to
392-
* [LocalDateTime] without exceptions in every time zone on all supported platforms.
392+
* [LocalDateTime] without exceptions in every time zone.
393393
*
394394
* [isDistantPast] returns true for this value and all earlier ones.
395395
*/
@@ -399,7 +399,7 @@ public expect class Instant : Comparable<Instant> {
399399
* An instant value that is far in the future.
400400
*
401401
* All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to
402-
* [LocalDateTime] without exceptions in every time zone on all supported platforms.
402+
* [LocalDateTime] without exceptions in every time zone.
403403
*
404404
* [isDistantFuture] returns true for this value and all later ones.
405405
*/
@@ -467,7 +467,7 @@ public expect fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Inst
467467
public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant =
468468
/* An overflow can happen for any component, but we are only worried about nanoseconds, as having an overflow in
469469
any other component means that `plus` will throw due to the minimum value of the numeric type overflowing the
470-
platform-specific limits. */
470+
`Instant` limits. */
471471
if (period.totalNanoseconds != Long.MIN_VALUE) {
472472
val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -totalNanoseconds) }
473473
plus(negatedPeriod, timeZone)
@@ -487,7 +487,6 @@ public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant =
487487
* - Exactly zero if this instant is equal to the other.
488488
*
489489
* @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
490-
* Or (only on the JVM) if the number of months between the two dates exceeds an Int.
491490
* @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil
492491
*/
493492
public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod
@@ -526,7 +525,7 @@ public fun Instant.until(other: Instant, unit: DateTimeUnit.TimeBased): Long =
526525
NANOS_PER_ONE.toLong(),
527526
(other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(),
528527
unit.nanoseconds)
529-
} catch (e: ArithmeticException) {
528+
} catch (_: ArithmeticException) {
530529
if (this < other) Long.MAX_VALUE else Long.MIN_VALUE
531530
}
532531

@@ -577,7 +576,6 @@ public fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int =
577576
* - Exactly zero if this instant is equal to the other.
578577
*
579578
* @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
580-
* Or (only on the JVM) if the number of months between the two dates exceeds an Int.
581579
* @see Instant.periodUntil
582580
* @sample kotlinx.datetime.test.samples.InstantSamples.minusInstantInZone
583581
*/
@@ -613,7 +611,7 @@ public fun Instant.minus(unit: DateTimeUnit, timeZone: TimeZone): Instant =
613611
*
614612
* The returned instant is later than this instant.
615613
*
616-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
614+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
617615
*/
618616
@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit)"))
619617
public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant =
@@ -624,7 +622,7 @@ public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant =
624622
*
625623
* The returned instant is earlier than this instant.
626624
*
627-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
625+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
628626
*/
629627
@Deprecated("Use the minus overload with an explicit number of units", ReplaceWith("this.minus(1, unit)"))
630628
public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant =
@@ -669,7 +667,7 @@ public expect fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZo
669667
* If the [value] is positive, the returned instant is later than this instant.
670668
* If the [value] is negative, the returned instant is earlier than this instant.
671669
*
672-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
670+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
673671
*
674672
* @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit
675673
*/
@@ -682,7 +680,7 @@ public fun Instant.plus(value: Int, unit: DateTimeUnit.TimeBased): Instant =
682680
* If the [value] is positive, the returned instant is earlier than this instant.
683681
* If the [value] is negative, the returned instant is later than this instant.
684682
*
685-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
683+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
686684
*
687685
* @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit
688686
*/
@@ -730,7 +728,7 @@ public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): I
730728
* If the [value] is positive, the returned instant is later than this instant.
731729
* If the [value] is negative, the returned instant is earlier than this instant.
732730
*
733-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
731+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
734732
*
735733
* @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit
736734
*/
@@ -742,7 +740,7 @@ public expect fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
742740
* If the [value] is positive, the returned instant is earlier than this instant.
743741
* If the [value] is negative, the returned instant is later than this instant.
744742
*
745-
* The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them.
743+
* The return value is clamped to the boundaries of [Instant] if the result exceeds them.
746744
*
747745
* @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit
748746
*/

0 commit comments

Comments
 (0)