| /* | |
| * 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)) | |
| } | |
| } |