Skip to content

Commit 2e197e5

Browse files
authored
Implement the same ranges for all platforms (#453)
Fixes #432
1 parent 797c7e4 commit 2e197e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+786
-2407
lines changed

core/androidNative/src/internal/TimeZoneNative.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
package kotlinx.datetime.internal
88

99
import kotlinx.cinterop.*
10+
import kotlinx.datetime.TimeZone
1011
import platform.posix.*
1112

12-
internal actual val systemTzdb: TimeZoneDatabase get() = tzdb.getOrThrow()
13+
internal actual fun timeZoneById(zoneId: String): TimeZone =
14+
RegionTimeZone(tzdb.getOrThrow().rulesForId(zoneId), zoneId)
15+
16+
internal actual fun getAvailableZoneIds(): Set<String> =
17+
tzdb.getOrThrow().availableTimeZoneIds()
1318

1419
private val tzdb = runCatching { TzdbBionic() }
1520

16-
internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> = memScoped {
21+
internal actual fun currentSystemDefaultZone(): Pair<String, TimeZone?> = memScoped {
1722
val name = readSystemProperty("persist.sys.timezone")
1823
?: throw IllegalStateException("The system property 'persist.sys.timezone' should contain the system timezone")
1924
return name to null

core/build.gradle.kts

+13-13
Original file line numberDiff line numberDiff line change
@@ -174,22 +174,33 @@ kotlin {
174174
}
175175
}
176176

177+
val commonKotlinMain by creating {
178+
dependsOn(commonMain.get())
179+
dependencies {
180+
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
181+
}
182+
}
183+
184+
val commonKotlinTest by creating {
185+
dependsOn(commonTest.get())
186+
}
187+
177188
val jvmMain by getting {
178189
}
179190

180191
val jvmTest by getting {
181192
}
182193

183194
val commonJsMain by creating {
184-
dependsOn(commonMain.get())
195+
dependsOn(commonKotlinMain)
185196
dependencies {
186197
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
187198
implementation(npm("@js-joda/core", "3.2.0"))
188199
}
189200
}
190201

191202
val commonJsTest by creating {
192-
dependsOn(commonTest.get())
203+
dependsOn(commonKotlinTest)
193204
dependencies {
194205
implementation(npm("@js-joda/timezone", "2.3.0"))
195206
}
@@ -211,17 +222,6 @@ kotlin {
211222
dependsOn(commonJsTest)
212223
}
213224

214-
val commonKotlinMain by creating {
215-
dependsOn(commonMain.get())
216-
dependencies {
217-
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
218-
}
219-
}
220-
221-
val commonKotlinTest by creating {
222-
dependsOn(commonTest.get())
223-
}
224-
225225
val nativeMain by getting {
226226
dependsOn(commonKotlinMain)
227227
}

core/common/src/DateTimePeriod.kt

+20-19
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
/**
@@ -448,7 +448,8 @@ public class DatePeriod internal constructor(
448448
* (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit.DateBased] instead.
449449
* For example, instead of `DatePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`.
450450
*
451-
* @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int].
451+
* @throws IllegalArgumentException if the total number of years
452+
* (together with full years in [months]) overflows an [Int].
452453
* @sample kotlinx.datetime.test.samples.DatePeriodSamples.construction
453454
*/
454455
public constructor(years: Int = 0, months: Int = 0, days: Int = 0): this(totalMonths(years, months), days)
@@ -494,17 +495,16 @@ public class DatePeriod internal constructor(
494495
public fun String.toDatePeriod(): DatePeriod = DatePeriod.parse(this)
495496

496497
private class DateTimePeriodImpl(
497-
internal override val totalMonths: Int,
498+
internal override val totalMonths: Long,
498499
override val days: Int,
499500
internal override val totalNanoseconds: Long,
500501
) : DateTimePeriod()
501502

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")
503+
private fun totalMonths(years: Int, months: Int): Long = (years.toLong() * 12 + months.toLong()).also {
504+
require(it / 12 in Int.MIN_VALUE..Int.MAX_VALUE) {
505+
"The total number of years in $years years and $months months overflows an Int"
507506
}
507+
}
508508

509509
private fun totalNanoseconds(hours: Int, minutes: Int, seconds: Int, nanoseconds: Long): Long {
510510
val totalMinutes: Long = hours.toLong() * 60 + minutes
@@ -517,12 +517,12 @@ private fun totalNanoseconds(hours: Int, minutes: Int, seconds: Int, nanoseconds
517517
// absolute value at most 2^44 + 2^31 < 2^45
518518
return try {
519519
multiplyAndAdd(totalSeconds, 1_000_000_000, nanoseconds % NANOS_PER_ONE)
520-
} catch (e: ArithmeticException) {
520+
} catch (_: ArithmeticException) {
521521
throw IllegalArgumentException("The total number of nanoseconds in $hours hours, $minutes minutes, $seconds seconds, and $nanoseconds nanoseconds overflows a Long")
522522
}
523523
}
524524

525-
internal fun buildDateTimePeriod(totalMonths: Int = 0, days: Int = 0, totalNanoseconds: Long): DateTimePeriod =
525+
internal fun buildDateTimePeriod(totalMonths: Long = 0, days: Int = 0, totalNanoseconds: Long): DateTimePeriod =
526526
if (totalNanoseconds != 0L)
527527
DateTimePeriodImpl(totalMonths, days, totalNanoseconds)
528528
else
@@ -541,9 +541,10 @@ internal fun buildDateTimePeriod(totalMonths: Int = 0, days: Int = 0, totalNanos
541541
* (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit] instead.
542542
* For example, instead of `DateTimePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`.
543543
*
544-
* @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int].
545-
* @throws IllegalArgumentException if the total number of months in [hours], [minutes], [seconds] and [nanoseconds]
546-
* overflows a [Long].
544+
* @throws IllegalArgumentException if the total number of years
545+
* (together with full years in [months]) overflows an [Int].
546+
* @throws IllegalArgumentException if the total number of nanoseconds in
547+
* [hours], [minutes], [seconds] and [nanoseconds] overflows a [Long].
547548
* @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.constructorFunction
548549
*/
549550
public fun DateTimePeriod(

core/common/src/Instant.kt

+19-21
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)