blob: 05d08aa75a64afbd6ce1b8fac63e2c4dfd31cd36 [file]
/*
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
/*
* Based on the ThreeTenBp project.
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
*/
package test.time
import kotlin.random.Random
import kotlin.test.*
import kotlin.time.*
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds
class InstantTest {
private val largePositiveLongs = listOf(Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 50)
private val largeNegativeLongs = listOf(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 50)
private val largePositiveInstants = listOf(Instant.MAX, Instant.MAX - 1.seconds, Instant.MAX - 50.seconds)
private val largeNegativeInstants = listOf(Instant.MIN, Instant.MIN + 1.seconds, Instant.MIN + 50.seconds)
private val smallInstants = listOf(
Instant.fromEpochMilliseconds(0),
Instant.fromEpochMilliseconds(1003),
Instant.fromEpochMilliseconds(253112)
)
@Test
fun systemClockNow() {
val instant = Clock.System.now()
val millis = instant.toEpochMilliseconds()
assertTrue(millis > 1_500_000_000_000L, "instant: $instant, millis: $millis")
val millisInstant = Instant.fromEpochMilliseconds(millis)
assertEquals(millis, millisInstant.toEpochMilliseconds())
val notEqualInstant = Instant.fromEpochMilliseconds(millis + 1)
assertNotEquals(notEqualInstant, instant)
}
@Test
fun instantArithmetic() {
val instant = Clock.System.now().toEpochMilliseconds().let { Instant.fromEpochMilliseconds(it) } // round to millis
val diffMillis = Random.nextLong(1000, 1_000_000_000)
val diff = diffMillis.milliseconds
val nextInstant = (instant.toEpochMilliseconds() + diffMillis).let { Instant.fromEpochMilliseconds(it) }
assertEquals(diff, nextInstant - instant)
assertEquals(nextInstant, instant + diff)
assertEquals(instant, nextInstant - diff)
}
@Test
fun addingMultiplesOf2_32() {
val pow2_32 = 1L shl 32
val instant1 = Instant.fromEpochSeconds(0)
val instant2 = instant1.plus(pow2_32.toDuration(DurationUnit.NANOSECONDS))
assertEquals(pow2_32 / NANOS_PER_SECOND, instant2.epochSeconds)
assertEquals(pow2_32 % NANOS_PER_SECOND, instant2.nanosecondsOfSecond.toLong())
val instant3 = instant1.plus(pow2_32.toDuration(DurationUnit.SECONDS))
assertEquals(pow2_32, instant3.epochSeconds)
}
@Test
fun instantArithmeticWithOverflow() {
val future = Instant.fromEpochSeconds(Instant.DISTANT_FUTURE.toEpochMilliseconds() - 1L)
assertEquals(Long.MAX_VALUE, (future + Long.MAX_VALUE.milliseconds).toEpochMilliseconds())
val past = Instant.fromEpochSeconds(Instant.DISTANT_PAST.toEpochMilliseconds() + 1L)
assertEquals(Long.MIN_VALUE, (past - Long.MAX_VALUE.milliseconds).toEpochMilliseconds())
}
@Test
fun nanosecondAdjustment() {
for (i in -2..2L) {
for (j in 0..9) {
val t: Instant = Instant.fromEpochSeconds(i, j)
val t2: Instant = Instant.fromEpochSeconds(i, j.toLong())
assertEquals(i, t.epochSeconds)
assertEquals(j, t.nanosecondsOfSecond)
assertEquals(t, t2)
}
for (j in -10..-1) {
val t: Instant = Instant.fromEpochSeconds(i, j)
val t2: Instant = Instant.fromEpochSeconds(i, j.toLong())
assertEquals(i - 1, t.epochSeconds)
assertEquals(j + 1000000000, t.nanosecondsOfSecond)
assertEquals(t, t2)
}
for (j in 999_999_990..999_999_999) {
val t: Instant = Instant.fromEpochSeconds(i, j)
val t2: Instant = Instant.fromEpochSeconds(i, j.toLong())
assertEquals(i, t.epochSeconds)
assertEquals(j, t.nanosecondsOfSecond)
assertEquals(t, t2)
}
}
val t = Instant.fromEpochSeconds(0, Int.MAX_VALUE)
assertEquals((Int.MAX_VALUE / 1_000_000_000).toLong(), t.epochSeconds)
assertEquals(Int.MAX_VALUE % 1_000_000_000, t.nanosecondsOfSecond)
val t2 = Instant.fromEpochSeconds(0, Long.MAX_VALUE)
assertEquals(Long.MAX_VALUE / 1_000_000_000, t2.epochSeconds)
assertEquals((Long.MAX_VALUE % 1_000_000_000).toInt(), t2.nanosecondsOfSecond)
}
@Test
fun distantPastAndFuture() {
val distantFutureString = "+100000-01-01T00:00:00Z"
val distantPastString = "-100001-12-31T23:59:59.999999999Z"
assertEquals(distantFutureString, Instant.DISTANT_FUTURE.toString())
assertEquals(Instant.DISTANT_FUTURE, Instant.parse(distantFutureString))
assertEquals(distantPastString, Instant.DISTANT_PAST.toString())
assertEquals(Instant.DISTANT_PAST, Instant.parse(distantPastString))
assertTrue(Instant.DISTANT_PAST.isDistantPast)
assertTrue(Instant.DISTANT_FUTURE.isDistantFuture)
assertFalse(Instant.DISTANT_PAST.isDistantFuture)
assertFalse(Instant.DISTANT_FUTURE.isDistantPast)
assertFalse((Instant.DISTANT_PAST + 1.nanoseconds).isDistantPast)
assertFalse((Instant.DISTANT_FUTURE - 1.nanoseconds).isDistantFuture)
assertTrue((Instant.DISTANT_PAST - 1.nanoseconds).isDistantPast)
assertTrue((Instant.DISTANT_FUTURE + 1.nanoseconds).isDistantFuture)
assertTrue(Instant.MAX.isDistantFuture)
assertFalse(Instant.MAX.isDistantPast)
assertTrue(Instant.MIN.isDistantPast)
assertFalse(Instant.MIN.isDistantFuture)
}
@Test
fun epochMillisecondsClamping() {
/* Any number of milliseconds in Long is representable as an Instant */
for (instant in largePositiveInstants) {
assertEquals(Long.MAX_VALUE, instant.toEpochMilliseconds(), "$instant")
}
for (instant in largeNegativeInstants) {
assertEquals(Long.MIN_VALUE, instant.toEpochMilliseconds(), "$instant")
}
for (milliseconds in largePositiveLongs + largeNegativeLongs) {
assertEquals(milliseconds, Instant.fromEpochMilliseconds(milliseconds).toEpochMilliseconds(),
"$milliseconds")
}
}
@Test
fun epochSecondsClamping() {
// fromEpochSeconds
// On all platforms Long.MAX_VALUE of seconds is not a valid instant.
for (seconds in largePositiveLongs) {
assertEquals(Instant.MAX, Instant.fromEpochSeconds(seconds, 35))
}
for (seconds in largeNegativeLongs) {
assertEquals(Instant.MIN, Instant.fromEpochSeconds(seconds, 35))
}
for (instant in largePositiveInstants + smallInstants + largeNegativeInstants) {
assertEquals(instant, Instant.fromEpochSeconds(instant.epochSeconds, instant.nanosecondsOfSecond.toLong()))
}
}
@Test
fun durationArithmeticClamping() {
val longDurations = listOf(Duration.INFINITE)
for (duration in longDurations) {
for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) {
assertEquals(Instant.MAX, instant + duration)
}
for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) {
assertEquals(Instant.MIN, instant - duration)
}
}
assertEquals(Instant.MAX, (Instant.MAX - 4.seconds) + 5.seconds)
assertEquals(Instant.MIN, (Instant.MIN + 10.seconds) - 12.seconds)
}
@Test
fun arithmeticOutOfRange() {
// Arithmetic overflow
for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) {
assertEquals(Instant.MAX, instant.plus(Long.MAX_VALUE.seconds))
assertEquals(Instant.MIN, instant.plus(Long.MIN_VALUE.seconds))
}
// Overflow of Instant boundaries
for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) {
assertEquals(Instant.MAX, instant.plus((Instant.MAX.epochSeconds - instant.epochSeconds + 1).seconds))
assertEquals(Instant.MIN, instant.plus((Instant.MIN.epochSeconds - instant.epochSeconds - 1).seconds))
}
}
// https://github.com/Kotlin/kotlinx-datetime/issues/263
@Test
fun addSmallDurationsToLargeInstants() {
for (smallDuration in listOf(1.nanoseconds, 999_999.nanoseconds, 1.seconds - 1.nanoseconds)) {
assertEquals(expected = Instant.MAX, actual = Instant.MAX + smallDuration)
assertEquals(expected = Instant.MIN, actual = Instant.MIN - smallDuration)
}
}
@Test
fun subtractInstants() {
val max = Instant.fromEpochSeconds(31494816403199L)
val min = Instant.fromEpochSeconds(-31619119219200L)
assertEquals(max.epochSeconds - min.epochSeconds, (max - min).inWholeSeconds)
}
@Test
fun compareInstants() {
val instants = arrayOf(
Instant.fromEpochSeconds(-2L, 0),
Instant.fromEpochSeconds(-2L, 999999998),
Instant.fromEpochSeconds(-2L, 999999999),
Instant.fromEpochSeconds(-1L, 0),
Instant.fromEpochSeconds(-1L, 1),
Instant.fromEpochSeconds(-1L, 999999998),
Instant.fromEpochSeconds(-1L, 999999999),
Instant.fromEpochSeconds(0L, 0),
Instant.fromEpochSeconds(0L, 1),
Instant.fromEpochSeconds(0L, 2),
Instant.fromEpochSeconds(0L, 999999999),
Instant.fromEpochSeconds(1L, 0),
Instant.fromEpochSeconds(2L, 0)
)
for (i in instants.indices) {
val a = instants[i]
for (j in instants.indices) {
val b = instants[j]
when {
i < j -> {
assertTrue(a < b, "$a <=> $b")
assertNotEquals(a, b, "$a <=> $b")
}
i > j -> {
assertTrue(a > b, "$a <=> $b")
assertNotEquals(a, b, "$a <=> $b")
}
else -> {
assertEquals(0, a.compareTo(b), "$a <=> $b")
assertEquals(a, b, "$a <=> $b")
}
}
}
}
}
@Test
fun instantEquals() {
val test5a: Instant = Instant.fromEpochSeconds(5L, 20)
val test5b: Instant = Instant.fromEpochSeconds(5L, 20)
val test5n: Instant = Instant.fromEpochSeconds(5L, 30)
val test6: Instant = Instant.fromEpochSeconds(6L, 20)
assertEquals(test5a, test5a)
assertEquals(test5a, test5b)
assertNotEquals(test5a, test5n)
assertNotEquals(test5a, test6)
assertEquals(test5b, test5a)
assertEquals(test5b, test5b)
assertNotEquals(test5b, test5n)
assertNotEquals(test5b, test6)
assertNotEquals(test5n, test5a)
assertNotEquals(test5n, test5b)
assertEquals(test5n, test5n)
assertNotEquals(test5n, test6)
assertNotEquals(test6, test5a)
assertNotEquals(test6, test5b)
assertNotEquals(test6, test5n)
assertEquals(test6, test6)
}
@Test
fun toEpochMilliseconds() {
assertEquals(Instant.fromEpochSeconds(1L, 1000000).toEpochMilliseconds(), 1001L)
assertEquals(Instant.fromEpochSeconds(1L, 2000000).toEpochMilliseconds(), 1002L)
assertEquals(Instant.fromEpochSeconds(1L, 567).toEpochMilliseconds(), 1000L)
assertEquals(Instant.fromEpochSeconds(Long.MAX_VALUE / 1_000_000).toEpochMilliseconds(), Long.MAX_VALUE / 1_000_000 * 1000)
assertEquals(Instant.fromEpochSeconds(Long.MIN_VALUE / 1_000_000).toEpochMilliseconds(), Long.MIN_VALUE / 1_000_000 * 1000)
assertEquals(Instant.fromEpochSeconds(0L, -1000000).toEpochMilliseconds(), -1L)
assertEquals(Instant.fromEpochSeconds(0L, 1000000).toEpochMilliseconds(), 1)
assertEquals(Instant.fromEpochSeconds(0L, 999999).toEpochMilliseconds(), 0)
assertEquals(Instant.fromEpochSeconds(0L, 1).toEpochMilliseconds(), 0)
assertEquals(Instant.fromEpochSeconds(0L, 0).toEpochMilliseconds(), 0)
assertEquals(Instant.fromEpochSeconds(0L, -1).toEpochMilliseconds(), -1L)
assertEquals(Instant.fromEpochSeconds(0L, -999999).toEpochMilliseconds(), -1L)
assertEquals(Instant.fromEpochSeconds(0L, -1000000).toEpochMilliseconds(), -1L)
assertEquals(Instant.fromEpochSeconds(0L, -1000001).toEpochMilliseconds(), -2L)
assertEquals(Instant.fromEpochSeconds(-1L, 0L).toEpochMilliseconds(), -1000L)
assertEquals(Instant.fromEpochSeconds(-1L, 1000000L).toEpochMilliseconds(), -999L)
assertEquals(Instant.fromEpochSeconds(-1L, -1000000L).toEpochMilliseconds(), -1001L)
assertEquals(
Instant.fromEpochSeconds(Instant.MAX.epochSeconds - 1L, 0L).toEpochMilliseconds(),
(Instant.MAX.epochSeconds - 1L).seconds.toLong(DurationUnit.MILLISECONDS)
)
assertEquals(
Instant.fromEpochSeconds(Instant.MAX.epochSeconds, 0L).toEpochMilliseconds(),
Long.MAX_VALUE
)
assertEquals(Instant.fromEpochSeconds(Long.MAX_VALUE, 0L).toEpochMilliseconds(), Long.MAX_VALUE)
assertEquals(
Instant.fromEpochSeconds(Long.MAX_VALUE, 42.seconds.toLong(DurationUnit.NANOSECONDS)).toEpochMilliseconds(),
Long.MAX_VALUE
)
assertEquals(
Instant.fromEpochSeconds(Instant.MIN.epochSeconds + 1L, 0L).toEpochMilliseconds(),
(Instant.MIN.epochSeconds + 1L).seconds.toLong(DurationUnit.MILLISECONDS)
)
assertEquals(
Instant.fromEpochSeconds(Instant.MIN.epochSeconds, 0L).toEpochMilliseconds(),
Long.MIN_VALUE
)
assertEquals(Instant.fromEpochSeconds(Long.MIN_VALUE, 0L).toEpochMilliseconds(), Long.MIN_VALUE)
assertEquals(
Instant.fromEpochSeconds(Long.MIN_VALUE, -42.seconds.toLong(DurationUnit.NANOSECONDS)).toEpochMilliseconds(),
Long.MIN_VALUE
)
assertEquals(Instant.fromEpochSeconds(Instant.MAX.epochSeconds - 1L, Long.MAX_VALUE).toEpochMilliseconds(), Long.MAX_VALUE)
assertEquals(Instant.fromEpochSeconds(Instant.MIN.epochSeconds + 1L, Long.MIN_VALUE).toEpochMilliseconds(), Long.MIN_VALUE)
assertEquals(Instant.fromEpochSeconds(1L, 2L.seconds.toLong(DurationUnit.NANOSECONDS)).toEpochMilliseconds(), 3000L)
assertEquals(Instant.fromEpochSeconds(1L, -2L.seconds.toLong(DurationUnit.NANOSECONDS)).toEpochMilliseconds(), -1000L)
}
@Test
fun isLeapYear() {
assertEquals(false, isLeapYear(1999))
assertEquals(true, isLeapYear(2000))
assertEquals(false, isLeapYear(2001))
assertEquals(false, isLeapYear(2007))
assertEquals(true, isLeapYear(2008))
assertEquals(false, isLeapYear(2009))
assertEquals(false, isLeapYear(2010))
assertEquals(false, isLeapYear(2011))
assertEquals(true, isLeapYear(2012))
assertEquals(false, isLeapYear(2095))
assertEquals(true, isLeapYear(2096))
assertEquals(false, isLeapYear(2097))
assertEquals(false, isLeapYear(2098))
assertEquals(false, isLeapYear(2099))
assertEquals(false, isLeapYear(2100))
assertEquals(false, isLeapYear(2101))
assertEquals(false, isLeapYear(2102))
assertEquals(false, isLeapYear(2103))
assertEquals(true, isLeapYear(2104))
assertEquals(false, isLeapYear(2105))
assertEquals(false, isLeapYear(-500))
assertEquals(true, isLeapYear(-400))
assertEquals(false, isLeapYear(-300))
assertEquals(false, isLeapYear(-200))
assertEquals(false, isLeapYear(-100))
assertEquals(true, isLeapYear(0))
assertEquals(false, isLeapYear(100))
assertEquals(false, isLeapYear(200))
assertEquals(false, isLeapYear(300))
assertEquals(true, isLeapYear(400))
assertEquals(false, isLeapYear(500))
}
}