From a19e2f2f0e20c32353d03d2ebda5d9fff89fafaa Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 28 Nov 2024 12:48:30 +0100 Subject: [PATCH 1/7] Copy Instant.kt to kotlinx.time --- fake-kotlinx-time/build.gradle.kts | 101 +++ fake-kotlinx-time/common/src/Clock.kt | 128 ++++ fake-kotlinx-time/common/src/Instant.kt | 802 ++++++++++++++++++++++++ fake-kotlinx-time/js/src/Converters.kt | 21 + fake-kotlinx-time/jvm/src/Converters.kt | 95 +++ settings.gradle.kts | 2 + 6 files changed, 1149 insertions(+) create mode 100644 fake-kotlinx-time/build.gradle.kts create mode 100644 fake-kotlinx-time/common/src/Clock.kt create mode 100644 fake-kotlinx-time/common/src/Instant.kt create mode 100644 fake-kotlinx-time/js/src/Converters.kt create mode 100644 fake-kotlinx-time/jvm/src/Converters.kt diff --git a/fake-kotlinx-time/build.gradle.kts b/fake-kotlinx-time/build.gradle.kts new file mode 100644 index 000000000..25394044b --- /dev/null +++ b/fake-kotlinx-time/build.gradle.kts @@ -0,0 +1,101 @@ +import java.util.Locale + +plugins { + id("kotlin-multiplatform") + id("org.jetbrains.kotlinx.kover") +} + +val mainJavaToolchainVersion: String by project +val serializationVersion: String by project + +java { + toolchain { languageVersion.set(JavaLanguageVersion.of(mainJavaToolchainVersion)) } +} + +kotlin { + + // Tiers are in accordance with + // Tier 1 + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() + iosArm64() + // Tier 2 + linuxX64() + linuxArm64() + watchosSimulatorArm64() + watchosX64() + watchosArm32() + watchosArm64() + tvosSimulatorArm64() + tvosX64() + tvosArm64() + // Tier 3 + androidNativeArm32() + androidNativeArm64() + androidNativeX86() + androidNativeX64() + mingwX64() + watchosDeviceArm64() + // Deprecated + @Suppress("DEPRECATION") linuxArm32Hfp() + + applyDefaultHierarchyTemplate() + + jvm { + attributes { + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) + } + } + + js { + nodejs { + } + compilations.all { + kotlinOptions { + sourceMap = true + moduleKind = "umd" + } + } + } + + + wasmJs { + nodejs { + } + } + + wasmWasi { + nodejs { + } + } + + sourceSets.all { + val suffixIndex = name.indexOfLast { it.isUpperCase() } + val targetName = name.substring(0, suffixIndex) + val suffix = name.substring(suffixIndex).toLowerCase(Locale.ROOT).takeIf { it != "main" } + kotlin.srcDir("$targetName/${suffix ?: "src"}") + resources.srcDir("$targetName/${suffix?.let { it + "Resources" } ?: "resources"}") + } + + targets.withType { + compilations["test"].kotlinOptions { + freeCompilerArgs += listOf("-trw") + } + } + + sourceSets { + commonMain { + dependencies { + } + } + + commonTest { + dependencies { + api("org.jetbrains.kotlin:kotlin-test") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") + } + } + } +} diff --git a/fake-kotlinx-time/common/src/Clock.kt b/fake-kotlinx-time/common/src/Clock.kt new file mode 100644 index 000000000..e9cd9137e --- /dev/null +++ b/fake-kotlinx-time/common/src/Clock.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime + +import kotlin.time.* + +/** + * A source of [Instant] values. + * + * See [Clock.System][Clock.System] for the clock instance that queries the operating system. + * + * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a + * [Clock] explicitly to the necessary functions or classes. + * This way, tests can be written deterministically by providing custom [Clock] implementations + * to the system under test. + */ +public interface Clock { + /** + * Returns the [Instant] corresponding to the current time, according to this clock. + * + * Calling [now] later is not guaranteed to return a larger [Instant]. + * In particular, for [Clock.System], the opposite is completely expected, + * and it must be taken into account. + * See the [System] documentation for details. + * + * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling + * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. + */ + public fun now(): Instant + + /** + * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. + * + * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, + * these increases will not necessarily correspond to the elapsed time. + * + * For example, when using [Clock.System], the following could happen: + * - [now] returns `2023-01-02T22:35:01Z`. + * - The system queries the Internet and recognizes that its clock needs adjusting. + * - [now] returns `2023-01-02T22:32:05Z`. + * + * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. + * + * For improved testability, you should avoid using [Clock.System] directly in the implementation + * and pass a [Clock] explicitly instead. For example: + * + * @sample kotlinx.datetime.test.samples.ClockSamples.system + * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection + */ + public object System : Clock { + override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now() + } + + /** A companion object used purely for namespacing. */ + public companion object { + + } +} + +/** + * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. + * + * The time zone is important because the current date is not the same in all time zones at the same instant. + * + * @sample kotlinx.datetime.test.samples.ClockSamples.todayIn + */ +public fun Clock.todayIn(timeZone: TimeZone): LocalDate = + now().toLocalDateTime(timeZone).date + +/** + * Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark. + * + * **Pitfall**: using this function with [Clock.System] is error-prone + * because [Clock.System] is not well suited for measuring time intervals. + * Please only use this conversion function on the [Clock] instances that are fully controlled programmatically. + */ +@ExperimentalTime +public fun Clock.asTimeSource(): TimeSource.WithComparableMarks = object : TimeSource.WithComparableMarks { + override fun markNow(): ComparableTimeMark = InstantTimeMark(now(), this@asTimeSource) +} + +@ExperimentalTime +private class InstantTimeMark(private val instant: Instant, private val clock: Clock) : ComparableTimeMark { + override fun elapsedNow(): Duration = saturatingDiff(clock.now(), instant) + + override fun plus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(duration), clock) + override fun minus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(-duration), clock) + + override fun minus(other: ComparableTimeMark): Duration { + if (other !is InstantTimeMark || other.clock != this.clock) { + throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other") + } + return saturatingDiff(this.instant, other.instant) + } + + override fun equals(other: Any?): Boolean { + return other is InstantTimeMark && this.clock == other.clock && this.instant == other.instant + } + + override fun hashCode(): Int = instant.hashCode() + + override fun toString(): String = "InstantTimeMark($instant, $clock)" + + private fun Instant.isSaturated() = this == Instant.MAX || this == Instant.MIN + private fun Instant.saturatingAdd(duration: Duration): Instant { + if (isSaturated()) { + if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { + throw IllegalArgumentException("Summing infinities of different signs") + } + return this + } + return this + duration + } + private fun saturatingDiff(instant1: Instant, instant2: Instant): Duration = when { + instant1 == instant2 -> + Duration.ZERO + instant1.isSaturated() || instant2.isSaturated() -> + (instant1 - instant2) * Double.POSITIVE_INFINITY + else -> + instant1 - instant2 + } +} + +@Deprecated("Use Clock.todayIn instead", ReplaceWith("this.todayIn(timeZone)"), DeprecationLevel.WARNING) +public fun Clock.todayAt(timeZone: TimeZone): LocalDate = todayIn(timeZone) diff --git a/fake-kotlinx-time/common/src/Instant.kt b/fake-kotlinx-time/common/src/Instant.kt new file mode 100644 index 000000000..3deedd946 --- /dev/null +++ b/fake-kotlinx-time/common/src/Instant.kt @@ -0,0 +1,802 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime + +import kotlinx.datetime.format.* +import kotlinx.datetime.internal.* +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.datetime.serializers.InstantComponentSerializer +import kotlinx.serialization.Serializable +import kotlin.time.* + +/** + * A moment in time. + * + * A point in time must be uniquely identified in a way that is independent of a time zone. + * For example, `1970-01-01, 00:00:00` does not represent a moment in time since this would happen at different times + * in different time zones: someone in Tokyo would think it is already `1970-01-01` several hours earlier than someone in + * Berlin would. To represent such entities, use [LocalDateTime]. + * In contrast, "the moment the clocks in London first showed 00:00 on Jan 1, 2000" is a specific moment + * in time, as is "1970-01-01, 00:00:00 UTC+0", so it can be represented as an [Instant]. + * + * `Instant` uses the UTC-SLS (smeared leap second) time scale. This time scale doesn't contain instants + * corresponding to leap seconds, but instead "smears" positive and negative leap seconds among the last 1000 seconds + * of the day when a leap second happens. + * + * ### Obtaining the current moment + * + * The [Clock] interface is the primary way to obtain the current moment: + * + * ``` + * val clock: Clock = Clock.System + * val instant = clock.now() + * ``` + * + * The [Clock.System] implementation uses the platform-specific system clock to obtain the current moment. + * Note that this clock is not guaranteed to be monotonic, and the user or the system may adjust it at any time, + * so it should not be used for measuring time intervals. + * For that, consider using [TimeSource.Monotonic] and [TimeMark] instead of [Clock.System] and [Instant]. + * + * ### Obtaining human-readable representations + * + * #### Date and time + * + * [Instant] is essentially the number of seconds and nanoseconds since a designated moment in time, + * stored as something like `1709898983.123456789`. + * [Instant] does not contain information about the day or time, as this depends on the time zone. + * To work with this information for a specific time zone, obtain a [LocalDateTime] using [Instant.toLocalDateTime]: + * + * ``` + * val instant = Instant.fromEpochSeconds(1709898983, 123456789) + * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")) // 2024-03-08T12:56:23.123456789 + * instant.toLocalDateTime(TimeZone.UTC) // 2024-03-08T11:56:23.123456789 + * ``` + * + * For values very far in the past or the future, this conversion may fail. + * The specific range of values that can be converted to [LocalDateTime] is unspecified, but at least + * [DISTANT_PAST], [DISTANT_FUTURE], and all values between them are included in that range. + * + * #### Date or time separately + * + * To obtain a [LocalDate] or [LocalTime], first, obtain a [LocalDateTime], and then use its [LocalDateTime.date] + * and [LocalDateTime.time] properties: + * + * ``` + * val instant = Instant.fromEpochSeconds(1709898983, 123456789) + * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")).date // 2024-03-08 + * ``` + * + * ### Arithmetic operations + * + * #### Elapsed-time-based + * + * The [plus] and [minus] operators can be used to add [Duration]s to and subtract them from an [Instant]: + * + * ``` + * Clock.System.now() + 5.seconds // 5 seconds from now + * ``` + * + * Durations can also be represented as multiples of some [time-based datetime unit][DateTimeUnit.TimeBased]: + * + * ``` + * Clock.System.now().plus(4, DateTimeUnit.HOUR) // 4 hours from now + * ``` + * + * Also, there is a [minus] operator that returns the [Duration] representing the difference between two instants: + * + * ``` + * val start = Clock.System.now() + * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) + * val timeUntilConcert = concertStart - start + * ``` + * + * #### Calendar-based + * + * Since [Instant] represents a point in time, it is always well-defined what the result of arithmetic operations on it + * is, including the cases when a calendar is used. + * This is not the case for [LocalDateTime], where the result of arithmetic operations depends on the time zone. + * See the [LocalDateTime] documentation for more details. + * + * Adding and subtracting calendar-based units can be done using the [plus] and [minus] operators, + * requiring a [TimeZone]: + * + * ``` + * // One day from now in Berlin + * Clock.System.now().plus(1, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) + * + * // A day and two hours short from two months later in Berlin + * Clock.System.now().plus(DateTimePeriod(months = 2, days = -1, hours = -2), TimeZone.of("Europe/Berlin")) + * ``` + * + * The difference between [Instant] values in terms of calendar-based units can be obtained using the [periodUntil] + * method: + * + * ``` + * val start = Clock.System.now() + * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) + * val timeUntilConcert = start.periodUntil(concertStart, TimeZone.of("Europe/Berlin")) + * // Two months, three days, four hours, and five minutes until the concert + * ``` + * + * Or the [Instant.until] method, as well as [Instant.daysUntil], [Instant.monthsUntil], + * and [Instant.yearsUntil] extension functions: + * + * ``` + * val start = Clock.System.now() + * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) + * val timeUntilConcert = start.until(concertStart, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) + * // 63 days until the concert, rounded down + * ``` + * + * ### Platform specifics + * + * On the JVM, there are `Instant.toJavaInstant()` and `java.time.Instant.toKotlinInstant()` + * extension functions to convert between `kotlinx.datetime` and `java.time` objects used for the same purpose. + * Similarly, on the Darwin platforms, there are `Instant.toNSDate()` and `NSDate.toKotlinInstant()` + * extension functions. + * + * ### Construction, serialization, and deserialization + * + * [fromEpochSeconds] can be used to construct an instant from the number of seconds since + * `1970-01-01T00:00:00Z` (the Unix epoch). + * [epochSeconds] and [nanosecondsOfSecond] can be used to obtain the number of seconds and nanoseconds since the epoch. + * + * ``` + * val instant = Instant.fromEpochSeconds(1709898983, 123456789) + * instant.epochSeconds // 1709898983 + * instant.nanosecondsOfSecond // 123456789 + * ``` + * + * [fromEpochMilliseconds] allows constructing an instant from the number of milliseconds since the epoch. + * [toEpochMilliseconds] can be used to obtain the number of milliseconds since the epoch. + * Note that [Instant] supports nanosecond precision, so converting to milliseconds is a lossy operation. + * + * ``` + * val instant1 = Instant.fromEpochSeconds(1709898983, 123456789) + * instant1.nanosecondsOfSecond // 123456789 + * val milliseconds = instant1.toEpochMilliseconds() // 1709898983123 + * val instant2 = Instant.fromEpochMilliseconds(milliseconds) + * instant2.nanosecondsOfSecond // 123000000 + * ``` + * + * [parse] and [toString] methods can be used to obtain an [Instant] from and convert it to a string in the + * ISO 8601 extended format. + * + * ``` + * val instant = Instant.parse("2023-01-02T22:35:01+01:00") + * instant.toString() // 2023-01-02T21:35:01Z + * ``` + * + * During parsing, the UTC offset is not returned separately. + * If the UTC offset is important, use [DateTimeComponents] with [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] to + * parse the string instead. + * + * [Instant.parse] and [Instant.format] also accept custom formats: + * + * ``` + * val customFormat = DateTimeComponents.Format { + * date(LocalDate.Formats.ISO) + * char(' ') + * time(LocalTime.Formats.ISO) + * char(' ') + * offset(UtcOffset.Formats.ISO) + * } + * val instant = Instant.parse("2023-01-02 22:35:01.14 +01:00", customFormat) + * instant.format(customFormat, offset = UtcOffset(hours = 2)) // 2023-01-02 23:35:01.14 +02:00 + * ``` + * + * Additionally, there are several `kotlinx-serialization` serializers for [Instant]: + * - [InstantIso8601Serializer] for the ISO 8601 extended format. + * - [InstantComponentSerializer] for an object with components. + * + * @see LocalDateTime for a user-visible representation of moments in time in an unspecified time zone. + */ +@Serializable(with = InstantIso8601Serializer::class) +public expect class Instant : Comparable { + + /** + * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. + * + * The difference between the rounded number of seconds and the actual number of seconds + * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. + * + * Note that this number doesn't include leap seconds added or removed since the epoch. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds + */ + public val epochSeconds: Long + + /** + * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. + * + * The value is always non-negative and lies in the range `0..999_999_999`. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond + */ + public val nanosecondsOfSecond: Int + + /** + * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. + * + * If the result does not fit in [Long], + * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see fromEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds + */ + public fun toEpochMilliseconds(): Long + + /** + * Returns an instant that is the result of adding the specified [duration] to this instant. + * + * If the [duration] is positive, the returned instant is later than this instant. + * If the [duration] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration + */ + public operator fun plus(duration: Duration): Instant + + /** + * Returns an instant that is the result of subtracting the specified [duration] from this instant. + * + * If the [duration] is positive, the returned instant is earlier than this instant. + * If the [duration] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration + */ + public operator fun minus(duration: Duration): Instant + + // questionable + /** + * Returns the [Duration] between two instants: [other] and `this`. + * + * The duration returned is positive if this instant is later than the other, + * and negative if this instant is earlier than the other. + * + * The result is never clamped, but note that for instants that are far apart, + * the value returned may represent the duration between them inexactly due to the loss of precision. + * + * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other + * or even monotonic, so the result of this operation may be negative even if the other instant was observed later + * than this one, or vice versa. + * For measuring time intervals, consider using [TimeSource.Monotonic]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant + */ + public operator fun minus(other: Instant): Duration + + /** + * Compares `this` instant with the [other] instant. + * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), + * a negative number if this instant is earlier than the other, + * and a positive number if this instant is later than the other. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample + */ + public override operator fun compareTo(other: Instant): Int + + /** + * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. + * + * The representation uses the UTC-SLS time scale instead of UTC. + * In practice, this means that leap second handling will not be readjusted to the UTC. + * Leap seconds will not be added or skipped, so it is impossible to acquire a string + * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. + * + * @see parse + * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the + * fractional part of the second. + * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample + */ + public override fun toString(): String + + + public companion object { + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) + public fun now(): Instant + + /** + * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. + * + * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. + * + * @see Instant.toEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds + */ + public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant + + /** + * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. + * + * Parses a string that represents an instant, including date and time components and a mandatory + * time zone offset and returns the parsed [Instant] value. + * + * The string is considered to represent time on the UTC-SLS time scale instead of UTC. + * In practice, this means that, even if there is a leap second on the given day, it will not affect how the + * time is parsed, even if it's in the last 1000 seconds of the day. + * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. + * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. + * + * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. + * `2023-01-02T23:40:57.120Z` is an example of a string in this format. + * + * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. + * + * @see Instant.toString for formatting using the default format. + * @see Instant.format for formatting using a custom format. + * @sample kotlinx.datetime.test.samples.InstantSamples.parsing + */ + public fun parse( + input: CharSequence, + format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET + ): Instant + + /** + * An instant value that is far in the past. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantPast] returns true for this value and all earlier ones. + */ + public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z + + /** + * An instant value that is far in the future. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantFuture] returns true for this value and all later ones. + */ + public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z + + internal val MIN: Instant + internal val MAX: Instant + } +} + +/** + * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast + */ +public val Instant.isDistantPast: Boolean + get() = this <= Instant.DISTANT_PAST + +/** + * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture + */ +public val Instant.isDistantFuture: Boolean + get() = this >= Instant.DISTANT_FUTURE + +/** + * @suppress + */ +@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) +public fun String.toInstant(): Instant = Instant.parse(this) + +/** + * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are + * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider adding a [Duration] instead, + * as in `Clock.System.now() + 5.hours`. + * Then, it will not be necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], like in + * `Clock.System.now().plus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod + */ +public expect fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting components of [DateTimePeriod] from this instant. The components + * are subtracted in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider subtracting a [Duration] instead, + * as in `Clock.System.now() - 5.hours`. + * Then, it is not necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], as in + * `Clock.System.now().minus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusPeriod + */ +public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant = + /* An overflow can happen for any component, but we are only worried about nanoseconds, as having an overflow in + any other component means that `plus` will throw due to the minimum value of the numeric type overflowing the + `Instant` limits. */ + if (period.totalNanoseconds != Long.MIN_VALUE) { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -totalNanoseconds) } + plus(negatedPeriod, timeZone) + } else { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -(totalNanoseconds+1)) } + plus(negatedPeriod, timeZone).plus(1, DateTimeUnit.NANOSECOND) + } + +/** + * Returns a [DateTimePeriod] representing the difference between `this` and [other] instants. + * + * The components of [DateTimePeriod] are calculated so that adding it to `this` instant results in the [other] instant. + * + * All components of the [DateTimePeriod] returned are: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil + */ +public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod + +/** + * Returns the whole number of the specified date or time [units][unit] between `this` and [other] instants + * in the specified [timeZone]. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit + */ +public expect fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long + +/** + * Returns the whole number of the specified time [units][unit] between `this` and [other] instants. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsTimeBasedUnit + */ +public fun Instant.until(other: Instant, unit: DateTimeUnit.TimeBased): Long = + try { + multiplyAddAndDivide(other.epochSeconds - epochSeconds, + NANOS_PER_ONE.toLong(), + (other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(), + unit.nanoseconds) + } catch (_: ArithmeticException) { + if (this < other) Long.MAX_VALUE else Long.MIN_VALUE + } + +/** + * Returns the number of whole days between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.daysUntil + */ +public fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.DAY, timeZone).clampToInt() + +/** + * Returns the number of whole months between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.monthsUntil + */ +public fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.MONTH, timeZone).clampToInt() + +/** + * Returns the number of whole years between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.yearsUntil + */ +public fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.YEAR, timeZone).clampToInt() + +/** + * Returns a [DateTimePeriod] representing the difference between [other] and `this` instants. + * + * The components of [DateTimePeriod] are calculated so that adding it back to the `other` instant results in this instant. + * + * All components of the [DateTimePeriod] returned are: + * - Negative or zero if this instant is earlier than the other. + * - Positive or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.periodUntil + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstantInZone + */ +public fun Instant.minus(other: Instant, timeZone: TimeZone): DateTimePeriod = + other.periodUntil(this, timeZone) + + +/** + * Returns an instant that is the result of adding one [unit] to this instant + * in the specified [timeZone]. + * + * The returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit, timeZone)")) +public expect fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant + * in the specified [timeZone]. + * + * The returned instant is earlier than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("Use the minus overload with an explicit number of units", ReplaceWith("this.minus(1, unit, timeZone)")) +public fun Instant.minus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-1, unit, timeZone) + +/** + * Returns an instant that is the result of adding one [unit] to this instant. + * + * The returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit)")) +public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant = + plus(1L, unit) + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant. + * + * The returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("Use the minus overload with an explicit number of units", ReplaceWith("this.minus(1, unit)")) +public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant = + plus(-1L, unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate][LocalDate.plus]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +public expect fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +public expect fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +public fun Instant.plus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + plus(value.toLong(), unit) + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +public fun Instant.minus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + minus(value.toLong(), unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +public expect fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit, timeZone) + } else { + plus(-(value + 1), unit, timeZone).plus(1, unit, timeZone) + } + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +public expect fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +public fun Instant.minus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit) + } else { + plus(-(value + 1), unit).plus(1, unit) + } + +/** + * Returns the whole number of the specified date or time [units][unit] between [other] and `this` instants + * in the specified [timeZone]. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsDateTimeUnit + */ +public fun Instant.minus(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = + other.until(this, unit, timeZone) + +/** + * Returns the whole number of the specified time [units][unit] between [other] and `this` instants. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsTimeBasedUnit + */ +public fun Instant.minus(other: Instant, unit: DateTimeUnit.TimeBased): Long = + other.until(this, unit) + +/** + * Formats this value using the given [format] using the given [offset]. + * + * Equivalent to calling [DateTimeFormat.format] on [format] and using [DateTimeComponents.setDateTimeOffset] in + * the lambda. + * + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is a format very similar to the one used by [toString]. + * The only difference is that [Instant.toString] adds trailing zeros to the fraction-of-second component so that the + * number of digits after a dot is a multiple of three. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.formatting + */ +public fun Instant.format(format: DateTimeFormat, offset: UtcOffset = UtcOffset.ZERO): String { + val instant = this + return format.format { setDateTimeOffset(instant, offset) } +} + +internal const val DISTANT_PAST_SECONDS = -3217862419201 +internal const val DISTANT_FUTURE_SECONDS = 3093527980800 diff --git a/fake-kotlinx-time/js/src/Converters.kt b/fake-kotlinx-time/js/src/Converters.kt new file mode 100644 index 000000000..a4a8c9df9 --- /dev/null +++ b/fake-kotlinx-time/js/src/Converters.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019-2022 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime + +import kotlin.js.* + +/** + * Converts the [Instant] to an instance of JS [Date]. + * + * The conversion is lossy: JS uses millisecond precision to represent dates, and [Instant] allows for nanosecond + * resolution. + */ +public fun Instant.toJSDate(): Date = Date(milliseconds = toEpochMilliseconds().toDouble()) + +/** + * Converts the JS [Date] to the corresponding [Instant]. + */ +public fun Date.toKotlinInstant(): Instant = Instant.fromEpochMilliseconds(getTime().toLong()) diff --git a/fake-kotlinx-time/jvm/src/Converters.kt b/fake-kotlinx-time/jvm/src/Converters.kt new file mode 100644 index 000000000..6db8339d3 --- /dev/null +++ b/fake-kotlinx-time/jvm/src/Converters.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2019-2022 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime + +/** + * Converts this [kotlinx.datetime.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. + */ +public fun Instant.toJavaInstant(): java.time.Instant = this.value + +/** + * Converts this [java.time.Instant][java.time.Instant] value to a [kotlinx.datetime.Instant][Instant] value. + */ +public fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) + + +/** + * Converts this [kotlinx.datetime.LocalDateTime][LocalDateTime] value to a [java.time.LocalDateTime][java.time.LocalDateTime] value. + */ +public fun LocalDateTime.toJavaLocalDateTime(): java.time.LocalDateTime = this.value + +/** + * Converts this [java.time.LocalDateTime][java.time.LocalDateTime] value to a [kotlinx.datetime.LocalDateTime][LocalDateTime] value. + */ +public fun java.time.LocalDateTime.toKotlinLocalDateTime(): LocalDateTime = LocalDateTime(this) + +/** + * Converts this [kotlinx.datetime.LocalDateTime][LocalTime] value to a [java.time.LocalTime][java.time.LocalTime] value. + */ +public fun LocalTime.toJavaLocalTime(): java.time.LocalTime = this.value + +/** + * Converts this [java.time.LocalTime][java.time.LocalTime] value to a [kotlinx.datetime.LocalTime][LocalTime] value. + */ +public fun java.time.LocalTime.toKotlinLocalTime(): LocalTime = LocalTime(this) + + +/** + * Converts this [kotlinx.datetime.LocalDate][LocalDate] value to a [java.time.LocalDate][java.time.LocalDate] value. + */ +public fun LocalDate.toJavaLocalDate(): java.time.LocalDate = this.value + +/** + * Converts this [java.time.LocalDate][java.time.LocalDate] value to a [kotlinx.datetime.LocalDate][LocalDate] value. + */ +public fun java.time.LocalDate.toKotlinLocalDate(): LocalDate = LocalDate(this) + + +/** + * Converts this [kotlinx.datetime.DatePeriod][DatePeriod] value to a [java.time.Period][java.time.Period] value. + */ +public fun DatePeriod.toJavaPeriod(): java.time.Period = java.time.Period.of(this.years, this.months, this.days) + +/** + * Converts this [java.time.Period][java.time.Period] value to a [kotlinx.datetime.DatePeriod][DatePeriod] value. + */ +public fun java.time.Period.toKotlinDatePeriod(): DatePeriod = DatePeriod(this.years, this.months, this.days) + + +/** + * Converts this [kotlinx.datetime.TimeZone][TimeZone] value to a [java.time.ZoneId][java.time.ZoneId] value. + */ +public fun TimeZone.toJavaZoneId(): java.time.ZoneId = this.zoneId + +/** + * Converts this [java.time.ZoneId][java.time.ZoneId] value to a [kotlinx.datetime.TimeZone][TimeZone] value. + */ +public fun java.time.ZoneId.toKotlinTimeZone(): TimeZone = TimeZone.ofZone(this) + + +/** + * Converts this [kotlinx.datetime.FixedOffsetTimeZone][FixedOffsetTimeZone] value to a [java.time.ZoneOffset][java.time.ZoneOffset] value. + */ +public fun FixedOffsetTimeZone.toJavaZoneOffset(): java.time.ZoneOffset = this.offset.zoneOffset + +/** + * Converts this [java.time.ZoneOffset][java.time.ZoneOffset] value to a [kotlinx.datetime.FixedOffsetTimeZone][FixedOffsetTimeZone] value. + */ +public fun java.time.ZoneOffset.toKotlinFixedOffsetTimeZone(): FixedOffsetTimeZone = FixedOffsetTimeZone(UtcOffset(this)) + +@Deprecated("Use toKotlinFixedOffsetTimeZone() instead.", ReplaceWith("this.toKotlinFixedOffsetTimeZone()")) +public fun java.time.ZoneOffset.toKotlinZoneOffset(): FixedOffsetTimeZone = toKotlinFixedOffsetTimeZone() + +/** + * Converts this [kotlinx.datetime.UtcOffset][UtcOffset] value to a [java.time.ZoneOffset][java.time.ZoneOffset] value. + */ +public fun UtcOffset.toJavaZoneOffset(): java.time.ZoneOffset = this.zoneOffset + +/** + * Converts this [java.time.ZoneOffset][java.time.ZoneOffset] value to a [kotlinx.datetime.UtcOffset][UtcOffset] value. + */ +public fun java.time.ZoneOffset.toKotlinUtcOffset(): UtcOffset = UtcOffset(this) + diff --git a/settings.gradle.kts b/settings.gradle.kts index ab7a6af57..c1f6e2823 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,3 +25,5 @@ project(":serialization").name = "kotlinx-datetime-serialization" include(":js-without-timezones") project(":js-without-timezones").name = "kotlinx-datetime-js-test-without-timezones" include(":benchmarks") + +include(":fake-kotlinx-time") From bb1b3bb41a5bb09b97cd5fdaa2922dae752511b4 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 28 Nov 2024 13:01:10 +0100 Subject: [PATCH 2/7] Adapt the kotlinx.time classes API for the stdlib --- fake-kotlinx-time/common/src/Clock.kt | 69 +-- fake-kotlinx-time/common/src/Instant.kt | 546 ++---------------------- fake-kotlinx-time/js/src/Converters.kt | 2 +- fake-kotlinx-time/jvm/src/Converters.kt | 90 +--- 4 files changed, 39 insertions(+), 668 deletions(-) diff --git a/fake-kotlinx-time/common/src/Clock.kt b/fake-kotlinx-time/common/src/Clock.kt index e9cd9137e..c5e2feec7 100644 --- a/fake-kotlinx-time/common/src/Clock.kt +++ b/fake-kotlinx-time/common/src/Clock.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ -package kotlinx.datetime +package kotlinx.time import kotlin.time.* @@ -59,70 +59,3 @@ public interface Clock { } } - -/** - * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. - * - * The time zone is important because the current date is not the same in all time zones at the same instant. - * - * @sample kotlinx.datetime.test.samples.ClockSamples.todayIn - */ -public fun Clock.todayIn(timeZone: TimeZone): LocalDate = - now().toLocalDateTime(timeZone).date - -/** - * Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark. - * - * **Pitfall**: using this function with [Clock.System] is error-prone - * because [Clock.System] is not well suited for measuring time intervals. - * Please only use this conversion function on the [Clock] instances that are fully controlled programmatically. - */ -@ExperimentalTime -public fun Clock.asTimeSource(): TimeSource.WithComparableMarks = object : TimeSource.WithComparableMarks { - override fun markNow(): ComparableTimeMark = InstantTimeMark(now(), this@asTimeSource) -} - -@ExperimentalTime -private class InstantTimeMark(private val instant: Instant, private val clock: Clock) : ComparableTimeMark { - override fun elapsedNow(): Duration = saturatingDiff(clock.now(), instant) - - override fun plus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(duration), clock) - override fun minus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(-duration), clock) - - override fun minus(other: ComparableTimeMark): Duration { - if (other !is InstantTimeMark || other.clock != this.clock) { - throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other") - } - return saturatingDiff(this.instant, other.instant) - } - - override fun equals(other: Any?): Boolean { - return other is InstantTimeMark && this.clock == other.clock && this.instant == other.instant - } - - override fun hashCode(): Int = instant.hashCode() - - override fun toString(): String = "InstantTimeMark($instant, $clock)" - - private fun Instant.isSaturated() = this == Instant.MAX || this == Instant.MIN - private fun Instant.saturatingAdd(duration: Duration): Instant { - if (isSaturated()) { - if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { - throw IllegalArgumentException("Summing infinities of different signs") - } - return this - } - return this + duration - } - private fun saturatingDiff(instant1: Instant, instant2: Instant): Duration = when { - instant1 == instant2 -> - Duration.ZERO - instant1.isSaturated() || instant2.isSaturated() -> - (instant1 - instant2) * Double.POSITIVE_INFINITY - else -> - instant1 - instant2 - } -} - -@Deprecated("Use Clock.todayIn instead", ReplaceWith("this.todayIn(timeZone)"), DeprecationLevel.WARNING) -public fun Clock.todayAt(timeZone: TimeZone): LocalDate = todayIn(timeZone) diff --git a/fake-kotlinx-time/common/src/Instant.kt b/fake-kotlinx-time/common/src/Instant.kt index 3deedd946..fcd4f7ffe 100644 --- a/fake-kotlinx-time/common/src/Instant.kt +++ b/fake-kotlinx-time/common/src/Instant.kt @@ -3,13 +3,8 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ -package kotlinx.datetime +package kotlinx.time -import kotlinx.datetime.format.* -import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.datetime.serializers.InstantComponentSerializer -import kotlinx.serialization.Serializable import kotlin.time.* /** @@ -18,7 +13,7 @@ import kotlin.time.* * A point in time must be uniquely identified in a way that is independent of a time zone. * For example, `1970-01-01, 00:00:00` does not represent a moment in time since this would happen at different times * in different time zones: someone in Tokyo would think it is already `1970-01-01` several hours earlier than someone in - * Berlin would. To represent such entities, use [LocalDateTime]. + * Berlin would. To represent such entities, use the `LocalDateTime` from `kotlinx-datetime`. * In contrast, "the moment the clocks in London first showed 00:00 on Jan 1, 2000" is a specific moment * in time, as is "1970-01-01, 00:00:00 UTC+0", so it can be represented as an [Instant]. * @@ -40,103 +35,30 @@ import kotlin.time.* * so it should not be used for measuring time intervals. * For that, consider using [TimeSource.Monotonic] and [TimeMark] instead of [Clock.System] and [Instant]. * - * ### Obtaining human-readable representations - * - * #### Date and time - * - * [Instant] is essentially the number of seconds and nanoseconds since a designated moment in time, - * stored as something like `1709898983.123456789`. - * [Instant] does not contain information about the day or time, as this depends on the time zone. - * To work with this information for a specific time zone, obtain a [LocalDateTime] using [Instant.toLocalDateTime]: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")) // 2024-03-08T12:56:23.123456789 - * instant.toLocalDateTime(TimeZone.UTC) // 2024-03-08T11:56:23.123456789 - * ``` - * - * For values very far in the past or the future, this conversion may fail. - * The specific range of values that can be converted to [LocalDateTime] is unspecified, but at least - * [DISTANT_PAST], [DISTANT_FUTURE], and all values between them are included in that range. - * - * #### Date or time separately - * - * To obtain a [LocalDate] or [LocalTime], first, obtain a [LocalDateTime], and then use its [LocalDateTime.date] - * and [LocalDateTime.time] properties: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")).date // 2024-03-08 - * ``` - * * ### Arithmetic operations * - * #### Elapsed-time-based - * * The [plus] and [minus] operators can be used to add [Duration]s to and subtract them from an [Instant]: * * ``` * Clock.System.now() + 5.seconds // 5 seconds from now * ``` * - * Durations can also be represented as multiples of some [time-based datetime unit][DateTimeUnit.TimeBased]: - * - * ``` - * Clock.System.now().plus(4, DateTimeUnit.HOUR) // 4 hours from now - * ``` - * * Also, there is a [minus] operator that returns the [Duration] representing the difference between two instants: * * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = concertStart - start - * ``` - * - * #### Calendar-based - * - * Since [Instant] represents a point in time, it is always well-defined what the result of arithmetic operations on it - * is, including the cases when a calendar is used. - * This is not the case for [LocalDateTime], where the result of arithmetic operations depends on the time zone. - * See the [LocalDateTime] documentation for more details. - * - * Adding and subtracting calendar-based units can be done using the [plus] and [minus] operators, - * requiring a [TimeZone]: - * - * ``` - * // One day from now in Berlin - * Clock.System.now().plus(1, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * - * // A day and two hours short from two months later in Berlin - * Clock.System.now().plus(DateTimePeriod(months = 2, days = -1, hours = -2), TimeZone.of("Europe/Berlin")) - * ``` - * - * The difference between [Instant] values in terms of calendar-based units can be obtained using the [periodUntil] - * method: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.periodUntil(concertStart, TimeZone.of("Europe/Berlin")) - * // Two months, three days, four hours, and five minutes until the concert - * ``` - * - * Or the [Instant.until] method, as well as [Instant.daysUntil], [Instant.monthsUntil], - * and [Instant.yearsUntil] extension functions: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.until(concertStart, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * // 63 days until the concert, rounded down + * val kotlinRelease = Instant.parse("2016-02-15T02:00T12:00:00+03:00") + * val kotlinStableDuration = Clock.System.now() - kotlinRelease * ``` * * ### Platform specifics * * On the JVM, there are `Instant.toJavaInstant()` and `java.time.Instant.toKotlinInstant()` - * extension functions to convert between `kotlinx.datetime` and `java.time` objects used for the same purpose. - * Similarly, on the Darwin platforms, there are `Instant.toNSDate()` and `NSDate.toKotlinInstant()` - * extension functions. + * extension functions to convert between `kotlin.time` and `java.time` objects used for the same purpose. + * Likewise, on JS, there are `Instant.toJSDate()` and `Date.toKotlinInstant()` extension functions. + * + * For technical reasons, converting [Instant] to and from Foundation's `NSDate` is provided in + * `kotlinx-datetime` via `Instant.toNSDate()` and `NSDate.toKotlinInstant()` extension functions. + * These functions will be made available in `kotlin.time` in the future. * * ### Construction, serialization, and deserialization * @@ -169,32 +91,7 @@ import kotlin.time.* * val instant = Instant.parse("2023-01-02T22:35:01+01:00") * instant.toString() // 2023-01-02T21:35:01Z * ``` - * - * During parsing, the UTC offset is not returned separately. - * If the UTC offset is important, use [DateTimeComponents] with [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] to - * parse the string instead. - * - * [Instant.parse] and [Instant.format] also accept custom formats: - * - * ``` - * val customFormat = DateTimeComponents.Format { - * date(LocalDate.Formats.ISO) - * char(' ') - * time(LocalTime.Formats.ISO) - * char(' ') - * offset(UtcOffset.Formats.ISO) - * } - * val instant = Instant.parse("2023-01-02 22:35:01.14 +01:00", customFormat) - * instant.format(customFormat, offset = UtcOffset(hours = 2)) // 2023-01-02 23:35:01.14 +02:00 - * ``` - * - * Additionally, there are several `kotlinx-serialization` serializers for [Instant]: - * - [InstantIso8601Serializer] for the ISO 8601 extended format. - * - [InstantComponentSerializer] for an object with components. - * - * @see LocalDateTime for a user-visible representation of moments in time in an unspecified time zone. */ -@Serializable(with = InstantIso8601Serializer::class) public expect class Instant : Comparable { /** @@ -241,10 +138,9 @@ public expect class Instant : Comparable { * * The return value is clamped to the boundaries of [Instant] if the result exceeds them. * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours, but in some time zones, + * some days can be shorter or longer because clocks are shifted. + * Consider using `kotlinx-datetime` for arithmetic operations that take time zone transitions into account. * * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration */ @@ -258,10 +154,9 @@ public expect class Instant : Comparable { * * The return value is clamped to the boundaries of [Instant] if the result exceeds them. * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours, but in some time zones, + * some days can be shorter or longer because clocks are shifted. + * Consider using `kotlinx-datetime` for arithmetic operations that take time zone transitions into account. * * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration */ @@ -305,10 +200,6 @@ public expect class Instant : Comparable { * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. * * @see parse - * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that - * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the - * fractional part of the second. - * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample */ public override fun toString(): String @@ -360,10 +251,20 @@ public expect class Instant : Comparable { public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant /** - * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. + * Parses an ISO 8601 string that represents an instant (for example, `2020-08-30T18:43:00Z`). * - * Parses a string that represents an instant, including date and time components and a mandatory - * time zone offset and returns the parsed [Instant] value. + * Guaranteed to parse all strings that [Instant.toString] produces. + * + * Examples of instants in the ISO 8601 format: + * - `2020-08-30T18:43:00Z` + * - `2020-08-30T18:43:00.50Z` + * - `2020-08-30T18:43:00.123456789Z` + * - `2020-08-30T18:40:00+03:00` + * - `2020-08-30T18:40:00+03:30:20` + * * `2020-01-01T23:59:59.123456789+01` + * * `+12020-01-31T23:59:59Z` + * + * See ISO-8601-1:2019, 5.4.2.1b), excluding the format without the offset. * * The string is considered to represent time on the UTC-SLS time scale instead of UTC. * In practice, this means that, even if there is a leap second on the given day, it will not affect how the @@ -371,26 +272,16 @@ public expect class Instant : Comparable { * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. * - * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. - * `2023-01-02T23:40:57.120Z` is an example of a string in this format. - * * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. * - * @see Instant.toString for formatting using the default format. - * @see Instant.format for formatting using a custom format. + * @see Instant.toString for formatting. * @sample kotlinx.datetime.test.samples.InstantSamples.parsing */ - public fun parse( - input: CharSequence, - format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET - ): Instant + public fun parse(input: CharSequence): Instant /** * An instant value that is far in the past. * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * * [isDistantPast] returns true for this value and all earlier ones. */ public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z @@ -398,9 +289,6 @@ public expect class Instant : Comparable { /** * An instant value that is far in the future. * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * * [isDistantFuture] returns true for this value and all later ones. */ public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z @@ -426,377 +314,5 @@ public val Instant.isDistantPast: Boolean public val Instant.isDistantFuture: Boolean get() = this >= Instant.DISTANT_FUTURE -/** - * @suppress - */ -@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) -public fun String.toInstant(): Instant = Instant.parse(this) - -/** - * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are - * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. - * - * - If the [DateTimePeriod] only contains time-based components, please consider adding a [Duration] instead, - * as in `Clock.System.now() + 5.hours`. - * Then, it will not be necessary to pass the [timeZone]. - * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), - * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], like in - * `Clock.System.now().plus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. - * - * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in - * [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod - */ -public expect fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant - -/** - * Returns an instant that is the result of subtracting components of [DateTimePeriod] from this instant. The components - * are subtracted in the order from the largest units to the smallest, i.e., from years to nanoseconds. - * - * - If the [DateTimePeriod] only contains time-based components, please consider subtracting a [Duration] instead, - * as in `Clock.System.now() - 5.hours`. - * Then, it is not necessary to pass the [timeZone]. - * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), - * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], as in - * `Clock.System.now().minus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. - * - * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in - * [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.minusPeriod - */ -public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant = - /* An overflow can happen for any component, but we are only worried about nanoseconds, as having an overflow in - any other component means that `plus` will throw due to the minimum value of the numeric type overflowing the - `Instant` limits. */ - if (period.totalNanoseconds != Long.MIN_VALUE) { - val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -totalNanoseconds) } - plus(negatedPeriod, timeZone) - } else { - val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -(totalNanoseconds+1)) } - plus(negatedPeriod, timeZone).plus(1, DateTimeUnit.NANOSECOND) - } - -/** - * Returns a [DateTimePeriod] representing the difference between `this` and [other] instants. - * - * The components of [DateTimePeriod] are calculated so that adding it to `this` instant results in the [other] instant. - * - * All components of the [DateTimePeriod] returned are: - * - Positive or zero if this instant is earlier than the other. - * - Negative or zero if this instant is later than the other. - * - Exactly zero if this instant is equal to the other. - * - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil - */ -public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod - -/** - * Returns the whole number of the specified date or time [units][unit] between `this` and [other] instants - * in the specified [timeZone]. - * - * The value returned is: - * - Positive or zero if this instant is earlier than the other. - * - Negative or zero if this instant is later than the other. - * - Zero if this instant is equal to the other. - * - * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit - */ -public expect fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long - -/** - * Returns the whole number of the specified time [units][unit] between `this` and [other] instants. - * - * The value returned is: - * - Positive or zero if this instant is earlier than the other. - * - Negative or zero if this instant is later than the other. - * - Zero if this instant is equal to the other. - * - * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsTimeBasedUnit - */ -public fun Instant.until(other: Instant, unit: DateTimeUnit.TimeBased): Long = - try { - multiplyAddAndDivide(other.epochSeconds - epochSeconds, - NANOS_PER_ONE.toLong(), - (other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(), - unit.nanoseconds) - } catch (_: ArithmeticException) { - if (this < other) Long.MAX_VALUE else Long.MIN_VALUE - } - -/** - * Returns the number of whole days between two instants in the specified [timeZone]. - * - * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. - * - * @see Instant.until - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.daysUntil - */ -public fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int = - until(other, DateTimeUnit.DAY, timeZone).clampToInt() - -/** - * Returns the number of whole months between two instants in the specified [timeZone]. - * - * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. - * - * @see Instant.until - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.monthsUntil - */ -public fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int = - until(other, DateTimeUnit.MONTH, timeZone).clampToInt() - -/** - * Returns the number of whole years between two instants in the specified [timeZone]. - * - * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. - * - * @see Instant.until - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.yearsUntil - */ -public fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int = - until(other, DateTimeUnit.YEAR, timeZone).clampToInt() - -/** - * Returns a [DateTimePeriod] representing the difference between [other] and `this` instants. - * - * The components of [DateTimePeriod] are calculated so that adding it back to the `other` instant results in this instant. - * - * All components of the [DateTimePeriod] returned are: - * - Negative or zero if this instant is earlier than the other. - * - Positive or zero if this instant is later than the other. - * - Exactly zero if this instant is equal to the other. - * - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @see Instant.periodUntil - * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstantInZone - */ -public fun Instant.minus(other: Instant, timeZone: TimeZone): DateTimePeriod = - other.periodUntil(this, timeZone) - - -/** - * Returns an instant that is the result of adding one [unit] to this instant - * in the specified [timeZone]. - * - * The returned instant is later than this instant. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - */ -@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit, timeZone)")) -public expect fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant - -/** - * Returns an instant that is the result of subtracting one [unit] from this instant - * in the specified [timeZone]. - * - * The returned instant is earlier than this instant. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - */ -@Deprecated("Use the minus overload with an explicit number of units", ReplaceWith("this.minus(1, unit, timeZone)")) -public fun Instant.minus(unit: DateTimeUnit, timeZone: TimeZone): Instant = - plus(-1, unit, timeZone) - -/** - * Returns an instant that is the result of adding one [unit] to this instant. - * - * The returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - */ -@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit)")) -public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant = - plus(1L, unit) - -/** - * Returns an instant that is the result of subtracting one [unit] from this instant. - * - * The returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - */ -@Deprecated("Use the minus overload with an explicit number of units", ReplaceWith("this.minus(1, unit)")) -public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant = - plus(-1L, unit) - -/** - * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant - * in the specified [timeZone]. - * - * If the [value] is positive, the returned instant is later than this instant. - * If the [value] is negative, the returned instant is earlier than this instant. - * - * Note that the time zone does not need to be passed when the [unit] is a time-based unit. - * It is also not needed when adding date-based units to a [LocalDate][LocalDate.plus]. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit - */ -public expect fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant - -/** - * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant - * in the specified [timeZone]. - * - * If the [value] is positive, the returned instant is earlier than this instant. - * If the [value] is negative, the returned instant is later than this instant. - * - * Note that the time zone does not need to be passed when the [unit] is a time-based unit. - * It is also not needed when subtracting date-based units from a [LocalDate]. - * - * If the [value] is positive, the returned instant is earlier than this instant. - * If the [value] is negative, the returned instant is later than this instant. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit - */ -public expect fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant - -/** - * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. - * - * If the [value] is positive, the returned instant is later than this instant. - * If the [value] is negative, the returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit - */ -public fun Instant.plus(value: Int, unit: DateTimeUnit.TimeBased): Instant = - plus(value.toLong(), unit) - -/** - * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. - * - * If the [value] is positive, the returned instant is earlier than this instant. - * If the [value] is negative, the returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit - */ -public fun Instant.minus(value: Int, unit: DateTimeUnit.TimeBased): Instant = - minus(value.toLong(), unit) - -/** - * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant - * in the specified [timeZone]. - * - * If the [value] is positive, the returned instant is later than this instant. - * If the [value] is negative, the returned instant is earlier than this instant. - * - * Note that the time zone does not need to be passed when the [unit] is a time-based unit. - * It is also not needed when adding date-based units to a [LocalDate]. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit - */ -public expect fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant - -/** - * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant - * in the specified [timeZone]. - * - * If the [value] is positive, the returned instant is earlier than this instant. - * If the [value] is negative, the returned instant is later than this instant. - * - * Note that the time zone does not need to be passed when the [unit] is a time-based unit. - * It is also not needed when subtracting date-based units from a [LocalDate]. - * - * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. - * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit - */ -public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = - if (value != Long.MIN_VALUE) { - plus(-value, unit, timeZone) - } else { - plus(-(value + 1), unit, timeZone).plus(1, unit, timeZone) - } - -/** - * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. - * - * If the [value] is positive, the returned instant is later than this instant. - * If the [value] is negative, the returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit - */ -public expect fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant - -/** - * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. - * - * If the [value] is positive, the returned instant is earlier than this instant. - * If the [value] is negative, the returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit - */ -public fun Instant.minus(value: Long, unit: DateTimeUnit.TimeBased): Instant = - if (value != Long.MIN_VALUE) { - plus(-value, unit) - } else { - plus(-(value + 1), unit).plus(1, unit) - } - -/** - * Returns the whole number of the specified date or time [units][unit] between [other] and `this` instants - * in the specified [timeZone]. - * - * The value returned is negative or zero if this instant is earlier than the other, - * and positive or zero if this instant is later than the other. - * - * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. - * @see Instant.until for the same operation but with swapped arguments. - * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsDateTimeUnit - */ -public fun Instant.minus(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = - other.until(this, unit, timeZone) - -/** - * Returns the whole number of the specified time [units][unit] between [other] and `this` instants. - * - * The value returned is negative or zero if this instant is earlier than the other, - * and positive or zero if this instant is later than the other. - * - * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @see Instant.until for the same operation but with swapped arguments. - * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsTimeBasedUnit - */ -public fun Instant.minus(other: Instant, unit: DateTimeUnit.TimeBased): Long = - other.until(this, unit) - -/** - * Formats this value using the given [format] using the given [offset]. - * - * Equivalent to calling [DateTimeFormat.format] on [format] and using [DateTimeComponents.setDateTimeOffset] in - * the lambda. - * - * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is a format very similar to the one used by [toString]. - * The only difference is that [Instant.toString] adds trailing zeros to the fraction-of-second component so that the - * number of digits after a dot is a multiple of three. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.formatting - */ -public fun Instant.format(format: DateTimeFormat, offset: UtcOffset = UtcOffset.ZERO): String { - val instant = this - return format.format { setDateTimeOffset(instant, offset) } -} - internal const val DISTANT_PAST_SECONDS = -3217862419201 internal const val DISTANT_FUTURE_SECONDS = 3093527980800 diff --git a/fake-kotlinx-time/js/src/Converters.kt b/fake-kotlinx-time/js/src/Converters.kt index a4a8c9df9..5e74ab188 100644 --- a/fake-kotlinx-time/js/src/Converters.kt +++ b/fake-kotlinx-time/js/src/Converters.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ -package kotlinx.datetime +package kotlinx.time import kotlin.js.* diff --git a/fake-kotlinx-time/jvm/src/Converters.kt b/fake-kotlinx-time/jvm/src/Converters.kt index 6db8339d3..3774d4b37 100644 --- a/fake-kotlinx-time/jvm/src/Converters.kt +++ b/fake-kotlinx-time/jvm/src/Converters.kt @@ -3,93 +3,15 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ -package kotlinx.datetime +package kotlinx.time /** - * Converts this [kotlinx.datetime.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. + * Converts this [kotlin.time.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. */ -public fun Instant.toJavaInstant(): java.time.Instant = this.value +public fun Instant.toJavaInstant(): java.time.Instant = + java.time.Instant.ofEpochSecond(epochSeconds, nanosecondsOfSecond.toLong()) /** - * Converts this [java.time.Instant][java.time.Instant] value to a [kotlinx.datetime.Instant][Instant] value. + * Converts this [java.time.Instant][java.time.Instant] value to a [kotlin.time.Instant][Instant] value. */ -public fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) - - -/** - * Converts this [kotlinx.datetime.LocalDateTime][LocalDateTime] value to a [java.time.LocalDateTime][java.time.LocalDateTime] value. - */ -public fun LocalDateTime.toJavaLocalDateTime(): java.time.LocalDateTime = this.value - -/** - * Converts this [java.time.LocalDateTime][java.time.LocalDateTime] value to a [kotlinx.datetime.LocalDateTime][LocalDateTime] value. - */ -public fun java.time.LocalDateTime.toKotlinLocalDateTime(): LocalDateTime = LocalDateTime(this) - -/** - * Converts this [kotlinx.datetime.LocalDateTime][LocalTime] value to a [java.time.LocalTime][java.time.LocalTime] value. - */ -public fun LocalTime.toJavaLocalTime(): java.time.LocalTime = this.value - -/** - * Converts this [java.time.LocalTime][java.time.LocalTime] value to a [kotlinx.datetime.LocalTime][LocalTime] value. - */ -public fun java.time.LocalTime.toKotlinLocalTime(): LocalTime = LocalTime(this) - - -/** - * Converts this [kotlinx.datetime.LocalDate][LocalDate] value to a [java.time.LocalDate][java.time.LocalDate] value. - */ -public fun LocalDate.toJavaLocalDate(): java.time.LocalDate = this.value - -/** - * Converts this [java.time.LocalDate][java.time.LocalDate] value to a [kotlinx.datetime.LocalDate][LocalDate] value. - */ -public fun java.time.LocalDate.toKotlinLocalDate(): LocalDate = LocalDate(this) - - -/** - * Converts this [kotlinx.datetime.DatePeriod][DatePeriod] value to a [java.time.Period][java.time.Period] value. - */ -public fun DatePeriod.toJavaPeriod(): java.time.Period = java.time.Period.of(this.years, this.months, this.days) - -/** - * Converts this [java.time.Period][java.time.Period] value to a [kotlinx.datetime.DatePeriod][DatePeriod] value. - */ -public fun java.time.Period.toKotlinDatePeriod(): DatePeriod = DatePeriod(this.years, this.months, this.days) - - -/** - * Converts this [kotlinx.datetime.TimeZone][TimeZone] value to a [java.time.ZoneId][java.time.ZoneId] value. - */ -public fun TimeZone.toJavaZoneId(): java.time.ZoneId = this.zoneId - -/** - * Converts this [java.time.ZoneId][java.time.ZoneId] value to a [kotlinx.datetime.TimeZone][TimeZone] value. - */ -public fun java.time.ZoneId.toKotlinTimeZone(): TimeZone = TimeZone.ofZone(this) - - -/** - * Converts this [kotlinx.datetime.FixedOffsetTimeZone][FixedOffsetTimeZone] value to a [java.time.ZoneOffset][java.time.ZoneOffset] value. - */ -public fun FixedOffsetTimeZone.toJavaZoneOffset(): java.time.ZoneOffset = this.offset.zoneOffset - -/** - * Converts this [java.time.ZoneOffset][java.time.ZoneOffset] value to a [kotlinx.datetime.FixedOffsetTimeZone][FixedOffsetTimeZone] value. - */ -public fun java.time.ZoneOffset.toKotlinFixedOffsetTimeZone(): FixedOffsetTimeZone = FixedOffsetTimeZone(UtcOffset(this)) - -@Deprecated("Use toKotlinFixedOffsetTimeZone() instead.", ReplaceWith("this.toKotlinFixedOffsetTimeZone()")) -public fun java.time.ZoneOffset.toKotlinZoneOffset(): FixedOffsetTimeZone = toKotlinFixedOffsetTimeZone() - -/** - * Converts this [kotlinx.datetime.UtcOffset][UtcOffset] value to a [java.time.ZoneOffset][java.time.ZoneOffset] value. - */ -public fun UtcOffset.toJavaZoneOffset(): java.time.ZoneOffset = this.zoneOffset - -/** - * Converts this [java.time.ZoneOffset][java.time.ZoneOffset] value to a [kotlinx.datetime.UtcOffset][UtcOffset] value. - */ -public fun java.time.ZoneOffset.toKotlinUtcOffset(): UtcOffset = UtcOffset(this) - +public fun java.time.Instant.toKotlinInstant(): Instant = Instant.fromEpochSeconds(epochSecond, nano) From 3123d055fce4a83db872757ab0bb3193c7938695 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 28 Nov 2024 13:55:38 +0100 Subject: [PATCH 3/7] Copy the commonKotlin Instant implementation to kotlinx.time --- fake-kotlinx-time/build.gradle.kts | 1 + fake-kotlinx-time/common/src/Instant.kt | 492 ++++++++++++++++++++- fake-kotlinx-time/js/src/Platform.kt | 10 + fake-kotlinx-time/jvm/src/Platform.kt | 8 + fake-kotlinx-time/native/src/Platform.kt | 21 + fake-kotlinx-time/wasmJs/src/Platform.kt | 12 + fake-kotlinx-time/wasmWasi/src/Platform.kt | 38 ++ 7 files changed, 564 insertions(+), 18 deletions(-) create mode 100644 fake-kotlinx-time/js/src/Platform.kt create mode 100644 fake-kotlinx-time/jvm/src/Platform.kt create mode 100644 fake-kotlinx-time/native/src/Platform.kt create mode 100644 fake-kotlinx-time/wasmJs/src/Platform.kt create mode 100644 fake-kotlinx-time/wasmWasi/src/Platform.kt diff --git a/fake-kotlinx-time/build.gradle.kts b/fake-kotlinx-time/build.gradle.kts index 25394044b..d8966591c 100644 --- a/fake-kotlinx-time/build.gradle.kts +++ b/fake-kotlinx-time/build.gradle.kts @@ -13,6 +13,7 @@ java { } kotlin { + explicitApi() // Tiers are in accordance with // Tier 1 diff --git a/fake-kotlinx-time/common/src/Instant.kt b/fake-kotlinx-time/common/src/Instant.kt index fcd4f7ffe..a9abcd514 100644 --- a/fake-kotlinx-time/common/src/Instant.kt +++ b/fake-kotlinx-time/common/src/Instant.kt @@ -5,7 +5,10 @@ package kotlinx.time +import kotlin.math.absoluteValue import kotlin.time.* +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds /** * A moment in time. @@ -92,8 +95,7 @@ import kotlin.time.* * instant.toString() // 2023-01-02T21:35:01Z * ``` */ -public expect class Instant : Comparable { - +public class Instant internal constructor( /** * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. * @@ -105,8 +107,7 @@ public expect class Instant : Comparable { * @see fromEpochSeconds * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds */ - public val epochSeconds: Long - + public val epochSeconds: Long, /** * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. * @@ -116,6 +117,11 @@ public expect class Instant : Comparable { * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond */ public val nanosecondsOfSecond: Int +) : Comparable { + + init { + require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } + } /** * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. @@ -128,7 +134,39 @@ public expect class Instant : Comparable { * @see fromEpochMilliseconds * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds */ - public fun toEpochMilliseconds(): Long + // org.threeten.bp.Instant#toEpochMilli + public fun toEpochMilliseconds(): Long = try { + if (epochSeconds >= 0) { + val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) + } else { + // prevent an overflow in seconds * 1000 + // instead of going form the second farther away from 0 + // going toward 0 + // we go from the second closer to 0 away from 0 + // that way we always stay in the valid long range + // seconds + 1 can not overflow because it is negative + val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) + } + } catch (_: ArithmeticException) { + if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE + } + + // org.threeten.bp.Instant#plus(long, long) + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { + if ((secondsToAdd or nanosToAdd) == 0L) { + return this + } + val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) + val newNanosToAdd = nanosToAdd % NANOS_PER_ONE + val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE + return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) + } /** * Returns an instant that is the result of adding the specified [duration] to this instant. @@ -144,7 +182,15 @@ public expect class Instant : Comparable { * * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration */ - public operator fun plus(duration: Duration): Instant + public operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> + try { + plus(secondsToAdd, nanosecondsToAdd.toLong()) + } catch (_: IllegalArgumentException) { + if (duration.isPositive()) MAX else MIN + } catch (_: ArithmeticException) { + if (duration.isPositive()) MAX else MIN + } + } /** * Returns an instant that is the result of subtracting the specified [duration] from this instant. @@ -160,7 +206,7 @@ public expect class Instant : Comparable { * * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration */ - public operator fun minus(duration: Duration): Instant + public operator fun minus(duration: Duration): Instant = plus(-duration) // questionable /** @@ -179,7 +225,9 @@ public expect class Instant : Comparable { * * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant */ - public operator fun minus(other: Instant): Duration + public operator fun minus(other: Instant): Duration = + (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds + (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds /** * Compares `this` instant with the [other] instant. @@ -189,7 +237,21 @@ public expect class Instant : Comparable { * * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample */ - public override operator fun compareTo(other: Instant): Int + public override operator fun compareTo(other: Instant): Int { + val s = epochSeconds.compareTo(other.epochSeconds) + if (s != 0) { + return s + } + return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) + } + + override fun equals(other: Any?): Boolean = + this === other || other is Instant && this.epochSeconds == other.epochSeconds + && this.nanosecondsOfSecond == other.nanosecondsOfSecond + + // org.threeten.bp.Instant#hashCode + override fun hashCode(): Int = + (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond /** * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. @@ -201,12 +263,11 @@ public expect class Instant : Comparable { * * @see parse */ - public override fun toString(): String - + public override fun toString(): String = formatIso(this) public companion object { @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public fun now(): Instant + public fun now(): Instant = currentTime() /** * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. @@ -218,7 +279,16 @@ public expect class Instant : Comparable { * @see Instant.toEpochMilliseconds * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds */ - public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant + // org.threeten.bp.Instant#ofEpochMilli + public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { + val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) + val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() + return when { + epochSeconds < MIN_SECOND -> MIN + epochSeconds > MAX_SECOND -> MAX + else -> fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + } /** * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` @@ -233,7 +303,15 @@ public expect class Instant : Comparable { * @see Instant.nanosecondsOfSecond * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant + // org.threeten.bp.Instant#ofEpochSecond(long, long) + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant = + try { + fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) + } catch (_: ArithmeticException) { + if (epochSeconds > 0) MAX else MIN + } catch (_: IllegalArgumentException) { + if (epochSeconds > 0) MAX else MIN + } /** * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` @@ -248,7 +326,8 @@ public expect class Instant : Comparable { * @see Instant.nanosecondsOfSecond * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) /** * Parses an ISO 8601 string that represents an instant (for example, `2020-08-30T18:43:00Z`). @@ -277,7 +356,8 @@ public expect class Instant : Comparable { * @see Instant.toString for formatting. * @sample kotlinx.datetime.test.samples.InstantSamples.parsing */ - public fun parse(input: CharSequence): Instant + public fun parse(input: CharSequence): Instant = parseIso(input) + /** * An instant value that is far in the past. @@ -285,6 +365,7 @@ public expect class Instant : Comparable { * [isDistantPast] returns true for this value and all earlier ones. */ public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z + get() = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) /** * An instant value that is far in the future. @@ -292,9 +373,20 @@ public expect class Instant : Comparable { * [isDistantFuture] returns true for this value and all later ones. */ public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z + get() = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) + + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { + val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) + val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() + return Instant(secs, nos) + } - internal val MIN: Instant - internal val MAX: Instant + internal val MIN = Instant(MIN_SECOND, 0) + internal val MAX = Instant(MAX_SECOND, 999_999_999) } } @@ -316,3 +408,367 @@ public val Instant.isDistantFuture: Boolean internal const val DISTANT_PAST_SECONDS = -3217862419201 internal const val DISTANT_FUTURE_SECONDS = 3093527980800 + +internal expect fun currentTime(): Instant + +/** + * The minimum supported epoch second. + */ +private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z + +/** + * The maximum supported epoch second. + */ +private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59 + +private class UnboundedLocalDateTime( + val year: Int, + val month: Int, + val day: Int, + val hour: Int, + val minute: Int, + val second: Int, + val nanosecond: Int, +) { + fun toInstant(offsetSeconds: Int): Instant { + val epochSeconds = run { + // org.threeten.bp.LocalDate#toEpochDay + val epochDays = run { + val y = year.toLong() + var total = 365 * y + if (y >= 0) { + total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400 + } else { + total -= y / -4 - y / -100 + y / -400 + } + total += ((367 * month - 362) / 12) + total += day - 1 + if (month > 2) { + total-- + if (!isLeapYear(year)) { + total-- + } + } + total - DAYS_0000_TO_1970 + } + // org.threeten.bp.LocalTime#toSecondOfDay + val daySeconds = hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second + // org.threeten.bp.chrono.ChronoLocalDateTime#toEpochSecond + epochDays * 86400L + daySeconds - offsetSeconds + } + if (epochSeconds < Instant.MIN.epochSeconds || epochSeconds > Instant.MAX.epochSeconds) + throw IllegalArgumentException( + "The parsed date is outside the range representable by Instant (Unix epoch second $epochSeconds)" + ) + return Instant.fromEpochSeconds(epochSeconds, nanosecond) + } + + override fun toString(): String = "UnboundedLocalDateTime($year-$month-$day $hour:$minute:$second.$nanosecond)" + + companion object { + fun fromInstant(instant: Instant, offsetSeconds: Int): UnboundedLocalDateTime { + val localSecond: Long = instant.epochSeconds + offsetSeconds + val epochDays = localSecond.floorDiv(SECONDS_PER_DAY.toLong()) + val secsOfDay = localSecond.mod(SECONDS_PER_DAY.toLong()).toInt() + val year: Int + val month: Int + val day: Int + // org.threeten.bp.LocalDate#toEpochDay + run { + var zeroDay = epochDays + DAYS_0000_TO_1970 + // find the march-based year + zeroDay -= 60 // adjust to 0000-03-01 so leap day is at end of four year cycle + + var adjust = 0L + if (zeroDay < 0) { // adjust negative years to positive for calculation + val adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1 + adjust = adjustCycles * 400 + zeroDay += -adjustCycles * DAYS_PER_CYCLE + } + var yearEst = ((400 * zeroDay + 591) / DAYS_PER_CYCLE) + var doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400) + if (doyEst < 0) { // fix estimate + yearEst-- + doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400) + } + yearEst += adjust // reset any negative year + + val marchDoy0 = doyEst.toInt() + + // convert march-based values back to january-based + val marchMonth0 = (marchDoy0 * 5 + 2) / 153 + month = (marchMonth0 + 2) % 12 + 1 + day = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1 + year = (yearEst + marchMonth0 / 10).toInt() + } + val hours = (secsOfDay / SECONDS_PER_HOUR) + val secondWithoutHours = secsOfDay - hours * SECONDS_PER_HOUR + val minutes = (secondWithoutHours / SECONDS_PER_MINUTE) + val second = secondWithoutHours - minutes * SECONDS_PER_MINUTE + return UnboundedLocalDateTime(year, month, day, hours, minutes, second, instant.nanosecondsOfSecond) + } + } +} + +private fun parseIso(isoString: CharSequence): Instant { + fun parseFailure(error: String): Nothing { + throw IllegalArgumentException("$error when parsing an Instant from $isoString") + } + fun expect(what: String, where: Int, predicate: (Char) -> Boolean) { + val c = isoString[where] + if (!predicate(c)) { + parseFailure("Expected $what, but got $c at position $where") + } + } + val s = isoString + var i = 0 + require(s.isNotEmpty()) { "An empty string is not a valid Instant" } + val yearSign = when (val c = s[i]) { + '+', '-' -> { ++i; c } + else -> ' ' + } + val yearStart = i + var absYear = 0 + while (i < s.length && s[i] in '0'..'9') { + absYear = absYear * 10 + (s[i] - '0') + ++i + } + val year = when { + i > yearStart + 10 -> { + parseFailure("Expected at most 10 digits for the year number, got ${i - yearStart}") + } + i == yearStart + 10 && s[yearStart] >= '2' -> { + parseFailure("Expected at most 9 digits for the year number or year 1000000000, got ${i - yearStart}") + } + i - yearStart < 4 -> { + parseFailure("The year number must be padded to 4 digits, got ${i - yearStart} digits") + } + else -> { + if (yearSign == '+' && i - yearStart == 4) { + parseFailure("The '+' sign at the start is only valid for year numbers longer than 4 digits") + } + if (yearSign == ' ' && i - yearStart != 4) { + parseFailure("A '+' or '-' sign is required for year numbers longer than 4 digits") + } + if (yearSign == '-') -absYear else absYear + } + } + // reading at least -MM-DDTHH:MM:SSZ + // 0123456789012345 16 chars + if (s.length < i + 16) { + parseFailure("The input string is too short") + } + expect("'-'", i) { it == '-' } + expect("'-'", i + 3) { it == '-' } + expect("'T' or 't'", i + 6) { it == 'T' || it == 't' } + expect("':'", i + 9) { it == ':' } + expect("':'", i + 12) { it == ':' } + for (j in listOf(1, 2, 4, 5, 7, 8, 10, 11, 13, 14)) { + expect("an ASCII digit", i + j) { it in '0'..'9' } + } + fun twoDigitNumber(index: Int) = s[index].code * 10 + s[index + 1].code - '0'.code * 11 + val month = twoDigitNumber(i + 1) + val day = twoDigitNumber(i + 4) + val hour = twoDigitNumber(i + 7) + val minute = twoDigitNumber(i + 10) + val second = twoDigitNumber(i + 13) + val nanosecond = if (s[i + 15] == '.') { + val fractionStart = i + 16 + i = fractionStart + var fraction = 0 + while (i < s.length && s[i] in '0'..'9') { + fraction = fraction * 10 + (s[i] - '0') + ++i + } + if (i - fractionStart in 1..9) { + fraction * POWERS_OF_TEN[fractionStart + 9 - i] + } else { + parseFailure("1..9 digits are supported for the fraction of the second, got {i - fractionStart}") + } + } else { + i += 15 + 0 + } + val offsetSeconds = when (val sign = s.getOrNull(i)) { + null -> { + parseFailure("The UTC offset at the end of the string is missing") + } + 'z', 'Z' -> if (s.length == i + 1) { + 0 + } else { + parseFailure("Extra text after the instant at position ${i + 1}") + } + '-', '+' -> { + val offsetStrLength = s.length - i + if (offsetStrLength % 3 != 0) { parseFailure("Invalid UTC offset string '${s.substring(i)}'") } + if (offsetStrLength > 9) { parseFailure("The UTC offset string '${s.substring(i)}' is too long") } + for (j in listOf(3, 6)) { + if ((s.getOrNull(i + j) ?: break) != ':') + parseFailure("Expected ':' at index ${i + j}, got '${s[i + j]}'") + } + for (j in listOf(1, 2, 4, 5, 7, 8)) { + if ((s.getOrNull(i + j) ?: break) !in '0'..'9') + parseFailure("Expected a digit at index ${i + j}, got '${s[i + j]}'") + } + val offsetHour = twoDigitNumber(i + 1) + val offsetMinute = if (offsetStrLength > 3) { twoDigitNumber(i + 4) } else { 0 } + val offsetSecond = if (offsetStrLength > 6) { twoDigitNumber(i + 7) } else { 0 } + if (offsetMinute > 59) { parseFailure("Expected offset-minute-of-hour in 0..59, got $offsetMinute") } + if (offsetSecond > 59) { parseFailure("Expected offset-second-of-minute in 0..59, got $offsetSecond") } + if (offsetHour > 17 && !(offsetHour == 18 && offsetMinute == 0 && offsetSecond == 0)) { + parseFailure("Expected an offset in -18:00..+18:00, got $sign$offsetHour:$offsetMinute:$offsetSecond") + } + (offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == '-') -1 else 1 + } + else -> { + parseFailure("Expected the UTC offset at position $i, got '$sign'") + } + } + if (month !in 1..12) { parseFailure("Expected a month number in 1..12, got $month") } + if (day !in 1..month.monthLength(isLeapYear(year))) { + parseFailure("Expected a valid day-of-month for $year-$month, got $day") + } + if (hour > 23) { parseFailure("Expected hour in 0..23, got $hour") } + if (minute > 59) { parseFailure("Expected minute-of-hour in 0..59, got $minute") } + if (second > 59) { parseFailure("Expected second-of-minute in 0..59, got $second") } + return UnboundedLocalDateTime(year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds) +} + +private fun formatIso(instant: Instant): String = buildString { + val ldt = UnboundedLocalDateTime.fromInstant(instant, 0) + fun Appendable.appendTwoDigits(number: Int) { + if (number < 10) append('0') + append(number) + } + run { + val number = ldt.year + when { + number.absoluteValue < 1_000 -> { + val innerBuilder = StringBuilder() + if (number >= 0) { + innerBuilder.append((number + 10_000)).deleteAt(0) + } else { + innerBuilder.append((number - 10_000)).deleteAt(1) + } + append(innerBuilder) + } + else -> { + if (number >= 10_000) append('+') + append(number) + } + } + } + append('-') + appendTwoDigits(ldt.month) + append('-') + appendTwoDigits(ldt.day) + append('T') + appendTwoDigits(ldt.hour) + append(':') + appendTwoDigits(ldt.minute) + append(':') + appendTwoDigits(ldt.second) + if (ldt.nanosecond != 0) { + append('.') + var zerosToStrip = 0 + while (ldt.nanosecond % POWERS_OF_TEN[zerosToStrip + 1] == 0) { + ++zerosToStrip + } + zerosToStrip -= (zerosToStrip.mod(3)) // rounding down to a multiple of 3 + val numberToOutput = ldt.nanosecond / POWERS_OF_TEN[zerosToStrip] + append((numberToOutput + POWERS_OF_TEN[9 - zerosToStrip]).toString().substring(1)) + } + append('Z') +} + +/** + * All code below was taken from various places of https://github.com/ThreeTen/threetenbp with few changes + */ + +/** + * The number of days in a 400 year cycle. + */ +private const val DAYS_PER_CYCLE = 146097 + +/** + * The number of days from year zero to year 1970. + * There are five 400 year cycles from year zero to 2000. + * There are 7 leap years from 1970 to 2000. + */ +private const val DAYS_0000_TO_1970 = DAYS_PER_CYCLE * 5 - (30 * 365 + 7) + +/** + * Safely adds two long values. + * throws [ArithmeticException] if the result overflows a long + */ +private fun safeAdd(a: Long, b: Long): Long { + val sum = a + b + // check for a change of sign in the result when the inputs have the same sign + if ((a xor sum) < 0 && (a xor b) >= 0) { + throw ArithmeticException("Addition overflows a long: $a + $b") + } + return sum +} + +/** + * Safely multiply a long by a long. + * + * @param a the first value + * @param b the second value + * @return the new total + * @throws ArithmeticException if the result overflows a long + */ +private fun safeMultiply(a: Long, b: Long): Long { + if (b == 1L) { + return a + } + if (a == 1L) { + return b + } + if (a == 0L || b == 0L) { + return 0 + } + val total = a * b + if (total / b != a || a == Long.MIN_VALUE && b == -1L || b == Long.MIN_VALUE && a == -1L) { + throw ArithmeticException("Multiplication overflows a long: $a * $b") + } + return total +} + +private const val SECONDS_PER_HOUR = 60 * 60 + +private const val SECONDS_PER_MINUTE = 60 + +private const val HOURS_PER_DAY = 24 + +private const val SECONDS_PER_DAY: Int = SECONDS_PER_HOUR * HOURS_PER_DAY + +internal const val NANOS_PER_ONE = 1_000_000_000 +private const val NANOS_PER_MILLI = 1_000_000 +private const val MILLIS_PER_ONE = 1_000 + +// org.threeten.bp.chrono.IsoChronology#isLeapYear +private fun isLeapYear(year: Int): Boolean { + val prolepticYear: Long = year.toLong() + return prolepticYear and 3 == 0L && (prolepticYear % 100 != 0L || prolepticYear % 400 == 0L) +} + +private fun Int.monthLength(isLeapYear: Boolean): Int = + when (this) { + 2 -> if (isLeapYear) 29 else 28 + 4, 6, 9, 11 -> 30 + else -> 31 + } + +private val POWERS_OF_TEN = intArrayOf( + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000 +) diff --git a/fake-kotlinx-time/js/src/Platform.kt b/fake-kotlinx-time/js/src/Platform.kt new file mode 100644 index 000000000..52a3cbd31 --- /dev/null +++ b/fake-kotlinx-time/js/src/Platform.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time + +import kotlin.js.Date + +internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) diff --git a/fake-kotlinx-time/jvm/src/Platform.kt b/fake-kotlinx-time/jvm/src/Platform.kt new file mode 100644 index 000000000..71a7516b7 --- /dev/null +++ b/fake-kotlinx-time/jvm/src/Platform.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time + +internal actual fun currentTime(): Instant = java.time.Instant.now().toKotlinInstant() diff --git a/fake-kotlinx-time/native/src/Platform.kt b/fake-kotlinx-time/native/src/Platform.kt new file mode 100644 index 000000000..8e036c159 --- /dev/null +++ b/fake-kotlinx-time/native/src/Platform.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time +import kotlinx.cinterop.* +import platform.posix.* + +@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) +internal actual fun currentTime(): Instant = memScoped { + val tm = alloc() + val error = clock_gettime(CLOCK_REALTIME.convert(), tm.ptr) + check(error == 0) { "Error when reading the system clock: ${strerror(errno)?.toKString() ?: "Unknown error"}" } + try { + require(tm.tv_nsec in 0 until NANOS_PER_ONE) + Instant(tm.tv_sec.convert(), tm.tv_nsec.convert()) + } catch (e: IllegalArgumentException) { + throw IllegalStateException("The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant") + } +} diff --git a/fake-kotlinx-time/wasmJs/src/Platform.kt b/fake-kotlinx-time/wasmJs/src/Platform.kt new file mode 100644 index 000000000..f8c26688b --- /dev/null +++ b/fake-kotlinx-time/wasmJs/src/Platform.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time + +internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) + +private external class Date { + fun getTime(): Double +} diff --git a/fake-kotlinx-time/wasmWasi/src/Platform.kt b/fake-kotlinx-time/wasmWasi/src/Platform.kt new file mode 100644 index 000000000..012e08c18 --- /dev/null +++ b/fake-kotlinx-time/wasmWasi/src/Platform.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time + +import kotlin.wasm.WasmImport +import kotlin.wasm.unsafe.UnsafeWasmMemoryApi +import kotlin.wasm.unsafe.withScopedMemoryAllocator + +/** + * Return the time value of a clock. Note: This is similar to `clock_gettime` in POSIX. + */ +@WasmImport("wasi_snapshot_preview1", "clock_time_get") +private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int + +private const val CLOCKID_REALTIME = 0 + +@OptIn(UnsafeWasmMemoryApi::class) +private fun clockTimeGet(): Long = withScopedMemoryAllocator { allocator -> + val rp0 = allocator.allocate(8) + val ret = wasiRawClockTimeGet( + clockId = CLOCKID_REALTIME, + precision = 1, + resultPtr = rp0.address.toInt() + ) + if (ret == 0) { + rp0.loadLong() + } else { + error("WASI call failed with $ret") + } +} + +internal actual fun currentTime(): Instant = clockTimeGet().let { time -> + // Instant.MAX and Instant.MIN are never going to be exceeded using just the Long number of nanoseconds + Instant(time.floorDiv(NANOS_PER_ONE.toLong()), time.mod(NANOS_PER_ONE.toLong()).toInt()) +} From 22915d50d6f216c1734600dd22d9823505613495 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 13 Dec 2024 10:14:23 +0100 Subject: [PATCH 4/7] Support Android devices with API level < 24 --- fake-kotlinx-time/jvm/src/Platform.kt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/fake-kotlinx-time/jvm/src/Platform.kt b/fake-kotlinx-time/jvm/src/Platform.kt index 71a7516b7..96031eab0 100644 --- a/fake-kotlinx-time/jvm/src/Platform.kt +++ b/fake-kotlinx-time/jvm/src/Platform.kt @@ -5,4 +5,22 @@ package kotlinx.time -internal actual fun currentTime(): Instant = java.time.Instant.now().toKotlinInstant() +internal actual fun currentTime(): Instant = if (javaTimeAvailable) { + java.time.Instant.now().toKotlinInstant() +} else { + /* After experimenting in Android Studio, it seems like on Android with API < 24, only millisecond precision + is available in `Instant.now()` with core library desugaring enabled. Because of that, `currentTimeMillis` + is good enough + suggesting that our users enable core library desugaring isn't going to bring any benefits, + so the KDoc for [Clock] does not mention any of this. */ + Instant.fromEpochMilliseconds(System.currentTimeMillis()) +} + +/** + * `false` for Android devices with API level < 24, where java.time is not available. + */ +private val javaTimeAvailable = try { + java.time.Instant.now() + true +} catch (e: NoClassDefFoundError) { + false +} From fb88a097ccac0a0b18bcd5b6450b2f138f41fd53 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 28 Nov 2024 14:34:55 +0100 Subject: [PATCH 5/7] Add tests and samples to kotlinx.time --- fake-kotlinx-time/common/src/Instant.kt | 6 +- fake-kotlinx-time/common/test/InstantTest.kt | 608 ++++++++++++++++++ .../common/test/ThreeTenBpInstantTest.kt | 96 +++ .../common/test/ThreeTenBpUtilTest.kt | 49 ++ fake-kotlinx-time/common/test/assertions.kt | 20 + .../common/test/samples/ClockSamples.kt | 36 ++ .../common/test/samples/InstantSamples.kt | 122 ++++ fake-kotlinx-time/js/test/ConvertersTest.kt | 29 + fake-kotlinx-time/jvm/test/ConvertersTest.kt | 34 + 9 files changed, 997 insertions(+), 3 deletions(-) create mode 100644 fake-kotlinx-time/common/test/InstantTest.kt create mode 100644 fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt create mode 100644 fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt create mode 100644 fake-kotlinx-time/common/test/assertions.kt create mode 100644 fake-kotlinx-time/common/test/samples/ClockSamples.kt create mode 100644 fake-kotlinx-time/common/test/samples/InstantSamples.kt create mode 100644 fake-kotlinx-time/js/test/ConvertersTest.kt create mode 100644 fake-kotlinx-time/jvm/test/ConvertersTest.kt diff --git a/fake-kotlinx-time/common/src/Instant.kt b/fake-kotlinx-time/common/src/Instant.kt index a9abcd514..24ebc7d13 100644 --- a/fake-kotlinx-time/common/src/Instant.kt +++ b/fake-kotlinx-time/common/src/Instant.kt @@ -234,8 +234,6 @@ public class Instant internal constructor( * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), * a negative number if this instant is earlier than the other, * and a positive number if this instant is later than the other. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample */ public override operator fun compareTo(other: Instant): Int { val s = epochSeconds.compareTo(other.epochSeconds) @@ -262,6 +260,8 @@ public class Instant internal constructor( * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. * * @see parse + * + * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample */ public override fun toString(): String = formatIso(this) @@ -748,7 +748,7 @@ private const val NANOS_PER_MILLI = 1_000_000 private const val MILLIS_PER_ONE = 1_000 // org.threeten.bp.chrono.IsoChronology#isLeapYear -private fun isLeapYear(year: Int): Boolean { +internal fun isLeapYear(year: Int): Boolean { val prolepticYear: Long = year.toLong() return prolepticYear and 3 == 0L && (prolepticYear % 100 != 0L || prolepticYear % 400 == 0L) } diff --git a/fake-kotlinx-time/common/test/InstantTest.kt b/fake-kotlinx-time/common/test/InstantTest.kt new file mode 100644 index 000000000..c42fa9e2a --- /dev/null +++ b/fake-kotlinx-time/common/test/InstantTest.kt @@ -0,0 +1,608 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time.test + +import kotlinx.time.* +import kotlin.math.absoluteValue +import kotlin.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 { + + @Test + fun testNow() { + val instant = Clock.System.now() + val millis = instant.toEpochMilliseconds() + + assertTrue(millis > 1_500_000_000_000L) + + println(instant) + println(instant.toEpochMilliseconds()) + + 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) + + println("this: $instant, next: $nextInstant, diff: ${diff.toIsoString()}") + } + + @Test + fun addingMultiplesOf2_32() { + val pow2_32 = 1L shl 32 + val instant1 = Instant.fromEpochSeconds(0) + val instant2 = instant1.plus(pow2_32.nanoseconds) + assertEquals(pow2_32 / NANOS_PER_ONE, instant2.epochSeconds) + assertEquals(pow2_32 % NANOS_PER_ONE, instant2.nanosecondsOfSecond.toLong()) + + val instant3 = instant1.plus(pow2_32.seconds) + assertEquals(pow2_32, instant3.epochSeconds) + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @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) + } + +} + +class InstantRangeTest { + 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 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 timeBasedUnitArithmeticOutOfRange() { + // Instant.plus(Long, DateTimeUnit.TimeBased) + // 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) + } +} + +class InstantIsoStringsTest { + + @Test + fun parseDates() { + fun Int.zeroPadded(digits: Int): String = when { + this >= 0 -> toString().padStart(digits, '0') + else -> "-${absoluteValue.toString().padStart(digits, '0')}" + } + + fun localDateToString(year: Int, month: Int, day: Int) = + "${year.zeroPadded(4)}-${month.zeroPadded(2)}-${day.zeroPadded(2)}" + + // only works for 1-4-digit years + fun assertMonthBoundariesAreCorrect(year: Int, month: Int, lastDayOfMonth: Int) { + val validString = "${localDateToString(year, month, lastDayOfMonth)}T23:59:59Z" + val invalidString = "${localDateToString(year, month, lastDayOfMonth + 1)}T23:59:59Z" + Instant.parse(validString) // shouldn't throw + assertInvalidFormat(invalidString) { Instant.parse(invalidString) } + } + + val nonLeapYears = listOf( + 1970, 1971, 1973, 1974, 1975, 2021, 2022, 2023, 2100, 1100, 1, 2, 3, 5, -1, -2, -1971, 100, -100 + ) + val leapYears = listOf( + 0, 1972, 1976, 1980, 2000, 1200, 400, -400, -4, -8, + ) + for ((month, lastDayOfMonth) in arrayOf( + 1 to 31, 3 to 31, 4 to 30, 5 to 31, 6 to 30, + 7 to 31, 8 to 31, 9 to 30, 10 to 31, 11 to 30, 12 to 31, + )) { + for (year in nonLeapYears + leapYears) { + assertMonthBoundariesAreCorrect(year, month, lastDayOfMonth) + } + } + for (leapYear in leapYears) { + assertMonthBoundariesAreCorrect(leapYear, 2, 29) + } + for (nonLeapYear in nonLeapYears) { + assertMonthBoundariesAreCorrect(nonLeapYear, 2, 28) + } + } + + @Test + fun parseIsoString() { + for ((str, seconds, nanos) in arrayOf>( + // all components are taken into account + Triple("1970-01-01T00:00:00Z", 0, 0), + Triple("1970-01-01T00:00:00.000000001Z", 0, 1), + Triple("1970-01-01T00:00:00.100Z", 0, 100000000), + Triple("1970-01-01T00:00:01Z", 1, 0), + Triple("1970-01-01T00:01:00Z", 60, 0), + Triple("1970-01-01T00:01:01Z", 61, 0), + Triple("1970-01-01T00:01:01.000000001Z", 61, 1), + Triple("1970-01-01T01:00:00Z", 3600, 0), + Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), + Triple("1970-01-02T01:01:01.100Z", 90061, 100000000), + Triple("1970-02-02T01:01:01.100Z", 31 * 86400 + 90061, 100000000), + Triple("1971-02-02T01:01:01.100Z", (365 + 31) * 86400 + 90061, 100000000), + // how many digits get output for various precision of the sub-second portion + Triple("1970-01-01T00:00:00.100Z", 0, 100_000_000), + Triple("1970-01-01T00:00:00.010Z", 0, 10_000_000), + Triple("1970-01-01T00:00:00.001Z", 0, 1_000_000), + Triple("1970-01-01T00:00:00.000100Z", 0, 100_000), + Triple("1970-01-01T00:00:00.000010Z", 0, 10_000), + Triple("1970-01-01T00:00:00.000001Z", 0, 1_000), + Triple("1970-01-01T00:00:00.000000100Z", 0, 100), + Triple("1970-01-01T00:00:00.000000010Z", 0, 10), + Triple("1970-01-01T00:00:00.000000001Z", 0, 1), + // random data queried from java.time + Triple("+51861-09-21T11:07:43.782719883Z", 1574430692863, 782719883), + Triple("+395069-04-30T01:28:37.454777349Z", 12405016603717, 454777349), + Triple("-551259-03-05T08:01:36.195722269Z", -17458215523104, 195722269), + Triple("+498403-02-11T17:47:05.156642423Z", 15665915958425, 156642423), + Triple("+283686-10-14T23:00:25.666521845Z", 8890123158025, 666521845), + Triple("-910329-04-04T09:27:54.456784744Z", -28789367639526, 456784744), + Triple("-37222-03-21T18:04:37.006055123Z", -1236773166923, 6055123), + Triple("-189377-03-30T01:37:14.288808090Z", -6038320515766, 288808090), + Triple("-67394-03-24T03:19:41.794404047Z", -2188909341619, 794404047), + Triple("-870649-05-27T13:47:39.925150102Z", -27537183223941, 925150102), + Triple("+94020-04-10T14:51:21.569206089Z", 2904826114281, 569206089), + Triple("-945485-07-11T23:28:58.240153828Z", -29898775384262, 240153828), + Triple("-73722-02-22T11:19:54.364548772Z", -2388604250406, 364548772), + Triple("-645899-05-17T16:44:21.522135477Z", -20444759104539, 522135477), + Triple("-702594-10-20T10:13:53.212104714Z", -22233867083167, 212104714), + Triple("-442579-11-22T01:35:44.591216727Z", -14028583357456, 591216727), + Triple("-849915-06-25T01:28:27.625015449Z", -26882878833093, 625015449), + Triple("-481897-08-13T05:44:47.077814711Z", -15269348340913, 77814711), + Triple("+295919-02-07T15:47:37.850981753Z", 9276137682457, 850981753), + Triple("+967334-01-15T15:08:10.235167075Z", 30463946694490, 235167075), + Triple("+774237-04-30T16:00:32.810606451Z", 24370403011232, 810606451), + Triple("+792959-05-03T08:18:31.616194572Z", 24961212490711, 616194572), + Triple("-261823-02-16T03:17:35.085815500Z", -8324498983345, 85815500), + Triple("+931062-03-22T17:04:54.135075640Z", 29319318637494, 135075640), + Triple("+623320-01-26T03:08:05.121769356Z", 19607914264085, 121769356), + Triple("+322804-03-06T11:31:24.788006817Z", 10124548774284, 788006817), + Triple("-784322-04-03T21:25:19.666588404Z", -24812970806081, 666588404), + Triple("+403293-01-07T05:59:41.601460200Z", 12664531288781, 601460200), + Triple("-835821-06-01T00:52:15.782852248Z", -26438117296065, 782852248), + Triple("+222483-07-15T08:29:55.019931345Z", 6958735086595, 19931345), + Triple("-663595-09-05T04:36:24.110433196Z", -21003181356216, 110433196), + Triple("+166626-02-15T22:16:34.070665743Z", 5196045449794, 70665743), + Triple("-517158-01-02T22:52:24.155574933Z", -16382097162456, 155574933), + Triple("+850155-01-02T10:25:31.349473798Z", 26766133467931, 349473798), + Triple("-967697-04-25T20:43:33.328060156Z", -30599725115787, 328060156), + Triple("+437131-04-26T07:32:58.134219875Z", 13732364705578, 134219875), + Triple("+372920-11-25T13:38:22.852562723Z", 11706079786702, 852562723), + Triple("+169255-09-07T11:28:18.481625778Z", 5279026303698, 481625778), + Triple("-980786-08-18T17:05:22.581779094Z", -31012764044078, 581779094), + Triple("+182945-05-25T20:39:24.545585221Z", 5711031952764, 545585221), + Triple("+300811-12-15T02:53:38.676752671Z", 9430541175218, 676752671), + Triple("-807816-01-18T18:04:26.291749218Z", -25554376389334, 291749218), + Triple("-53033-12-30T22:02:01.398533618Z", -1735695568679, 398533618), + Triple("-354903-06-14T10:08:46.111648055Z", -11261809864274, 111648055), + Triple("+842009-03-11T23:58:06.537554993Z", 26509076495886, 537554993), + Triple("-391976-11-09T04:16:17.862484469Z", -12431707962223, 862484469), + Triple("-733019-10-28T17:07:13.450343935Z", -23193986539967, 450343935), + Triple("+595280-03-05T23:36:27.765851400Z", 18723060833787, 765851400), + Triple("-930296-07-17T03:33:33.094509320Z", -29419456335987, 94509320), + Triple("+609508-02-29T10:58:02.703241053Z", 19172052557882, 703241053), + Triple("+996233-06-25T06:01:55.647461964Z", 31375924927315, 647461964), + Triple("-93200-12-06T21:29:56.140938343Z", -3003245692204, 140938343), + Triple("+794143-07-02T09:49:35.585085194Z", 24998581100975, 585085194), + Triple("-783550-12-31T17:10:16.577723428Z", -24788585371784, 577723428), + Triple("-240168-11-03T17:22:09.108424624Z", -7641110702271, 108424624), + Triple("+613419-02-15T12:00:07.012460989Z", 19295470641607, 12460989), + Triple("-521405-03-25T02:03:46.552711998Z", -16516112536574, 552711998), + Triple("-938829-01-22T16:48:43.582709371Z", -29688747030677, 582709371), + Triple("+916785-05-16T21:54:45.983221956Z", 28868784818085, 983221956), + Triple("+482425-06-09T04:24:32.683186155Z", 15161709183872, 683186155), + Triple("+622585-08-20T05:45:52.555088343Z", 19584737819152, 555088343), + Triple("-451048-11-02T01:49:29.076392891Z", -14295840847831, 76392891), + Triple("+721083-09-17T00:31:34.648020241Z", 22693036811494, 648020241), + Triple("+235979-10-28T12:07:33.706273641Z", 7384636728453, 706273641), + Triple("+285234-04-12T18:30:25.215363003Z", 8938957285825, 215363003), + Triple("-917176-03-10T10:03:25.943265324Z", -29005440213395, 943265324), + Triple("-381932-09-05T02:47:17.004960541Z", -12114755529163, 4960541), + Triple("-52158-11-11T09:38:45.489915403Z", -1708087530075, 489915403), + Triple("-584290-11-15T20:15:24.377620606Z", -18500551127076, 377620606), + Triple("-645616-05-05T17:36:59.941608628Z", -20435829488581, 941608628), + Triple("+794405-06-22T21:08:20.853641989Z", 25006848239300, 853641989), + Triple("+986590-08-01T05:15:25.827177433Z", 31071624470125, 827177433), + Triple("+527158-02-06T12:34:35.088546391Z", 16573335654875, 88546391), + Triple("-513116-05-01T07:28:44.448204123Z", -16254533665876, 448204123), + Triple("+397065-10-19T21:59:05.831855226Z", 12468019211945, 831855226), + Triple("+312769-04-26T11:33:07.802217284Z", 9807879123187, 802217284), + Triple("+682473-04-14T01:00:38.067076018Z", 21474609498038, 67076018), + Triple("+731560-02-15T02:15:06.599802467Z", 23023640456106, 599802467), + Triple("-877354-10-27T22:55:02.723751549Z", -27748759338298, 723751549), + Triple("-746193-01-02T07:19:56.258497483Z", -23609743807204, 258497483), + Triple("-822112-07-28T08:55:19.319285417Z", -26005498038281, 319285417), + Triple("-400365-04-30T00:05:51.210582736Z", -12696455980449, 210582736), + Triple("+436254-07-11T18:08:06.937065549Z", 13704695921286, 937065549), + Triple("-340854-01-07T03:17:32.367173472Z", -10818479997748, 367173472), + Triple("-985221-04-25T22:57:01.511559459Z", -31152729085379, 511559459), + Triple("+859861-09-01T02:21:20.289341591Z", 27072446149280, 289341591), + Triple("-0131-07-16T10:47:54.756333457Z", -66284140326, 756333457), + Triple("-327041-11-18T22:55:21.885337272Z", -10382556503079, 885337272), + Triple("-268616-05-06T10:27:54.420166505Z", -8538858480726, 420166505), + Triple("-228012-05-16T15:26:54.680432991Z", -7257519160386, 680432991), + Triple("+857168-09-12T13:29:36.945689251Z", 26987464272576, 945689251), + Triple("-974181-04-12T08:47:35.627678735Z", -30804341526745, 627678735), + Triple("-435700-10-20T22:33:13.897477229Z", -13811505874007, 897477229), + Triple("-507467-01-19T23:06:05.156792267Z", -16076277276835, 156792267), + Triple("-382257-11-19T08:00:10.407963305Z", -12125005142390, 407963305), + Triple("+83082-01-04T20:18:56.409867424Z", 2559647852336, 409867424), + Triple("-916839-09-12T22:45:39.091941363Z", -28994789466861, 91941363), + Triple("-147771-05-07T08:31:34.950238979Z", -4725358615706, 950238979), + // random enormous values queried from java.time + Triple("+639727757-10-17T17:26:30.359003681Z", 20187795978609990, 359003681), + Triple("-375448814-11-11T03:04:48.637504595Z", -11848082341899312, 637504595), + Triple("+99162559-10-16T11:21:05.057717803Z", 3129205972303265, 57717803), + Triple("-826813174-05-22T21:35:39.018693830Z", -26091765799784661, 18693830), + Triple("+254125623-04-28T11:42:22.659957596Z", 8019367929949342, 659957596), + Triple("+540978343-01-03T20:31:36.945404442Z", 17071565436102696, 945404442), + Triple("+988277529-06-06T13:25:41.942771350Z", 31186964391657941, 942771350), + Triple("+566487909-09-04T08:09:46.007490076Z", 17876569606963786, 7490076), + Triple("+442225124-02-16T18:23:29.975096773Z", 13955214848034209, 975096773), + Triple("-399250586-02-19T22:58:29.585918917Z", -12599193741267691, 585918917), + Triple("-190217791-04-28T00:49:29.270751921Z", -6002755857213031, 270751921), + Triple("-716173704-05-24T22:05:33.639928802Z", -22600321355469267, 639928802), + Triple("+910504788-10-26T05:23:43.517192887Z", 28732693749312223, 517192887), + Triple("+515896807-08-17T06:36:25.012343642Z", 16280068627982185, 12343642), + Triple("-623794742-03-20T17:09:07.396143995Z", -19685122891483853, 396143995), + Triple("-416781718-10-06T01:41:32.866307162Z", -13152422812544308, 866307162), + Triple("+287346593-09-30T23:30:55.109337183Z", 9067720499134255, 109337183), + Triple("-819839065-09-06T07:25:58.953784983Z", -25871684167672442, 953784983), + Triple("+673467211-06-05T02:15:40.712392732Z", 21252510297310540, 712392732), + Triple("+982441727-04-13T12:12:06.776817565Z", 31002804263391126, 776817565), + )) { + val instant = Instant.parse(str) + assertEquals( + Instant.fromEpochSeconds(seconds, nanos), instant, + "Parsed $instant from $str, with Unix time = `$seconds + 10^-9 * $nanos`" + ) + assertEquals(str, instant.toString()) + } + // non-canonical strings are parsed as well, but formatted differently + for ((str, seconds, nanos) in arrayOf( + // upper, lower case, trailing zeros + Triple("2024-07-15T14:06:29.461245000z", 1721052389, 461245000), + Triple("2024-07-15t14:06:29.4612450z", 1721052389, 461245000), + // current time + Triple("2024-07-15T16:06:29.461245691+02:00", 1721052389, 461245691), + )) { + val instant = Instant.parse(str) + assertEquals( + seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds(), + "Parsed $instant from $str, with Unix time = `$seconds + 10^-9 * $nanos`" + ) + } + } + + @Test + fun nonParseableInstantStrings() { + for (nonIsoString in listOf( + // empty string + "", + // a non-empty but clearly unsuitable string + "x", + // something other than a sign at the beginning + " 1970-01-01T00:00:00Z", + // too many digits for the year + "+1234567890-01-01T00:00:00Z", + "-1234567890-01-01T00:00:00Z", + // not enough padding for the year + "003-01-01T00:00:00Z", + "-003-01-01T00:00:00Z", + // a plus sign even though there is only 4 digits + "+1970-01-01T00:00:00Z", + // too many digits without padding + "11970-01-01T00:00:00Z", + // incorrect separators between the components + "1970/01-01T00:00:00Z", + "1970-01/01T00:00:00Z", + "1970-01-01 00:00:00Z", + "1970-01-01T00-00:00Z", + "1970-01-01T00:00-00Z", + // non-digits where digits are expected + "1970-X1-01T00:00:00Z", + "1970-1X-01T00:00:00Z", + "1970-11-X1T00:00:00Z", + "1970-11-1XT00:00:00Z", + "1970-11-10TX0:00:00Z", + "1970-11-10T0X:00:00Z", + "1970-11-10T00:X0:00Z", + "1970-11-10T00:0X:00Z", + "1970-11-10T00:00:X0Z", + "1970-11-10T00:00:0XZ", + // a non-ascii digit + "1970-11-10T00:00:0٩Z", + // not enough components + "1970-11-10T00:00Z", + // not enough components, even if the length is sufficient + "1970-11-10T00:00+01:15", + // a dot without any fraction of the second following it + "1970-11-10T00:00:00.Z", + // too many digits in the fraction of the second + "1970-11-10T00:00:00.1234567890Z", + // out-of-range values + "1970-00-10T00:00:00Z", + "1970-13-10T00:00:00Z", + "1970-01-32T00:00:00Z", + "1970-02-29T00:00:00Z", + "1972-02-30T00:00:00Z", + "2000-02-30T00:00:00Z", + "2100-02-29T00:00:00Z", + "2004-02-30T00:00:00Z", + "2005-02-29T00:00:00Z", + "2005-04-31T00:00:00Z", + "2005-04-01T24:00:00Z", + "2005-04-01T00:60:00Z", + "2005-04-01T00:00:60Z", + // leap second + "1970-01-01T23:59:60Z", + // lack of padding + "1970-1-10T00:00:00+05:00", + "1970-10-1T00:00:00+05:00", + "1970-10-10T0:00:00+05:00", + "1970-10-10T00:0:00+05:00", + "1970-10-10T00:00:0+05:00", + // no offset + "1970-02-03T04:05:06.123456789", + // some invalid single-character offsets + "1970-02-03T04:05:06.123456789A", + "1970-02-03T04:05:06.123456789+", + "1970-02-03T04:05:06.123456789-", + // too many components in the offset + "1970-02-03T04:05:06.123456789+03:02:01:00", + "1970-02-03T04:05:06.123456789+03:02:01.02", + // single-digit offset + "1970-02-03T04:05:06.123456789+3", + // incorrect sign in the offset + "1970-02-03T04:05:06.123456789 03", + // non-digits in the offset + "1970-02-03T04:05:06.123456789+X3", + "1970-02-03T04:05:06.123456789+1X", + "1970-02-03T04:05:06.123456789+X3:12", + "1970-02-03T04:05:06.123456789+1X:12", + "1970-02-03T04:05:06.123456789+X3:12", + "1970-02-03T04:05:06.123456789+13:X2", + "1970-02-03T04:05:06.123456789+13:1X", + "1970-02-03T04:05:06.123456789+X3:12:59", + "1970-02-03T04:05:06.123456789+1X:12:59", + "1970-02-03T04:05:06.123456789+X3:12:59", + "1970-02-03T04:05:06.123456789+13:X2:59", + "1970-02-03T04:05:06.123456789+13:1X:59", + "1970-02-03T04:05:06.123456789+13:12:X9", + "1970-02-03T04:05:06.123456789+13:12:5X", + // incorrect separators in the offset + "1970-02-03T04:05:06.123456789+13/12", + "1970-02-03T04:05:06.123456789+13/12:59", + "1970-02-03T04:05:06.123456789+13:12/59", + "1970-02-03T04:05:06.123456789+0130", + "1970-02-03T04:05:06.123456789-0130", + // incorrect field length + "1970-02-03T04:05:06.123456789-18:001", + // out-of-range offsets + "1970-02-03T04:05:06.123456789+18:12:59", + "1970-02-03T04:05:06.123456789-18:12:59", + "1970-02-03T04:05:06.123456789+18:00:01", + "1970-02-03T04:05:06.123456789-18:00:01", + "1970-02-03T04:05:06.123456789+18:01", + "1970-02-03T04:05:06.123456789-18:01", + "1970-02-03T04:05:06.123456789+19", + "1970-02-03T04:05:06.123456789-19", + // out-of-range fields of the offset + "1970-02-03T04:05:06.123456789+01:12:60", + "1970-02-03T04:05:06.123456789-01:12:60", + "1970-02-03T04:05:06.123456789+01:60", + "1970-02-03T04:05:06.123456789-01:60", + // lack of padding in the offset + "1970-02-03T04:05:06.123456789+1:12:50", + "1970-02-03T04:05:06.123456789+01:2:60", + "1970-02-03T04:05:06.123456789+01:12:6", + )) { + assertInvalidFormat(nonIsoString) { Instant.parse(nonIsoString) } + } + // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: + assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } + } + + @Test + fun parseStringsWithOffsets() { + val strings = arrayOf( + Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), + Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), + Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), + Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), + Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), + ) + strings.forEach { (str, strInZ) -> + val instant = Instant.parse(str) + assertEquals(Instant.parse(strInZ), instant, str) + assertEquals(strInZ, instant.toString(), str) + } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } + + val instants = listOf( + Instant.DISTANT_FUTURE, + Instant.DISTANT_PAST, + Instant.fromEpochSeconds(0, 0), + Instant.parse("2020-01-02T03:04:05.6789Z"), + Instant.MAX, + Instant.MIN, + ) + + val offsets = listOf( + 0 to "Z", + 3 * 3600 + 12 * 60 + 14 to "+03:12:14", + - 3 * 3600 - 12 * 60 - 14 to "-03:12:14", + 2 * 3600 + 35 * 60 to "+02:35", + - 2 * 3600 - 35 * 60 to "-02:35", + 4 * 3600 to "+04", + - 4 * 3600 to "-04", + ) + + for (instant in instants) { + for ((offsetSeconds, offsetString) in offsets) { + if (instant == Instant.MAX && offsetSeconds < 0 || + instant == Instant.MIN && offsetSeconds > 0 + ) continue + val newInstant = Instant.parse("${instant.toString().dropLast(1)}$offsetString") + assertEquals(newInstant, instant.minus(offsetSeconds.seconds)) + } + } + } + +} diff --git a/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt b/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt new file mode 100644 index 000000000..b56e18152 --- /dev/null +++ b/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +/* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + +package kotlinx.time.test + +import kotlinx.time.* +import kotlin.test.* + +class ThreeTenBpInstantTest { + + @Test + fun instantComparisons() { + 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(true, test5a == test5a) + assertEquals(true, test5a == test5b) + assertEquals(false, test5a == test5n) + assertEquals(false, test5a == test6) + assertEquals(true, test5b == test5a) + assertEquals(true, test5b == test5b) + assertEquals(false, test5b == test5n) + assertEquals(false, test5b == test6) + assertEquals(false, test5n == test5a) + assertEquals(false, test5n == test5b) + assertEquals(true, test5n == test5n) + assertEquals(false, test5n == test6) + assertEquals(false, test6 == test5a) + assertEquals(false, test6 == test5b) + assertEquals(false, test6 == test5n) + assertEquals(true, 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) + } +} diff --git a/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt b/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt new file mode 100644 index 000000000..a698f88df --- /dev/null +++ b/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +/* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + +package kotlinx.time.test + +import kotlinx.time.* +import kotlin.test.* + +class ThreeTenBpUtilTest { + @Test + fun isLeap() { + 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)) + } +} diff --git a/fake-kotlinx-time/common/test/assertions.kt b/fake-kotlinx-time/common/test/assertions.kt new file mode 100644 index 000000000..368f4c6b4 --- /dev/null +++ b/fake-kotlinx-time/common/test/assertions.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2019-2021 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +package kotlinx.time.test + +import kotlin.test.assertFailsWith +import kotlin.test.fail + +inline fun assertInvalidFormat(message: String? = null, f: () -> T) { + assertFailsWith(message) { + val result = f() + fail(result.toString()) + } +} + +/** + * The number of iterations to perform in nondeterministic tests. + */ +const val STRESS_TEST_ITERATIONS = 1000 diff --git a/fake-kotlinx-time/common/test/samples/ClockSamples.kt b/fake-kotlinx-time/common/test/samples/ClockSamples.kt new file mode 100644 index 000000000..d799e6bf1 --- /dev/null +++ b/fake-kotlinx-time/common/test/samples/ClockSamples.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.test.samples + +import kotlinx.time.* +import kotlin.test.* + +class ClockSamples { + @Test + fun system() { + // Getting the current date and time + val currentInstant = Clock.System.now() + currentInstant.toEpochMilliseconds() // the number of milliseconds since the Unix epoch + } + + @Test + fun dependencyInjection() { + fun formatCurrentTime(clock: Clock): String = + clock.now().toString() + + // In the production code: + val currentTimeInProduction = formatCurrentTime(Clock.System) + // Testing this value is tricky because it changes all the time. + + // In the test code: + val testClock = object: Clock { + override fun now(): Instant = Instant.parse("2023-01-02T22:35:01Z") + } + // Then, one can write a completely deterministic test: + val currentTimeForTests = formatCurrentTime(testClock) + check(currentTimeForTests == "2023-01-02T22:35:01Z") + } +} diff --git a/fake-kotlinx-time/common/test/samples/InstantSamples.kt b/fake-kotlinx-time/common/test/samples/InstantSamples.kt new file mode 100644 index 000000000..af141866e --- /dev/null +++ b/fake-kotlinx-time/common/test/samples/InstantSamples.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime.test.samples + +import kotlinx.time.* +import kotlin.random.* +import kotlin.test.* +import kotlin.time.Duration.Companion.hours + +class InstantSamples { + + @Test + fun epochSeconds() { + // Getting the number of whole seconds that passed since the Unix epoch + val instant1 = Instant.fromEpochSeconds(999_999, nanosecondAdjustment = 123_456_789) + check(instant1.epochSeconds == 999_999L) + val instant2 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = 100_123_456_789) + check(instant2.epochSeconds == 1_000_000 + 100L) + val instant3 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = -100_876_543_211) + check(instant3.epochSeconds == 1_000_000 - 101L) + } + + @Test + fun nanosecondsOfSecond() { + // Getting the number of nanoseconds that passed since the start of the second + val instant1 = Instant.fromEpochSeconds(999_999, nanosecondAdjustment = 123_456_789) + check(instant1.nanosecondsOfSecond == 123_456_789) + val instant2 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = 100_123_456_789) + check(instant2.nanosecondsOfSecond == 123_456_789) + val instant3 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = -100_876_543_211) + check(instant3.nanosecondsOfSecond == 123_456_789) + } + + @Test + fun toEpochMilliseconds() { + // Converting an Instant to the number of milliseconds since the Unix epoch + check(Instant.fromEpochMilliseconds(0).toEpochMilliseconds() == 0L) + check(Instant.fromEpochMilliseconds(1_000_000_000_123).toEpochMilliseconds() == 1_000_000_000_123L) + check(Instant.fromEpochSeconds(1_000_000_000, nanosecondAdjustment = 123_999_999) + .toEpochMilliseconds() == 1_000_000_000_123L) + } + + @Test + fun plusDuration() { + // Finding a moment that's later than the starting point by the given amount of real time + val instant = Instant.fromEpochSeconds(7 * 60 * 60, nanosecondAdjustment = 123_456_789) + val fiveHoursLater = instant + 5.hours + check(fiveHoursLater.epochSeconds == 12 * 60 * 60L) + check(fiveHoursLater.nanosecondsOfSecond == 123_456_789) + } + + @Test + fun minusDuration() { + // Finding a moment that's earlier than the starting point by the given amount of real time + val instant = Instant.fromEpochSeconds(7 * 60 * 60, nanosecondAdjustment = 123_456_789) + val fiveHoursEarlier = instant - 5.hours + check(fiveHoursEarlier.epochSeconds == 2 * 60 * 60L) + check(fiveHoursEarlier.nanosecondsOfSecond == 123_456_789) + } + + @Test + fun minusInstant() { + // Finding the difference between two instants in terms of elapsed time + check(Instant.fromEpochSeconds(0) - Instant.fromEpochSeconds(epochSeconds = 7 * 60 * 60) == (-7).hours) + } + + @Test + fun toStringSample() { + // Converting an Instant to a string + check(Instant.fromEpochSeconds(0).toString() == "1970-01-01T00:00:00Z") + } + + @Test + fun fromEpochMilliseconds() { + // Constructing an Instant from the number of milliseconds since the Unix epoch + check(Instant.fromEpochMilliseconds(epochMilliseconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) + check(Instant.fromEpochMilliseconds(epochMilliseconds = 1_000_000_000_123) + == Instant.parse("2001-09-09T01:46:40.123Z")) + } + + @Test + fun fromEpochSeconds() { + // Constructing an Instant from the number of seconds and nanoseconds since the Unix epoch + check(Instant.fromEpochSeconds(epochSeconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) + check(Instant.fromEpochSeconds(epochSeconds = 1_000_001_234, nanosecondAdjustment = -1_234_000_000_001) + == Instant.parse("2001-09-09T01:46:39.999999999Z")) + } + + @Test + fun fromEpochSecondsIntNanos() { + // Constructing an Instant from the number of seconds and nanoseconds since the Unix epoch + check(Instant.fromEpochSeconds(epochSeconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) + check(Instant.fromEpochSeconds(epochSeconds = 1_000_000_000, nanosecondAdjustment = -1) == Instant.parse("2001-09-09T01:46:39.999999999Z")) + } + + @Test + fun parsing() { + // Parsing an Instant from a string + check(Instant.parse("1970-01-01T00:00:00Z") == Instant.fromEpochSeconds(0)) + } + + @Test + fun isDistantPast() { + // Checking if an instant is so far in the past that it's probably irrelevant + val currentInstant = Clock.System.now() + val tenThousandYearsAgo = currentInstant.minus(24.hours * 365 * 1_000) + check(!tenThousandYearsAgo.isDistantPast) + check(Instant.DISTANT_PAST.isDistantPast) + } + + @Test + fun isDistantFuture() { + // Checking if an instant is so far in the future that it's probably irrelevant + val currentInstant = Clock.System.now() + val tenThousandYearsLater = currentInstant.plus(24.hours * 365 * 10_000) + check(!tenThousandYearsLater.isDistantFuture) + check(Instant.DISTANT_FUTURE.isDistantFuture) + } +} diff --git a/fake-kotlinx-time/js/test/ConvertersTest.kt b/fake-kotlinx-time/js/test/ConvertersTest.kt new file mode 100644 index 000000000..2d067b258 --- /dev/null +++ b/fake-kotlinx-time/js/test/ConvertersTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2022 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.time.test + +import kotlinx.time.* +import kotlin.js.Date +import kotlin.test.* + +class ConvertersTest { + @Test + fun toJSDateTest() { + val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") + val releaseDate = releaseInstant.toJSDate() + assertEquals(2016, releaseDate.getUTCFullYear()) + assertEquals(1, releaseDate.getUTCMonth()) + assertEquals(15, releaseDate.getUTCDate()) + } + + @Test + fun toInstantTest() { + val kotlinReleaseEpochMilliseconds = 1455494400000 + val releaseDate = Date(milliseconds = kotlinReleaseEpochMilliseconds) + val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") + assertEquals(releaseInstant, releaseDate.toKotlinInstant()) + } +} diff --git a/fake-kotlinx-time/jvm/test/ConvertersTest.kt b/fake-kotlinx-time/jvm/test/ConvertersTest.kt new file mode 100644 index 000000000..33b3018b7 --- /dev/null +++ b/fake-kotlinx-time/jvm/test/ConvertersTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2022 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +package kotlinx.time.test + +import kotlinx.time.* +import kotlin.test.* +import kotlin.random.* +import java.time.Instant as JTInstant + +class ConvertersTest { + + @Test + fun instant() { + fun test(seconds: Long, nanosecond: Int) { + val ktInstant = Instant.fromEpochSeconds(seconds, nanosecond.toLong()) + val jtInstant = JTInstant.ofEpochSecond(seconds, nanosecond.toLong()) + + assertEquals(ktInstant, jtInstant.toKotlinInstant()) + assertEquals(jtInstant, ktInstant.toJavaInstant()) + + assertEquals(ktInstant, jtInstant.toString().let(Instant::parse)) + assertEquals(jtInstant, ktInstant.toString().let(JTInstant::parse)) + } + + repeat(STRESS_TEST_ITERATIONS) { + val seconds = Random.nextLong(1_000_000_000_000) + val nanos = Random.nextInt() + test(seconds, nanos) + } + } + +} From 351ce59f86b1339fcde851c169580c21fa813617 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 9 Dec 2024 16:05:06 +0100 Subject: [PATCH 6/7] Deprecate Instant while ensuring binary compatibility --- core/api/kotlinx-datetime.api | 54 +- core/api/kotlinx-datetime.klib.api | 53 +- core/build.gradle.kts | 1 + core/common/src/Clock.kt | 63 +- core/common/src/DeprecatedClock.kt | 164 ++++ core/common/src/DeprecatedInstant.kt | 801 ++++++++++++++++++ core/common/src/Instant.kt | 442 +--------- core/common/src/TimeZone.kt | 86 +- core/common/src/format/DateTimeComponents.kt | 23 +- ...ers.kt => DeprecatedInstantSerializers.kt} | 11 + core/common/test/ClockTimeSourceTest.kt | 4 +- .../test/DeprecatedClockTimeSourceTest.kt | 87 ++ core/common/test/DeprecatedInstantTest.kt | 640 ++++++++++++++ core/common/test/InstantTest.kt | 107 +-- core/common/test/LocalDateTest.kt | 1 + core/common/test/LocalDateTimeTest.kt | 9 +- core/common/test/ReadmeTest.kt | 2 + core/common/test/TimeZoneTest.kt | 2 + .../format/DateTimeComponentsFormatTest.kt | 1 + .../test/format/DateTimeComponentsTest.kt | 1 + core/common/test/samples/ClockSamples.kt | 2 + core/common/test/samples/DayOfWeekSamples.kt | 1 + core/common/test/samples/InstantSamples.kt | 4 + core/common/test/samples/MonthSamples.kt | 1 + core/common/test/samples/TimeZoneSamples.kt | 2 + .../format/DateTimeComponentsSamples.kt | 1 + core/commonJs/src/internal/Platform.kt | 4 +- core/commonJs/test/JsJodaTimezoneTest.kt | 1 + core/commonKotlin/src/DeprecatedInstant.kt | 355 ++++++++ core/commonKotlin/src/Instant.kt | 182 +--- core/commonKotlin/src/TimeZone.kt | 31 +- core/commonKotlin/src/ZonedDateTime.kt | 4 +- .../commonKotlin/src/internal/MonthDayTime.kt | 1 + core/commonKotlin/src/internal/OffsetInfo.kt | 2 +- core/commonKotlin/src/internal/Platform.kt | 3 - .../src/internal/RegionTimeZone.kt | 1 + .../src/internal/TimeZoneRules.kt | 5 +- .../test/ThreeTenBpInstantTest.kt | 96 --- core/commonKotlin/test/TimeZoneRulesTest.kt | 1 + core/darwin/src/Converters.kt | 14 +- core/darwin/test/ConvertersTest.kt | 2 + ...{Converters.kt => DeprecatedConverters.kt} | 1 + ...erTest.kt => DeprecatedJsConverterTest.kt} | 3 +- core/jvm/src/Converters.kt | 9 +- core/jvm/src/DeprecatedInstant.kt | 253 ++++++ core/jvm/src/Instant.kt | 107 +-- core/jvm/src/TimeZoneJvm.kt | 42 +- core/jvm/test/ConvertersTest.kt | 2 + core/jvm/test/InstantParsing.kt | 2 + core/native/src/internal/Platform.kt | 23 - .../test/TimeZoneRulesCompleteTest.kt | 1 + core/wasmWasi/src/internal/Platform.kt | 39 - .../windows/test/TimeZoneRulesCompleteTest.kt | 2 + .../test/TimezonesWithoutDatabaseTest.kt | 2 + ... => DeprecatedInstantSerializationTest.kt} | 4 +- serialization/common/test/IntegrationTest.kt | 4 +- 56 files changed, 2727 insertions(+), 1032 deletions(-) create mode 100644 core/common/src/DeprecatedClock.kt create mode 100644 core/common/src/DeprecatedInstant.kt rename core/common/src/serializers/{InstantSerializers.kt => DeprecatedInstantSerializers.kt} (86%) create mode 100644 core/common/test/DeprecatedClockTimeSourceTest.kt create mode 100644 core/common/test/DeprecatedInstantTest.kt create mode 100644 core/commonKotlin/src/DeprecatedInstant.kt delete mode 100644 core/commonKotlin/test/ThreeTenBpInstantTest.kt rename core/js/src/{Converters.kt => DeprecatedConverters.kt} (95%) rename core/js/test/{JsConverterTest.kt => DeprecatedJsConverterTest.kt} (93%) create mode 100644 core/jvm/src/DeprecatedInstant.kt delete mode 100644 core/native/src/internal/Platform.kt delete mode 100644 core/wasmWasi/src/internal/Platform.kt rename serialization/common/test/{InstantSerializationTest.kt => DeprecatedInstantSerializationTest.kt} (97%) diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index 1f554210f..e872da178 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -12,10 +12,15 @@ public final class kotlinx/datetime/Clock$System : kotlinx/datetime/Clock { } public final class kotlinx/datetime/ClockKt { - public static final fun asClock (Lkotlin/time/TimeSource;Lkotlinx/datetime/Instant;)Lkotlinx/datetime/Clock; + public static final fun asClock (Lkotlin/time/TimeSource;Lkotlinx/time/Instant;)Lkotlinx/time/Clock; public static final fun asTimeSource (Lkotlinx/datetime/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; + public static final fun asTimeSource (Lkotlinx/time/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; + public static final fun toDeprecatedClock (Lkotlinx/time/Clock;)Lkotlinx/datetime/Clock; + public static final fun toStdlibClock (Lkotlinx/datetime/Clock;)Lkotlinx/time/Clock; public static final fun todayAt (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static final fun todayAt (Lkotlinx/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; public static final fun todayIn (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static final fun todayIn (Lkotlinx/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; } public final class kotlinx/datetime/ConvertersKt { @@ -240,19 +245,30 @@ public final class kotlinx/datetime/Instant$Companion { public final class kotlinx/datetime/InstantJvmKt { public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun minus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; public static final fun periodUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun periodUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun plus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun plus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun plus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J + public static final fun until (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J } public final class kotlinx/datetime/InstantKt { public static final fun daysUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun daysUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun format (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; + public static final fun format (Lkotlinx/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; public static synthetic fun format$default (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; + public static synthetic fun format$default (Lkotlinx/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; public static final fun isDistantFuture (Lkotlinx/datetime/Instant;)Z public static final fun isDistantPast (Lkotlinx/datetime/Instant;)Z public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; @@ -264,12 +280,29 @@ public final class kotlinx/datetime/InstantKt { public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun minus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J + public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; public static final fun monthsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun monthsUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun parse (Lkotlinx/time/Instant$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/time/Instant; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; + public static final fun plus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; + public static final fun toDeprecatedInstant (Lkotlinx/time/Instant;)Lkotlinx/datetime/Instant; public static final fun toInstant (Ljava/lang/String;)Lkotlinx/datetime/Instant; + public static final fun toStdlibInstant (Lkotlinx/datetime/Instant;)Lkotlinx/time/Instant; public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun until (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun yearsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun yearsUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I } public final class kotlinx/datetime/LocalDate : java/io/Serializable, java/lang/Comparable { @@ -472,6 +505,9 @@ public final class kotlinx/datetime/MonthKt { public static final fun getNumber (Lkotlinx/datetime/Month;)I } +public final class kotlinx/datetime/OverloadMarker { +} + public final class kotlinx/datetime/Ser : java/io/Externalizable { public static final field Companion Lkotlinx/datetime/Ser$Companion; public static final field DATE_TAG I @@ -493,7 +529,10 @@ public class kotlinx/datetime/TimeZone { public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toInstant (Lkotlinx/datetime/LocalDateTime;)Lkotlinx/datetime/Instant; + public final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; public final fun toLocalDateTime (Lkotlinx/datetime/Instant;)Lkotlinx/datetime/LocalDateTime; + public final fun toLocalDateTime (Lkotlinx/time/Instant;)Lkotlinx/datetime/LocalDateTime; public fun toString ()Ljava/lang/String; } @@ -507,11 +546,21 @@ public final class kotlinx/datetime/TimeZone$Companion { public final class kotlinx/datetime/TimeZoneKt { public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; + public static synthetic fun atStartOfDayIn$default (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/Instant;)Lkotlinx/datetime/UtcOffset; + public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlinx/time/Instant;)Lkotlinx/datetime/UtcOffset; public static final fun offsetIn (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; + public static final fun offsetIn (Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; + public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/LocalDateTime; + public static final fun toLocalDateTime (Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; } public final class kotlinx/datetime/UtcOffset : java/io/Serializable { @@ -584,6 +633,7 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setDateTime (Lkotlinx/datetime/LocalDateTime;)V public final fun setDateTimeOffset (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDateTimeOffset (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)V + public final fun setDateTimeOffset (Lkotlinx/time/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDay (Ljava/lang/Integer;)V public final fun setDayOfMonth (Ljava/lang/Integer;)V public final fun setDayOfWeek (Lkotlinx/datetime/DayOfWeek;)V @@ -604,6 +654,8 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setTimeZoneId (Ljava/lang/String;)V public final fun setYear (Ljava/lang/Integer;)V public final fun toInstantUsingOffset ()Lkotlinx/datetime/Instant; + public final fun toInstantUsingOffset (Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; + public static synthetic fun toInstantUsingOffset$default (Lkotlinx/datetime/format/DateTimeComponents;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; public final fun toLocalDate ()Lkotlinx/datetime/LocalDate; public final fun toLocalDateTime ()Lkotlinx/datetime/LocalDateTime; public final fun toLocalTime ()Lkotlinx/datetime/LocalTime; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 129b736dc..6f9902743 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -195,9 +195,11 @@ final class kotlinx.datetime.format/DateTimeComponents { // kotlinx.datetime.for final fun setDateTime(kotlinx.datetime/LocalDateTime) // kotlinx.datetime.format/DateTimeComponents.setDateTime|setDateTime(kotlinx.datetime.LocalDateTime){}[0] final fun setDateTimeOffset(kotlinx.datetime/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setDateTimeOffset(kotlinx.datetime/LocalDateTime, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.LocalDateTime;kotlinx.datetime.UtcOffset){}[0] + final fun setDateTimeOffset(kotlinx.time/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.time.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setOffset(kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setOffset|setOffset(kotlinx.datetime.UtcOffset){}[0] final fun setTime(kotlinx.datetime/LocalTime) // kotlinx.datetime.format/DateTimeComponents.setTime|setTime(kotlinx.datetime.LocalTime){}[0] final fun toInstantUsingOffset(): kotlinx.datetime/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(){}[0] + final fun toInstantUsingOffset(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(kotlinx.datetime.OverloadMarker){}[0] final fun toLocalDate(): kotlinx.datetime/LocalDate // kotlinx.datetime.format/DateTimeComponents.toLocalDate|toLocalDate(){}[0] final fun toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime.format/DateTimeComponents.toLocalDateTime|toLocalDateTime(){}[0] final fun toLocalTime(): kotlinx.datetime/LocalTime // kotlinx.datetime.format/DateTimeComponents.toLocalTime|toLocalTime(){}[0] @@ -461,6 +463,8 @@ final class kotlinx.datetime/LocalTime : kotlin/Comparable(): kotlin/Int // kotlinx.datetime/UtcOffset.totalSeconds.|(){}[0] @@ -495,6 +499,8 @@ open class kotlinx.datetime/TimeZone { // kotlinx.datetime/TimeZone|null[0] final fun (kotlinx.datetime/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlinx.datetime.Instant(){}[0] final fun (kotlinx.datetime/LocalDateTime).toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(){}[0] + final fun (kotlinx.datetime/LocalDateTime).toInstant(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.OverloadMarker){}[0] + final fun (kotlinx.time/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlinx.time.Instant(){}[0] open fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.datetime/TimeZone.equals|equals(kotlin.Any?){}[0] open fun hashCode(): kotlin/Int // kotlinx.datetime/TimeZone.hashCode|hashCode(){}[0] open fun toString(): kotlin/String // kotlinx.datetime/TimeZone.toString|toString(){}[0] @@ -818,7 +824,7 @@ final val kotlinx.datetime/number // kotlinx.datetime/number|@kotlinx.datetime.M final fun (kotlinx.datetime/Month).(): kotlin/Int // kotlinx.datetime/number.|@kotlinx.datetime.Month(){}[0] final fun (kotlin.time/Duration).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.time.Duration(){}[0] -final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlinx.datetime/Instant): kotlinx.datetime/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlinx.datetime.Instant){}[0] +final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlinx.time/Instant): kotlinx.time/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlinx.time.Instant){}[0] final fun (kotlin/String).kotlinx.datetime/toDatePeriod(): kotlinx.datetime/DatePeriod // kotlinx.datetime/toDatePeriod|toDatePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlin.String(){}[0] @@ -830,6 +836,7 @@ final fun (kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalDateTime(kotlinx.datetime.format.DateTimeFormat){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone, kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset, kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Unit = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Unit){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] @@ -888,8 +900,41 @@ final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotli final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlinx.datetime/LocalDate): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlinx.datetime.LocalDate){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalTime(kotlinx.datetime.format.DateTimeFormat){}[0] final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlinx.datetime/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlinx.datetime.Instant){}[0] +final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlinx.time/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlinx.time.Instant){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/asTimeZone(): kotlinx.datetime/FixedOffsetTimeZone // kotlinx.datetime/asTimeZone|asTimeZone@kotlinx.datetime.UtcOffset(){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.UtcOffset(kotlinx.datetime.format.DateTimeFormat){}[0] +final fun (kotlinx.time/Clock).kotlinx.datetime/asTimeSource(): kotlin.time/TimeSource.WithComparableMarks // kotlinx.datetime/asTimeSource|asTimeSource@kotlinx.time.Clock(){}[0] +final fun (kotlinx.time/Clock).kotlinx.datetime/toDeprecatedClock(): kotlinx.datetime/Clock // kotlinx.datetime/toDeprecatedClock|toDeprecatedClock@kotlinx.time.Clock(){}[0] +final fun (kotlinx.time/Clock).kotlinx.datetime/todayAt(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayAt|todayAt@kotlinx.time.Clock(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Clock).kotlinx.datetime/todayIn(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayIn|todayIn@kotlinx.time.Clock(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/daysUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/daysUntil|daysUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat, kotlinx.datetime/UtcOffset = ...): kotlin/String // kotlinx.datetime/format|format@kotlinx.time.Instant(kotlinx.datetime.format.DateTimeFormat;kotlinx.datetime.UtcOffset){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/monthsUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/monthsUntil|monthsUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/offsetIn(kotlinx.datetime/TimeZone): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetIn|offsetIn@kotlinx.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/periodUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/periodUntil|periodUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/toDeprecatedInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toDeprecatedInstant|toDeprecatedInstant@kotlinx.time.Instant(){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/toLocalDateTime(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDateTime // kotlinx.datetime/toLocalDateTime|toLocalDateTime@kotlinx.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/until(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/until|until@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/until(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/until|until@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlinx.time/Instant).kotlinx.datetime/yearsUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/yearsUntil|yearsUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlinx.time/Instant.Companion).kotlinx.datetime/parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat): kotlinx.time/Instant // kotlinx.datetime/parse|parse@kotlinx.time.Instant.Companion(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun <#A: kotlinx.datetime.format/DateTimeFormatBuilder> (#A).kotlinx.datetime.format/alternativeParsing(kotlin/Array>..., kotlin/Function1<#A, kotlin/Unit>) // kotlinx.datetime.format/alternativeParsing|alternativeParsing@0:0(kotlin.Array>...;kotlin.Function1<0:0,kotlin.Unit>){0§}[0] final fun <#A: kotlinx.datetime.format/DateTimeFormatBuilder> (#A).kotlinx.datetime.format/optional(kotlin/String = ..., kotlin/Function1<#A, kotlin/Unit>) // kotlinx.datetime.format/optional|optional@0:0(kotlin.String;kotlin.Function1<0:0,kotlin.Unit>){0§}[0] final fun kotlinx.datetime/DateTimePeriod(kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Long = ...): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/DateTimePeriod|DateTimePeriod(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Long){}[0] @@ -914,9 +959,15 @@ final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toNSDateComponents() // Targets: [apple] final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/toNSTimeZone(): platform.Foundation/NSTimeZone // kotlinx.datetime/toNSTimeZone|toNSTimeZone@kotlinx.datetime.TimeZone(){}[0] +// Targets: [apple] +final fun (kotlinx.time/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlinx.time.Instant(){}[0] + // Targets: [apple] final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(){}[0] +// Targets: [apple] +final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(kotlinx.datetime.OverloadMarker){}[0] + // Targets: [apple] final fun (platform.Foundation/NSTimeZone).kotlinx.datetime/toKotlinTimeZone(): kotlinx.datetime/TimeZone // kotlinx.datetime/toKotlinTimeZone|toKotlinTimeZone@platform.Foundation.NSTimeZone(){}[0] diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 64697efd2..e54f9d856 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -188,6 +188,7 @@ kotlin { commonMain { dependencies { compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") + api(project(":fake-kotlinx-time")) } } diff --git a/core/common/src/Clock.kt b/core/common/src/Clock.kt index 8e7eb10b3..587ee60da 100644 --- a/core/common/src/Clock.kt +++ b/core/common/src/Clock.kt @@ -3,62 +3,17 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass +@file:JvmName("ClockKt") package kotlinx.datetime import kotlin.time.* - -/** - * A source of [Instant] values. - * - * See [Clock.System][Clock.System] for the clock instance that queries the operating system. - * - * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a - * [Clock] explicitly to the necessary functions or classes. - * This way, tests can be written deterministically by providing custom [Clock] implementations - * to the system under test. - */ -public interface Clock { - /** - * Returns the [Instant] corresponding to the current time, according to this clock. - * - * Calling [now] later is not guaranteed to return a larger [Instant]. - * In particular, for [Clock.System], the opposite is completely expected, - * and it must be taken into account. - * See the [System] documentation for details. - * - * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling - * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. - */ - public fun now(): Instant - - /** - * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. - * - * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, - * these increases will not necessarily correspond to the elapsed time. - * - * For example, when using [Clock.System], the following could happen: - * - [now] returns `2023-01-02T22:35:01Z`. - * - The system queries the Internet and recognizes that its clock needs adjusting. - * - [now] returns `2023-01-02T22:32:05Z`. - * - * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. - * - * For improved testability, you should avoid using [Clock.System] directly in the implementation - * and pass a [Clock] explicitly instead. For example: - * - * @sample kotlinx.datetime.test.samples.ClockSamples.system - * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection - */ - public object System : Clock { - override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now() - } - - /** A companion object used purely for namespacing. */ - public companion object { - - } -} +import kotlinx.time.Clock +import kotlinx.time.Instant +import kotlinx.time.isDistantFuture +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration.Companion.seconds /** * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. @@ -108,7 +63,7 @@ private class InstantTimeMark(private val instant: Instant, private val clock: C override fun toString(): String = "InstantTimeMark($instant, $clock)" - private fun Instant.isSaturated() = this == Instant.MAX || this == Instant.MIN + private fun Instant.isSaturated() = this.plus(1.seconds) == this || this.plus((-1).seconds) == this private fun Instant.saturatingAdd(duration: Duration): Instant { if (isSaturated()) { if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { diff --git a/core/common/src/DeprecatedClock.kt b/core/common/src/DeprecatedClock.kt new file mode 100644 index 000000000..113f4f530 --- /dev/null +++ b/core/common/src/DeprecatedClock.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("ClockKt") +package kotlinx.datetime + +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.ComparableTimeMark +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource + +/** + * Creates a [kotlinx.time.Clock] (the standard library version of `Clock`) delegating to `this`. + */ +public fun Clock.toStdlibClock(): kotlinx.time.Clock = + (this as? DateTimeClock)?.clock ?: StdlibClock(this) + +/** + * Creates a [kotlinx.datetime.Clock] delegating to the version of `Clock` from the standard library. + */ +public fun kotlinx.time.Clock.toDeprecatedClock(): Clock = + (this as? StdlibClock)?.clock ?: DateTimeClock(this) + +private class DateTimeClock(val clock: kotlinx.time.Clock): kotlinx.datetime.Clock { + override fun now(): Instant = clock.now().toDeprecatedInstant() +} + +private class StdlibClock(val clock: Clock): kotlinx.time.Clock { + override fun now(): kotlinx.time.Instant = clock.now().toStdlibInstant() +} + +/** + * A source of [Instant] values. + * + * See [Clock.System][Clock.System] for the clock instance that queries the operating system. + * + * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a + * [Clock] explicitly to the necessary functions or classes. + * This way, tests can be written deterministically by providing custom [Clock] implementations + * to the system under test. + */ +@Deprecated( + "Use kotlin.time.Clock instead", + ReplaceWith("kotlinx.time.Clock", "kotlinx.time.Clock"), + level = DeprecationLevel.WARNING +) +public interface Clock { + /** + * Returns the [Instant] corresponding to the current time, according to this clock. + * + * Calling [now] later is not guaranteed to return a larger [Instant]. + * In particular, for [Clock.System], the opposite is completely expected, + * and it must be taken into account. + * See the [System] documentation for details. + * + * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling + * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. + */ + public fun now(): Instant + + /** + * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. + * + * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, + * these increases will not necessarily correspond to the elapsed time. + * + * For example, when using [Clock.System], the following could happen: + * - [now] returns `2023-01-02T22:35:01Z`. + * - The system queries the Internet and recognizes that its clock needs adjusting. + * - [now] returns `2023-01-02T22:32:05Z`. + * + * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. + * + * For improved testability, you should avoid using [Clock.System] directly in the implementation + * and pass a [Clock] explicitly instead. For example: + * + * @sample kotlinx.datetime.test.samples.ClockSamples.system + * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection + */ + public object System : Clock { + override fun now(): Instant = kotlinx.time.Clock.System.now().toDeprecatedInstant() + } + + /** A companion object used purely for namespacing. */ + public companion object { + + } +} + +/** + * Returns the current date at the given [time zone][timeZone], according to [this Clock][this]. + * + * The time zone is important because the current date is not the same in all time zones at the same instant. + * + * @sample kotlinx.datetime.test.samples.ClockSamples.todayIn + */ +public fun Clock.todayIn(timeZone: TimeZone): LocalDate = + now().toStdlibInstant().toLocalDateTime(timeZone).date + +/** + * Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark. + * + * **Pitfall**: using this function with [Clock.System] is error-prone + * because [Clock.System] is not well suited for measuring time intervals. + * Please only use this conversion function on the [Clock] instances that are fully controlled programmatically. + */ +@ExperimentalTime +public fun Clock.asTimeSource(): TimeSource.WithComparableMarks = object : TimeSource.WithComparableMarks { + override fun markNow(): ComparableTimeMark = DeprecatedInstantTimeMark(now(), this@asTimeSource) +} + +@ExperimentalTime +private class DeprecatedInstantTimeMark(private val instant: Instant, private val clock: Clock) : ComparableTimeMark { + override fun elapsedNow(): Duration = saturatingDiff(clock.now(), instant) + + override fun plus(duration: Duration): ComparableTimeMark = + DeprecatedInstantTimeMark(instant.saturatingAdd(duration), clock) + override fun minus(duration: Duration): ComparableTimeMark = + DeprecatedInstantTimeMark(instant.saturatingAdd(-duration), clock) + + override fun minus(other: ComparableTimeMark): Duration { + if (other !is DeprecatedInstantTimeMark || other.clock != this.clock) { + throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other") + } + return saturatingDiff(this.instant, other.instant) + } + + override fun equals(other: Any?): Boolean { + return other is DeprecatedInstantTimeMark && this.clock == other.clock && this.instant == other.instant + } + + override fun hashCode(): Int = instant.hashCode() + + override fun toString(): String = "InstantTimeMark($instant, $clock)" + + private fun Instant.isSaturated() = this.plus(1.seconds) == this || this.plus((-1).seconds) == this + private fun Instant.saturatingAdd(duration: Duration): Instant { + if (isSaturated()) { + if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) { + throw IllegalArgumentException("Summing infinities of different signs") + } + return this + } + return this + duration + } + private fun saturatingDiff(instant1: Instant, instant2: Instant): Duration = when { + instant1 == instant2 -> + Duration.ZERO + instant1.isSaturated() || instant2.isSaturated() -> + (instant1 - instant2) * Double.POSITIVE_INFINITY + else -> + instant1 - instant2 + } +} + +@Deprecated("Use Clock.todayIn instead", ReplaceWith("this.todayIn(timeZone)"), DeprecationLevel.WARNING) +public fun Clock.todayAt(timeZone: TimeZone): LocalDate = todayIn(timeZone) diff --git a/core/common/src/DeprecatedInstant.kt b/core/common/src/DeprecatedInstant.kt new file mode 100644 index 000000000..9bf6a69a4 --- /dev/null +++ b/core/common/src/DeprecatedInstant.kt @@ -0,0 +1,801 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantKt") +package kotlinx.datetime + +import kotlinx.datetime.format.DateTimeComponents +import kotlinx.datetime.format.DateTimeFormat +import kotlinx.datetime.format.format +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.clampToInt +import kotlinx.datetime.internal.multiplyAddAndDivide +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration +import kotlin.time.TimeSource + +/** + * Creates a [kotlinx.time.Instant] (the standard library version of `Instant`) identical to `this`. + */ +public fun Instant.toStdlibInstant(): kotlinx.time.Instant = + kotlinx.time.Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + +/** + * Creates a [kotlinx.datetime.Instant] identical to the version of `Instant` from the standard library. + */ +public fun kotlinx.time.Instant.toDeprecatedInstant(): Instant = + Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public expect class Instant : Comparable { + + /** + * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. + * + * The difference between the rounded number of seconds and the actual number of seconds + * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. + * + * Note that this number doesn't include leap seconds added or removed since the epoch. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public val epochSeconds: Long + + /** + * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. + * + * The value is always non-negative and lies in the range `0..999_999_999`. + * + * @see fromEpochSeconds + * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public val nanosecondsOfSecond: Int + + /** + * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. + * + * If the result does not fit in [Long], + * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see fromEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public fun toEpochMilliseconds(): Long + + /** + * Returns an instant that is the result of adding the specified [duration] to this instant. + * + * If the [duration] is positive, the returned instant is later than this instant. + * If the [duration] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public operator fun plus(duration: Duration): Instant + + /** + * Returns an instant that is the result of subtracting the specified [duration] from this instant. + * + * If the [duration] is positive, the returned instant is earlier than this instant. + * If the [duration] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. + * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based + * operations instead of using [Duration]. + * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public operator fun minus(duration: Duration): Instant + + // questionable + /** + * Returns the [Duration] between two instants: [other] and `this`. + * + * The duration returned is positive if this instant is later than the other, + * and negative if this instant is earlier than the other. + * + * The result is never clamped, but note that for instants that are far apart, + * the value returned may represent the duration between them inexactly due to the loss of precision. + * + * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other + * or even monotonic, so the result of this operation may be negative even if the other instant was observed later + * than this one, or vice versa. + * For measuring time intervals, consider using [TimeSource.Monotonic]. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public operator fun minus(other: Instant): Duration + + /** + * Compares `this` instant with the [other] instant. + * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), + * a negative number if this instant is earlier than the other, + * and a positive number if this instant is later than the other. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample + */ + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + public override operator fun compareTo(other: Instant): Int + + /** + * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. + * + * The representation uses the UTC-SLS time scale instead of UTC. + * In practice, this means that leap second handling will not be readjusted to the UTC. + * Leap seconds will not be added or skipped, so it is impossible to acquire a string + * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. + * + * @see parse + * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the + * fractional part of the second. + * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample + */ + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + public override fun toString(): String + + + public companion object { + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.time.Clock"), level = DeprecationLevel.ERROR) + public fun now(): Instant + + /** + * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. + * + * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. + * + * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. + * + * @see Instant.toEpochMilliseconds + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds + */ + public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant + + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. + * + * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. + * + * @see Instant.epochSeconds + * @see Instant.nanosecondsOfSecond + * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos + */ + public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant + + /** + * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. + * + * Parses a string that represents an instant, including date and time components and a mandatory + * time zone offset and returns the parsed [Instant] value. + * + * The string is considered to represent time on the UTC-SLS time scale instead of UTC. + * In practice, this means that, even if there is a leap second on the given day, it will not affect how the + * time is parsed, even if it's in the last 1000 seconds of the day. + * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. + * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. + * + * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. + * `2023-01-02T23:40:57.120Z` is an example of a string in this format. + * + * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. + * + * @see Instant.toString for formatting using the default format. + * @see Instant.format for formatting using a custom format. + * @sample kotlinx.datetime.test.samples.InstantSamples.parsing + */ + public fun parse( + input: CharSequence, + format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET + ): Instant + + /** + * An instant value that is far in the past. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantPast] returns true for this value and all earlier ones. + */ + public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z + + /** + * An instant value that is far in the future. + * + * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to + * [LocalDateTime] without exceptions in every time zone. + * + * [isDistantFuture] returns true for this value and all later ones. + */ + public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z + + internal val MIN: Instant + internal val MAX: Instant + } +} + +/** + * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlinx.time.isDistantPast") +) +public val Instant.isDistantPast: Boolean + get() = this <= Instant.DISTANT_PAST + +/** + * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlinx.time.isDistantFuture") +) +public val Instant.isDistantFuture: Boolean + get() = this >= Instant.DISTANT_FUTURE + +/** + * @suppress + */ +@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) +public fun String.toInstant(): Instant = Instant.parse(this) + +/** + * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are + * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider adding a [Duration] instead, + * as in `Clock.System.now() + 5.hours`. + * Then, it will not be necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], like in + * `Clock.System.now().plus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting components of [DateTimePeriod] from this instant. The components + * are subtracted in the order from the largest units to the smallest, i.e., from years to nanoseconds. + * + * - If the [DateTimePeriod] only contains time-based components, please consider subtracting a [Duration] instead, + * as in `Clock.System.now() - 5.hours`. + * Then, it is not necessary to pass the [timeZone]. + * - If the [DateTimePeriod] only has a single non-zero component (only the months or only the days), + * please consider using a multiple of [DateTimeUnit.DAY] or [DateTimeUnit.MONTH], as in + * `Clock.System.now().minus(5, DateTimeUnit.DAY, TimeZone.currentSystemDefault())`. + * + * @throws DateTimeArithmeticException if this value or the results of intermediate computations are too large to fit in + * [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusPeriod + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(period, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant = + /* An overflow can happen for any component, but we are only worried about nanoseconds, as having an overflow in + any other component means that `plus` will throw due to the minimum value of the numeric type overflowing the + `Instant` limits. */ + if (period.totalNanoseconds != Long.MIN_VALUE) { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -totalNanoseconds) } + plus(negatedPeriod, timeZone) + } else { + val negatedPeriod = with(period) { buildDateTimePeriod(-totalMonths, -days, -(totalNanoseconds+1)) } + plus(negatedPeriod, timeZone).plus(1, DateTimeUnit.NANOSECOND) + } + +/** + * Returns a [DateTimePeriod] representing the difference between `this` and [other] instants. + * + * The components of [DateTimePeriod] are calculated so that adding it to `this` instant results in the [other] instant. + * + * All components of the [DateTimePeriod] returned are: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod + +/** + * Returns the whole number of the specified date or time [units][unit] between `this` and [other] instants + * in the specified [timeZone]. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public expect fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long + +/** + * Returns the whole number of the specified time [units][unit] between `this` and [other] instants. + * + * The value returned is: + * - Positive or zero if this instant is earlier than the other. + * - Negative or zero if this instant is later than the other. + * - Zero if this instant is equal to the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit)") +) +public fun Instant.until(other: Instant, unit: DateTimeUnit.TimeBased): Long = + try { + multiplyAddAndDivide(other.epochSeconds - epochSeconds, + NANOS_PER_ONE.toLong(), + (other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(), + unit.nanoseconds) + } catch (_: ArithmeticException) { + if (this < other) Long.MAX_VALUE else Long.MIN_VALUE + } + +/** + * Returns the number of whole days between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.daysUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().daysUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.DAY, timeZone).clampToInt() + +/** + * Returns the number of whole months between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.monthsUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().monthsUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.MONTH, timeZone).clampToInt() + +/** + * Returns the number of whole years between two instants in the specified [timeZone]. + * + * If the result does not fit in [Int], returns [Int.MAX_VALUE] for a positive result or [Int.MIN_VALUE] for a negative result. + * + * @see Instant.until + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.yearsUntil + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().yearsUntil(other.toStdlibInstant(), timeZone)") +) +public fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int = + until(other, DateTimeUnit.YEAR, timeZone).clampToInt() + +/** + * Returns a [DateTimePeriod] representing the difference between [other] and `this` instants. + * + * The components of [DateTimePeriod] are calculated so that adding it back to the `other` instant results in this instant. + * + * All components of the [DateTimePeriod] returned are: + * - Negative or zero if this instant is earlier than the other. + * - Positive or zero if this instant is later than the other. + * - Exactly zero if this instant is equal to the other. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.periodUntil + * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstantInZone + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), timeZone)") +) +public fun Instant.minus(other: Instant, timeZone: TimeZone): DateTimePeriod = + other.periodUntil(this, timeZone) + + +/** + * Returns an instant that is the result of adding one [unit] to this instant + * in the specified [timeZone]. + * + * The returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant + * in the specified [timeZone]. + * + * The returned instant is earlier than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(1, unit, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-1, unit, timeZone) + +/** + * Returns an instant that is the result of adding one [unit] to this instant. + * + * The returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit).toDeprecatedInstant()") +) +public fun Instant.plus(unit: DateTimeUnit.TimeBased): Instant = + plus(1L, unit) + +/** + * Returns an instant that is the result of subtracting one [unit] from this instant. + * + * The returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(1, unit).toDeprecatedInstant()") +) +public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant = + plus(-1L, unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate][LocalDate.plus]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public fun Instant.plus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + plus(value.toLong(), unit) + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit).toDeprecatedInstant()") +) +public fun Instant.minus(value: Int, unit: DateTimeUnit.TimeBased): Instant = + minus(value.toLong(), unit) + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when adding date-based units to a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant + * in the specified [timeZone]. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * Note that the time zone does not need to be passed when the [unit] is a time-based unit. + * It is also not needed when subtracting date-based units from a [LocalDate]. + * + * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit, timeZone) + } else { + plus(-(value + 1), unit, timeZone).plus(1, unit, timeZone) + } + +/** + * Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant. + * + * If the [value] is positive, the returned instant is later than this instant. + * If the [value] is negative, the returned instant is earlier than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public expect fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant + +/** + * Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant. + * + * If the [value] is positive, the returned instant is earlier than this instant. + * If the [value] is negative, the returned instant is later than this instant. + * + * The return value is clamped to the boundaries of [Instant] if the result exceeds them. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.minusTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit).toDeprecatedInstant()") +) +public fun Instant.minus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + if (value != Long.MIN_VALUE) { + plus(-value, unit) + } else { + plus(-(value + 1), unit).plus(1, unit) + } + +/** + * Returns the whole number of the specified date or time [units][unit] between [other] and `this` instants + * in the specified [timeZone]. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime]. + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsDateTimeUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), unit, timeZone)") +) +public fun Instant.minus(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = + other.until(this, unit, timeZone) + +/** + * Returns the whole number of the specified time [units][unit] between [other] and `this` instants. + * + * The value returned is negative or zero if this instant is earlier than the other, + * and positive or zero if this instant is later than the other. + * + * If the result does not fit in [Long], returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. + * + * @see Instant.until for the same operation but with swapped arguments. + * @sample kotlinx.datetime.test.samples.InstantSamples.minusAsTimeBasedUnit + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(other.toStdlibInstant(), unit)") +) +public fun Instant.minus(other: Instant, unit: DateTimeUnit.TimeBased): Long = + other.until(this, unit) + +/** + * Formats this value using the given [format] using the given [offset]. + * + * Equivalent to calling [DateTimeFormat.format] on [format] and using [DateTimeComponents.setDateTimeOffset] in + * the lambda. + * + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is a format very similar to the one used by [toString]. + * The only difference is that [Instant.toString] adds trailing zeros to the fraction-of-second component so that the + * number of digits after a dot is a multiple of three. + * + * @sample kotlinx.datetime.test.samples.InstantSamples.formatting + */ +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().format(format, offset)") +) +public fun Instant.format(format: DateTimeFormat, offset: UtcOffset = UtcOffset.ZERO): String { + val instant = this + return format.format { setDateTimeOffset(instant.toStdlibInstant(), offset) } +} + +/** + * An instance of this class can not be obtained, and it should not be used. + * + * The purpose of this class is to allow defining functions that return `kotlin.time.Instant`, + * but still keep the functions returning `kotlinx.datetime.Instant` for binary compatibility. + * Kotlin does not allow two functions with the same name and parameter types but different return types, + * so we need to use a trick to achieve this. + * By introducing a fictional parameter of this type, we can pretend that the function has a different signature, + * even though it can only be called exactly the same way as the function without this parameter used to be. + * There is no ambiguity, as the old functions are deprecated and hidden and can not actually be called. + * + * @suppress this class is not meant to be used, so the documentation is only here for the curious reader. + */ +@Deprecated( + "It is meaningless to try to pass an OverloadMarker to a function directly. " + + "All functions accepting it have its instance as a default value.", + level = DeprecationLevel.ERROR +) +public class OverloadMarker private constructor() { + internal companion object { + @Suppress("DEPRECATION_ERROR") + internal val INSTANCE = OverloadMarker() + } +} diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 3deedd946..7130daaa0 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -3,435 +3,49 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass +@file:JvmName("InstantKt") package kotlinx.datetime import kotlinx.datetime.format.* import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.datetime.serializers.InstantComponentSerializer -import kotlinx.serialization.Serializable import kotlin.time.* +import kotlinx.time.Instant +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** - * A moment in time. + * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. * - * A point in time must be uniquely identified in a way that is independent of a time zone. - * For example, `1970-01-01, 00:00:00` does not represent a moment in time since this would happen at different times - * in different time zones: someone in Tokyo would think it is already `1970-01-01` several hours earlier than someone in - * Berlin would. To represent such entities, use [LocalDateTime]. - * In contrast, "the moment the clocks in London first showed 00:00 on Jan 1, 2000" is a specific moment - * in time, as is "1970-01-01, 00:00:00 UTC+0", so it can be represented as an [Instant]. + * Parses a string that represents an instant, including date and time components and a mandatory + * time zone offset and returns the parsed [Instant] value. * - * `Instant` uses the UTC-SLS (smeared leap second) time scale. This time scale doesn't contain instants - * corresponding to leap seconds, but instead "smears" positive and negative leap seconds among the last 1000 seconds - * of the day when a leap second happens. + * The string is considered to represent time on the UTC-SLS time scale instead of UTC. + * In practice, this means that, even if there is a leap second on the given day, it will not affect how the + * time is parsed, even if it's in the last 1000 seconds of the day. + * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. + * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. * - * ### Obtaining the current moment + * [Instant.parse] is equivalent to calling this function with the + * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] format. + * `2023-01-02T23:40:57.120Z` is an example of a string in this format. * - * The [Clock] interface is the primary way to obtain the current moment: + * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. * - * ``` - * val clock: Clock = Clock.System - * val instant = clock.now() - * ``` - * - * The [Clock.System] implementation uses the platform-specific system clock to obtain the current moment. - * Note that this clock is not guaranteed to be monotonic, and the user or the system may adjust it at any time, - * so it should not be used for measuring time intervals. - * For that, consider using [TimeSource.Monotonic] and [TimeMark] instead of [Clock.System] and [Instant]. - * - * ### Obtaining human-readable representations - * - * #### Date and time - * - * [Instant] is essentially the number of seconds and nanoseconds since a designated moment in time, - * stored as something like `1709898983.123456789`. - * [Instant] does not contain information about the day or time, as this depends on the time zone. - * To work with this information for a specific time zone, obtain a [LocalDateTime] using [Instant.toLocalDateTime]: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")) // 2024-03-08T12:56:23.123456789 - * instant.toLocalDateTime(TimeZone.UTC) // 2024-03-08T11:56:23.123456789 - * ``` - * - * For values very far in the past or the future, this conversion may fail. - * The specific range of values that can be converted to [LocalDateTime] is unspecified, but at least - * [DISTANT_PAST], [DISTANT_FUTURE], and all values between them are included in that range. - * - * #### Date or time separately - * - * To obtain a [LocalDate] or [LocalTime], first, obtain a [LocalDateTime], and then use its [LocalDateTime.date] - * and [LocalDateTime.time] properties: - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.toLocalDateTime(TimeZone.of("Europe/Berlin")).date // 2024-03-08 - * ``` - * - * ### Arithmetic operations - * - * #### Elapsed-time-based - * - * The [plus] and [minus] operators can be used to add [Duration]s to and subtract them from an [Instant]: - * - * ``` - * Clock.System.now() + 5.seconds // 5 seconds from now - * ``` - * - * Durations can also be represented as multiples of some [time-based datetime unit][DateTimeUnit.TimeBased]: - * - * ``` - * Clock.System.now().plus(4, DateTimeUnit.HOUR) // 4 hours from now - * ``` - * - * Also, there is a [minus] operator that returns the [Duration] representing the difference between two instants: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = concertStart - start - * ``` - * - * #### Calendar-based - * - * Since [Instant] represents a point in time, it is always well-defined what the result of arithmetic operations on it - * is, including the cases when a calendar is used. - * This is not the case for [LocalDateTime], where the result of arithmetic operations depends on the time zone. - * See the [LocalDateTime] documentation for more details. - * - * Adding and subtracting calendar-based units can be done using the [plus] and [minus] operators, - * requiring a [TimeZone]: - * - * ``` - * // One day from now in Berlin - * Clock.System.now().plus(1, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * - * // A day and two hours short from two months later in Berlin - * Clock.System.now().plus(DateTimePeriod(months = 2, days = -1, hours = -2), TimeZone.of("Europe/Berlin")) - * ``` - * - * The difference between [Instant] values in terms of calendar-based units can be obtained using the [periodUntil] - * method: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.periodUntil(concertStart, TimeZone.of("Europe/Berlin")) - * // Two months, three days, four hours, and five minutes until the concert - * ``` - * - * Or the [Instant.until] method, as well as [Instant.daysUntil], [Instant.monthsUntil], - * and [Instant.yearsUntil] extension functions: - * - * ``` - * val start = Clock.System.now() - * val concertStart = LocalDateTime(2023, 1, 1, 20, 0, 0).toInstant(TimeZone.of("Europe/Berlin")) - * val timeUntilConcert = start.until(concertStart, DateTimeUnit.DAY, TimeZone.of("Europe/Berlin")) - * // 63 days until the concert, rounded down - * ``` - * - * ### Platform specifics - * - * On the JVM, there are `Instant.toJavaInstant()` and `java.time.Instant.toKotlinInstant()` - * extension functions to convert between `kotlinx.datetime` and `java.time` objects used for the same purpose. - * Similarly, on the Darwin platforms, there are `Instant.toNSDate()` and `NSDate.toKotlinInstant()` - * extension functions. - * - * ### Construction, serialization, and deserialization - * - * [fromEpochSeconds] can be used to construct an instant from the number of seconds since - * `1970-01-01T00:00:00Z` (the Unix epoch). - * [epochSeconds] and [nanosecondsOfSecond] can be used to obtain the number of seconds and nanoseconds since the epoch. - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.epochSeconds // 1709898983 - * instant.nanosecondsOfSecond // 123456789 - * ``` - * - * [fromEpochMilliseconds] allows constructing an instant from the number of milliseconds since the epoch. - * [toEpochMilliseconds] can be used to obtain the number of milliseconds since the epoch. - * Note that [Instant] supports nanosecond precision, so converting to milliseconds is a lossy operation. - * - * ``` - * val instant1 = Instant.fromEpochSeconds(1709898983, 123456789) - * instant1.nanosecondsOfSecond // 123456789 - * val milliseconds = instant1.toEpochMilliseconds() // 1709898983123 - * val instant2 = Instant.fromEpochMilliseconds(milliseconds) - * instant2.nanosecondsOfSecond // 123000000 - * ``` - * - * [parse] and [toString] methods can be used to obtain an [Instant] from and convert it to a string in the - * ISO 8601 extended format. - * - * ``` - * val instant = Instant.parse("2023-01-02T22:35:01+01:00") - * instant.toString() // 2023-01-02T21:35:01Z - * ``` - * - * During parsing, the UTC offset is not returned separately. - * If the UTC offset is important, use [DateTimeComponents] with [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] to - * parse the string instead. - * - * [Instant.parse] and [Instant.format] also accept custom formats: - * - * ``` - * val customFormat = DateTimeComponents.Format { - * date(LocalDate.Formats.ISO) - * char(' ') - * time(LocalTime.Formats.ISO) - * char(' ') - * offset(UtcOffset.Formats.ISO) - * } - * val instant = Instant.parse("2023-01-02 22:35:01.14 +01:00", customFormat) - * instant.format(customFormat, offset = UtcOffset(hours = 2)) // 2023-01-02 23:35:01.14 +02:00 - * ``` - * - * Additionally, there are several `kotlinx-serialization` serializers for [Instant]: - * - [InstantIso8601Serializer] for the ISO 8601 extended format. - * - [InstantComponentSerializer] for an object with components. - * - * @see LocalDateTime for a user-visible representation of moments in time in an unspecified time zone. + * @see Instant.toString for formatting using the default format. + * @see Instant.format for formatting using a custom format. + * @see Instant.parse for parsing an ISO string without involving `kotlinx-datetime`. + * @sample kotlinx.datetime.test.samples.InstantSamples.parsing */ -@Serializable(with = InstantIso8601Serializer::class) -public expect class Instant : Comparable { - - /** - * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. - * - * The difference between the rounded number of seconds and the actual number of seconds - * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. - * - * Note that this number doesn't include leap seconds added or removed since the epoch. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds - */ - public val epochSeconds: Long - - /** - * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. - * - * The value is always non-negative and lies in the range `0..999_999_999`. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond - */ - public val nanosecondsOfSecond: Int - - /** - * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. - * - * If the result does not fit in [Long], - * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @see fromEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds - */ - public fun toEpochMilliseconds(): Long - - /** - * Returns an instant that is the result of adding the specified [duration] to this instant. - * - * If the [duration] is positive, the returned instant is later than this instant. - * If the [duration] is negative, the returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [plus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration - */ - public operator fun plus(duration: Duration): Instant - - /** - * Returns an instant that is the result of subtracting the specified [duration] from this instant. - * - * If the [duration] is positive, the returned instant is earlier than this instant. - * If the [duration] is negative, the returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours and are not calendar-based. - * Consider using the [minus] overload that accepts a multiple of a [DateTimeUnit] instead for calendar-based - * operations instead of using [Duration]. - * For an explanation of why some days are not 24 hours, see [DateTimeUnit.DayBased]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration - */ - public operator fun minus(duration: Duration): Instant - - // questionable - /** - * Returns the [Duration] between two instants: [other] and `this`. - * - * The duration returned is positive if this instant is later than the other, - * and negative if this instant is earlier than the other. - * - * The result is never clamped, but note that for instants that are far apart, - * the value returned may represent the duration between them inexactly due to the loss of precision. - * - * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other - * or even monotonic, so the result of this operation may be negative even if the other instant was observed later - * than this one, or vice versa. - * For measuring time intervals, consider using [TimeSource.Monotonic]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant - */ - public operator fun minus(other: Instant): Duration - - /** - * Compares `this` instant with the [other] instant. - * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), - * a negative number if this instant is earlier than the other, - * and a positive number if this instant is later than the other. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.compareToSample - */ - public override operator fun compareTo(other: Instant): Int - - /** - * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. - * - * The representation uses the UTC-SLS time scale instead of UTC. - * In practice, this means that leap second handling will not be readjusted to the UTC. - * Leap seconds will not be added or skipped, so it is impossible to acquire a string - * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. - * - * @see parse - * @see DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET for a very similar format. The difference is that - * [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] will not add trailing zeros for readability to the - * fractional part of the second. - * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample - */ - public override fun toString(): String - - - public companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public fun now(): Instant - - /** - * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. - * - * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. - * - * @see Instant.toEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds - */ - public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds - */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos - */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant - - /** - * A shortcut for calling [DateTimeFormat.parse], followed by [DateTimeComponents.toInstantUsingOffset]. - * - * Parses a string that represents an instant, including date and time components and a mandatory - * time zone offset and returns the parsed [Instant] value. - * - * The string is considered to represent time on the UTC-SLS time scale instead of UTC. - * In practice, this means that, even if there is a leap second on the given day, it will not affect how the - * time is parsed, even if it's in the last 1000 seconds of the day. - * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. - * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. - * - * If the format is not specified, [DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET] is used. - * `2023-01-02T23:40:57.120Z` is an example of a string in this format. - * - * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. - * - * @see Instant.toString for formatting using the default format. - * @see Instant.format for formatting using a custom format. - * @sample kotlinx.datetime.test.samples.InstantSamples.parsing - */ - public fun parse( - input: CharSequence, - format: DateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET - ): Instant - - /** - * An instant value that is far in the past. - * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * - * [isDistantPast] returns true for this value and all earlier ones. - */ - public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z - - /** - * An instant value that is far in the future. - * - * All instants in the range `DISTANT_PAST..DISTANT_FUTURE` can be [converted][Instant.toLocalDateTime] to - * [LocalDateTime] without exceptions in every time zone. - * - * [isDistantFuture] returns true for this value and all later ones. - */ - public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z - - internal val MIN: Instant - internal val MAX: Instant - } +public fun Instant.Companion.parse( + input: CharSequence, + format: DateTimeFormat, +): Instant = try { + format.parse(input).toInstantUsingOffset() +} catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) } -/** - * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast - */ -public val Instant.isDistantPast: Boolean - get() = this <= Instant.DISTANT_PAST - -/** - * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture - */ -public val Instant.isDistantFuture: Boolean - get() = this >= Instant.DISTANT_FUTURE - -/** - * @suppress - */ -@Deprecated("Removed to support more idiomatic code. See https://github.com/Kotlin/kotlinx-datetime/issues/339", ReplaceWith("Instant.parse(this)"), DeprecationLevel.WARNING) -public fun String.toInstant(): Instant = Instant.parse(this) - /** * Returns an instant that is the result of adding components of [DateTimePeriod] to this instant. The components are * added in the order from the largest units to the smallest, i.e., from years to nanoseconds. diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index 23767380d..c7e39efa9 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -10,6 +10,7 @@ package kotlinx.datetime import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable +import kotlinx.time.Instant /** * A time zone, provides the conversion between [Instant] and [LocalDateTime] values @@ -126,6 +127,24 @@ public expect open class TimeZone { */ public fun Instant.toLocalDateTime(): LocalDateTime + /** + * Return the civil datetime value that this instant has in the time zone provided as an implicit receiver. + * + * Note that while this conversion is unambiguous, the inverse ([LocalDateTime.toInstant]) + * is not necessarily so. + * + * @see LocalDateTime.toInstant + * @see Instant.offsetIn + * @throws DateTimeArithmeticException if this value is too large to fit in [LocalDateTime]. + * @sample kotlinx.datetime.test.samples.TimeZoneSamples.toLocalDateTimeWithTwoReceivers + */ + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime + /** * Returns an instant that corresponds to this civil datetime value in the time zone provided as an implicit receiver. * @@ -141,7 +160,13 @@ public expect open class TimeZone { * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.toInstantWithTwoReceivers */ - public fun LocalDateTime.toInstant(): Instant + @Suppress("DEPRECATION_ERROR") + public fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal fun LocalDateTime.toInstant(): kotlinx.datetime.Instant } /** @@ -195,6 +220,14 @@ public typealias ZoneOffset = FixedOffsetTimeZone */ public expect fun TimeZone.offsetAt(instant: Instant): UtcOffset +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.offsetAt(instant.toStdlibInstant())") +) +public fun TimeZone.offsetAt(instant: kotlinx.datetime.Instant): UtcOffset = + offsetAt(instant.toStdlibInstant()) + /** * Returns a civil datetime value that this instant has in the specified [timeZone]. * @@ -208,6 +241,14 @@ public expect fun TimeZone.offsetAt(instant: Instant): UtcOffset */ public expect fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime(timeZone)") +) +public fun kotlinx.datetime.Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime = + toStdlibInstant().toLocalDateTime(timeZone) + /** * Returns a civil datetime value that this instant has in the specified [UTC offset][offset]. * @@ -221,6 +262,14 @@ public expect fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime */ internal expect fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime(offset)") +) +public fun kotlinx.datetime.Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime = + toStdlibInstant().toLocalDateTime(offset) + /** * Finds the offset from UTC the specified [timeZone] has at this instant of physical time. * @@ -235,6 +284,14 @@ internal expect fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime public fun Instant.offsetIn(timeZone: TimeZone): UtcOffset = timeZone.offsetAt(this) +@Suppress("DEPRECATION") +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().offsetIn(timeZone)") +) +public fun kotlinx.datetime.Instant.offsetIn(timeZone: TimeZone): UtcOffset = + timeZone.offsetAt(toStdlibInstant()) + /** * Returns an instant that corresponds to this civil datetime value in the specified [timeZone]. * @@ -250,7 +307,14 @@ public fun Instant.offsetIn(timeZone: TimeZone): UtcOffset = * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.localDateTimeToInstantInZone */ -public expect fun LocalDateTime.toInstant(timeZone: TimeZone): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDateTime.toInstant(timeZone: TimeZone): kotlinx.datetime.Instant = + toInstant(timeZone).toDeprecatedInstant() /** * Returns an instant that corresponds to this civil datetime value that happens at the specified [UTC offset][offset]. @@ -258,7 +322,14 @@ public expect fun LocalDateTime.toInstant(timeZone: TimeZone): Instant * @see Instant.toLocalDateTime * @sample kotlinx.datetime.test.samples.TimeZoneSamples.localDateTimeToInstantInOffset */ -public expect fun LocalDateTime.toInstant(offset: UtcOffset): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDateTime.toInstant(offset: UtcOffset): kotlinx.datetime.Instant = + toInstant(offset).toDeprecatedInstant() /** * Returns an instant that corresponds to the start of this date in the specified [timeZone]. @@ -273,4 +344,11 @@ public expect fun LocalDateTime.toInstant(offset: UtcOffset): Instant * * @sample kotlinx.datetime.test.samples.TimeZoneSamples.atStartOfDayIn */ -public expect fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant +@Suppress("DEPRECATION_ERROR") +public expect fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant + +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun LocalDate.atStartOfDayIn(timeZone: TimeZone): kotlinx.datetime.Instant = + atStartOfDayIn(timeZone).toDeprecatedInstant() diff --git a/core/common/src/format/DateTimeComponents.kt b/core/common/src/format/DateTimeComponents.kt index 67e430673..196bc09c1 100644 --- a/core/common/src/format/DateTimeComponents.kt +++ b/core/common/src/format/DateTimeComponents.kt @@ -12,6 +12,7 @@ import kotlinx.datetime.internal.format.* import kotlinx.datetime.internal.format.parser.Copyable import kotlinx.datetime.internal.safeMultiply import kotlin.reflect.* +import kotlinx.time.Instant /** * A collection of datetime fields used specifically for parsing and formatting. @@ -244,6 +245,15 @@ public class DateTimeComponents internal constructor(internal val contents: Date year = year!! + ((instant.epochSeconds / SECONDS_PER_10000_YEARS) * 10000).toInt() } + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.setDateTimeOffset(instant.toStdlibInstant(), utcOffset)") + ) + public fun setDateTimeOffset(instant: kotlinx.datetime.Instant, utcOffset: UtcOffset) { + setDateTimeOffset(instant.toStdlibInstant(), utcOffset) + } + /** * Writes the contents of the specified [localDateTime] and [utcOffset] to this [DateTimeComponents]. * @@ -481,7 +491,8 @@ public class DateTimeComponents internal constructor(internal val contents: Date * with one another. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.toInstantUsingOffset */ - public fun toInstantUsingOffset(): Instant { + @Suppress("DEPRECATION_ERROR") + public fun toInstantUsingOffset(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant { val offset = toUtcOffset() val time = toLocalTime() val truncatedDate = contents.date.copy() @@ -499,10 +510,16 @@ public class DateTimeComponents internal constructor(internal val contents: Date } catch (e: ArithmeticException) { throw DateTimeFormatException("The parsed date is outside the range representable by Instant", e) } - if (totalSeconds < Instant.MIN.epochSeconds || totalSeconds > Instant.MAX.epochSeconds) + val result = Instant.fromEpochSeconds(totalSeconds, nanosecond ?: 0) + if (result.epochSeconds != totalSeconds) throw DateTimeFormatException("The parsed date is outside the range representable by Instant") - return Instant.fromEpochSeconds(totalSeconds, nanosecond ?: 0) + return result } + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal fun toInstantUsingOffset(): kotlinx.datetime.Instant = toInstantUsingOffset().toDeprecatedInstant() } /** diff --git a/core/common/src/serializers/InstantSerializers.kt b/core/common/src/serializers/DeprecatedInstantSerializers.kt similarity index 86% rename from core/common/src/serializers/InstantSerializers.kt rename to core/common/src/serializers/DeprecatedInstantSerializers.kt index c64bdf475..094a9d829 100644 --- a/core/common/src/serializers/InstantSerializers.kt +++ b/core/common/src/serializers/DeprecatedInstantSerializers.kt @@ -3,6 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.serializers import kotlinx.datetime.Instant @@ -18,6 +19,11 @@ import kotlinx.serialization.encoding.* * @see Instant.toString * @see Instant.parse */ +@Deprecated( + "kotlinx.datetime.Instant is superseded by kotlin.time.Instant, " + + "whose serializers are provided by kotlinx.serialization.", + level = DeprecationLevel.WARNING, +) public object InstantIso8601Serializer : KSerializer { override val descriptor: SerialDescriptor = @@ -37,6 +43,11 @@ public object InstantIso8601Serializer : KSerializer { * * JSON example: `{"epochSeconds":1607505416,"nanosecondsOfSecond":124000}` */ +@Deprecated( + "kotlinx.datetime.Instant is superseded by kotlin.time.Instant, " + + "whose serializers are provided by kotlinx.serialization.", + level = DeprecationLevel.WARNING, +) public object InstantComponentSerializer : KSerializer { override val descriptor: SerialDescriptor = diff --git a/core/common/test/ClockTimeSourceTest.kt b/core/common/test/ClockTimeSourceTest.kt index 75e30106d..53a6dfca6 100644 --- a/core/common/test/ClockTimeSourceTest.kt +++ b/core/common/test/ClockTimeSourceTest.kt @@ -11,6 +11,8 @@ import kotlin.time.* import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds +import kotlinx.time.Clock +import kotlinx.time.Instant @OptIn(ExperimentalTime::class) @Suppress("DEPRECATION") @@ -45,7 +47,7 @@ class ClockTimeSourceTest { clock.instant -= 2.days assertEquals(-1.days, mark.elapsedNow()) - clock.instant = Instant.MAX + clock.instant = Instant.fromEpochSeconds(Long.MAX_VALUE) assertEquals(Duration.INFINITE, mark.elapsedNow()) } diff --git a/core/common/test/DeprecatedClockTimeSourceTest.kt b/core/common/test/DeprecatedClockTimeSourceTest.kt new file mode 100644 index 000000000..f327c910a --- /dev/null +++ b/core/common/test/DeprecatedClockTimeSourceTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2023 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +package kotlinx.datetime.test + +import kotlinx.datetime.* +import kotlin.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.nanoseconds + +@OptIn(ExperimentalTime::class) +class DeprecatedClockTimeSourceTest { + @Test + fun arithmetic() { + val timeSource = Clock.System.asTimeSource() + val mark0 = timeSource.markNow() + + val markPast = mark0 - 1.days + val markFuture = mark0 + 1.days + + assertTrue(markPast < mark0) + assertTrue(markFuture > mark0) + assertEquals(mark0, markPast + 1.days) + assertEquals(2.days, markFuture - markPast) + } + + @Test + fun elapsed() { + val clock = object : Clock { + var instant = Clock.System.now() + override fun now(): Instant = instant + } + val timeSource = clock.asTimeSource() + val mark = timeSource.markNow() + assertEquals(Duration.ZERO, mark.elapsedNow()) + + clock.instant += 1.days + assertEquals(1.days, mark.elapsedNow()) + + clock.instant -= 2.days + assertEquals(-1.days, mark.elapsedNow()) + + clock.instant = Instant.fromEpochSeconds(Long.MAX_VALUE) + assertEquals(Duration.INFINITE, mark.elapsedNow()) + } + + @Test + fun differentSources() { + val mark1 = Clock.System.asTimeSource().markNow() + val mark2 = object : Clock { + override fun now(): Instant = Instant.DISTANT_FUTURE + }.asTimeSource().markNow() + assertNotEquals(mark1, mark2) + assertFailsWith { mark1 - mark2 } + assertFailsWith { mark1 compareTo mark2 } + } + + @Test + fun saturation() { + val mark0 = Clock.System.asTimeSource().markNow() + + val markFuture = mark0 + Duration.INFINITE + val markPast = mark0 - Duration.INFINITE + + for (delta in listOf(Duration.ZERO, 1.nanoseconds, 1.days)) { + assertEquals(markFuture, markFuture - delta) + assertEquals(markFuture, markFuture + delta) + + assertEquals(markPast, markPast - delta) + assertEquals(markPast, markPast + delta) + } + val infinitePairs = listOf(markFuture to markPast, markFuture to mark0, mark0 to markPast) + for ((later, earlier) in infinitePairs) { + assertEquals(Duration.INFINITE, later - earlier) + assertEquals(-Duration.INFINITE, earlier - later) + } + assertEquals(Duration.ZERO, markFuture - markFuture) + assertEquals(Duration.ZERO, markPast - markPast) + + assertFailsWith { markFuture - Duration.INFINITE } + assertFailsWith { markPast + Duration.INFINITE } + } +} diff --git a/core/common/test/DeprecatedInstantTest.kt b/core/common/test/DeprecatedInstantTest.kt new file mode 100644 index 000000000..e76524eb9 --- /dev/null +++ b/core/common/test/DeprecatedInstantTest.kt @@ -0,0 +1,640 @@ +/* + * Copyright 2019-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +package kotlinx.datetime.test + +import kotlinx.datetime.* +import kotlinx.datetime.format.* +import kotlinx.datetime.internal.* +import kotlin.random.* +import kotlin.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +class DeprecatedInstantTest { + + @Test + fun testNow() { + val instant = Clock.System.now() + val millis = instant.toEpochMilliseconds() + + assertTrue(millis > 1_500_000_000_000L) + + println(instant) + println(instant.toEpochMilliseconds()) + + 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) + + println("this: $instant, next: $nextInstant, diff: ${diff.toIsoString()}") + } + + @Test + fun instantToLocalDTConversion() { + val now = Clock.System.now() + println(now.toLocalDateTime(TimeZone.UTC)) + println(now.toLocalDateTime(TimeZone.currentSystemDefault())) + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @Test + fun parseIsoString() { + val instants = arrayOf( + Triple("1970-01-01T00:00:00Z", 0, 0), + Triple("1970-01-01t00:00:00Z", 0, 0), + Triple("1970-01-01T00:00:00z", 0, 0), + Triple("1970-01-01T00:00:00.0Z", 0, 0), + Triple("1970-01-01T00:00:00.000000000Z", 0, 0), + Triple("1970-01-01T00:00:00.000000001Z", 0, 1), + Triple("1970-01-01T00:00:00.100000000Z", 0, 100000000), + Triple("1970-01-01T00:00:01Z", 1, 0), + Triple("1970-01-01T00:01:00Z", 60, 0), + Triple("1970-01-01T00:01:01Z", 61, 0), + Triple("1970-01-01T00:01:01.000000001Z", 61, 1), + Triple("1970-01-01T01:00:00.000000000Z", 3600, 0), + Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), + Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000)) + instants.forEach { + val (str, seconds, nanos) = it + val instant = Instant.parse(str) + assertEquals(seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds()) + } + + assertInvalidFormat { Instant.parse("1970-01-01T23:59:60Z")} + assertInvalidFormat { Instant.parse("1970-01-01T24:00:00Z")} + assertInvalidFormat { Instant.parse("1970-01-01T23:59Z")} + assertInvalidFormat { Instant.parse("x") } + assertInvalidFormat { Instant.parse("12020-12-31T23:59:59.000000000Z") } + // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: + assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } + } + + @Test + fun parseStringsWithOffsets() { + val strings = arrayOf( + Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), + Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), + Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), + Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), + Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), + ) + strings.forEach { (str, strInZ) -> + val instant = Instant.parse(str) + assertEquals(Instant.parse(strInZ), instant, str) + assertEquals(strInZ, instant.toString(), str) + } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } + assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } + + val instants = listOf( + Instant.DISTANT_FUTURE, + Instant.DISTANT_PAST, + Instant.fromEpochSeconds(0, 0)) + + val offsetStrings = listOf( + "Z", + "+03:12:14", + "-03:12:14", + "+02:35", + "-02:35", + "+04", + "-04", + ) + + val offsetFormat = UtcOffset.Format { + optional("Z") { + offsetHours() + optional { + char(':'); offsetMinutesOfHour() + optional { char(':'); offsetSecondsOfMinute() } + } + } + } + val offsets = offsetStrings.map { UtcOffset.parse(it, offsetFormat) } + + for (instant in instants) { + for (offsetIx in offsets.indices) { + val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx]) + val offsetString = offsets[offsetIx].toString() + assertEquals(offsetString, offsetString.commonSuffixWith(str)) + assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)) + assertEquals(instant, Instant.parse(str)) + } + } + } + + @Test + fun instantCalendarArithmetic() { + val zone = TimeZone.of("Europe/Berlin") + + fun expectBetween(instant1: Instant, instant2: Instant, expected: Long, unit: DateTimeUnit) { + assertEquals(expected, instant1.until(instant2, unit, zone), "i1.until(i2)") + assertEquals(expected, -instant2.until(instant1, unit, zone), "i2.until(i1)") + assertEquals(expected, instant2.minus(instant1, unit, zone), "i2.minus(i1)") + assertEquals(expected, -instant1.minus(instant2, unit, zone), "i1.minus(i2)") + + for (timeUnit in listOf(DateTimeUnit.MICROSECOND, DateTimeUnit.MILLISECOND, DateTimeUnit.SECOND, DateTimeUnit.MINUTE, DateTimeUnit.HOUR)) { + val diff = instant2.minus(instant1, timeUnit, zone) + assertEquals(instant2 - instant1, timeUnit.duration * diff.toDouble()) + assertEquals(instant2, instant1.plus(diff, timeUnit, zone)) + assertEquals(instant1, instant2.minus(diff, timeUnit, zone)) + assertEquals(instant2, instant1.plus(diff, timeUnit)) + assertEquals(instant1, instant2.minus(diff, timeUnit)) + } + } + + val instant1 = LocalDateTime(2019, Month.OCTOBER, 27, 2, 59).toInstant(zone).toDeprecatedInstant() + checkComponents(instant1.toLocalDateTime(zone), 2019, 10, 27, 2, 59) + + val instant2 = instant1.plus(DateTimePeriod(hours = 24), zone) + checkComponents(instant2.toLocalDateTime(zone), 2019, 10, 28, 1, 59) + expectBetween(instant1, instant2, 24, DateTimeUnit.HOUR) + assertEquals(instant1, instant2.minus(DateTimePeriod(hours = 24), zone)) + + val instant3 = instant1.plus(1, DateTimeUnit.DAY, zone) + checkComponents(instant3.toLocalDateTime(zone), 2019, 10, 28, 2, 59) + expectBetween(instant1, instant3, 25, DateTimeUnit.HOUR) + expectBetween(instant1, instant3, 1, DateTimeUnit.DAY) + assertEquals(1, instant1.daysUntil(instant3, zone)) + assertEquals(instant1.minus(1, DateTimeUnit.HOUR), instant2.minus(1, DateTimeUnit.DAY, zone)) + + val instant4 = instant1.plus(14, DateTimeUnit.MONTH, zone) + checkComponents(instant4.toLocalDateTime(zone), 2020, 12, 27, 2, 59) + expectBetween(instant1, instant4, 1, DateTimeUnit.YEAR) + expectBetween(instant1, instant4, 4, DateTimeUnit.QUARTER) + expectBetween(instant1, instant4, 14, DateTimeUnit.MONTH) + expectBetween(instant1, instant4, 61, DateTimeUnit.WEEK) + expectBetween(instant1, instant4, 366 + 31 + 30, DateTimeUnit.DAY) + expectBetween(instant1, instant4, (366 + 31 + 30) * 24 + 1, DateTimeUnit.HOUR) + assertEquals(instant1.plus(1, DateTimeUnit.HOUR), instant4.minus(14, DateTimeUnit.MONTH, zone)) + + val period = DateTimePeriod(days = 1, hours = 1) + val instant5 = instant1.plus(period, zone) + checkComponents(instant5.toLocalDateTime(zone), 2019, 10, 28, 3, 59) + assertEquals(period, instant1.periodUntil(instant5, zone)) + assertEquals(period, instant5.minus(instant1, zone)) + assertEquals(26.hours, instant5.minus(instant1)) + assertEquals(instant1.plus(1, DateTimeUnit.HOUR), instant5.minus(period, zone)) + + val instant6 = instant1.plus(23, DateTimeUnit.HOUR, zone) + checkComponents(instant6.toLocalDateTime(zone), 2019, 10, 28, 0, 59) + expectBetween(instant1, instant6, 23, DateTimeUnit.HOUR) + expectBetween(instant1, instant6, 0, DateTimeUnit.DAY) + assertEquals(instant1, instant6.minus(23, DateTimeUnit.HOUR, zone)) + } + + @Test + fun addingMultiplesOf2_32() { + val pow2_32 = 1L shl 32 + val instant1 = Instant.fromEpochSeconds(0) + val instant2 = instant1.plus(pow2_32, DateTimeUnit.NANOSECOND, TimeZone.UTC) + assertEquals(pow2_32 / NANOS_PER_ONE, instant2.epochSeconds) + assertEquals(pow2_32 % NANOS_PER_ONE, instant2.nanosecondsOfSecond.toLong()) + + val instant3 = instant1.plus(pow2_32, DateTimeUnit.SECOND, TimeZone.UTC) + assertEquals(pow2_32, instant3.epochSeconds) + } + + @Test + fun unitMultiplesUntil() { + val unit1000days = DateTimeUnit.DAY * 1000 + val unit4years = DateTimeUnit.YEAR * 4 // longer than 1000-DAY + + val zone = TimeZone.UTC + val min = LocalDateTime.MIN.toInstant(zone) + val max = LocalDateTime.MAX.toInstant(zone) + val diffDays = min.until(max, unit1000days, zone) + val diffYears = min.until(max, unit4years, zone) + assertTrue(diffDays in 0..Int.MAX_VALUE, "difference in $unit1000days should fit in Int, was $diffDays") + assertTrue(diffDays > diffYears, "difference in $unit1000days unit must be more than in $unit4years unit, was $diffDays $diffYears") + + val unit500ns = DateTimeUnit.NANOSECOND * 500 + val start = Instant.parse("1700-01-01T00:00:00Z") + val end = start.plus(300, DateTimeUnit.YEAR, zone) + val diffNs = start.until(end, unit500ns, zone) + val diffUs = start.until(end, DateTimeUnit.MICROSECOND, zone) + assertEquals(diffUs * 2, diffNs) + + assertEquals(end, start.plus(diffNs, unit500ns, zone)) + assertEquals(start, end.plus(-diffUs, DateTimeUnit.MICROSECOND, zone)) + } + + @Test + fun instantOffset() { + val zone = TimeZone.of("Europe/Berlin") + val instant1 = LocalDateTime(2019, 10, 27, 2, 59, 0, 0).toInstant(zone) + val ldt1 = instant1.toLocalDateTime(zone) + val offset1 = instant1.offsetIn(zone) + checkComponents(ldt1, 2019, 10, 27, 2, 59) + assertEquals(instant1, ldt1.toInstant(offset1)) + + val instant2 = instant1 + 1.hours + val ldt2 = instant2.toLocalDateTime(zone) + val offset2 = instant2.offsetIn(zone) + assertEquals(ldt1, ldt2) + assertEquals(instant2, ldt2.toInstant(offset2)) + assertNotEquals(offset1, offset2) + assertEquals(offset1.totalSeconds.seconds, offset2.totalSeconds.seconds + 1.hours) + + val instant3 = instant2 - 2.hours + val offset3 = instant3.offsetIn(zone) + assertEquals(offset1, offset3) + + // without the minus, this test fails on JVM + (Instant.MAX - (2 * 365).days).offsetIn(zone) + } + + @Test + fun changingTimeZoneRules() { + val start = Instant.parse("1991-01-25T23:15:15.855Z") + val end = Instant.parse("2006-04-24T22:07:32.561Z") + val diff = start.periodUntil(end, TimeZone.of("Europe/Moscow")) + val end2 = start.plus(diff, TimeZone.of("Europe/Moscow")) + assertEquals(end, end2) + } + + @Test + fun diffInvariant() { + repeat(STRESS_TEST_ITERATIONS) { + val millis1 = Random.nextLong(2_000_000_000_000L) + val millis2 = Random.nextLong(2_000_000_000_000L) + val instant1 = Instant.fromEpochMilliseconds(millis1) + val instant2 = Instant.fromEpochMilliseconds(millis2) + + val diff = instant1.periodUntil(instant2, TimeZone.currentSystemDefault()) + val instant3 = instant1.plus(diff, TimeZone.currentSystemDefault()) + + if (instant2 != instant3) + println("start: $instant1, end: $instant2, start + diff: $instant3, diff: $diff") + } + } + + @Test + fun diffInvariantSameAsDate() { + repeat(STRESS_TEST_ITERATIONS) { + val millis1 = Random.nextLong(2_000_000_000_000L) + val millis2 = Random.nextLong(2_000_000_000_000L) + with(TimeZone.UTC) TZ@ { + val date1 = Instant.fromEpochMilliseconds(millis1).toLocalDateTime().date + val date2 = Instant.fromEpochMilliseconds(millis2).toLocalDateTime().date + val instant1 = date1.atStartOfDayIn(this@TZ) + val instant2 = date2.atStartOfDayIn(this@TZ) + + val diff1 = instant1.periodUntil(instant2, this@TZ) + val diff2 = date1.periodUntil(date2) + + if (diff1 != diff2) + throw AssertionError( + "start: $instant1, end: $instant2, diff by instants: $diff1, diff by dates: $diff2" + ) + } + } + } + + + @Test + fun zoneDependentDiff() { + val instant1 = Instant.parse("2019-04-01T00:00:00Z") + val instant2 = Instant.parse("2019-05-01T04:00:00Z") + + for (zone in (-12..12 step 3).map { h -> TimeZone.of("${if (h >= 0) "+" else ""}$h") }) { + val dt1 = instant1.toLocalDateTime(zone) + val dt2 = instant2.toLocalDateTime(zone) + val diff = instant1.periodUntil(instant2, zone) + println("diff between $dt1 and $dt2 at zone $zone: $diff") + } + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @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) + } + + /* Based on the ThreeTenBp project. + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + */ + @Test + fun strings() { + assertEquals("0000-01-02T00:00:00Z", LocalDateTime(0, 1, 2, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T12:30:00Z", LocalDateTime(0, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T00:00:00.000000001Z", LocalDateTime(0, 1, 1, 0, 0, 0, 1).toInstant(TimeZone.UTC).toString()) + assertEquals("0000-01-01T00:00:00Z", LocalDateTime(0, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-31T23:59:59.999999999Z", LocalDateTime(-1, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-31T12:30:00Z", LocalDateTime(-1, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-0001-12-30T12:30:00Z", LocalDateTime(-1, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-02T12:30:00Z", LocalDateTime(-9999, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-01T12:30:00Z", LocalDateTime(-9999, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-9999-01-01T00:00:00Z", LocalDateTime(-9999, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-31T23:59:59.999999999Z", LocalDateTime(-10000, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-31T12:30:00Z", LocalDateTime(-10000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-10000-12-30T12:30:00Z", LocalDateTime(-10000, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-15000-12-31T12:30:00Z", LocalDateTime(-15000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-02T12:30:00Z", LocalDateTime(-19999, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-01T12:30:00Z", LocalDateTime(-19999, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-19999-01-01T00:00:00Z", LocalDateTime(-19999, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-31T23:59:59.999999999Z", LocalDateTime(-20000, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-31T12:30:00Z", LocalDateTime(-20000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-20000-12-30T12:30:00Z", LocalDateTime(-20000, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("-25000-12-31T12:30:00Z", LocalDateTime(-25000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-30T12:30:00Z", LocalDateTime(9999, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-31T12:30:00Z", LocalDateTime(9999, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("9999-12-31T23:59:59.999999999Z", LocalDateTime(9999, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-01T00:00:00Z", LocalDateTime(10000, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-01T12:30:00Z", LocalDateTime(10000, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+10000-01-02T12:30:00Z", LocalDateTime(10000, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+15000-12-31T12:30:00Z", LocalDateTime(15000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-30T12:30:00Z", LocalDateTime(19999, 12, 30, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T12:30:00Z", LocalDateTime(19999, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.999999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 999999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-01T00:00:00Z", LocalDateTime(20000, 1, 1, 0, 0, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-01T12:30:00Z", LocalDateTime(20000, 1, 1, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+20000-01-02T12:30:00Z", LocalDateTime(20000, 1, 2, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+25000-12-31T12:30:00Z", LocalDateTime(25000, 12, 31, 12, 30, 0, 0).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.009999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9999999).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.999999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 999999000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.009999Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9999000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.123Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 123000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.100Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 100000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.020Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 20000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.003Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 3000000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000400Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 400000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000050Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 50000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000006Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 6000).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000700Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 700).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000080Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 80).toInstant(TimeZone.UTC).toString()) + assertEquals("+19999-12-31T23:59:59.000000009Z", LocalDateTime(19999, 12, 31, 23, 59, 59, 9).toInstant(TimeZone.UTC).toString()) + } + + @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, distantFutureString.toInstant()) + assertEquals(distantPastString, Instant.DISTANT_PAST.toString()) + assertEquals(Instant.DISTANT_PAST, distantPastString.toInstant()) + 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) + } + +} + +class DeprecatedInstantRangeTest { + private val UTC = TimeZone.UTC + private val maxValidInstant = LocalDateTime.MAX.toInstant(UTC).toDeprecatedInstant() + private val minValidInstant = LocalDateTime.MIN.toInstant(UTC).toDeprecatedInstant() + + 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 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 periodArithmeticOutOfRange() { + // Instant.plus(DateTimePeriod(), TimeZone) + // Arithmetic overflow + for (instant in largePositiveInstants) { + assertArithmeticFails("$instant") { instant.plus(DateTimePeriod(nanoseconds = Long.MAX_VALUE), UTC) } + } + for (instant in largeNegativeInstants) { + assertArithmeticFails("$instant") { instant.plus(DateTimePeriod(nanoseconds = Long.MIN_VALUE), UTC) } + } + // Arithmetic overflow in an Int + for (instant in smallInstants + listOf(maxValidInstant)) { + assertEquals(instant.epochSeconds + Int.MIN_VALUE, + instant.plus(Int.MIN_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + assertEquals(instant.epochSeconds - Int.MAX_VALUE, + instant.minus(Int.MAX_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + } + for (instant in smallInstants + listOf(minValidInstant)) { + assertEquals(instant.epochSeconds + Int.MAX_VALUE, + instant.plus(Int.MAX_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + assertEquals(instant.epochSeconds - Int.MIN_VALUE, + instant.minus(Int.MIN_VALUE, DateTimeUnit.SECOND, UTC).epochSeconds) + } + // Overflowing a LocalDateTime in input + maxValidInstant.plus(DateTimePeriod(nanoseconds = -1), UTC) + minValidInstant.plus(DateTimePeriod(nanoseconds = 1), UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).plus(DateTimePeriod(nanoseconds = -2), UTC) } + assertArithmeticFails { (minValidInstant - 1.nanoseconds).plus(DateTimePeriod(nanoseconds = 2), UTC) } + // Overflowing a LocalDateTime in result + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(nanoseconds = 1), UTC) } + assertArithmeticFails { minValidInstant.plus(DateTimePeriod(nanoseconds = -1), UTC) } + // Overflowing a LocalDateTime in intermediate computations + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(days = 1, nanoseconds = -1_000_000_001), UTC) } + assertArithmeticFails { maxValidInstant.plus(DateTimePeriod(months = 1, days = -48), UTC) } + } + + @Test + fun unitArithmeticOutOfRange() { + // Instant.plus(Long, DateTimeUnit, TimeZone) + // Arithmetic overflow + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertArithmeticFails("$instant") { instant.plus(Long.MAX_VALUE, DateTimeUnit.SECOND, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MIN_VALUE, DateTimeUnit.SECOND, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MAX_VALUE, DateTimeUnit.YEAR, UTC) } + assertArithmeticFails("$instant") { instant.plus(Long.MIN_VALUE, DateTimeUnit.YEAR, UTC) } + } + for (instant in smallInstants) { + instant.plus(2 * Int.MAX_VALUE.toLong(), DateTimeUnit.DAY, UTC) + instant.plus(2 * Int.MIN_VALUE.toLong(), DateTimeUnit.DAY, UTC) + instant.plus(2 * Int.MAX_VALUE.toLong(), DateTimeUnit.MONTH, UTC) + instant.plus(2 * Int.MIN_VALUE.toLong(), DateTimeUnit.MONTH, UTC) + } + // Overflowing a LocalDateTime in input + maxValidInstant.plus(-1, DateTimeUnit.NANOSECOND, UTC) + minValidInstant.plus(1, DateTimeUnit.NANOSECOND, UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).plus(-2, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { (minValidInstant - 1.nanoseconds).plus(2, DateTimeUnit.NANOSECOND, UTC) } + // Overflowing a LocalDateTime in result + assertArithmeticFails { maxValidInstant.plus(1, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { maxValidInstant.plus(1, DateTimeUnit.YEAR, UTC) } + assertArithmeticFails { minValidInstant.plus(-1, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { minValidInstant.plus(-1, DateTimeUnit.YEAR, UTC) } + } + + @Test + fun timeBasedUnitArithmeticOutOfRange() { + // Instant.plus(Long, DateTimeUnit.TimeBased) + // Arithmetic overflow + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MAX, instant.plus(Long.MAX_VALUE, DateTimeUnit.SECOND)) + assertEquals(Instant.MIN, instant.plus(Long.MIN_VALUE, DateTimeUnit.SECOND)) + } + // Overflow of Instant boundaries + for (instant in smallInstants + largeNegativeInstants + largePositiveInstants) { + assertEquals(Instant.MAX, instant.plus(Instant.MAX.epochSeconds - instant.epochSeconds + 1, DateTimeUnit.SECOND)) + assertEquals(Instant.MIN, instant.plus(Instant.MIN.epochSeconds - instant.epochSeconds - 1, DateTimeUnit.SECOND)) + } + } + + + @Test + fun periodUntilOutOfRange() { + // Instant.periodUntil + maxValidInstant.periodUntil(maxValidInstant, UTC) + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).periodUntil(maxValidInstant, UTC) } + assertArithmeticFails { minValidInstant.periodUntil(minValidInstant - 1.nanoseconds, UTC) } + } + + @Test + fun unitsUntilClamping() { + // Arithmetic overflow of the resulting number + assertEquals(Long.MAX_VALUE, minValidInstant.until(maxValidInstant, DateTimeUnit.NANOSECOND, UTC)) + assertEquals(Long.MIN_VALUE, maxValidInstant.until(minValidInstant, DateTimeUnit.NANOSECOND, UTC)) + assertEquals(Long.MAX_VALUE, minValidInstant.until(maxValidInstant, DateTimeUnit.NANOSECOND)) + assertEquals(Long.MIN_VALUE, maxValidInstant.until(minValidInstant, DateTimeUnit.NANOSECOND)) + } + + @Test + fun unitsUntilOutOfRange() { + // Instant.until + // Overflowing a LocalDateTime in input + assertArithmeticFails { (maxValidInstant + 1.nanoseconds).until(maxValidInstant, DateTimeUnit.NANOSECOND, UTC) } + assertArithmeticFails { maxValidInstant.until(maxValidInstant + 1.nanoseconds, DateTimeUnit.NANOSECOND, UTC) } + // Overloads without a TimeZone should not fail on overflowing a LocalDateTime + (maxValidInstant + 1.nanoseconds).until(maxValidInstant, DateTimeUnit.NANOSECOND) + maxValidInstant.until(maxValidInstant + 1.nanoseconds, DateTimeUnit.NANOSECOND) + } + + // 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) + } +} diff --git a/core/common/test/InstantTest.kt b/core/common/test/InstantTest.kt index 8bc41f019..3479cc530 100644 --- a/core/common/test/InstantTest.kt +++ b/core/common/test/InstantTest.kt @@ -16,6 +16,10 @@ import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds +import kotlinx.time.Clock +import kotlinx.time.Instant +import kotlinx.time.isDistantFuture +import kotlinx.time.isDistantPast class InstantTest { @@ -59,99 +63,6 @@ class InstantTest { println(now.toLocalDateTime(TimeZone.currentSystemDefault())) } - /* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - @Test - fun parseIsoString() { - val instants = arrayOf( - Triple("1970-01-01T00:00:00Z", 0, 0), - Triple("1970-01-01t00:00:00Z", 0, 0), - Triple("1970-01-01T00:00:00z", 0, 0), - Triple("1970-01-01T00:00:00.0Z", 0, 0), - Triple("1970-01-01T00:00:00.000000000Z", 0, 0), - Triple("1970-01-01T00:00:00.000000001Z", 0, 1), - Triple("1970-01-01T00:00:00.100000000Z", 0, 100000000), - Triple("1970-01-01T00:00:01Z", 1, 0), - Triple("1970-01-01T00:01:00Z", 60, 0), - Triple("1970-01-01T00:01:01Z", 61, 0), - Triple("1970-01-01T00:01:01.000000001Z", 61, 1), - Triple("1970-01-01T01:00:00.000000000Z", 3600, 0), - Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), - Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000)) - instants.forEach { - val (str, seconds, nanos) = it - val instant = Instant.parse(str) - assertEquals(seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds()) - } - - assertInvalidFormat { Instant.parse("1970-01-01T23:59:60Z")} - assertInvalidFormat { Instant.parse("1970-01-01T24:00:00Z")} - assertInvalidFormat { Instant.parse("1970-01-01T23:59Z")} - assertInvalidFormat { Instant.parse("x") } - assertInvalidFormat { Instant.parse("12020-12-31T23:59:59.000000000Z") } - // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: - assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } - } - - @Test - fun parseStringsWithOffsets() { - val strings = arrayOf( - Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), - Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), - Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), - Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), - Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), - ) - strings.forEach { (str, strInZ) -> - val instant = Instant.parse(str) - assertEquals(Instant.parse(strInZ), instant, str) - assertEquals(strInZ, instant.toString(), str) - } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } - - val instants = listOf( - Instant.DISTANT_FUTURE, - Instant.DISTANT_PAST, - Instant.fromEpochSeconds(0, 0)) - - val offsetStrings = listOf( - "Z", - "+03:12:14", - "-03:12:14", - "+02:35", - "-02:35", - "+04", - "-04", - ) - - val offsetFormat = UtcOffset.Format { - optional("Z") { - offsetHours() - optional { - char(':'); offsetMinutesOfHour() - optional { char(':'); offsetSecondsOfMinute() } - } - } - } - val offsets = offsetStrings.map { UtcOffset.parse(it, offsetFormat) } - - for (instant in instants) { - for (offsetIx in offsets.indices) { - val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx]) - val offsetString = offsets[offsetIx].toString() - assertEquals(offsetString, offsetString.commonSuffixWith(str)) - assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)) - assertEquals(instant, Instant.parse(str)) - } - } - } - @Test fun instantCalendarArithmetic() { val zone = TimeZone.of("Europe/Berlin") @@ -428,9 +339,9 @@ class InstantTest { 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, distantFutureString.toInstant()) + assertEquals(Instant.DISTANT_FUTURE, distantFutureString.let(Instant::parse)) assertEquals(distantPastString, Instant.DISTANT_PAST.toString()) - assertEquals(Instant.DISTANT_PAST, distantPastString.toInstant()) + assertEquals(Instant.DISTANT_PAST, distantPastString.let(Instant::parse)) assertTrue(Instant.DISTANT_PAST.isDistantPast) assertTrue(Instant.DISTANT_FUTURE.isDistantFuture) assertFalse(Instant.DISTANT_PAST.isDistantFuture) @@ -638,3 +549,9 @@ class InstantRangeTest { assertEquals(max.epochSeconds - min.epochSeconds, (max - min).inWholeSeconds) } } + +private val maxInstant = Instant.fromEpochSeconds(Long.MAX_VALUE) +private val minInstant = Instant.fromEpochSeconds(Long.MIN_VALUE) + +internal val Instant.Companion.MAX get() = maxInstant +internal val Instant.Companion.MIN get() = minInstant diff --git a/core/common/test/LocalDateTest.kt b/core/common/test/LocalDateTest.kt index f6ded203e..cc1740e9c 100644 --- a/core/common/test/LocalDateTest.kt +++ b/core/common/test/LocalDateTest.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* +import kotlinx.time.Clock import kotlin.random.* import kotlin.test.* diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index fff490a30..041491bc9 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -6,7 +6,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* -import kotlinx.datetime.Clock +import kotlinx.time.Clock +import kotlinx.time.Instant import kotlin.test.* import kotlin.time.* import kotlin.time.Duration.Companion.hours @@ -50,8 +51,8 @@ class LocalDateTimeTest { val diff = with(TimeZone.UTC) { ldt2.toInstant() - ldt1.toInstant() } assertEquals(with(Duration) { 1.hours + 7.minutes - 15.seconds + 400100.microseconds }, diff) - assertFailsWith { (Instant.MAX - 3.days).toLocalDateTime(TimeZone.UTC) } - assertFailsWith { (Instant.MIN + 6.hours).toLocalDateTime(TimeZone.UTC) } + assertFailsWith { (Instant.fromEpochSeconds(Long.MAX_VALUE) - 3.days).toLocalDateTime(TimeZone.UTC) } + assertFailsWith { (Instant.fromEpochSeconds(Long.MIN_VALUE) + 6.hours).toLocalDateTime(TimeZone.UTC) } } @Test @@ -67,7 +68,7 @@ class LocalDateTimeTest { val instant = Instant.parse("2019-10-01T18:43:15.100500Z") val datetime = instant.toLocalDateTime(TimeZone.UTC) checkComponents(datetime, 2019, 10, 1, 18, 43, 15, 100500000) - assertFailsWith { Instant.MAX.toLocalDateTime(TimeZone.UTC) } + assertFailsWith { Instant.fromEpochSeconds(Long.MAX_VALUE).toLocalDateTime(TimeZone.UTC) } } @Test diff --git a/core/common/test/ReadmeTest.kt b/core/common/test/ReadmeTest.kt index a6d8636a1..15f384c21 100644 --- a/core/common/test/ReadmeTest.kt +++ b/core/common/test/ReadmeTest.kt @@ -9,6 +9,8 @@ import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* import kotlin.time.* +import kotlinx.time.Clock +import kotlinx.time.Instant /** * Tests the code snippets in the README.md file. diff --git a/core/common/test/TimeZoneTest.kt b/core/common/test/TimeZoneTest.kt index 24c6d9e87..add66c60c 100644 --- a/core/common/test/TimeZoneTest.kt +++ b/core/common/test/TimeZoneTest.kt @@ -9,6 +9,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* +import kotlinx.time.Clock +import kotlinx.time.Instant class TimeZoneTest { diff --git a/core/common/test/format/DateTimeComponentsFormatTest.kt b/core/common/test/format/DateTimeComponentsFormatTest.kt index 86d2c3a81..274106cd6 100644 --- a/core/common/test/format/DateTimeComponentsFormatTest.kt +++ b/core/common/test/format/DateTimeComponentsFormatTest.kt @@ -10,6 +10,7 @@ import kotlinx.datetime.format.* import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.test.* +import kotlinx.time.Instant class DateTimeComponentsFormatTest { @Test diff --git a/core/common/test/format/DateTimeComponentsTest.kt b/core/common/test/format/DateTimeComponentsTest.kt index 8575c601b..530aa4869 100644 --- a/core/common/test/format/DateTimeComponentsTest.kt +++ b/core/common/test/format/DateTimeComponentsTest.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlinx.time.Clock class DateTimeComponentsTest { @Test diff --git a/core/common/test/samples/ClockSamples.kt b/core/common/test/samples/ClockSamples.kt index 70683efa5..862df90df 100644 --- a/core/common/test/samples/ClockSamples.kt +++ b/core/common/test/samples/ClockSamples.kt @@ -9,6 +9,8 @@ import kotlinx.datetime.* import kotlin.test.* import kotlin.time.Duration.Companion.seconds import kotlin.time.TestTimeSource +import kotlinx.time.Clock +import kotlinx.time.Instant class ClockSamples { @Test diff --git a/core/common/test/samples/DayOfWeekSamples.kt b/core/common/test/samples/DayOfWeekSamples.kt index c8393ae98..4d4e1e0cd 100644 --- a/core/common/test/samples/DayOfWeekSamples.kt +++ b/core/common/test/samples/DayOfWeekSamples.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* +import kotlinx.time.Clock class DayOfWeekSamples { diff --git a/core/common/test/samples/InstantSamples.kt b/core/common/test/samples/InstantSamples.kt index 8fec354b7..7e2a483cf 100644 --- a/core/common/test/samples/InstantSamples.kt +++ b/core/common/test/samples/InstantSamples.kt @@ -10,6 +10,10 @@ import kotlinx.datetime.format.* import kotlin.random.* import kotlin.test.* import kotlin.time.Duration.Companion.hours +import kotlinx.time.Clock +import kotlinx.time.Instant +import kotlinx.time.isDistantFuture +import kotlinx.time.isDistantPast class InstantSamples { diff --git a/core/common/test/samples/MonthSamples.kt b/core/common/test/samples/MonthSamples.kt index 70128845a..3efe3d46b 100644 --- a/core/common/test/samples/MonthSamples.kt +++ b/core/common/test/samples/MonthSamples.kt @@ -7,6 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* +import kotlinx.time.Clock class MonthSamples { diff --git a/core/common/test/samples/TimeZoneSamples.kt b/core/common/test/samples/TimeZoneSamples.kt index 71ef13fb6..3456a9599 100644 --- a/core/common/test/samples/TimeZoneSamples.kt +++ b/core/common/test/samples/TimeZoneSamples.kt @@ -8,6 +8,8 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlinx.time.Instant +import kotlinx.time.Clock class TimeZoneSamples { diff --git a/core/common/test/samples/format/DateTimeComponentsSamples.kt b/core/common/test/samples/format/DateTimeComponentsSamples.kt index fbfd5d19c..e2e54a04a 100644 --- a/core/common/test/samples/format/DateTimeComponentsSamples.kt +++ b/core/common/test/samples/format/DateTimeComponentsSamples.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test.samples.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* +import kotlinx.time.Instant class DateTimeComponentsSamples { diff --git a/core/commonJs/src/internal/Platform.kt b/core/commonJs/src/internal/Platform.kt index c5ba347b7..ac759d3b0 100644 --- a/core/commonJs/src/internal/Platform.kt +++ b/core/commonJs/src/internal/Platform.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* import kotlinx.datetime.UtcOffset import kotlinx.datetime.internal.JSJoda.ZoneId +import kotlinx.time.Instant private val tzdb: Result = runCatching { /** @@ -139,10 +140,7 @@ internal fun rulesForId(zoneId: String): TimeZoneRules? = tzdb.getOrThrow()?.rul internal actual fun getAvailableZoneIds(): Set = tzdb.getOrThrow()?.availableTimeZoneIds() ?: setOf("UTC") -internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) - internal external class Date() { constructor(milliseconds: Double) - fun getTime(): Double fun getTimezoneOffset(): Double } diff --git a/core/commonJs/test/JsJodaTimezoneTest.kt b/core/commonJs/test/JsJodaTimezoneTest.kt index 138ed28ac..635fbf749 100644 --- a/core/commonJs/test/JsJodaTimezoneTest.kt +++ b/core/commonJs/test/JsJodaTimezoneTest.kt @@ -12,6 +12,7 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.datetime.test.JSJoda.Instant as jtInstant import kotlinx.datetime.test.JSJoda.ZoneId as jtZoneId +import kotlinx.time.Instant class JsJodaTimezoneTest { @Test diff --git a/core/commonKotlin/src/DeprecatedInstant.kt b/core/commonKotlin/src/DeprecatedInstant.kt new file mode 100644 index 000000000..d15614f32 --- /dev/null +++ b/core/commonKotlin/src/DeprecatedInstant.kt @@ -0,0 +1,355 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantKt") +package kotlinx.datetime + +import kotlinx.datetime.format.* +import kotlinx.datetime.internal.* +import kotlinx.datetime.internal.MILLIS_PER_ONE +import kotlinx.datetime.internal.NANOS_PER_MILLI +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.safeAdd +import kotlinx.datetime.internal.safeMultiply +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +/** + * The minimum supported epoch second. + */ +private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z + +/** + * The maximum supported epoch second. + */ +private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59Z + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public actual class Instant internal constructor( + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public actual val epochSeconds: Long, + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual val nanosecondsOfSecond: Int +) : Comparable { + + init { + require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + // org.threeten.bp.Instant#toEpochMilli + public actual fun toEpochMilliseconds(): Long = try { + if (epochSeconds >= 0) { + val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) + } else { + // prevent an overflow in seconds * 1000 + // instead of going form the second farther away from 0 + // going toward 0 + // we go from the second closer to 0 away from 0 + // that way we always stay in the valid long range + // seconds + 1 can not overflow because it is negative + val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) + safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) + } + } catch (_: ArithmeticException) { + if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE + } + + // org.threeten.bp.Instant#plus(long, long) + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { + if ((secondsToAdd or nanosToAdd) == 0L) { + return this + } + val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) + val newNanosToAdd = nanosToAdd % NANOS_PER_ONE + val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE + return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public actual operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> + try { + plus(secondsToAdd, nanosecondsToAdd.toLong()) + } catch (_: IllegalArgumentException) { + if (duration.isPositive()) MAX else MIN + } catch (_: ArithmeticException) { + if (duration.isPositive()) MAX else MIN + } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public actual operator fun minus(duration: Duration): Instant = plus(-duration) + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public actual operator fun minus(other: Instant): Duration = + (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds + (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + actual override fun compareTo(other: Instant): Int { + val s = epochSeconds.compareTo(other.epochSeconds) + if (s != 0) { + return s + } + return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) + } + + override fun equals(other: Any?): Boolean = + this === other || other is Instant && this.epochSeconds == other.epochSeconds && this.nanosecondsOfSecond == other.nanosecondsOfSecond + + // org.threeten.bp.Instant#hashCode + override fun hashCode(): Int = + (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond + + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + // org.threeten.bp.format.DateTimeFormatterBuilder.InstantPrinterParser#print + actual override fun toString(): String = format(ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS) + + public actual companion object { + internal actual val MIN = Instant(MIN_SECOND, 0) + internal actual val MAX = Instant(MAX_SECOND, 999_999_999) + + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) + public actual fun now(): Instant = Clock.System.now() + + // org.threeten.bp.Instant#ofEpochMilli + public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { + val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) + val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() + return when { + epochSeconds < MIN_SECOND -> MIN + epochSeconds > MAX_SECOND -> MAX + else -> fromEpochSeconds(epochSeconds, nanosecondsOfSecond) + } + } + + /** + * @throws ArithmeticException if arithmetic overflow occurs + * @throws IllegalArgumentException if the boundaries of Instant are overflown + */ + private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { + val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) + val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() + return Instant(secs, nos) + } + + // org.threeten.bp.Instant#ofEpochSecond(long, long) + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = + try { + fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) + } catch (_: ArithmeticException) { + if (epochSeconds > 0) MAX else MIN + } catch (_: IllegalArgumentException) { + if (epochSeconds > 0) MAX else MIN + } + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + + public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { + format.parse(input).toInstantUsingOffset().toDeprecatedInstant() + } catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) + } + + @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) + public fun parse(isoString: String): Instant = parse(input = isoString) + + public actual val DISTANT_PAST: Instant = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) + + public actual val DISTANT_FUTURE: Instant = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) + } + +} + +private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try { + toZonedDateTime(zone) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e) +} + +/** + * @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime] + */ +private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime { + val currentOffset = zone.offsetAtImpl(this.toStdlibInstant()) + return ZonedDateTime(toStdlibInstant().toLocalDateTimeImpl(currentOffset), zone, currentOffset) +} + +/** Check that [Instant] fits in [ZonedDateTime]. + * This is done on the results of computations for consistency with other platforms. + */ +private fun Instant.check(zone: TimeZone): Instant = this@check.also { + toZonedDateTimeFailing(zone) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try { + with(period) { + val withDate = toZonedDateTimeFailing(timeZone) + .run { if (totalMonths != 0L) plus(totalMonths, DateTimeUnit.MONTH) else this } + .run { if (days != 0) plus(days.toLong(), DateTimeUnit.DAY) else this } + withDate.toDeprecatedInstant() + .run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this } + }.check(timeZone) +} catch (e: ArithmeticException) { + throw DateTimeArithmeticException("Arithmetic overflow when adding CalendarPeriod to an Instant", e) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Boundaries of Instant exceeded when adding CalendarPeriod", e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(1L, unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(value.toLong(), unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-value.toLong(), unit, timeZone) +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = try { + when (unit) { + is DateTimeUnit.DateBased -> + toZonedDateTimeFailing(timeZone).plus(value, unit).toDeprecatedInstant() + is DateTimeUnit.TimeBased -> + check(timeZone).plus(value, unit).check(timeZone) + } +} catch (e: ArithmeticException) { + throw DateTimeArithmeticException("Arithmetic overflow when adding to an Instant", e) +} catch (e: IllegalArgumentException) { + throw DateTimeArithmeticException("Boundaries of Instant exceeded when adding a value", e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + try { + multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (seconds, nanoseconds) -> + plus(seconds, nanoseconds) + } + } catch (_: ArithmeticException) { + if (value > 0) Instant.MAX else Instant.MIN + } catch (_: IllegalArgumentException) { + if (value > 0) Instant.MAX else Instant.MIN + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { + var thisLdt = toZonedDateTimeFailing(timeZone) + val otherLdt = other.toZonedDateTimeFailing(timeZone) + + val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH) // `until` on dates never fails + thisLdt = thisLdt.plus(months, DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid + val days = thisLdt.until(otherLdt, DateTimeUnit.DAY) // `until` on dates never fails + thisLdt = thisLdt.plus(days, DateTimeUnit.DAY) // won't throw: thisLdt + days <= otherLdt + val nanoseconds = thisLdt.until(otherLdt, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h + + return buildDateTimePeriod(months, days.toInt(), nanoseconds) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = + when (unit) { + is DateTimeUnit.DateBased -> + toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit) + .toLong() + is DateTimeUnit.TimeBased -> { + check(timeZone); other.check(timeZone) + until(other, unit) + } + } + +private fun ZonedDateTime.toDeprecatedInstant(): Instant = + Instant.fromEpochSeconds(dateTime.toEpochSecond(offset), dateTime.nanosecond) + +private val ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS = DateTimeComponents.Format { + date(ISO_DATE) + alternativeParsing({ + char('t') + }) { + char('T') + } + hour() + char(':') + minute() + char(':') + second() + optional { + char('.') + secondFractionInternal(1, 9, FractionalSecondDirective.GROUP_BY_THREE) + } + isoOffset( + zOnZero = true, + useSeparator = true, + outputMinute = WhenToOutput.IF_NONZERO, + outputSecond = WhenToOutput.IF_NONZERO + ) +} diff --git a/core/commonKotlin/src/Instant.kt b/core/commonKotlin/src/Instant.kt index b4cff744c..ce922bddd 100644 --- a/core/commonKotlin/src/Instant.kt +++ b/core/commonKotlin/src/Instant.kt @@ -8,152 +8,11 @@ package kotlinx.datetime -import kotlinx.datetime.format.* +import kotlinx.time.Instant import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.serialization.Serializable -import kotlin.time.* import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -/** - * The minimum supported epoch second. - */ -private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z - -/** - * The maximum supported epoch second. - */ -private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59 - -@Serializable(with = InstantIso8601Serializer::class) -public actual class Instant internal constructor(public actual val epochSeconds: Long, public actual val nanosecondsOfSecond: Int) : Comparable { - - init { - require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } - } - - // org.threeten.bp.Instant#toEpochMilli - public actual fun toEpochMilliseconds(): Long = try { - if (epochSeconds >= 0) { - val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) - } else { - // prevent an overflow in seconds * 1000 - // instead of going form the second farther away from 0 - // going toward 0 - // we go from the second closer to 0 away from 0 - // that way we always stay in the valid long range - // seconds + 1 can not overflow because it is negative - val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) - } - } catch (_: ArithmeticException) { - if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE - } - - // org.threeten.bp.Instant#plus(long, long) - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { - if ((secondsToAdd or nanosToAdd) == 0L) { - return this - } - val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) - val newNanosToAdd = nanosToAdd % NANOS_PER_ONE - val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE - return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) - } - - public actual operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> - try { - plus(secondsToAdd, nanosecondsToAdd.toLong()) - } catch (_: IllegalArgumentException) { - if (duration.isPositive()) MAX else MIN - } catch (_: ArithmeticException) { - if (duration.isPositive()) MAX else MIN - } - } - - public actual operator fun minus(duration: Duration): Instant = plus(-duration) - - public actual operator fun minus(other: Instant): Duration = - (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds - (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds - - actual override fun compareTo(other: Instant): Int { - val s = epochSeconds.compareTo(other.epochSeconds) - if (s != 0) { - return s - } - return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) - } - - override fun equals(other: Any?): Boolean = - this === other || other is Instant && this.epochSeconds == other.epochSeconds && this.nanosecondsOfSecond == other.nanosecondsOfSecond - - // org.threeten.bp.Instant#hashCode - override fun hashCode(): Int = - (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond - - // org.threeten.bp.format.DateTimeFormatterBuilder.InstantPrinterParser#print - actual override fun toString(): String = format(ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS) - - public actual companion object { - internal actual val MIN = Instant(MIN_SECOND, 0) - internal actual val MAX = Instant(MAX_SECOND, 999_999_999) - - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public actual fun now(): Instant = currentTime() - - // org.threeten.bp.Instant#ofEpochMilli - public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { - val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) - val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() - return fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { - val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) - val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() - return Instant(secs, nos) - } - - // org.threeten.bp.Instant#ofEpochSecond(long, long) - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = - try { - fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) - } catch (_: ArithmeticException) { - if (epochSeconds > 0) MAX else MIN - } catch (_: IllegalArgumentException) { - if (epochSeconds > 0) MAX else MIN - } - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = - fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) - - public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { - format.parse(input).toInstantUsingOffset() - } catch (e: IllegalArgumentException) { - throw DateTimeFormatException("Failed to parse an instant from '$input'", e) - } - - @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) - public fun parse(isoString: String): Instant = parse(input = isoString) - - public actual val DISTANT_PAST: Instant = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) - - public actual val DISTANT_FUTURE: Instant = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) - } - -} - private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try { toZonedDateTime(zone) } catch (e: IllegalArgumentException) { @@ -181,7 +40,14 @@ public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Inst .run { if (totalMonths != 0L) plus(totalMonths, DateTimeUnit.MONTH) else this } .run { if (days != 0) plus(days.toLong(), DateTimeUnit.DAY) else this } withDate.toInstant() - .run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this } + .run { + if (totalNanoseconds != 0L) + // we don't add nanoseconds directly, as `totalNanoseconds.nanoseconds` can hit `Duration.INFINITE` + plus((totalNanoseconds / NANOS_PER_ONE).seconds) + .plus((totalNanoseconds % NANOS_PER_ONE).nanoseconds) + .check(timeZone) + else this + } }.check(timeZone) } catch (e: ArithmeticException) { throw DateTimeArithmeticException("Arithmetic overflow when adding CalendarPeriod to an Instant", e) @@ -212,12 +78,12 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = try { multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (seconds, nanoseconds) -> - plus(seconds, nanoseconds) + plus(seconds.seconds).plus(nanoseconds.nanoseconds) } } catch (_: ArithmeticException) { - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } catch (_: IllegalArgumentException) { - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { @@ -243,27 +109,3 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti until(other, unit) } } - -private val ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS = DateTimeComponents.Format { - date(ISO_DATE) - alternativeParsing({ - char('t') - }) { - char('T') - } - hour() - char(':') - minute() - char(':') - second() - optional { - char('.') - secondFractionInternal(1, 9, FractionalSecondDirective.GROUP_BY_THREE) - } - isoOffset( - zOnZero = true, - useSeparator = true, - outputMinute = WhenToOutput.IF_NONZERO, - outputSecond = WhenToOutput.IF_NONZERO - ) -} diff --git a/core/commonKotlin/src/TimeZone.kt b/core/commonKotlin/src/TimeZone.kt index 4aa6aa5e3..63e56ff6d 100644 --- a/core/commonKotlin/src/TimeZone.kt +++ b/core/commonKotlin/src/TimeZone.kt @@ -12,6 +12,7 @@ import kotlinx.datetime.format.* import kotlinx.datetime.internal.* import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable +import kotlinx.time.Instant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor() { @@ -80,7 +81,24 @@ public actual open class TimeZone internal constructor() { get() = error("Should be overridden") public actual fun Instant.toLocalDateTime(): LocalDateTime = instantToLocalDateTime(this) - public actual fun LocalDateTime.toInstant(): Instant = localDateTimeToInstant(this) + + @Suppress("DEPRECATION_ERROR") + public actual fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker): Instant = + localDateTimeToInstant(this) + + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public actual fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime = + toStdlibInstant().toLocalDateTime() + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal actual fun LocalDateTime.toInstant(): kotlinx.datetime.Instant = + toInstant(this@TimeZone).toDeprecatedInstant() internal open fun atStartOfDay(date: LocalDate): Instant = error("Should be overridden") //value.atStartOfDay(date) internal open fun offsetAtImpl(instant: Instant): UtcOffset = error("Should be overridden") @@ -147,13 +165,16 @@ internal fun Instant.toLocalDateTimeImpl(offset: UtcOffset): LocalDateTime { return LocalDateTime(date, time) } -public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant = +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = timeZone.localDateTimeToInstant(this) -public actual fun LocalDateTime.toInstant(offset: UtcOffset): Instant = - Instant(this.toEpochSecond(offset), this.nanosecond) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker): Instant = + Instant.fromEpochSeconds(this.toEpochSecond(offset), this.nanosecond) -public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant = +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = timeZone.atStartOfDay(this) private val lenientOffsetFormat = UtcOffsetFormat.build { diff --git a/core/commonKotlin/src/ZonedDateTime.kt b/core/commonKotlin/src/ZonedDateTime.kt index effd5acd0..c10011ded 100644 --- a/core/commonKotlin/src/ZonedDateTime.kt +++ b/core/commonKotlin/src/ZonedDateTime.kt @@ -8,6 +8,8 @@ package kotlinx.datetime +import kotlinx.time.Instant + internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: TimeZone, val offset: UtcOffset) { /** * @throws IllegalArgumentException if the result exceeds the boundaries @@ -46,7 +48,7 @@ internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: Time } internal fun ZonedDateTime.toInstant(): Instant = - Instant(dateTime.toEpochSecond(offset), dateTime.nanosecond) + Instant.fromEpochSeconds(dateTime.toEpochSecond(offset), dateTime.nanosecond) // org.threeten.bp.ZonedDateTime#until diff --git a/core/commonKotlin/src/internal/MonthDayTime.kt b/core/commonKotlin/src/internal/MonthDayTime.kt index 287dc9e5f..287021b4e 100644 --- a/core/commonKotlin/src/internal/MonthDayTime.kt +++ b/core/commonKotlin/src/internal/MonthDayTime.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlinx.time.Instant /** * A rule expressing how to create a date in a given year. diff --git a/core/commonKotlin/src/internal/OffsetInfo.kt b/core/commonKotlin/src/internal/OffsetInfo.kt index a89946da8..4685475dc 100644 --- a/core/commonKotlin/src/internal/OffsetInfo.kt +++ b/core/commonKotlin/src/internal/OffsetInfo.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlinx.time.Instant internal sealed interface OffsetInfo { data class Gap( @@ -43,4 +44,3 @@ internal fun OffsetInfo(transitionInstant: Instant, offsetBefore: UtcOffset, off } else { OffsetInfo.Overlap(transitionInstant, offsetBefore, offsetAfter) } - diff --git a/core/commonKotlin/src/internal/Platform.kt b/core/commonKotlin/src/internal/Platform.kt index 8227af253..d941eeded 100644 --- a/core/commonKotlin/src/internal/Platform.kt +++ b/core/commonKotlin/src/internal/Platform.kt @@ -5,7 +5,6 @@ package kotlinx.datetime.internal -import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone internal expect fun timeZoneById(zoneId: String): TimeZone @@ -13,5 +12,3 @@ internal expect fun timeZoneById(zoneId: String): TimeZone internal expect fun getAvailableZoneIds(): Set internal expect fun currentSystemDefaultZone(): Pair - -internal expect fun currentTime(): Instant diff --git a/core/commonKotlin/src/internal/RegionTimeZone.kt b/core/commonKotlin/src/internal/RegionTimeZone.kt index d4e7bc6b6..9bec9e27f 100644 --- a/core/commonKotlin/src/internal/RegionTimeZone.kt +++ b/core/commonKotlin/src/internal/RegionTimeZone.kt @@ -6,6 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* +import kotlinx.time.Instant internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id: String) : TimeZone() { diff --git a/core/commonKotlin/src/internal/TimeZoneRules.kt b/core/commonKotlin/src/internal/TimeZoneRules.kt index 0d56aceb7..0c9bd948a 100644 --- a/core/commonKotlin/src/internal/TimeZoneRules.kt +++ b/core/commonKotlin/src/internal/TimeZoneRules.kt @@ -5,8 +5,11 @@ package kotlinx.datetime.internal -import kotlinx.datetime.* +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.UtcOffset +import kotlinx.datetime.toLocalDateTime import kotlin.math.* +import kotlinx.time.Instant internal class TimeZoneRules( /** diff --git a/core/commonKotlin/test/ThreeTenBpInstantTest.kt b/core/commonKotlin/test/ThreeTenBpInstantTest.kt deleted file mode 100644 index baf538cef..000000000 --- a/core/commonKotlin/test/ThreeTenBpInstantTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -/* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - -package kotlinx.datetime.test - -import kotlinx.datetime.* -import kotlin.test.* - -class ThreeTenBpInstantTest { - - @Test - fun instantComparisons() { - 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(true, test5a == test5a) - assertEquals(true, test5a == test5b) - assertEquals(false, test5a == test5n) - assertEquals(false, test5a == test6) - assertEquals(true, test5b == test5a) - assertEquals(true, test5b == test5b) - assertEquals(false, test5b == test5n) - assertEquals(false, test5b == test6) - assertEquals(false, test5n == test5a) - assertEquals(false, test5n == test5b) - assertEquals(true, test5n == test5n) - assertEquals(false, test5n == test6) - assertEquals(false, test6 == test5a) - assertEquals(false, test6 == test5b) - assertEquals(false, test6 == test5n) - assertEquals(true, 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) - } -} diff --git a/core/commonKotlin/test/TimeZoneRulesTest.kt b/core/commonKotlin/test/TimeZoneRulesTest.kt index c4666a62b..03742b2d7 100644 --- a/core/commonKotlin/test/TimeZoneRulesTest.kt +++ b/core/commonKotlin/test/TimeZoneRulesTest.kt @@ -8,6 +8,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* import kotlin.test.* +import kotlinx.time.Instant class TimeZoneRulesTest { @Test diff --git a/core/darwin/src/Converters.kt b/core/darwin/src/Converters.kt index cf785dead..a37d2664f 100644 --- a/core/darwin/src/Converters.kt +++ b/core/darwin/src/Converters.kt @@ -10,6 +10,7 @@ package kotlinx.datetime import kotlinx.cinterop.* import kotlinx.datetime.internal.NANOS_PER_ONE import platform.Foundation.* +import kotlinx.time.Instant /** * Converts the [Instant] to an instance of [NSDate]. @@ -25,6 +26,10 @@ public fun Instant.toNSDate(): NSDate { return NSDate.dateWithTimeIntervalSince1970(secs) } +@PublishedApi +@Suppress("DEPRECATION_ERROR") +internal fun kotlinx.datetime.Instant.toNSDate(): NSDate = toStdlibInstant().toNSDate() + /** * Converts the [NSDate] to the corresponding [Instant]. * @@ -33,13 +38,20 @@ public fun Instant.toNSDate(): NSDate { * For example, if the [NSDate] only has millisecond or microsecond precision logically, * due to conversion artifacts in [Double] values, the result may include non-zero nanoseconds. */ -public fun NSDate.toKotlinInstant(): Instant { +@Suppress("DEPRECATION_ERROR") +public fun NSDate.toKotlinInstant(youShallNotPass: OverloadMarker = OverloadMarker.INSTANCE): Instant { val secs = timeIntervalSince1970() val fullSeconds = secs.toLong() val nanos = (secs - fullSeconds) * NANOS_PER_ONE return Instant.fromEpochSeconds(fullSeconds, nanos.toLong()) } +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun NSDate.toKotlinInstant(): kotlinx.datetime.Instant = + toKotlinInstant().toDeprecatedInstant() + /** * Converts the [TimeZone] to [NSTimeZone]. * diff --git a/core/darwin/test/ConvertersTest.kt b/core/darwin/test/ConvertersTest.kt index 9efe07750..38ccb959c 100644 --- a/core/darwin/test/ConvertersTest.kt +++ b/core/darwin/test/ConvertersTest.kt @@ -11,6 +11,8 @@ import platform.Foundation.* import kotlin.math.* import kotlin.random.* import kotlin.test.* +import kotlinx.time.Clock +import kotlinx.time.Instant class ConvertersTest { diff --git a/core/js/src/Converters.kt b/core/js/src/DeprecatedConverters.kt similarity index 95% rename from core/js/src/Converters.kt rename to core/js/src/DeprecatedConverters.kt index a4a8c9df9..7ac3e9fbd 100644 --- a/core/js/src/Converters.kt +++ b/core/js/src/DeprecatedConverters.kt @@ -3,6 +3,7 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime import kotlin.js.* diff --git a/core/js/test/JsConverterTest.kt b/core/js/test/DeprecatedJsConverterTest.kt similarity index 93% rename from core/js/test/JsConverterTest.kt rename to core/js/test/DeprecatedJsConverterTest.kt index de5eeb6f3..3e34d673c 100644 --- a/core/js/test/JsConverterTest.kt +++ b/core/js/test/DeprecatedJsConverterTest.kt @@ -3,13 +3,14 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.js.* import kotlin.test.* -class JsConverterTest { +class DeprecatedJsConverterTest { @Test fun toJSDateTest() { val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") diff --git a/core/jvm/src/Converters.kt b/core/jvm/src/Converters.kt index 688b0f84a..029f4cb3a 100644 --- a/core/jvm/src/Converters.kt +++ b/core/jvm/src/Converters.kt @@ -8,12 +8,17 @@ package kotlinx.datetime /** * Converts this [kotlinx.datetime.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. */ -public fun Instant.toJavaInstant(): java.time.Instant = this.value +@PublishedApi +@Suppress("DEPRECATION") +internal fun Instant.toJavaInstant(): java.time.Instant = this.value /** * Converts this [java.time.Instant][java.time.Instant] value to a [kotlinx.datetime.Instant][Instant] value. */ -public fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) +@PublishedApi +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") +@kotlin.internal.LowPriorityInOverloadResolution +internal fun java.time.Instant.toKotlinInstant(): Instant = Instant(this) /** diff --git a/core/jvm/src/DeprecatedInstant.kt b/core/jvm/src/DeprecatedInstant.kt new file mode 100644 index 000000000..7466a77d0 --- /dev/null +++ b/core/jvm/src/DeprecatedInstant.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:Suppress("DEPRECATION") +@file:JvmMultifileClass +@file:JvmName("InstantJvmKt") +package kotlinx.datetime + +import kotlinx.datetime.format.DateTimeComponents +import kotlinx.datetime.format.DateTimeFormat +import kotlinx.datetime.internal.NANOS_PER_ONE +import kotlinx.datetime.internal.multiplyAndDivide +import kotlinx.datetime.internal.safeMultiply +import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.serialization.Serializable +import java.time.Clock +import java.time.DateTimeException +import java.time.temporal.ChronoUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +@Deprecated( + "Use kotlin.time.Instant instead", + ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + level = DeprecationLevel.WARNING +) +@Serializable(with = InstantIso8601Serializer::class) +public actual class Instant internal constructor(internal val value: java.time.Instant) : Comparable { + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().epochSeconds") + ) + public actual val epochSeconds: Long + get() = value.epochSecond + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual val nanosecondsOfSecond: Int + get() = value.nano + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().nanosecondsOfSecond") + ) + public actual fun toEpochMilliseconds(): Long = try { + value.toEpochMilli() + } catch (e: ArithmeticException) { + if (value.isAfter(java.time.Instant.EPOCH)) Long.MAX_VALUE else Long.MIN_VALUE + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() + duration).toDeprecatedInstant()") + ) + public actual operator fun plus(duration: Duration): Instant = duration.toComponents { seconds, nanoseconds -> + try { + Instant(value.plusSeconds(seconds).plusNanos(nanoseconds.toLong())) + } catch (e: java.lang.Exception) { + if (e !is ArithmeticException && e !is DateTimeException) throw e + if (duration.isPositive()) MAX else MIN + } + } + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("(this.toStdlibInstant() - duration).toDeprecatedInstant()") + ) + public actual operator fun minus(duration: Duration): Instant = plus(-duration) + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant() - other.toStdlibInstant()") + ) + public actual operator fun minus(other: Instant): Duration = + (this.value.epochSecond - other.value.epochSecond).seconds + // won't overflow given the instant bounds + (this.value.nano - other.value.nano).nanoseconds + + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().compareTo(other.toStdlibInstant())") + ) + public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value) + + override fun equals(other: Any?): Boolean = + (this === other) || (other is Instant && this.value == other.value) + + override fun hashCode(): Int = value.hashCode() + + @Suppress("POTENTIALLY_NON_REPORTED_ANNOTATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toString()") + ) + actual override fun toString(): String = value.toString() + + public actual companion object { + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.time.Clock"), level = DeprecationLevel.ERROR) + public actual fun now(): Instant = + Instant(Clock.systemUTC().instant()) + + public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant = + Instant(java.time.Instant.ofEpochMilli(epochMilliseconds)) + + // TODO: implement a custom parser to 1) help DCE get rid of the formatting machinery 2) move Instant to stdlib + public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { + /** + * Can't use built-in Java Time's handling of `Instant.parse` because it supports 24:00:00 and + * 23:59:60, and also doesn't support non-`Z` UTC offsets on older JDKs. + * Can't use custom Java Time's formats because Java 8 doesn't support the UTC offset format with + * optional minutes and seconds and `:` between them: + * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendOffset-java.lang.String-java.lang.String- + */ + format.parse(input).toInstantUsingOffset().toDeprecatedInstant() + } catch (e: IllegalArgumentException) { + throw DateTimeFormatException("Failed to parse an instant from '$input'", e) + } + + @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) + public fun parse(isoString: String): Instant = parse(input = isoString) + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { + Instant(java.time.Instant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) + } catch (e: Exception) { + if (e !is ArithmeticException && e !is DateTimeException) throw e + if (epochSeconds > 0) MAX else MIN + } + + public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + + public actual val DISTANT_PAST: Instant = Instant(java.time.Instant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) + public actual val DISTANT_FUTURE: Instant = Instant(java.time.Instant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) + + internal actual val MIN: Instant = Instant(java.time.Instant.MIN) + internal actual val MAX: Instant = Instant(java.time.Instant.MAX) + } +} + +private fun Instant.atZone(zone: TimeZone): java.time.ZonedDateTime = try { + value.atZone(zone.zoneId) +} catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(period, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant { + try { + val thisZdt = atZone(timeZone) + return with(period) { + thisZdt + .run { if (totalMonths != 0L) plusMonths(totalMonths) else this } + .run { if (days != 0) plusDays(days.toLong()) else this } + .run { if (totalNanoseconds != 0L) plusNanos(totalNanoseconds) else this } + }.toInstant().let(::Instant) + } catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) + } +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(1, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(1L, unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(value.toLong(), unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().minus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant = + plus(-value.toLong(), unit, timeZone) + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit, timeZone).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = + try { + val thisZdt = atZone(timeZone) + when (unit) { + is DateTimeUnit.TimeBased -> + plus(value, unit).value.also { it.atZone(timeZone.zoneId) } + is DateTimeUnit.DayBased -> + thisZdt.plusDays(safeMultiply(value, unit.days.toLong())).toInstant() + is DateTimeUnit.MonthBased -> + thisZdt.plusMonths(safeMultiply(value, unit.months.toLong())).toInstant() + }.let(::Instant) + } catch (e: Exception) { + if (e !is DateTimeException && e !is ArithmeticException) throw e + throw DateTimeArithmeticException("Instant $this cannot be represented as local date when adding $value $unit to it", e) + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().plus(value, unit).toDeprecatedInstant()") +) +public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = + try { + multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (d, r) -> + Instant(this.value.plusSeconds(d).plusNanos(r)) + } + } catch (e: Exception) { + if (e !is DateTimeException && e !is ArithmeticException) throw e + if (value > 0) Instant.MAX else Instant.MIN + } + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().periodUntil(other.toStdlibInstant(), timeZone)") +) +public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { + var thisZdt = this.atZone(timeZone) + val otherZdt = other.atZone(timeZone) + + val months = thisZdt.until(otherZdt, ChronoUnit.MONTHS); thisZdt = thisZdt.plusMonths(months) + val days = thisZdt.until(otherZdt, ChronoUnit.DAYS); thisZdt = thisZdt.plusDays(days) + val nanoseconds = thisZdt.until(otherZdt, ChronoUnit.NANOS) + + return buildDateTimePeriod(months, days.toInt(), nanoseconds) +} + +@Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().until(other.toStdlibInstant(), unit, timeZone)") +) +public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = try { + val thisZdt = this.atZone(timeZone) + val otherZdt = other.atZone(timeZone) + when(unit) { + is DateTimeUnit.TimeBased -> until(other, unit) + is DateTimeUnit.DayBased -> thisZdt.until(otherZdt, ChronoUnit.DAYS) / unit.days + is DateTimeUnit.MonthBased -> thisZdt.until(otherZdt, ChronoUnit.MONTHS) / unit.months + } +} catch (e: DateTimeException) { + throw DateTimeArithmeticException(e) +} catch (e: ArithmeticException) { + if (this.value < other.value) Long.MAX_VALUE else Long.MIN_VALUE +} diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 78f82ed8b..69988c086 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -2,108 +2,23 @@ * Copyright 2019-2020 JetBrains s.r.o. * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:JvmMultifileClass @file:JvmName("InstantJvmKt") package kotlinx.datetime -import kotlinx.datetime.format.* import kotlinx.datetime.internal.safeMultiply import kotlinx.datetime.internal.* -import kotlinx.datetime.serializers.InstantIso8601Serializer -import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.temporal.* -import kotlin.time.* +import kotlinx.time.Instant +import kotlinx.time.toJavaInstant +import kotlinx.time.toKotlinInstant import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -import java.time.Instant as jtInstant -import java.time.Clock as jtClock - -@Serializable(with = InstantIso8601Serializer::class) -public actual class Instant internal constructor( - internal val value: jtInstant -) : Comparable { - - public actual val epochSeconds: Long - get() = value.epochSecond - public actual val nanosecondsOfSecond: Int - get() = value.nano - - public actual fun toEpochMilliseconds(): Long = try { - value.toEpochMilli() - } catch (e: ArithmeticException) { - if (value.isAfter(java.time.Instant.EPOCH)) Long.MAX_VALUE else Long.MIN_VALUE - } - - public actual operator fun plus(duration: Duration): Instant = duration.toComponents { seconds, nanoseconds -> - try { - Instant(value.plusSeconds(seconds).plusNanos(nanoseconds.toLong())) - } catch (e: java.lang.Exception) { - if (e !is ArithmeticException && e !is DateTimeException) throw e - if (duration.isPositive()) MAX else MIN - } - } - - public actual operator fun minus(duration: Duration): Instant = plus(-duration) - - public actual operator fun minus(other: Instant): Duration = - (this.value.epochSecond - other.value.epochSecond).seconds + // won't overflow given the instant bounds - (this.value.nano - other.value.nano).nanoseconds - - public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value) - - override fun equals(other: Any?): Boolean = - (this === other) || (other is Instant && this.value == other.value) - - override fun hashCode(): Int = value.hashCode() - - actual override fun toString(): String = value.toString() - - public actual companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public actual fun now(): Instant = - Instant(jtClock.systemUTC().instant()) - - public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant = - Instant(jtInstant.ofEpochMilli(epochMilliseconds)) - - // TODO: implement a custom parser to 1) help DCE get rid of the formatting machinery 2) move Instant to stdlib - public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { - /** - * Can't use built-in Java Time's handling of `Instant.parse` because it supports 24:00:00 and - * 23:59:60, and also doesn't support non-`Z` UTC offsets on older JDKs. - * Can't use custom Java Time's formats because Java 8 doesn't support the UTC offset format with - * optional minutes and seconds and `:` between them: - * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendOffset-java.lang.String-java.lang.String- - */ - format.parse(input).toInstantUsingOffset() - } catch (e: IllegalArgumentException) { - throw DateTimeFormatException("Failed to parse an instant from '$input'", e) - } - - @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) - public fun parse(isoString: String): Instant = parse(input = isoString) - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { - Instant(jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) - } catch (e: Exception) { - if (e !is ArithmeticException && e !is DateTimeException) throw e - if (epochSeconds > 0) MAX else MIN - } - - public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = - fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) - - public actual val DISTANT_PAST: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) - public actual val DISTANT_FUTURE: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) - - internal actual val MIN: Instant = Instant(jtInstant.MIN) - internal actual val MAX: Instant = Instant(jtInstant.MAX) - } -} private fun Instant.atZone(zone: TimeZone): java.time.ZonedDateTime = try { - value.atZone(zone.zoneId) + toJavaInstant().atZone(zone.zoneId) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } @@ -116,7 +31,7 @@ public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Inst .run { if (totalMonths != 0L) plusMonths(totalMonths) else this } .run { if (days != 0) plusDays(days.toLong()) else this } .run { if (totalNanoseconds != 0L) plusNanos(totalNanoseconds) else this } - }.toInstant().let(::Instant) + }.toInstant().toKotlinInstant() } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } @@ -137,12 +52,12 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo val thisZdt = atZone(timeZone) when (unit) { is DateTimeUnit.TimeBased -> - plus(value, unit).value.also { it.atZone(timeZone.zoneId) } + plus(value, unit).toJavaInstant().also { it.atZone(timeZone.zoneId) } is DateTimeUnit.DayBased -> thisZdt.plusDays(safeMultiply(value, unit.days.toLong())).toInstant() is DateTimeUnit.MonthBased -> thisZdt.plusMonths(safeMultiply(value, unit.months.toLong())).toInstant() - }.let(::Instant) + }.toKotlinInstant() } catch (e: Exception) { if (e !is DateTimeException && e !is ArithmeticException) throw e throw DateTimeArithmeticException("Instant $this cannot be represented as local date when adding $value $unit to it", e) @@ -151,11 +66,11 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Instant = try { multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE.toLong()).let { (d, r) -> - Instant(this.value.plusSeconds(d).plusNanos(r)) + this.plus(d.seconds).plus(r.nanoseconds) } } catch (e: Exception) { if (e !is DateTimeException && e !is ArithmeticException) throw e - if (value > 0) Instant.MAX else Instant.MIN + Instant.fromEpochSeconds(if (value > 0) Long.MAX_VALUE else Long.MIN_VALUE) } public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod { @@ -180,5 +95,5 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } catch (e: ArithmeticException) { - if (this.value < other.value) Long.MAX_VALUE else Long.MIN_VALUE + if (this < other) Long.MAX_VALUE else Long.MIN_VALUE } diff --git a/core/jvm/src/TimeZoneJvm.kt b/core/jvm/src/TimeZoneJvm.kt index cd90993ef..60490e53c 100644 --- a/core/jvm/src/TimeZoneJvm.kt +++ b/core/jvm/src/TimeZoneJvm.kt @@ -13,6 +13,9 @@ import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.ZoneId import java.time.ZoneOffset as jtZoneOffset +import kotlinx.time.Instant +import kotlinx.time.toJavaInstant +import kotlinx.time.toKotlinInstant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { @@ -21,7 +24,23 @@ public actual open class TimeZone internal constructor(internal val zoneId: Zone // experimental member-extensions public actual fun Instant.toLocalDateTime(): LocalDateTime = toLocalDateTime(this@TimeZone) - public actual fun LocalDateTime.toInstant(): Instant = toInstant(this@TimeZone) + + @Suppress("DEPRECATION_ERROR") + public actual fun LocalDateTime.toInstant(youShallNotPass: OverloadMarker): Instant = toInstant(this@TimeZone) + + @Suppress("DEPRECATION") + @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this.toStdlibInstant().toLocalDateTime()") + ) + public actual fun kotlinx.datetime.Instant.toLocalDateTime(): LocalDateTime = + toStdlibInstant().toLocalDateTime() + + @PublishedApi + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION") + @kotlin.internal.LowPriorityInOverloadResolution + internal actual fun LocalDateTime.toInstant(): kotlinx.datetime.Instant = + toInstant(this@TimeZone).toDeprecatedInstant() actual override fun equals(other: Any?): Boolean = (this === other) || (other is TimeZone && this.zoneId == other.zoneId) @@ -74,26 +93,29 @@ internal constructor(public actual val offset: UtcOffset, zoneId: ZoneId): TimeZ } public actual fun TimeZone.offsetAt(instant: Instant): UtcOffset = - zoneId.rules.getOffset(instant.value).let(::UtcOffset) + zoneId.rules.getOffset(instant.toJavaInstant()).let(::UtcOffset) public actual fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime = try { - java.time.LocalDateTime.ofInstant(this.value, timeZone.zoneId).let(::LocalDateTime) + java.time.LocalDateTime.ofInstant(this.toJavaInstant(), timeZone.zoneId).let(::LocalDateTime) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } internal actual fun Instant.toLocalDateTime(offset: UtcOffset): LocalDateTime = try { - java.time.LocalDateTime.ofInstant(this.value, offset.zoneOffset).let(::LocalDateTime) + java.time.LocalDateTime.ofInstant(this.toJavaInstant(), offset.zoneOffset).let(::LocalDateTime) } catch (e: DateTimeException) { throw DateTimeArithmeticException(e) } -public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant = - this.value.atZone(timeZone.zoneId).toInstant().let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = + this.value.atZone(timeZone.zoneId).toInstant().toKotlinInstant() -public actual fun LocalDateTime.toInstant(offset: UtcOffset): Instant = - this.value.toInstant(offset.zoneOffset).let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDateTime.toInstant(offset: UtcOffset, youShallNotPass: OverloadMarker): Instant = + this.value.toInstant(offset.zoneOffset).toKotlinInstant() -public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant = - this.value.atStartOfDay(timeZone.zoneId).toInstant().let(::Instant) +@Suppress("DEPRECATION_ERROR") +public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone, youShallNotPass: OverloadMarker): Instant = + this.value.atStartOfDay(timeZone.zoneId).toInstant().toKotlinInstant() diff --git a/core/jvm/test/ConvertersTest.kt b/core/jvm/test/ConvertersTest.kt index 06b7e2239..2d54ab7fa 100644 --- a/core/jvm/test/ConvertersTest.kt +++ b/core/jvm/test/ConvertersTest.kt @@ -14,6 +14,8 @@ import java.time.LocalDate as JTLocalDate import java.time.Period as JTPeriod import java.time.ZoneId import java.time.ZoneOffset as JTZoneOffset +import kotlinx.time.Instant +import kotlinx.time.* class ConvertersTest { diff --git a/core/jvm/test/InstantParsing.kt b/core/jvm/test/InstantParsing.kt index 4c29b81e9..aac16b775 100644 --- a/core/jvm/test/InstantParsing.kt +++ b/core/jvm/test/InstantParsing.kt @@ -2,6 +2,8 @@ package kotlinx.datetime import kotlinx.datetime.format.* import kotlin.test.* +import kotlinx.time.Instant +import kotlinx.time.* class InstantParsing { @Test diff --git a/core/native/src/internal/Platform.kt b/core/native/src/internal/Platform.kt deleted file mode 100644 index 63b890610..000000000 --- a/core/native/src/internal/Platform.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.internal - -import kotlinx.cinterop.* -import kotlinx.datetime.Instant -import platform.posix.* - -@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) -internal actual fun currentTime(): Instant = memScoped { - val tm = alloc() - val error = clock_gettime(CLOCK_REALTIME.convert(), tm.ptr) - check(error == 0) { "Error when reading the system clock: ${strerror(errno)?.toKString() ?: "Unknown error"}" } - try { - require(tm.tv_nsec in 0 until NANOS_PER_ONE) - Instant(tm.tv_sec.convert(), tm.tv_nsec.convert()) - } catch (e: IllegalArgumentException) { - throw IllegalStateException("The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant") - } -} \ No newline at end of file diff --git a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt index 43437708a..40fea247f 100644 --- a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt +++ b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt @@ -11,6 +11,7 @@ import kotlinx.datetime.internal.* import platform.posix.* import kotlin.io.encoding.* import kotlin.test.* +import kotlinx.time.Instant class TimeZoneRulesCompleteTest { @OptIn(ExperimentalEncodingApi::class) diff --git a/core/wasmWasi/src/internal/Platform.kt b/core/wasmWasi/src/internal/Platform.kt deleted file mode 100644 index 1403cae7d..000000000 --- a/core/wasmWasi/src/internal/Platform.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.internal - -import kotlinx.datetime.Instant -import kotlin.wasm.WasmImport -import kotlin.wasm.unsafe.UnsafeWasmMemoryApi -import kotlin.wasm.unsafe.withScopedMemoryAllocator - -/** - * Return the time value of a clock. Note: This is similar to `clock_gettime` in POSIX. - */ -@WasmImport("wasi_snapshot_preview1", "clock_time_get") -private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int - -private const val CLOCKID_REALTIME = 0 - -@OptIn(UnsafeWasmMemoryApi::class) -private fun clockTimeGet(): Long = withScopedMemoryAllocator { allocator -> - val rp0 = allocator.allocate(8) - val ret = wasiRawClockTimeGet( - clockId = CLOCKID_REALTIME, - precision = 1, - resultPtr = rp0.address.toInt() - ) - if (ret == 0) { - rp0.loadLong() - } else { - error("WASI call failed with $ret") - } -} - -internal actual fun currentTime(): Instant = clockTimeGet().let { time -> - // Instant.MAX and Instant.MIN are never going to be exceeded using just the Long number of nanoseconds - Instant(time.floorDiv(NANOS_PER_ONE.toLong()), time.mod(NANOS_PER_ONE.toLong()).toInt()) -} diff --git a/core/windows/test/TimeZoneRulesCompleteTest.kt b/core/windows/test/TimeZoneRulesCompleteTest.kt index 6352494f8..eb1ff6538 100644 --- a/core/windows/test/TimeZoneRulesCompleteTest.kt +++ b/core/windows/test/TimeZoneRulesCompleteTest.kt @@ -14,6 +14,8 @@ import platform.windows.* import kotlin.test.* import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds +import kotlinx.time.Instant +import kotlinx.time.Clock class TimeZoneRulesCompleteTest { diff --git a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt index 153b505cd..7124792ba 100644 --- a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt +++ b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt @@ -7,6 +7,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* +import kotlinx.time.Instant +import kotlinx.time.Clock class TimezonesWithoutDatabaseTest { @Test diff --git a/serialization/common/test/InstantSerializationTest.kt b/serialization/common/test/DeprecatedInstantSerializationTest.kt similarity index 97% rename from serialization/common/test/InstantSerializationTest.kt rename to serialization/common/test/DeprecatedInstantSerializationTest.kt index dea5c2dbd..daaa054e2 100644 --- a/serialization/common/test/InstantSerializationTest.kt +++ b/serialization/common/test/DeprecatedInstantSerializationTest.kt @@ -2,6 +2,8 @@ * Copyright 2019-2020 JetBrains s.r.o. * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ + +@file:Suppress("DEPRECATION") package kotlinx.datetime.serialization.test import kotlinx.datetime.* @@ -10,7 +12,7 @@ import kotlinx.serialization.* import kotlinx.serialization.json.* import kotlin.test.* -class InstantSerializationTest { +class DeprecatedInstantSerializationTest { private fun iso8601Serialization(serializer: KSerializer) { for ((instant, json) in listOf( diff --git a/serialization/common/test/IntegrationTest.kt b/serialization/common/test/IntegrationTest.kt index 0452a10fd..e792e442c 100644 --- a/serialization/common/test/IntegrationTest.kt +++ b/serialization/common/test/IntegrationTest.kt @@ -3,13 +3,13 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:Suppress("DEPRECATION") package kotlinx.datetime.serialization.test import kotlinx.datetime.* import kotlinx.datetime.serializers.* import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule @@ -127,4 +127,4 @@ class IntegrationTest { assertEquals(dummyValue, format.decodeFromString(json)) assertEquals(json, format.encodeToString(dummyValue)) } -} \ No newline at end of file +} From 6f629bbb3c0c18679abad52c910af4d05ecc9389 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 23 Jan 2025 11:11:30 +0100 Subject: [PATCH 7/7] Use the newest Kotlin --- build.gradle.kts | 2 +- core/api/kotlinx-datetime.api | 98 +-- core/api/kotlinx-datetime.klib.api | 94 +-- core/build.gradle.kts | 5 +- core/common/src/Clock.kt | 6 +- core/common/src/DeprecatedClock.kt | 16 +- core/common/src/DeprecatedInstant.kt | 16 +- core/common/src/Instant.kt | 2 +- core/common/src/TimeZone.kt | 2 +- core/common/src/format/DateTimeComponents.kt | 2 +- core/common/test/ClockTimeSourceTest.kt | 4 +- .../test/DeprecatedClockTimeSourceTest.kt | 3 +- core/common/test/DeprecatedInstantTest.kt | 2 +- core/common/test/InstantTest.kt | 8 +- core/common/test/LocalDateTest.kt | 2 +- core/common/test/LocalDateTimeTest.kt | 4 +- core/common/test/ReadmeTest.kt | 4 +- core/common/test/TimeZoneTest.kt | 4 +- .../format/DateTimeComponentsFormatTest.kt | 2 +- .../test/format/DateTimeComponentsTest.kt | 2 +- core/common/test/samples/ClockSamples.kt | 4 +- core/common/test/samples/DayOfWeekSamples.kt | 2 +- core/common/test/samples/InstantSamples.kt | 8 +- core/common/test/samples/MonthSamples.kt | 2 +- core/common/test/samples/TimeZoneSamples.kt | 4 +- .../format/DateTimeComponentsSamples.kt | 2 +- core/commonJs/src/internal/Platform.kt | 2 +- core/commonJs/test/JsJodaTimezoneTest.kt | 2 +- core/commonKotlin/src/DeprecatedInstant.kt | 4 +- core/commonKotlin/src/Instant.kt | 2 +- core/commonKotlin/src/TimeZone.kt | 2 +- core/commonKotlin/src/ZonedDateTime.kt | 2 +- .../commonKotlin/src/internal/MonthDayTime.kt | 2 +- core/commonKotlin/src/internal/OffsetInfo.kt | 2 +- .../src/internal/RegionTimeZone.kt | 2 +- .../src/internal/TimeZoneRules.kt | 2 +- core/commonKotlin/test/TimeZoneRulesTest.kt | 2 +- core/darwin/src/Converters.kt | 2 +- core/darwin/test/ConvertersTest.kt | 4 +- core/jvm/src/DeprecatedInstant.kt | 4 +- core/jvm/src/Instant.kt | 6 +- core/jvm/src/TimeZoneJvm.kt | 6 +- core/jvm/test/ConvertersTest.kt | 4 +- core/jvm/test/InstantParsing.kt | 4 +- .../test/TimeZoneRulesCompleteTest.kt | 2 +- .../windows/test/TimeZoneRulesCompleteTest.kt | 4 +- fake-kotlinx-time/build.gradle.kts | 102 --- fake-kotlinx-time/common/src/Clock.kt | 61 -- fake-kotlinx-time/common/src/Instant.kt | 774 ------------------ fake-kotlinx-time/common/test/InstantTest.kt | 608 -------------- .../common/test/ThreeTenBpInstantTest.kt | 96 --- .../common/test/ThreeTenBpUtilTest.kt | 49 -- fake-kotlinx-time/common/test/assertions.kt | 20 - .../common/test/samples/ClockSamples.kt | 36 - .../common/test/samples/InstantSamples.kt | 122 --- fake-kotlinx-time/js/src/Converters.kt | 21 - fake-kotlinx-time/js/src/Platform.kt | 10 - fake-kotlinx-time/js/test/ConvertersTest.kt | 29 - fake-kotlinx-time/jvm/src/Converters.kt | 17 - fake-kotlinx-time/jvm/src/Platform.kt | 26 - fake-kotlinx-time/jvm/test/ConvertersTest.kt | 34 - fake-kotlinx-time/native/src/Platform.kt | 21 - fake-kotlinx-time/wasmJs/src/Platform.kt | 12 - fake-kotlinx-time/wasmWasi/src/Platform.kt | 38 - gradle.properties | 2 +- .../test/TimezonesWithoutDatabaseTest.kt | 5 +- settings.gradle.kts | 3 +- 67 files changed, 187 insertions(+), 2259 deletions(-) delete mode 100644 fake-kotlinx-time/build.gradle.kts delete mode 100644 fake-kotlinx-time/common/src/Clock.kt delete mode 100644 fake-kotlinx-time/common/src/Instant.kt delete mode 100644 fake-kotlinx-time/common/test/InstantTest.kt delete mode 100644 fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt delete mode 100644 fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt delete mode 100644 fake-kotlinx-time/common/test/assertions.kt delete mode 100644 fake-kotlinx-time/common/test/samples/ClockSamples.kt delete mode 100644 fake-kotlinx-time/common/test/samples/InstantSamples.kt delete mode 100644 fake-kotlinx-time/js/src/Converters.kt delete mode 100644 fake-kotlinx-time/js/src/Platform.kt delete mode 100644 fake-kotlinx-time/js/test/ConvertersTest.kt delete mode 100644 fake-kotlinx-time/jvm/src/Converters.kt delete mode 100644 fake-kotlinx-time/jvm/src/Platform.kt delete mode 100644 fake-kotlinx-time/jvm/test/ConvertersTest.kt delete mode 100644 fake-kotlinx-time/native/src/Platform.kt delete mode 100644 fake-kotlinx-time/wasmJs/src/Platform.kt delete mode 100644 fake-kotlinx-time/wasmWasi/src/Platform.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5db167372..6eefe39f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("kotlinx.team.infra") version "0.4.0-dev-81" + id("kotlinx.team.infra") version "0.4.0-dev-82" kotlin("multiplatform") apply false id("org.jetbrains.kotlinx.kover") version "0.8.0-Beta2" } diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index e872da178..0b7e0e04a 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -12,15 +12,15 @@ public final class kotlinx/datetime/Clock$System : kotlinx/datetime/Clock { } public final class kotlinx/datetime/ClockKt { - public static final fun asClock (Lkotlin/time/TimeSource;Lkotlinx/time/Instant;)Lkotlinx/time/Clock; + public static final fun asClock (Lkotlin/time/TimeSource;Lkotlin/time/Instant;)Lkotlin/time/Clock; + public static final fun asTimeSource (Lkotlin/time/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; public static final fun asTimeSource (Lkotlinx/datetime/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; - public static final fun asTimeSource (Lkotlinx/time/Clock;)Lkotlin/time/TimeSource$WithComparableMarks; - public static final fun toDeprecatedClock (Lkotlinx/time/Clock;)Lkotlinx/datetime/Clock; - public static final fun toStdlibClock (Lkotlinx/datetime/Clock;)Lkotlinx/time/Clock; + public static final fun toDeprecatedClock (Lkotlin/time/Clock;)Lkotlinx/datetime/Clock; + public static final fun toStdlibClock (Lkotlinx/datetime/Clock;)Lkotlin/time/Clock; + public static final fun todayAt (Lkotlin/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; public static final fun todayAt (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; - public static final fun todayAt (Lkotlinx/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; + public static final fun todayIn (Lkotlin/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; public static final fun todayIn (Lkotlinx/datetime/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; - public static final fun todayIn (Lkotlinx/time/Clock;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDate; } public final class kotlinx/datetime/ConvertersKt { @@ -244,33 +244,42 @@ public final class kotlinx/datetime/Instant$Companion { } public final class kotlinx/datetime/InstantJvmKt { + public static final fun minus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; - public static final fun minus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun periodUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; public static final fun periodUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; - public static final fun periodUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun plus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; - public static final fun plus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun plus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun plus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; + public static final fun until (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J - public static final fun until (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J } public final class kotlinx/datetime/InstantKt { + public static final fun daysUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun daysUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I - public static final fun daysUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I + public static final fun format (Lkotlin/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; public static final fun format (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; - public static final fun format (Lkotlinx/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;)Ljava/lang/String; + public static synthetic fun format$default (Lkotlin/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; public static synthetic fun format$default (Lkotlinx/datetime/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; - public static synthetic fun format$default (Lkotlinx/time/Instant;Lkotlinx/datetime/format/DateTimeFormat;Lkotlinx/datetime/UtcOffset;ILjava/lang/Object;)Ljava/lang/String; public static final fun isDistantFuture (Lkotlinx/datetime/Instant;)Z public static final fun isDistantPast (Lkotlinx/datetime/Instant;)Z + public static final fun minus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J + public static final fun minus (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun minus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlin/time/Instant; public static final fun minus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun minus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun minus (Lkotlinx/datetime/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; @@ -280,29 +289,20 @@ public final class kotlinx/datetime/InstantKt { public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J public static final fun minus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; - public static final fun minus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;JLkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimePeriod;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)Lkotlinx/time/Instant; - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit;Lkotlinx/datetime/TimeZone;)J - public static final fun minus (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/DateTimePeriod; + public static final fun monthsUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun monthsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I - public static final fun monthsUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I - public static final fun parse (Lkotlinx/time/Instant$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/time/Instant; + public static final fun parse (Lkotlin/time/Instant$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; + public static final fun plus (Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlin/time/Instant; public static final fun plus (Lkotlinx/datetime/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; public static final fun plus (Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/datetime/Instant; - public static final fun plus (Lkotlinx/time/Instant;ILkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun plus (Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)Lkotlinx/time/Instant; - public static final fun toDeprecatedInstant (Lkotlinx/time/Instant;)Lkotlinx/datetime/Instant; + public static final fun toDeprecatedInstant (Lkotlin/time/Instant;)Lkotlinx/datetime/Instant; public static final fun toInstant (Ljava/lang/String;)Lkotlinx/datetime/Instant; - public static final fun toStdlibInstant (Lkotlinx/datetime/Instant;)Lkotlinx/time/Instant; + public static final fun toStdlibInstant (Lkotlinx/datetime/Instant;)Lkotlin/time/Instant; + public static final fun until (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J public static final fun until (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J - public static final fun until (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/DateTimeUnit$TimeBased;)J + public static final fun yearsUntil (Lkotlin/time/Instant;Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)I public static final fun yearsUntil (Lkotlinx/datetime/Instant;Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)I - public static final fun yearsUntil (Lkotlinx/time/Instant;Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)I } public final class kotlinx/datetime/LocalDate : java/io/Serializable, java/lang/Comparable { @@ -529,10 +529,10 @@ public class kotlinx/datetime/TimeZone { public final fun getId ()Ljava/lang/String; public fun hashCode ()I public final fun toInstant (Lkotlinx/datetime/LocalDateTime;)Lkotlinx/datetime/Instant; - public final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; - public static synthetic fun toInstant$default (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; + public final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public final fun toLocalDateTime (Lkotlin/time/Instant;)Lkotlinx/datetime/LocalDateTime; public final fun toLocalDateTime (Lkotlinx/datetime/Instant;)Lkotlinx/datetime/LocalDateTime; - public final fun toLocalDateTime (Lkotlinx/time/Instant;)Lkotlinx/datetime/LocalDateTime; public fun toString ()Ljava/lang/String; } @@ -546,21 +546,21 @@ public final class kotlinx/datetime/TimeZone$Companion { public final class kotlinx/datetime/TimeZoneKt { public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; - public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; - public static synthetic fun atStartOfDayIn$default (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; + public static final fun atStartOfDayIn (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun atStartOfDayIn$default (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlin/time/Instant;)Lkotlinx/datetime/UtcOffset; public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/Instant;)Lkotlinx/datetime/UtcOffset; - public static final fun offsetAt (Lkotlinx/datetime/TimeZone;Lkotlinx/time/Instant;)Lkotlinx/datetime/UtcOffset; + public static final fun offsetIn (Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; public static final fun offsetIn (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; - public static final fun offsetIn (Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/UtcOffset; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/Instant; - public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/Instant; - public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; - public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; - public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; + public static final fun toInstant (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/TimeZone;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static synthetic fun toInstant$default (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; + public static final fun toLocalDateTime (Lkotlin/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; public static final fun toLocalDateTime (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)Lkotlinx/datetime/LocalDateTime; - public static final fun toLocalDateTime (Lkotlinx/time/Instant;Lkotlinx/datetime/TimeZone;)Lkotlinx/datetime/LocalDateTime; } public final class kotlinx/datetime/UtcOffset : java/io/Serializable { @@ -631,9 +631,9 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setAmPm (Lkotlinx/datetime/format/AmPmMarker;)V public final fun setDate (Lkotlinx/datetime/LocalDate;)V public final fun setDateTime (Lkotlinx/datetime/LocalDateTime;)V + public final fun setDateTimeOffset (Lkotlin/time/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDateTimeOffset (Lkotlinx/datetime/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDateTimeOffset (Lkotlinx/datetime/LocalDateTime;Lkotlinx/datetime/UtcOffset;)V - public final fun setDateTimeOffset (Lkotlinx/time/Instant;Lkotlinx/datetime/UtcOffset;)V public final fun setDay (Ljava/lang/Integer;)V public final fun setDayOfMonth (Ljava/lang/Integer;)V public final fun setDayOfWeek (Lkotlinx/datetime/DayOfWeek;)V @@ -654,8 +654,8 @@ public final class kotlinx/datetime/format/DateTimeComponents { public final fun setTimeZoneId (Ljava/lang/String;)V public final fun setYear (Ljava/lang/Integer;)V public final fun toInstantUsingOffset ()Lkotlinx/datetime/Instant; - public final fun toInstantUsingOffset (Lkotlinx/datetime/OverloadMarker;)Lkotlinx/time/Instant; - public static synthetic fun toInstantUsingOffset$default (Lkotlinx/datetime/format/DateTimeComponents;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlinx/time/Instant; + public final fun toInstantUsingOffset (Lkotlinx/datetime/OverloadMarker;)Lkotlin/time/Instant; + public static synthetic fun toInstantUsingOffset$default (Lkotlinx/datetime/format/DateTimeComponents;Lkotlinx/datetime/OverloadMarker;ILjava/lang/Object;)Lkotlin/time/Instant; public final fun toLocalDate ()Lkotlinx/datetime/LocalDate; public final fun toLocalDateTime ()Lkotlinx/datetime/LocalDateTime; public final fun toLocalTime ()Lkotlinx/datetime/LocalTime; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 6f9902743..53cb2380b 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -193,13 +193,13 @@ final class kotlinx.datetime.format/DateTimeComponents { // kotlinx.datetime.for final fun setDate(kotlinx.datetime/LocalDate) // kotlinx.datetime.format/DateTimeComponents.setDate|setDate(kotlinx.datetime.LocalDate){}[0] final fun setDateTime(kotlinx.datetime/LocalDateTime) // kotlinx.datetime.format/DateTimeComponents.setDateTime|setDateTime(kotlinx.datetime.LocalDateTime){}[0] + final fun setDateTimeOffset(kotlin.time/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlin.time.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setDateTimeOffset(kotlinx.datetime/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setDateTimeOffset(kotlinx.datetime/LocalDateTime, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.datetime.LocalDateTime;kotlinx.datetime.UtcOffset){}[0] - final fun setDateTimeOffset(kotlinx.time/Instant, kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setDateTimeOffset|setDateTimeOffset(kotlinx.time.Instant;kotlinx.datetime.UtcOffset){}[0] final fun setOffset(kotlinx.datetime/UtcOffset) // kotlinx.datetime.format/DateTimeComponents.setOffset|setOffset(kotlinx.datetime.UtcOffset){}[0] final fun setTime(kotlinx.datetime/LocalTime) // kotlinx.datetime.format/DateTimeComponents.setTime|setTime(kotlinx.datetime.LocalTime){}[0] final fun toInstantUsingOffset(): kotlinx.datetime/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(){}[0] - final fun toInstantUsingOffset(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(kotlinx.datetime.OverloadMarker){}[0] + final fun toInstantUsingOffset(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime.format/DateTimeComponents.toInstantUsingOffset|toInstantUsingOffset(kotlinx.datetime.OverloadMarker){}[0] final fun toLocalDate(): kotlinx.datetime/LocalDate // kotlinx.datetime.format/DateTimeComponents.toLocalDate|toLocalDate(){}[0] final fun toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime.format/DateTimeComponents.toLocalDateTime|toLocalDateTime(){}[0] final fun toLocalTime(): kotlinx.datetime/LocalTime // kotlinx.datetime.format/DateTimeComponents.toLocalTime|toLocalTime(){}[0] @@ -497,10 +497,10 @@ open class kotlinx.datetime/TimeZone { // kotlinx.datetime/TimeZone|null[0] open val id // kotlinx.datetime/TimeZone.id|{}id[0] open fun (): kotlin/String // kotlinx.datetime/TimeZone.id.|(){}[0] + final fun (kotlin.time/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlin.time.Instant(){}[0] final fun (kotlinx.datetime/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlinx.datetime.Instant(){}[0] final fun (kotlinx.datetime/LocalDateTime).toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(){}[0] - final fun (kotlinx.datetime/LocalDateTime).toInstant(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.OverloadMarker){}[0] - final fun (kotlinx.time/Instant).toLocalDateTime(): kotlinx.datetime/LocalDateTime // kotlinx.datetime/TimeZone.toLocalDateTime|toLocalDateTime@kotlinx.time.Instant(){}[0] + final fun (kotlinx.datetime/LocalDateTime).toInstant(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/TimeZone.toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.OverloadMarker){}[0] open fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.datetime/TimeZone.equals|equals(kotlin.Any?){}[0] open fun hashCode(): kotlin/Int // kotlinx.datetime/TimeZone.hashCode|hashCode(){}[0] open fun toString(): kotlin/String // kotlinx.datetime/TimeZone.toString|toString(){}[0] @@ -823,8 +823,40 @@ final val kotlinx.datetime/isoDayNumber // kotlinx.datetime/isoDayNumber|@kotlin final val kotlinx.datetime/number // kotlinx.datetime/number|@kotlinx.datetime.Month{}number[0] final fun (kotlinx.datetime/Month).(): kotlin/Int // kotlinx.datetime/number.|@kotlinx.datetime.Month(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/asTimeSource(): kotlin.time/TimeSource.WithComparableMarks // kotlinx.datetime/asTimeSource|asTimeSource@kotlin.time.Clock(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/toDeprecatedClock(): kotlinx.datetime/Clock // kotlinx.datetime/toDeprecatedClock|toDeprecatedClock@kotlin.time.Clock(){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/todayAt(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayAt|todayAt@kotlin.time.Clock(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Clock).kotlinx.datetime/todayIn(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayIn|todayIn@kotlin.time.Clock(kotlinx.datetime.TimeZone){}[0] final fun (kotlin.time/Duration).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.time.Duration(){}[0] -final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlinx.time/Instant): kotlinx.time/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlinx.time.Instant){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/daysUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/daysUntil|daysUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat, kotlinx.datetime/UtcOffset = ...): kotlin/String // kotlinx.datetime/format|format@kotlin.time.Instant(kotlinx.datetime.format.DateTimeFormat;kotlinx.datetime.UtcOffset){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/minus|minus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/monthsUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/monthsUntil|monthsUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/offsetIn(kotlinx.datetime/TimeZone): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetIn|offsetIn@kotlin.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/periodUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/periodUntil|periodUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlin.time/Instant // kotlinx.datetime/plus|plus@kotlin.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/toDeprecatedInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toDeprecatedInstant|toDeprecatedInstant@kotlin.time.Instant(){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/toLocalDateTime(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDateTime // kotlinx.datetime/toLocalDateTime|toLocalDateTime@kotlin.time.Instant(kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/until(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/until|until@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/until(kotlin.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/until|until@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] +final fun (kotlin.time/Instant).kotlinx.datetime/yearsUntil(kotlin.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/yearsUntil|yearsUntil@kotlin.time.Instant(kotlin.time.Instant;kotlinx.datetime.TimeZone){}[0] +final fun (kotlin.time/Instant.Companion).kotlinx.datetime/parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat): kotlin.time/Instant // kotlinx.datetime/parse|parse@kotlin.time.Instant.Companion(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] +final fun (kotlin.time/TimeSource).kotlinx.datetime/asClock(kotlin.time/Instant): kotlin.time/Clock // kotlinx.datetime/asClock|asClock@kotlin.time.TimeSource(kotlin.time.Instant){}[0] final fun (kotlin/String).kotlinx.datetime/toDatePeriod(): kotlinx.datetime/DatePeriod // kotlinx.datetime/toDatePeriod|toDatePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toDateTimePeriod(): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/toDateTimePeriod|toDateTimePeriod@kotlin.String(){}[0] final fun (kotlin/String).kotlinx.datetime/toInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlin.String(){}[0] @@ -836,7 +868,7 @@ final fun (kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalDateTime(kotlinx.datetime.format.DateTimeFormat){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone, kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone;kotlinx.datetime.OverloadMarker){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/TimeZone, kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.TimeZone;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset): kotlinx.datetime/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset){}[0] -final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset, kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset;kotlinx.datetime.OverloadMarker){}[0] +final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toInstant(kotlinx.datetime/UtcOffset, kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toInstant|toInstant@kotlinx.datetime.LocalDateTime(kotlinx.datetime.UtcOffset;kotlinx.datetime.OverloadMarker){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Unit = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Unit){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlin/Int, kotlinx.datetime/Month, kotlin/Int, kotlin/Unit = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlin.Int;kotlinx.datetime.Month;kotlin.Int;kotlin.Unit){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/atDate(kotlinx.datetime/LocalDate): kotlinx.datetime/LocalDateTime // kotlinx.datetime/atDate|atDate@kotlinx.datetime.LocalTime(kotlinx.datetime.LocalDate){}[0] final fun (kotlinx.datetime/LocalTime).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.LocalTime(kotlinx.datetime.format.DateTimeFormat){}[0] +final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlin.time/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlin.time.Instant){}[0] final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlinx.datetime/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlinx.datetime.Instant){}[0] -final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/offsetAt(kotlinx.time/Instant): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetAt|offsetAt@kotlinx.datetime.TimeZone(kotlinx.time.Instant){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/asTimeZone(): kotlinx.datetime/FixedOffsetTimeZone // kotlinx.datetime/asTimeZone|asTimeZone@kotlinx.datetime.UtcOffset(){}[0] final fun (kotlinx.datetime/UtcOffset).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat): kotlin/String // kotlinx.datetime/format|format@kotlinx.datetime.UtcOffset(kotlinx.datetime.format.DateTimeFormat){}[0] -final fun (kotlinx.time/Clock).kotlinx.datetime/asTimeSource(): kotlin.time/TimeSource.WithComparableMarks // kotlinx.datetime/asTimeSource|asTimeSource@kotlinx.time.Clock(){}[0] -final fun (kotlinx.time/Clock).kotlinx.datetime/toDeprecatedClock(): kotlinx.datetime/Clock // kotlinx.datetime/toDeprecatedClock|toDeprecatedClock@kotlinx.time.Clock(){}[0] -final fun (kotlinx.time/Clock).kotlinx.datetime/todayAt(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayAt|todayAt@kotlinx.time.Clock(kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Clock).kotlinx.datetime/todayIn(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDate // kotlinx.datetime/todayIn|todayIn@kotlinx.time.Clock(kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/daysUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/daysUntil|daysUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/format(kotlinx.datetime.format/DateTimeFormat, kotlinx.datetime/UtcOffset = ...): kotlin/String // kotlinx.datetime/format|format@kotlinx.time.Instant(kotlinx.datetime.format.DateTimeFormat;kotlinx.datetime.UtcOffset){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/minus(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/minus|minus@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/monthsUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/monthsUntil|monthsUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/offsetIn(kotlinx.datetime/TimeZone): kotlinx.datetime/UtcOffset // kotlinx.datetime/offsetIn|offsetIn@kotlinx.time.Instant(kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/periodUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/periodUntil|periodUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Int, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Int;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlin/Long, kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlin.Long;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimePeriod, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimePeriod;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/plus(kotlinx.datetime/DateTimeUnit.TimeBased): kotlinx.time/Instant // kotlinx.datetime/plus|plus@kotlinx.time.Instant(kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/toDeprecatedInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toDeprecatedInstant|toDeprecatedInstant@kotlinx.time.Instant(){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/toLocalDateTime(kotlinx.datetime/TimeZone): kotlinx.datetime/LocalDateTime // kotlinx.datetime/toLocalDateTime|toLocalDateTime@kotlinx.time.Instant(kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/until(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit, kotlinx.datetime/TimeZone): kotlin/Long // kotlinx.datetime/until|until@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/until(kotlinx.time/Instant, kotlinx.datetime/DateTimeUnit.TimeBased): kotlin/Long // kotlinx.datetime/until|until@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.DateTimeUnit.TimeBased){}[0] -final fun (kotlinx.time/Instant).kotlinx.datetime/yearsUntil(kotlinx.time/Instant, kotlinx.datetime/TimeZone): kotlin/Int // kotlinx.datetime/yearsUntil|yearsUntil@kotlinx.time.Instant(kotlinx.time.Instant;kotlinx.datetime.TimeZone){}[0] -final fun (kotlinx.time/Instant.Companion).kotlinx.datetime/parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat): kotlinx.time/Instant // kotlinx.datetime/parse|parse@kotlinx.time.Instant.Companion(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun <#A: kotlinx.datetime.format/DateTimeFormatBuilder> (#A).kotlinx.datetime.format/alternativeParsing(kotlin/Array>..., kotlin/Function1<#A, kotlin/Unit>) // kotlinx.datetime.format/alternativeParsing|alternativeParsing@0:0(kotlin.Array>...;kotlin.Function1<0:0,kotlin.Unit>){0§}[0] final fun <#A: kotlinx.datetime.format/DateTimeFormatBuilder> (#A).kotlinx.datetime.format/optional(kotlin/String = ..., kotlin/Function1<#A, kotlin/Unit>) // kotlinx.datetime.format/optional|optional@0:0(kotlin.String;kotlin.Function1<0:0,kotlin.Unit>){0§}[0] final fun kotlinx.datetime/DateTimePeriod(kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Long = ...): kotlinx.datetime/DateTimePeriod // kotlinx.datetime/DateTimePeriod|DateTimePeriod(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Long){}[0] @@ -947,6 +947,9 @@ final fun kotlinx.datetime/Month(kotlin/Int): kotlinx.datetime/Month // kotlinx. final fun kotlinx.datetime/UtcOffset(): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset|UtcOffset(){}[0] final fun kotlinx.datetime/UtcOffset(kotlin/Int? = ..., kotlin/Int? = ..., kotlin/Int? = ...): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset|UtcOffset(kotlin.Int?;kotlin.Int?;kotlin.Int?){}[0] +// Targets: [apple] +final fun (kotlin.time/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlin.time.Instant(){}[0] + // Targets: [apple] final fun (kotlinx.datetime/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlinx.datetime.Instant(){}[0] @@ -959,14 +962,11 @@ final fun (kotlinx.datetime/LocalDateTime).kotlinx.datetime/toNSDateComponents() // Targets: [apple] final fun (kotlinx.datetime/TimeZone).kotlinx.datetime/toNSTimeZone(): platform.Foundation/NSTimeZone // kotlinx.datetime/toNSTimeZone|toNSTimeZone@kotlinx.datetime.TimeZone(){}[0] -// Targets: [apple] -final fun (kotlinx.time/Instant).kotlinx.datetime/toNSDate(): platform.Foundation/NSDate // kotlinx.datetime/toNSDate|toNSDate@kotlinx.time.Instant(){}[0] - // Targets: [apple] final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(): kotlinx.datetime/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(){}[0] // Targets: [apple] -final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(kotlinx.datetime/OverloadMarker = ...): kotlinx.time/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(kotlinx.datetime.OverloadMarker){}[0] +final fun (platform.Foundation/NSDate).kotlinx.datetime/toKotlinInstant(kotlinx.datetime/OverloadMarker = ...): kotlin.time/Instant // kotlinx.datetime/toKotlinInstant|toKotlinInstant@platform.Foundation.NSDate(kotlinx.datetime.OverloadMarker){}[0] // Targets: [apple] final fun (platform.Foundation/NSTimeZone).kotlinx.datetime/toKotlinTimeZone(): kotlinx.datetime/TimeZone // kotlinx.datetime/toKotlinTimeZone|toKotlinTimeZone@platform.Foundation.NSTimeZone(){}[0] diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e54f9d856..f2bf2e101 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -188,7 +188,6 @@ kotlin { commonMain { dependencies { compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") - api(project(":fake-kotlinx-time")) } } @@ -223,6 +222,10 @@ kotlin { } } } + + compilerOptions { + optIn.add("kotlin.time.ExperimentalTime") + } } tasks { diff --git a/core/common/src/Clock.kt b/core/common/src/Clock.kt index 587ee60da..24cd7a737 100644 --- a/core/common/src/Clock.kt +++ b/core/common/src/Clock.kt @@ -8,9 +8,9 @@ package kotlinx.datetime import kotlin.time.* -import kotlinx.time.Clock -import kotlinx.time.Instant -import kotlinx.time.isDistantFuture +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.time.Duration.Companion.seconds diff --git a/core/common/src/DeprecatedClock.kt b/core/common/src/DeprecatedClock.kt index 113f4f530..34e2f67af 100644 --- a/core/common/src/DeprecatedClock.kt +++ b/core/common/src/DeprecatedClock.kt @@ -17,23 +17,23 @@ import kotlin.time.ExperimentalTime import kotlin.time.TimeSource /** - * Creates a [kotlinx.time.Clock] (the standard library version of `Clock`) delegating to `this`. + * Creates a [kotlin.time.Clock] (the standard library version of `Clock`) delegating to `this`. */ -public fun Clock.toStdlibClock(): kotlinx.time.Clock = +public fun Clock.toStdlibClock(): kotlin.time.Clock = (this as? DateTimeClock)?.clock ?: StdlibClock(this) /** * Creates a [kotlinx.datetime.Clock] delegating to the version of `Clock` from the standard library. */ -public fun kotlinx.time.Clock.toDeprecatedClock(): Clock = +public fun kotlin.time.Clock.toDeprecatedClock(): Clock = (this as? StdlibClock)?.clock ?: DateTimeClock(this) -private class DateTimeClock(val clock: kotlinx.time.Clock): kotlinx.datetime.Clock { +private class DateTimeClock(val clock: kotlin.time.Clock): kotlinx.datetime.Clock { override fun now(): Instant = clock.now().toDeprecatedInstant() } -private class StdlibClock(val clock: Clock): kotlinx.time.Clock { - override fun now(): kotlinx.time.Instant = clock.now().toStdlibInstant() +private class StdlibClock(val clock: Clock): kotlin.time.Clock { + override fun now(): kotlin.time.Instant = clock.now().toStdlibInstant() } /** @@ -48,7 +48,7 @@ private class StdlibClock(val clock: Clock): kotlinx.time.Clock { */ @Deprecated( "Use kotlin.time.Clock instead", - ReplaceWith("kotlinx.time.Clock", "kotlinx.time.Clock"), + ReplaceWith("kotlin.time.Clock", "kotlin.time.Clock"), level = DeprecationLevel.WARNING ) public interface Clock { @@ -85,7 +85,7 @@ public interface Clock { * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection */ public object System : Clock { - override fun now(): Instant = kotlinx.time.Clock.System.now().toDeprecatedInstant() + override fun now(): Instant = kotlin.time.Clock.System.now().toDeprecatedInstant() } /** A companion object used purely for namespacing. */ diff --git a/core/common/src/DeprecatedInstant.kt b/core/common/src/DeprecatedInstant.kt index 9bf6a69a4..272952449 100644 --- a/core/common/src/DeprecatedInstant.kt +++ b/core/common/src/DeprecatedInstant.kt @@ -22,20 +22,20 @@ import kotlin.time.Duration import kotlin.time.TimeSource /** - * Creates a [kotlinx.time.Instant] (the standard library version of `Instant`) identical to `this`. + * Creates a [kotlin.time.Instant] (the standard library version of `Instant`) identical to `this`. */ -public fun Instant.toStdlibInstant(): kotlinx.time.Instant = - kotlinx.time.Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) +public fun Instant.toStdlibInstant(): kotlin.time.Instant = + kotlin.time.Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) /** * Creates a [kotlinx.datetime.Instant] identical to the version of `Instant` from the standard library. */ -public fun kotlinx.time.Instant.toDeprecatedInstant(): Instant = +public fun kotlin.time.Instant.toDeprecatedInstant(): Instant = Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) @Deprecated( "Use kotlin.time.Instant instead", - ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), level = DeprecationLevel.WARNING ) @Serializable(with = InstantIso8601Serializer::class) @@ -191,7 +191,7 @@ public expect class Instant : Comparable { public companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.time.Clock"), level = DeprecationLevel.ERROR) + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) public fun now(): Instant /** @@ -294,7 +294,7 @@ public expect class Instant : Comparable { */ @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", level = DeprecationLevel.WARNING, - replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlinx.time.isDistantPast") + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlin.time.isDistantPast") ) public val Instant.isDistantPast: Boolean get() = this <= Instant.DISTANT_PAST @@ -306,7 +306,7 @@ public val Instant.isDistantPast: Boolean */ @Deprecated("kotlinx.datetime.Instant is superseded by kotlin.time.Instant", level = DeprecationLevel.WARNING, - replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlinx.time.isDistantFuture") + replaceWith = ReplaceWith("this.toStdlibInstant().isDistantPast", "kotlin.time.isDistantFuture") ) public val Instant.isDistantFuture: Boolean get() = this >= Instant.DISTANT_FUTURE diff --git a/core/common/src/Instant.kt b/core/common/src/Instant.kt index 7130daaa0..f55211d04 100644 --- a/core/common/src/Instant.kt +++ b/core/common/src/Instant.kt @@ -10,7 +10,7 @@ package kotlinx.datetime import kotlinx.datetime.format.* import kotlinx.datetime.internal.* import kotlin.time.* -import kotlinx.time.Instant +import kotlin.time.Instant import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName diff --git a/core/common/src/TimeZone.kt b/core/common/src/TimeZone.kt index c7e39efa9..8a98b4f56 100644 --- a/core/common/src/TimeZone.kt +++ b/core/common/src/TimeZone.kt @@ -10,7 +10,7 @@ package kotlinx.datetime import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable -import kotlinx.time.Instant +import kotlin.time.Instant /** * A time zone, provides the conversion between [Instant] and [LocalDateTime] values diff --git a/core/common/src/format/DateTimeComponents.kt b/core/common/src/format/DateTimeComponents.kt index 196bc09c1..f79c91ff8 100644 --- a/core/common/src/format/DateTimeComponents.kt +++ b/core/common/src/format/DateTimeComponents.kt @@ -12,7 +12,7 @@ import kotlinx.datetime.internal.format.* import kotlinx.datetime.internal.format.parser.Copyable import kotlinx.datetime.internal.safeMultiply import kotlin.reflect.* -import kotlinx.time.Instant +import kotlin.time.Instant /** * A collection of datetime fields used specifically for parsing and formatting. diff --git a/core/common/test/ClockTimeSourceTest.kt b/core/common/test/ClockTimeSourceTest.kt index 53a6dfca6..3db2fce99 100644 --- a/core/common/test/ClockTimeSourceTest.kt +++ b/core/common/test/ClockTimeSourceTest.kt @@ -11,8 +11,8 @@ import kotlin.time.* import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant @OptIn(ExperimentalTime::class) @Suppress("DEPRECATION") diff --git a/core/common/test/DeprecatedClockTimeSourceTest.kt b/core/common/test/DeprecatedClockTimeSourceTest.kt index f327c910a..85a453ee5 100644 --- a/core/common/test/DeprecatedClockTimeSourceTest.kt +++ b/core/common/test/DeprecatedClockTimeSourceTest.kt @@ -8,7 +8,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* -import kotlin.time.* +import kotlin.time.ExperimentalTime +import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.nanoseconds diff --git a/core/common/test/DeprecatedInstantTest.kt b/core/common/test/DeprecatedInstantTest.kt index e76524eb9..b8523ae26 100644 --- a/core/common/test/DeprecatedInstantTest.kt +++ b/core/common/test/DeprecatedInstantTest.kt @@ -11,7 +11,7 @@ import kotlinx.datetime.format.* import kotlinx.datetime.internal.* import kotlin.random.* import kotlin.test.* -import kotlin.time.* +import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds diff --git a/core/common/test/InstantTest.kt b/core/common/test/InstantTest.kt index 3479cc530..082c2f30e 100644 --- a/core/common/test/InstantTest.kt +++ b/core/common/test/InstantTest.kt @@ -16,10 +16,10 @@ import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds -import kotlinx.time.Clock -import kotlinx.time.Instant -import kotlinx.time.isDistantFuture -import kotlinx.time.isDistantPast +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture +import kotlin.time.isDistantPast class InstantTest { diff --git a/core/common/test/LocalDateTest.kt b/core/common/test/LocalDateTest.kt index cc1740e9c..60f4f841b 100644 --- a/core/common/test/LocalDateTest.kt +++ b/core/common/test/LocalDateTest.kt @@ -7,7 +7,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* -import kotlinx.time.Clock +import kotlin.time.Clock import kotlin.random.* import kotlin.test.* diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index 041491bc9..07aed3d80 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -6,8 +6,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant import kotlin.test.* import kotlin.time.* import kotlin.time.Duration.Companion.hours diff --git a/core/common/test/ReadmeTest.kt b/core/common/test/ReadmeTest.kt index 15f384c21..880afc3f5 100644 --- a/core/common/test/ReadmeTest.kt +++ b/core/common/test/ReadmeTest.kt @@ -9,8 +9,8 @@ import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* import kotlin.time.* -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant /** * Tests the code snippets in the README.md file. diff --git a/core/common/test/TimeZoneTest.kt b/core/common/test/TimeZoneTest.kt index add66c60c..027e25e6a 100644 --- a/core/common/test/TimeZoneTest.kt +++ b/core/common/test/TimeZoneTest.kt @@ -9,8 +9,8 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant class TimeZoneTest { diff --git a/core/common/test/format/DateTimeComponentsFormatTest.kt b/core/common/test/format/DateTimeComponentsFormatTest.kt index 274106cd6..79a085691 100644 --- a/core/common/test/format/DateTimeComponentsFormatTest.kt +++ b/core/common/test/format/DateTimeComponentsFormatTest.kt @@ -10,7 +10,7 @@ import kotlinx.datetime.format.* import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty import kotlin.test.* -import kotlinx.time.Instant +import kotlin.time.Instant class DateTimeComponentsFormatTest { @Test diff --git a/core/common/test/format/DateTimeComponentsTest.kt b/core/common/test/format/DateTimeComponentsTest.kt index 530aa4869..3c9e3859b 100644 --- a/core/common/test/format/DateTimeComponentsTest.kt +++ b/core/common/test/format/DateTimeComponentsTest.kt @@ -8,7 +8,7 @@ package kotlinx.datetime.test.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* -import kotlinx.time.Clock +import kotlin.time.Clock class DateTimeComponentsTest { @Test diff --git a/core/common/test/samples/ClockSamples.kt b/core/common/test/samples/ClockSamples.kt index 862df90df..278fa9e0f 100644 --- a/core/common/test/samples/ClockSamples.kt +++ b/core/common/test/samples/ClockSamples.kt @@ -9,8 +9,8 @@ import kotlinx.datetime.* import kotlin.test.* import kotlin.time.Duration.Companion.seconds import kotlin.time.TestTimeSource -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant class ClockSamples { @Test diff --git a/core/common/test/samples/DayOfWeekSamples.kt b/core/common/test/samples/DayOfWeekSamples.kt index 4d4e1e0cd..4c491fcc1 100644 --- a/core/common/test/samples/DayOfWeekSamples.kt +++ b/core/common/test/samples/DayOfWeekSamples.kt @@ -7,7 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* -import kotlinx.time.Clock +import kotlin.time.Clock class DayOfWeekSamples { diff --git a/core/common/test/samples/InstantSamples.kt b/core/common/test/samples/InstantSamples.kt index 7e2a483cf..cbb574354 100644 --- a/core/common/test/samples/InstantSamples.kt +++ b/core/common/test/samples/InstantSamples.kt @@ -10,10 +10,10 @@ import kotlinx.datetime.format.* import kotlin.random.* import kotlin.test.* import kotlin.time.Duration.Companion.hours -import kotlinx.time.Clock -import kotlinx.time.Instant -import kotlinx.time.isDistantFuture -import kotlinx.time.isDistantPast +import kotlin.time.Clock +import kotlin.time.Instant +import kotlin.time.isDistantFuture +import kotlin.time.isDistantPast class InstantSamples { diff --git a/core/common/test/samples/MonthSamples.kt b/core/common/test/samples/MonthSamples.kt index 3efe3d46b..a2ab2edd1 100644 --- a/core/common/test/samples/MonthSamples.kt +++ b/core/common/test/samples/MonthSamples.kt @@ -7,7 +7,7 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* -import kotlinx.time.Clock +import kotlin.time.Clock class MonthSamples { diff --git a/core/common/test/samples/TimeZoneSamples.kt b/core/common/test/samples/TimeZoneSamples.kt index 3456a9599..a777f4d98 100644 --- a/core/common/test/samples/TimeZoneSamples.kt +++ b/core/common/test/samples/TimeZoneSamples.kt @@ -8,8 +8,8 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* -import kotlinx.time.Instant -import kotlinx.time.Clock +import kotlin.time.Instant +import kotlin.time.Clock class TimeZoneSamples { diff --git a/core/common/test/samples/format/DateTimeComponentsSamples.kt b/core/common/test/samples/format/DateTimeComponentsSamples.kt index e2e54a04a..3c2939e0c 100644 --- a/core/common/test/samples/format/DateTimeComponentsSamples.kt +++ b/core/common/test/samples/format/DateTimeComponentsSamples.kt @@ -8,7 +8,7 @@ package kotlinx.datetime.test.samples.format import kotlinx.datetime.* import kotlinx.datetime.format.* import kotlin.test.* -import kotlinx.time.Instant +import kotlin.time.Instant class DateTimeComponentsSamples { diff --git a/core/commonJs/src/internal/Platform.kt b/core/commonJs/src/internal/Platform.kt index ac759d3b0..940777826 100644 --- a/core/commonJs/src/internal/Platform.kt +++ b/core/commonJs/src/internal/Platform.kt @@ -8,7 +8,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* import kotlinx.datetime.UtcOffset import kotlinx.datetime.internal.JSJoda.ZoneId -import kotlinx.time.Instant +import kotlin.time.Instant private val tzdb: Result = runCatching { /** diff --git a/core/commonJs/test/JsJodaTimezoneTest.kt b/core/commonJs/test/JsJodaTimezoneTest.kt index 635fbf749..db316b779 100644 --- a/core/commonJs/test/JsJodaTimezoneTest.kt +++ b/core/commonJs/test/JsJodaTimezoneTest.kt @@ -12,7 +12,7 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.datetime.test.JSJoda.Instant as jtInstant import kotlinx.datetime.test.JSJoda.ZoneId as jtZoneId -import kotlinx.time.Instant +import kotlin.time.Instant class JsJodaTimezoneTest { @Test diff --git a/core/commonKotlin/src/DeprecatedInstant.kt b/core/commonKotlin/src/DeprecatedInstant.kt index d15614f32..30d73cce9 100644 --- a/core/commonKotlin/src/DeprecatedInstant.kt +++ b/core/commonKotlin/src/DeprecatedInstant.kt @@ -35,7 +35,7 @@ private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59Z @Deprecated( "Use kotlin.time.Instant instead", - ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), level = DeprecationLevel.WARNING ) @Serializable(with = InstantIso8601Serializer::class) @@ -153,7 +153,7 @@ public actual class Instant internal constructor( internal actual val MIN = Instant(MIN_SECOND, 0) internal actual val MAX = Instant(MAX_SECOND, 999_999_999) - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) public actual fun now(): Instant = Clock.System.now() // org.threeten.bp.Instant#ofEpochMilli diff --git a/core/commonKotlin/src/Instant.kt b/core/commonKotlin/src/Instant.kt index ce922bddd..bcb78979b 100644 --- a/core/commonKotlin/src/Instant.kt +++ b/core/commonKotlin/src/Instant.kt @@ -8,7 +8,7 @@ package kotlinx.datetime -import kotlinx.time.Instant +import kotlin.time.Instant import kotlinx.datetime.internal.* import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds diff --git a/core/commonKotlin/src/TimeZone.kt b/core/commonKotlin/src/TimeZone.kt index 63e56ff6d..6fb495cac 100644 --- a/core/commonKotlin/src/TimeZone.kt +++ b/core/commonKotlin/src/TimeZone.kt @@ -12,7 +12,7 @@ import kotlinx.datetime.format.* import kotlinx.datetime.internal.* import kotlinx.datetime.serializers.* import kotlinx.serialization.Serializable -import kotlinx.time.Instant +import kotlin.time.Instant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor() { diff --git a/core/commonKotlin/src/ZonedDateTime.kt b/core/commonKotlin/src/ZonedDateTime.kt index c10011ded..1847d8ca4 100644 --- a/core/commonKotlin/src/ZonedDateTime.kt +++ b/core/commonKotlin/src/ZonedDateTime.kt @@ -8,7 +8,7 @@ package kotlinx.datetime -import kotlinx.time.Instant +import kotlin.time.Instant internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: TimeZone, val offset: UtcOffset) { /** diff --git a/core/commonKotlin/src/internal/MonthDayTime.kt b/core/commonKotlin/src/internal/MonthDayTime.kt index 287021b4e..09b47e28e 100644 --- a/core/commonKotlin/src/internal/MonthDayTime.kt +++ b/core/commonKotlin/src/internal/MonthDayTime.kt @@ -6,7 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* -import kotlinx.time.Instant +import kotlin.time.Instant /** * A rule expressing how to create a date in a given year. diff --git a/core/commonKotlin/src/internal/OffsetInfo.kt b/core/commonKotlin/src/internal/OffsetInfo.kt index 4685475dc..c769e919d 100644 --- a/core/commonKotlin/src/internal/OffsetInfo.kt +++ b/core/commonKotlin/src/internal/OffsetInfo.kt @@ -6,7 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* -import kotlinx.time.Instant +import kotlin.time.Instant internal sealed interface OffsetInfo { data class Gap( diff --git a/core/commonKotlin/src/internal/RegionTimeZone.kt b/core/commonKotlin/src/internal/RegionTimeZone.kt index 9bec9e27f..335402b29 100644 --- a/core/commonKotlin/src/internal/RegionTimeZone.kt +++ b/core/commonKotlin/src/internal/RegionTimeZone.kt @@ -6,7 +6,7 @@ package kotlinx.datetime.internal import kotlinx.datetime.* -import kotlinx.time.Instant +import kotlin.time.Instant internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id: String) : TimeZone() { diff --git a/core/commonKotlin/src/internal/TimeZoneRules.kt b/core/commonKotlin/src/internal/TimeZoneRules.kt index 0c9bd948a..aa8332b4d 100644 --- a/core/commonKotlin/src/internal/TimeZoneRules.kt +++ b/core/commonKotlin/src/internal/TimeZoneRules.kt @@ -9,7 +9,7 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.UtcOffset import kotlinx.datetime.toLocalDateTime import kotlin.math.* -import kotlinx.time.Instant +import kotlin.time.Instant internal class TimeZoneRules( /** diff --git a/core/commonKotlin/test/TimeZoneRulesTest.kt b/core/commonKotlin/test/TimeZoneRulesTest.kt index 03742b2d7..516a0fa21 100644 --- a/core/commonKotlin/test/TimeZoneRulesTest.kt +++ b/core/commonKotlin/test/TimeZoneRulesTest.kt @@ -8,7 +8,7 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlinx.datetime.internal.* import kotlin.test.* -import kotlinx.time.Instant +import kotlin.time.Instant class TimeZoneRulesTest { @Test diff --git a/core/darwin/src/Converters.kt b/core/darwin/src/Converters.kt index a37d2664f..afe31a370 100644 --- a/core/darwin/src/Converters.kt +++ b/core/darwin/src/Converters.kt @@ -10,7 +10,7 @@ package kotlinx.datetime import kotlinx.cinterop.* import kotlinx.datetime.internal.NANOS_PER_ONE import platform.Foundation.* -import kotlinx.time.Instant +import kotlin.time.Instant /** * Converts the [Instant] to an instance of [NSDate]. diff --git a/core/darwin/test/ConvertersTest.kt b/core/darwin/test/ConvertersTest.kt index 38ccb959c..0ad290fa2 100644 --- a/core/darwin/test/ConvertersTest.kt +++ b/core/darwin/test/ConvertersTest.kt @@ -11,8 +11,8 @@ import platform.Foundation.* import kotlin.math.* import kotlin.random.* import kotlin.test.* -import kotlinx.time.Clock -import kotlinx.time.Instant +import kotlin.time.Clock +import kotlin.time.Instant class ConvertersTest { diff --git a/core/jvm/src/DeprecatedInstant.kt b/core/jvm/src/DeprecatedInstant.kt index 7466a77d0..8929d4055 100644 --- a/core/jvm/src/DeprecatedInstant.kt +++ b/core/jvm/src/DeprecatedInstant.kt @@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.seconds @Deprecated( "Use kotlin.time.Instant instead", - ReplaceWith("kotlinx.time.Instant", "kotlinx.time.Instant"), + ReplaceWith("kotlin.time.Instant", "kotlin.time.Instant"), level = DeprecationLevel.WARNING ) @Serializable(with = InstantIso8601Serializer::class) @@ -98,7 +98,7 @@ public actual class Instant internal constructor(internal val value: java.time.I actual override fun toString(): String = value.toString() public actual companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.time.Clock"), level = DeprecationLevel.ERROR) + @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlin.time.Clock"), level = DeprecationLevel.ERROR) public actual fun now(): Instant = Instant(Clock.systemUTC().instant()) diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 69988c086..0ec2f6fc9 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -11,9 +11,9 @@ import kotlinx.datetime.internal.safeMultiply import kotlinx.datetime.internal.* import java.time.DateTimeException import java.time.temporal.* -import kotlinx.time.Instant -import kotlinx.time.toJavaInstant -import kotlinx.time.toKotlinInstant +import kotlin.time.Instant +import kotlin.time.toJavaInstant +import kotlin.time.toKotlinInstant import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds diff --git a/core/jvm/src/TimeZoneJvm.kt b/core/jvm/src/TimeZoneJvm.kt index 60490e53c..c9f68cdba 100644 --- a/core/jvm/src/TimeZoneJvm.kt +++ b/core/jvm/src/TimeZoneJvm.kt @@ -13,9 +13,9 @@ import kotlinx.serialization.Serializable import java.time.DateTimeException import java.time.ZoneId import java.time.ZoneOffset as jtZoneOffset -import kotlinx.time.Instant -import kotlinx.time.toJavaInstant -import kotlinx.time.toKotlinInstant +import kotlin.time.Instant +import kotlin.time.toJavaInstant +import kotlin.time.toKotlinInstant @Serializable(with = TimeZoneSerializer::class) public actual open class TimeZone internal constructor(internal val zoneId: ZoneId) { diff --git a/core/jvm/test/ConvertersTest.kt b/core/jvm/test/ConvertersTest.kt index 2d54ab7fa..26d05ec8e 100644 --- a/core/jvm/test/ConvertersTest.kt +++ b/core/jvm/test/ConvertersTest.kt @@ -14,8 +14,8 @@ import java.time.LocalDate as JTLocalDate import java.time.Period as JTPeriod import java.time.ZoneId import java.time.ZoneOffset as JTZoneOffset -import kotlinx.time.Instant -import kotlinx.time.* +import kotlin.time.Instant +import kotlin.time.* class ConvertersTest { diff --git a/core/jvm/test/InstantParsing.kt b/core/jvm/test/InstantParsing.kt index aac16b775..a50ee5213 100644 --- a/core/jvm/test/InstantParsing.kt +++ b/core/jvm/test/InstantParsing.kt @@ -2,8 +2,8 @@ package kotlinx.datetime import kotlinx.datetime.format.* import kotlin.test.* -import kotlinx.time.Instant -import kotlinx.time.* +import kotlin.time.Instant +import kotlin.time.* class InstantParsing { @Test diff --git a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt index 40fea247f..f50942db6 100644 --- a/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt +++ b/core/tzdbOnFilesystem/test/TimeZoneRulesCompleteTest.kt @@ -11,7 +11,7 @@ import kotlinx.datetime.internal.* import platform.posix.* import kotlin.io.encoding.* import kotlin.test.* -import kotlinx.time.Instant +import kotlin.time.Instant class TimeZoneRulesCompleteTest { @OptIn(ExperimentalEncodingApi::class) diff --git a/core/windows/test/TimeZoneRulesCompleteTest.kt b/core/windows/test/TimeZoneRulesCompleteTest.kt index eb1ff6538..12d4c7964 100644 --- a/core/windows/test/TimeZoneRulesCompleteTest.kt +++ b/core/windows/test/TimeZoneRulesCompleteTest.kt @@ -14,8 +14,8 @@ import platform.windows.* import kotlin.test.* import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds -import kotlinx.time.Instant -import kotlinx.time.Clock +import kotlin.time.Instant +import kotlin.time.Clock class TimeZoneRulesCompleteTest { diff --git a/fake-kotlinx-time/build.gradle.kts b/fake-kotlinx-time/build.gradle.kts deleted file mode 100644 index d8966591c..000000000 --- a/fake-kotlinx-time/build.gradle.kts +++ /dev/null @@ -1,102 +0,0 @@ -import java.util.Locale - -plugins { - id("kotlin-multiplatform") - id("org.jetbrains.kotlinx.kover") -} - -val mainJavaToolchainVersion: String by project -val serializationVersion: String by project - -java { - toolchain { languageVersion.set(JavaLanguageVersion.of(mainJavaToolchainVersion)) } -} - -kotlin { - explicitApi() - - // Tiers are in accordance with - // Tier 1 - macosX64() - macosArm64() - iosSimulatorArm64() - iosX64() - iosArm64() - // Tier 2 - linuxX64() - linuxArm64() - watchosSimulatorArm64() - watchosX64() - watchosArm32() - watchosArm64() - tvosSimulatorArm64() - tvosX64() - tvosArm64() - // Tier 3 - androidNativeArm32() - androidNativeArm64() - androidNativeX86() - androidNativeX64() - mingwX64() - watchosDeviceArm64() - // Deprecated - @Suppress("DEPRECATION") linuxArm32Hfp() - - applyDefaultHierarchyTemplate() - - jvm { - attributes { - attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) - } - } - - js { - nodejs { - } - compilations.all { - kotlinOptions { - sourceMap = true - moduleKind = "umd" - } - } - } - - - wasmJs { - nodejs { - } - } - - wasmWasi { - nodejs { - } - } - - sourceSets.all { - val suffixIndex = name.indexOfLast { it.isUpperCase() } - val targetName = name.substring(0, suffixIndex) - val suffix = name.substring(suffixIndex).toLowerCase(Locale.ROOT).takeIf { it != "main" } - kotlin.srcDir("$targetName/${suffix ?: "src"}") - resources.srcDir("$targetName/${suffix?.let { it + "Resources" } ?: "resources"}") - } - - targets.withType { - compilations["test"].kotlinOptions { - freeCompilerArgs += listOf("-trw") - } - } - - sourceSets { - commonMain { - dependencies { - } - } - - commonTest { - dependencies { - api("org.jetbrains.kotlin:kotlin-test") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") - } - } - } -} diff --git a/fake-kotlinx-time/common/src/Clock.kt b/fake-kotlinx-time/common/src/Clock.kt deleted file mode 100644 index c5e2feec7..000000000 --- a/fake-kotlinx-time/common/src/Clock.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -import kotlin.time.* - -/** - * A source of [Instant] values. - * - * See [Clock.System][Clock.System] for the clock instance that queries the operating system. - * - * It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a - * [Clock] explicitly to the necessary functions or classes. - * This way, tests can be written deterministically by providing custom [Clock] implementations - * to the system under test. - */ -public interface Clock { - /** - * Returns the [Instant] corresponding to the current time, according to this clock. - * - * Calling [now] later is not guaranteed to return a larger [Instant]. - * In particular, for [Clock.System], the opposite is completely expected, - * and it must be taken into account. - * See the [System] documentation for details. - * - * Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling - * leap seconds, [now] is not guaranteed to handle leap seconds in any specific way. - */ - public fun now(): Instant - - /** - * The [Clock] instance that queries the platform-specific system clock as its source of time knowledge. - * - * Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do, - * these increases will not necessarily correspond to the elapsed time. - * - * For example, when using [Clock.System], the following could happen: - * - [now] returns `2023-01-02T22:35:01Z`. - * - The system queries the Internet and recognizes that its clock needs adjusting. - * - [now] returns `2023-01-02T22:32:05Z`. - * - * When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic]. - * - * For improved testability, you should avoid using [Clock.System] directly in the implementation - * and pass a [Clock] explicitly instead. For example: - * - * @sample kotlinx.datetime.test.samples.ClockSamples.system - * @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection - */ - public object System : Clock { - override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now() - } - - /** A companion object used purely for namespacing. */ - public companion object { - - } -} diff --git a/fake-kotlinx-time/common/src/Instant.kt b/fake-kotlinx-time/common/src/Instant.kt deleted file mode 100644 index 24ebc7d13..000000000 --- a/fake-kotlinx-time/common/src/Instant.kt +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -import kotlin.math.absoluteValue -import kotlin.time.* -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.Duration.Companion.seconds - -/** - * A moment in time. - * - * A point in time must be uniquely identified in a way that is independent of a time zone. - * For example, `1970-01-01, 00:00:00` does not represent a moment in time since this would happen at different times - * in different time zones: someone in Tokyo would think it is already `1970-01-01` several hours earlier than someone in - * Berlin would. To represent such entities, use the `LocalDateTime` from `kotlinx-datetime`. - * In contrast, "the moment the clocks in London first showed 00:00 on Jan 1, 2000" is a specific moment - * in time, as is "1970-01-01, 00:00:00 UTC+0", so it can be represented as an [Instant]. - * - * `Instant` uses the UTC-SLS (smeared leap second) time scale. This time scale doesn't contain instants - * corresponding to leap seconds, but instead "smears" positive and negative leap seconds among the last 1000 seconds - * of the day when a leap second happens. - * - * ### Obtaining the current moment - * - * The [Clock] interface is the primary way to obtain the current moment: - * - * ``` - * val clock: Clock = Clock.System - * val instant = clock.now() - * ``` - * - * The [Clock.System] implementation uses the platform-specific system clock to obtain the current moment. - * Note that this clock is not guaranteed to be monotonic, and the user or the system may adjust it at any time, - * so it should not be used for measuring time intervals. - * For that, consider using [TimeSource.Monotonic] and [TimeMark] instead of [Clock.System] and [Instant]. - * - * ### Arithmetic operations - * - * The [plus] and [minus] operators can be used to add [Duration]s to and subtract them from an [Instant]: - * - * ``` - * Clock.System.now() + 5.seconds // 5 seconds from now - * ``` - * - * Also, there is a [minus] operator that returns the [Duration] representing the difference between two instants: - * - * ``` - * val kotlinRelease = Instant.parse("2016-02-15T02:00T12:00:00+03:00") - * val kotlinStableDuration = Clock.System.now() - kotlinRelease - * ``` - * - * ### Platform specifics - * - * On the JVM, there are `Instant.toJavaInstant()` and `java.time.Instant.toKotlinInstant()` - * extension functions to convert between `kotlin.time` and `java.time` objects used for the same purpose. - * Likewise, on JS, there are `Instant.toJSDate()` and `Date.toKotlinInstant()` extension functions. - * - * For technical reasons, converting [Instant] to and from Foundation's `NSDate` is provided in - * `kotlinx-datetime` via `Instant.toNSDate()` and `NSDate.toKotlinInstant()` extension functions. - * These functions will be made available in `kotlin.time` in the future. - * - * ### Construction, serialization, and deserialization - * - * [fromEpochSeconds] can be used to construct an instant from the number of seconds since - * `1970-01-01T00:00:00Z` (the Unix epoch). - * [epochSeconds] and [nanosecondsOfSecond] can be used to obtain the number of seconds and nanoseconds since the epoch. - * - * ``` - * val instant = Instant.fromEpochSeconds(1709898983, 123456789) - * instant.epochSeconds // 1709898983 - * instant.nanosecondsOfSecond // 123456789 - * ``` - * - * [fromEpochMilliseconds] allows constructing an instant from the number of milliseconds since the epoch. - * [toEpochMilliseconds] can be used to obtain the number of milliseconds since the epoch. - * Note that [Instant] supports nanosecond precision, so converting to milliseconds is a lossy operation. - * - * ``` - * val instant1 = Instant.fromEpochSeconds(1709898983, 123456789) - * instant1.nanosecondsOfSecond // 123456789 - * val milliseconds = instant1.toEpochMilliseconds() // 1709898983123 - * val instant2 = Instant.fromEpochMilliseconds(milliseconds) - * instant2.nanosecondsOfSecond // 123000000 - * ``` - * - * [parse] and [toString] methods can be used to obtain an [Instant] from and convert it to a string in the - * ISO 8601 extended format. - * - * ``` - * val instant = Instant.parse("2023-01-02T22:35:01+01:00") - * instant.toString() // 2023-01-02T21:35:01Z - * ``` - */ -public class Instant internal constructor( - /** - * The number of seconds from the epoch instant `1970-01-01T00:00:00Z` rounded down to a [Long] number. - * - * The difference between the rounded number of seconds and the actual number of seconds - * is returned by [nanosecondsOfSecond] property expressed in nanoseconds. - * - * Note that this number doesn't include leap seconds added or removed since the epoch. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.epochSeconds - */ - public val epochSeconds: Long, - /** - * The number of nanoseconds by which this instant is later than [epochSeconds] from the epoch instant. - * - * The value is always non-negative and lies in the range `0..999_999_999`. - * - * @see fromEpochSeconds - * @sample kotlinx.datetime.test.samples.InstantSamples.nanosecondsOfSecond - */ - public val nanosecondsOfSecond: Int -) : Comparable { - - init { - require(epochSeconds in MIN_SECOND..MAX_SECOND) { "Instant exceeds minimum or maximum instant" } - } - - /** - * Returns the number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Any fractional part of a millisecond is rounded toward zero to the whole number of milliseconds. - * - * If the result does not fit in [Long], - * returns [Long.MAX_VALUE] for a positive result or [Long.MIN_VALUE] for a negative result. - * - * @see fromEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.toEpochMilliseconds - */ - // org.threeten.bp.Instant#toEpochMilli - public fun toEpochMilliseconds(): Long = try { - if (epochSeconds >= 0) { - val millis = safeMultiply(epochSeconds, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI).toLong()) - } else { - // prevent an overflow in seconds * 1000 - // instead of going form the second farther away from 0 - // going toward 0 - // we go from the second closer to 0 away from 0 - // that way we always stay in the valid long range - // seconds + 1 can not overflow because it is negative - val millis = safeMultiply(epochSeconds + 1, MILLIS_PER_ONE.toLong()) - safeAdd(millis, (nanosecondsOfSecond / NANOS_PER_MILLI - MILLIS_PER_ONE).toLong()) - } - } catch (_: ArithmeticException) { - if (epochSeconds > 0) Long.MAX_VALUE else Long.MIN_VALUE - } - - // org.threeten.bp.Instant#plus(long, long) - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - internal fun plus(secondsToAdd: Long, nanosToAdd: Long): Instant { - if ((secondsToAdd or nanosToAdd) == 0L) { - return this - } - val newEpochSeconds: Long = safeAdd(safeAdd(epochSeconds, secondsToAdd), (nanosToAdd / NANOS_PER_ONE)) - val newNanosToAdd = nanosToAdd % NANOS_PER_ONE - val nanoAdjustment = (nanosecondsOfSecond + newNanosToAdd) // safe int+NANOS_PER_ONE - return fromEpochSecondsThrowing(newEpochSeconds, nanoAdjustment) - } - - /** - * Returns an instant that is the result of adding the specified [duration] to this instant. - * - * If the [duration] is positive, the returned instant is later than this instant. - * If the [duration] is negative, the returned instant is earlier than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours, but in some time zones, - * some days can be shorter or longer because clocks are shifted. - * Consider using `kotlinx-datetime` for arithmetic operations that take time zone transitions into account. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.plusDuration - */ - public operator fun plus(duration: Duration): Instant = duration.toComponents { secondsToAdd, nanosecondsToAdd -> - try { - plus(secondsToAdd, nanosecondsToAdd.toLong()) - } catch (_: IllegalArgumentException) { - if (duration.isPositive()) MAX else MIN - } catch (_: ArithmeticException) { - if (duration.isPositive()) MAX else MIN - } - } - - /** - * Returns an instant that is the result of subtracting the specified [duration] from this instant. - * - * If the [duration] is positive, the returned instant is earlier than this instant. - * If the [duration] is negative, the returned instant is later than this instant. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * - * **Pitfall**: [Duration.Companion.days] are multiples of 24 hours, but in some time zones, - * some days can be shorter or longer because clocks are shifted. - * Consider using `kotlinx-datetime` for arithmetic operations that take time zone transitions into account. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusDuration - */ - public operator fun minus(duration: Duration): Instant = plus(-duration) - - // questionable - /** - * Returns the [Duration] between two instants: [other] and `this`. - * - * The duration returned is positive if this instant is later than the other, - * and negative if this instant is earlier than the other. - * - * The result is never clamped, but note that for instants that are far apart, - * the value returned may represent the duration between them inexactly due to the loss of precision. - * - * Note that sources of [Instant] values (in particular, [Clock]) are not guaranteed to be in sync with each other - * or even monotonic, so the result of this operation may be negative even if the other instant was observed later - * than this one, or vice versa. - * For measuring time intervals, consider using [TimeSource.Monotonic]. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.minusInstant - */ - public operator fun minus(other: Instant): Duration = - (this.epochSeconds - other.epochSeconds).seconds + // won't overflow given the instant bounds - (this.nanosecondsOfSecond - other.nanosecondsOfSecond).nanoseconds - - /** - * Compares `this` instant with the [other] instant. - * Returns zero if this instant represents the same moment as the other (meaning they are equal to one another), - * a negative number if this instant is earlier than the other, - * and a positive number if this instant is later than the other. - */ - public override operator fun compareTo(other: Instant): Int { - val s = epochSeconds.compareTo(other.epochSeconds) - if (s != 0) { - return s - } - return nanosecondsOfSecond.compareTo(other.nanosecondsOfSecond) - } - - override fun equals(other: Any?): Boolean = - this === other || other is Instant && this.epochSeconds == other.epochSeconds - && this.nanosecondsOfSecond == other.nanosecondsOfSecond - - // org.threeten.bp.Instant#hashCode - override fun hashCode(): Int = - (epochSeconds xor (epochSeconds ushr 32)).toInt() + 51 * nanosecondsOfSecond - - /** - * Converts this instant to the ISO 8601 string representation, for example, `2023-01-02T23:40:57.120Z`. - * - * The representation uses the UTC-SLS time scale instead of UTC. - * In practice, this means that leap second handling will not be readjusted to the UTC. - * Leap seconds will not be added or skipped, so it is impossible to acquire a string - * where the component for seconds is 60, and for any day, it's possible to observe 23:59:59. - * - * @see parse - * - * @sample kotlinx.datetime.test.samples.InstantSamples.toStringSample - */ - public override fun toString(): String = formatIso(this) - - public companion object { - @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) - public fun now(): Instant = currentTime() - - /** - * Returns an [Instant] that is [epochMilliseconds] number of milliseconds from the epoch instant `1970-01-01T00:00:00Z`. - * - * Every value of [epochMilliseconds] is guaranteed to be representable as an [Instant]. - * - * Note that [Instant] also supports nanosecond precision via [fromEpochSeconds]. - * - * @see Instant.toEpochMilliseconds - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochMilliseconds - */ - // org.threeten.bp.Instant#ofEpochMilli - public fun fromEpochMilliseconds(epochMilliseconds: Long): Instant { - val epochSeconds = epochMilliseconds.floorDiv(MILLIS_PER_ONE.toLong()) - val nanosecondsOfSecond = (epochMilliseconds.mod(MILLIS_PER_ONE.toLong()) * NANOS_PER_MILLI).toInt() - return when { - epochSeconds < MIN_SECOND -> MIN - epochSeconds > MAX_SECOND -> MAX - else -> fromEpochSeconds(epochSeconds, nanosecondsOfSecond) - } - } - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSeconds - */ - // org.threeten.bp.Instant#ofEpochSecond(long, long) - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant = - try { - fromEpochSecondsThrowing(epochSeconds, nanosecondAdjustment) - } catch (_: ArithmeticException) { - if (epochSeconds > 0) MAX else MIN - } catch (_: IllegalArgumentException) { - if (epochSeconds > 0) MAX else MIN - } - - /** - * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` - * and the [nanosecondAdjustment] number of nanoseconds from the whole second. - * - * The return value is clamped to the boundaries of [Instant] if the result exceeds them. - * In any case, it is guaranteed that instants between [DISTANT_PAST] and [DISTANT_FUTURE] can be represented. - * - * [fromEpochMilliseconds] is a similar function for when input data only has millisecond precision. - * - * @see Instant.epochSeconds - * @see Instant.nanosecondsOfSecond - * @sample kotlinx.datetime.test.samples.InstantSamples.fromEpochSecondsIntNanos - */ - public fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = - fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) - - /** - * Parses an ISO 8601 string that represents an instant (for example, `2020-08-30T18:43:00Z`). - * - * Guaranteed to parse all strings that [Instant.toString] produces. - * - * Examples of instants in the ISO 8601 format: - * - `2020-08-30T18:43:00Z` - * - `2020-08-30T18:43:00.50Z` - * - `2020-08-30T18:43:00.123456789Z` - * - `2020-08-30T18:40:00+03:00` - * - `2020-08-30T18:40:00+03:30:20` - * * `2020-01-01T23:59:59.123456789+01` - * * `+12020-01-31T23:59:59Z` - * - * See ISO-8601-1:2019, 5.4.2.1b), excluding the format without the offset. - * - * The string is considered to represent time on the UTC-SLS time scale instead of UTC. - * In practice, this means that, even if there is a leap second on the given day, it will not affect how the - * time is parsed, even if it's in the last 1000 seconds of the day. - * Instead, even if there is a negative leap second on the given day, 23:59:59 is still considered a valid time. - * 23:59:60 is invalid on UTC-SLS, so parsing it will fail. - * - * @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [Instant] are exceeded. - * - * @see Instant.toString for formatting. - * @sample kotlinx.datetime.test.samples.InstantSamples.parsing - */ - public fun parse(input: CharSequence): Instant = parseIso(input) - - - /** - * An instant value that is far in the past. - * - * [isDistantPast] returns true for this value and all earlier ones. - */ - public val DISTANT_PAST: Instant // -100001-12-31T23:59:59.999999999Z - get() = fromEpochSeconds(DISTANT_PAST_SECONDS, 999_999_999) - - /** - * An instant value that is far in the future. - * - * [isDistantFuture] returns true for this value and all later ones. - */ - public val DISTANT_FUTURE: Instant // +100000-01-01T00:00:00Z - get() = fromEpochSeconds(DISTANT_FUTURE_SECONDS, 0) - - /** - * @throws ArithmeticException if arithmetic overflow occurs - * @throws IllegalArgumentException if the boundaries of Instant are overflown - */ - private fun fromEpochSecondsThrowing(epochSeconds: Long, nanosecondAdjustment: Long): Instant { - val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong())) - val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt() - return Instant(secs, nos) - } - - internal val MIN = Instant(MIN_SECOND, 0) - internal val MAX = Instant(MAX_SECOND, 999_999_999) - } -} - -/** - * Returns true if the instant is [Instant.DISTANT_PAST] or earlier. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantPast - */ -public val Instant.isDistantPast: Boolean - get() = this <= Instant.DISTANT_PAST - -/** - * Returns true if the instant is [Instant.DISTANT_FUTURE] or later. - * - * @sample kotlinx.datetime.test.samples.InstantSamples.isDistantFuture - */ -public val Instant.isDistantFuture: Boolean - get() = this >= Instant.DISTANT_FUTURE - -internal const val DISTANT_PAST_SECONDS = -3217862419201 -internal const val DISTANT_FUTURE_SECONDS = 3093527980800 - -internal expect fun currentTime(): Instant - -/** - * The minimum supported epoch second. - */ -private const val MIN_SECOND = -31557014167219200L // -1000000000-01-01T00:00:00Z - -/** - * The maximum supported epoch second. - */ -private const val MAX_SECOND = 31556889864403199L // +1000000000-12-31T23:59:59 - -private class UnboundedLocalDateTime( - val year: Int, - val month: Int, - val day: Int, - val hour: Int, - val minute: Int, - val second: Int, - val nanosecond: Int, -) { - fun toInstant(offsetSeconds: Int): Instant { - val epochSeconds = run { - // org.threeten.bp.LocalDate#toEpochDay - val epochDays = run { - val y = year.toLong() - var total = 365 * y - if (y >= 0) { - total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400 - } else { - total -= y / -4 - y / -100 + y / -400 - } - total += ((367 * month - 362) / 12) - total += day - 1 - if (month > 2) { - total-- - if (!isLeapYear(year)) { - total-- - } - } - total - DAYS_0000_TO_1970 - } - // org.threeten.bp.LocalTime#toSecondOfDay - val daySeconds = hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second - // org.threeten.bp.chrono.ChronoLocalDateTime#toEpochSecond - epochDays * 86400L + daySeconds - offsetSeconds - } - if (epochSeconds < Instant.MIN.epochSeconds || epochSeconds > Instant.MAX.epochSeconds) - throw IllegalArgumentException( - "The parsed date is outside the range representable by Instant (Unix epoch second $epochSeconds)" - ) - return Instant.fromEpochSeconds(epochSeconds, nanosecond) - } - - override fun toString(): String = "UnboundedLocalDateTime($year-$month-$day $hour:$minute:$second.$nanosecond)" - - companion object { - fun fromInstant(instant: Instant, offsetSeconds: Int): UnboundedLocalDateTime { - val localSecond: Long = instant.epochSeconds + offsetSeconds - val epochDays = localSecond.floorDiv(SECONDS_PER_DAY.toLong()) - val secsOfDay = localSecond.mod(SECONDS_PER_DAY.toLong()).toInt() - val year: Int - val month: Int - val day: Int - // org.threeten.bp.LocalDate#toEpochDay - run { - var zeroDay = epochDays + DAYS_0000_TO_1970 - // find the march-based year - zeroDay -= 60 // adjust to 0000-03-01 so leap day is at end of four year cycle - - var adjust = 0L - if (zeroDay < 0) { // adjust negative years to positive for calculation - val adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1 - adjust = adjustCycles * 400 - zeroDay += -adjustCycles * DAYS_PER_CYCLE - } - var yearEst = ((400 * zeroDay + 591) / DAYS_PER_CYCLE) - var doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400) - if (doyEst < 0) { // fix estimate - yearEst-- - doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400) - } - yearEst += adjust // reset any negative year - - val marchDoy0 = doyEst.toInt() - - // convert march-based values back to january-based - val marchMonth0 = (marchDoy0 * 5 + 2) / 153 - month = (marchMonth0 + 2) % 12 + 1 - day = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1 - year = (yearEst + marchMonth0 / 10).toInt() - } - val hours = (secsOfDay / SECONDS_PER_HOUR) - val secondWithoutHours = secsOfDay - hours * SECONDS_PER_HOUR - val minutes = (secondWithoutHours / SECONDS_PER_MINUTE) - val second = secondWithoutHours - minutes * SECONDS_PER_MINUTE - return UnboundedLocalDateTime(year, month, day, hours, minutes, second, instant.nanosecondsOfSecond) - } - } -} - -private fun parseIso(isoString: CharSequence): Instant { - fun parseFailure(error: String): Nothing { - throw IllegalArgumentException("$error when parsing an Instant from $isoString") - } - fun expect(what: String, where: Int, predicate: (Char) -> Boolean) { - val c = isoString[where] - if (!predicate(c)) { - parseFailure("Expected $what, but got $c at position $where") - } - } - val s = isoString - var i = 0 - require(s.isNotEmpty()) { "An empty string is not a valid Instant" } - val yearSign = when (val c = s[i]) { - '+', '-' -> { ++i; c } - else -> ' ' - } - val yearStart = i - var absYear = 0 - while (i < s.length && s[i] in '0'..'9') { - absYear = absYear * 10 + (s[i] - '0') - ++i - } - val year = when { - i > yearStart + 10 -> { - parseFailure("Expected at most 10 digits for the year number, got ${i - yearStart}") - } - i == yearStart + 10 && s[yearStart] >= '2' -> { - parseFailure("Expected at most 9 digits for the year number or year 1000000000, got ${i - yearStart}") - } - i - yearStart < 4 -> { - parseFailure("The year number must be padded to 4 digits, got ${i - yearStart} digits") - } - else -> { - if (yearSign == '+' && i - yearStart == 4) { - parseFailure("The '+' sign at the start is only valid for year numbers longer than 4 digits") - } - if (yearSign == ' ' && i - yearStart != 4) { - parseFailure("A '+' or '-' sign is required for year numbers longer than 4 digits") - } - if (yearSign == '-') -absYear else absYear - } - } - // reading at least -MM-DDTHH:MM:SSZ - // 0123456789012345 16 chars - if (s.length < i + 16) { - parseFailure("The input string is too short") - } - expect("'-'", i) { it == '-' } - expect("'-'", i + 3) { it == '-' } - expect("'T' or 't'", i + 6) { it == 'T' || it == 't' } - expect("':'", i + 9) { it == ':' } - expect("':'", i + 12) { it == ':' } - for (j in listOf(1, 2, 4, 5, 7, 8, 10, 11, 13, 14)) { - expect("an ASCII digit", i + j) { it in '0'..'9' } - } - fun twoDigitNumber(index: Int) = s[index].code * 10 + s[index + 1].code - '0'.code * 11 - val month = twoDigitNumber(i + 1) - val day = twoDigitNumber(i + 4) - val hour = twoDigitNumber(i + 7) - val minute = twoDigitNumber(i + 10) - val second = twoDigitNumber(i + 13) - val nanosecond = if (s[i + 15] == '.') { - val fractionStart = i + 16 - i = fractionStart - var fraction = 0 - while (i < s.length && s[i] in '0'..'9') { - fraction = fraction * 10 + (s[i] - '0') - ++i - } - if (i - fractionStart in 1..9) { - fraction * POWERS_OF_TEN[fractionStart + 9 - i] - } else { - parseFailure("1..9 digits are supported for the fraction of the second, got {i - fractionStart}") - } - } else { - i += 15 - 0 - } - val offsetSeconds = when (val sign = s.getOrNull(i)) { - null -> { - parseFailure("The UTC offset at the end of the string is missing") - } - 'z', 'Z' -> if (s.length == i + 1) { - 0 - } else { - parseFailure("Extra text after the instant at position ${i + 1}") - } - '-', '+' -> { - val offsetStrLength = s.length - i - if (offsetStrLength % 3 != 0) { parseFailure("Invalid UTC offset string '${s.substring(i)}'") } - if (offsetStrLength > 9) { parseFailure("The UTC offset string '${s.substring(i)}' is too long") } - for (j in listOf(3, 6)) { - if ((s.getOrNull(i + j) ?: break) != ':') - parseFailure("Expected ':' at index ${i + j}, got '${s[i + j]}'") - } - for (j in listOf(1, 2, 4, 5, 7, 8)) { - if ((s.getOrNull(i + j) ?: break) !in '0'..'9') - parseFailure("Expected a digit at index ${i + j}, got '${s[i + j]}'") - } - val offsetHour = twoDigitNumber(i + 1) - val offsetMinute = if (offsetStrLength > 3) { twoDigitNumber(i + 4) } else { 0 } - val offsetSecond = if (offsetStrLength > 6) { twoDigitNumber(i + 7) } else { 0 } - if (offsetMinute > 59) { parseFailure("Expected offset-minute-of-hour in 0..59, got $offsetMinute") } - if (offsetSecond > 59) { parseFailure("Expected offset-second-of-minute in 0..59, got $offsetSecond") } - if (offsetHour > 17 && !(offsetHour == 18 && offsetMinute == 0 && offsetSecond == 0)) { - parseFailure("Expected an offset in -18:00..+18:00, got $sign$offsetHour:$offsetMinute:$offsetSecond") - } - (offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == '-') -1 else 1 - } - else -> { - parseFailure("Expected the UTC offset at position $i, got '$sign'") - } - } - if (month !in 1..12) { parseFailure("Expected a month number in 1..12, got $month") } - if (day !in 1..month.monthLength(isLeapYear(year))) { - parseFailure("Expected a valid day-of-month for $year-$month, got $day") - } - if (hour > 23) { parseFailure("Expected hour in 0..23, got $hour") } - if (minute > 59) { parseFailure("Expected minute-of-hour in 0..59, got $minute") } - if (second > 59) { parseFailure("Expected second-of-minute in 0..59, got $second") } - return UnboundedLocalDateTime(year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds) -} - -private fun formatIso(instant: Instant): String = buildString { - val ldt = UnboundedLocalDateTime.fromInstant(instant, 0) - fun Appendable.appendTwoDigits(number: Int) { - if (number < 10) append('0') - append(number) - } - run { - val number = ldt.year - when { - number.absoluteValue < 1_000 -> { - val innerBuilder = StringBuilder() - if (number >= 0) { - innerBuilder.append((number + 10_000)).deleteAt(0) - } else { - innerBuilder.append((number - 10_000)).deleteAt(1) - } - append(innerBuilder) - } - else -> { - if (number >= 10_000) append('+') - append(number) - } - } - } - append('-') - appendTwoDigits(ldt.month) - append('-') - appendTwoDigits(ldt.day) - append('T') - appendTwoDigits(ldt.hour) - append(':') - appendTwoDigits(ldt.minute) - append(':') - appendTwoDigits(ldt.second) - if (ldt.nanosecond != 0) { - append('.') - var zerosToStrip = 0 - while (ldt.nanosecond % POWERS_OF_TEN[zerosToStrip + 1] == 0) { - ++zerosToStrip - } - zerosToStrip -= (zerosToStrip.mod(3)) // rounding down to a multiple of 3 - val numberToOutput = ldt.nanosecond / POWERS_OF_TEN[zerosToStrip] - append((numberToOutput + POWERS_OF_TEN[9 - zerosToStrip]).toString().substring(1)) - } - append('Z') -} - -/** - * All code below was taken from various places of https://github.com/ThreeTen/threetenbp with few changes - */ - -/** - * The number of days in a 400 year cycle. - */ -private const val DAYS_PER_CYCLE = 146097 - -/** - * The number of days from year zero to year 1970. - * There are five 400 year cycles from year zero to 2000. - * There are 7 leap years from 1970 to 2000. - */ -private const val DAYS_0000_TO_1970 = DAYS_PER_CYCLE * 5 - (30 * 365 + 7) - -/** - * Safely adds two long values. - * throws [ArithmeticException] if the result overflows a long - */ -private fun safeAdd(a: Long, b: Long): Long { - val sum = a + b - // check for a change of sign in the result when the inputs have the same sign - if ((a xor sum) < 0 && (a xor b) >= 0) { - throw ArithmeticException("Addition overflows a long: $a + $b") - } - return sum -} - -/** - * Safely multiply a long by a long. - * - * @param a the first value - * @param b the second value - * @return the new total - * @throws ArithmeticException if the result overflows a long - */ -private fun safeMultiply(a: Long, b: Long): Long { - if (b == 1L) { - return a - } - if (a == 1L) { - return b - } - if (a == 0L || b == 0L) { - return 0 - } - val total = a * b - if (total / b != a || a == Long.MIN_VALUE && b == -1L || b == Long.MIN_VALUE && a == -1L) { - throw ArithmeticException("Multiplication overflows a long: $a * $b") - } - return total -} - -private const val SECONDS_PER_HOUR = 60 * 60 - -private const val SECONDS_PER_MINUTE = 60 - -private const val HOURS_PER_DAY = 24 - -private const val SECONDS_PER_DAY: Int = SECONDS_PER_HOUR * HOURS_PER_DAY - -internal const val NANOS_PER_ONE = 1_000_000_000 -private const val NANOS_PER_MILLI = 1_000_000 -private const val MILLIS_PER_ONE = 1_000 - -// org.threeten.bp.chrono.IsoChronology#isLeapYear -internal fun isLeapYear(year: Int): Boolean { - val prolepticYear: Long = year.toLong() - return prolepticYear and 3 == 0L && (prolepticYear % 100 != 0L || prolepticYear % 400 == 0L) -} - -private fun Int.monthLength(isLeapYear: Boolean): Int = - when (this) { - 2 -> if (isLeapYear) 29 else 28 - 4, 6, 9, 11 -> 30 - else -> 31 - } - -private val POWERS_OF_TEN = intArrayOf( - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000 -) diff --git a/fake-kotlinx-time/common/test/InstantTest.kt b/fake-kotlinx-time/common/test/InstantTest.kt deleted file mode 100644 index c42fa9e2a..000000000 --- a/fake-kotlinx-time/common/test/InstantTest.kt +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time.test - -import kotlinx.time.* -import kotlin.math.absoluteValue -import kotlin.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 { - - @Test - fun testNow() { - val instant = Clock.System.now() - val millis = instant.toEpochMilliseconds() - - assertTrue(millis > 1_500_000_000_000L) - - println(instant) - println(instant.toEpochMilliseconds()) - - 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) - - println("this: $instant, next: $nextInstant, diff: ${diff.toIsoString()}") - } - - @Test - fun addingMultiplesOf2_32() { - val pow2_32 = 1L shl 32 - val instant1 = Instant.fromEpochSeconds(0) - val instant2 = instant1.plus(pow2_32.nanoseconds) - assertEquals(pow2_32 / NANOS_PER_ONE, instant2.epochSeconds) - assertEquals(pow2_32 % NANOS_PER_ONE, instant2.nanosecondsOfSecond.toLong()) - - val instant3 = instant1.plus(pow2_32.seconds) - assertEquals(pow2_32, instant3.epochSeconds) - } - - /* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - @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) - } - -} - -class InstantRangeTest { - 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 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 timeBasedUnitArithmeticOutOfRange() { - // Instant.plus(Long, DateTimeUnit.TimeBased) - // 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) - } -} - -class InstantIsoStringsTest { - - @Test - fun parseDates() { - fun Int.zeroPadded(digits: Int): String = when { - this >= 0 -> toString().padStart(digits, '0') - else -> "-${absoluteValue.toString().padStart(digits, '0')}" - } - - fun localDateToString(year: Int, month: Int, day: Int) = - "${year.zeroPadded(4)}-${month.zeroPadded(2)}-${day.zeroPadded(2)}" - - // only works for 1-4-digit years - fun assertMonthBoundariesAreCorrect(year: Int, month: Int, lastDayOfMonth: Int) { - val validString = "${localDateToString(year, month, lastDayOfMonth)}T23:59:59Z" - val invalidString = "${localDateToString(year, month, lastDayOfMonth + 1)}T23:59:59Z" - Instant.parse(validString) // shouldn't throw - assertInvalidFormat(invalidString) { Instant.parse(invalidString) } - } - - val nonLeapYears = listOf( - 1970, 1971, 1973, 1974, 1975, 2021, 2022, 2023, 2100, 1100, 1, 2, 3, 5, -1, -2, -1971, 100, -100 - ) - val leapYears = listOf( - 0, 1972, 1976, 1980, 2000, 1200, 400, -400, -4, -8, - ) - for ((month, lastDayOfMonth) in arrayOf( - 1 to 31, 3 to 31, 4 to 30, 5 to 31, 6 to 30, - 7 to 31, 8 to 31, 9 to 30, 10 to 31, 11 to 30, 12 to 31, - )) { - for (year in nonLeapYears + leapYears) { - assertMonthBoundariesAreCorrect(year, month, lastDayOfMonth) - } - } - for (leapYear in leapYears) { - assertMonthBoundariesAreCorrect(leapYear, 2, 29) - } - for (nonLeapYear in nonLeapYears) { - assertMonthBoundariesAreCorrect(nonLeapYear, 2, 28) - } - } - - @Test - fun parseIsoString() { - for ((str, seconds, nanos) in arrayOf>( - // all components are taken into account - Triple("1970-01-01T00:00:00Z", 0, 0), - Triple("1970-01-01T00:00:00.000000001Z", 0, 1), - Triple("1970-01-01T00:00:00.100Z", 0, 100000000), - Triple("1970-01-01T00:00:01Z", 1, 0), - Triple("1970-01-01T00:01:00Z", 60, 0), - Triple("1970-01-01T00:01:01Z", 61, 0), - Triple("1970-01-01T00:01:01.000000001Z", 61, 1), - Triple("1970-01-01T01:00:00Z", 3600, 0), - Triple("1970-01-01T01:01:01.000000001Z", 3661, 1), - Triple("1970-01-02T01:01:01.100Z", 90061, 100000000), - Triple("1970-02-02T01:01:01.100Z", 31 * 86400 + 90061, 100000000), - Triple("1971-02-02T01:01:01.100Z", (365 + 31) * 86400 + 90061, 100000000), - // how many digits get output for various precision of the sub-second portion - Triple("1970-01-01T00:00:00.100Z", 0, 100_000_000), - Triple("1970-01-01T00:00:00.010Z", 0, 10_000_000), - Triple("1970-01-01T00:00:00.001Z", 0, 1_000_000), - Triple("1970-01-01T00:00:00.000100Z", 0, 100_000), - Triple("1970-01-01T00:00:00.000010Z", 0, 10_000), - Triple("1970-01-01T00:00:00.000001Z", 0, 1_000), - Triple("1970-01-01T00:00:00.000000100Z", 0, 100), - Triple("1970-01-01T00:00:00.000000010Z", 0, 10), - Triple("1970-01-01T00:00:00.000000001Z", 0, 1), - // random data queried from java.time - Triple("+51861-09-21T11:07:43.782719883Z", 1574430692863, 782719883), - Triple("+395069-04-30T01:28:37.454777349Z", 12405016603717, 454777349), - Triple("-551259-03-05T08:01:36.195722269Z", -17458215523104, 195722269), - Triple("+498403-02-11T17:47:05.156642423Z", 15665915958425, 156642423), - Triple("+283686-10-14T23:00:25.666521845Z", 8890123158025, 666521845), - Triple("-910329-04-04T09:27:54.456784744Z", -28789367639526, 456784744), - Triple("-37222-03-21T18:04:37.006055123Z", -1236773166923, 6055123), - Triple("-189377-03-30T01:37:14.288808090Z", -6038320515766, 288808090), - Triple("-67394-03-24T03:19:41.794404047Z", -2188909341619, 794404047), - Triple("-870649-05-27T13:47:39.925150102Z", -27537183223941, 925150102), - Triple("+94020-04-10T14:51:21.569206089Z", 2904826114281, 569206089), - Triple("-945485-07-11T23:28:58.240153828Z", -29898775384262, 240153828), - Triple("-73722-02-22T11:19:54.364548772Z", -2388604250406, 364548772), - Triple("-645899-05-17T16:44:21.522135477Z", -20444759104539, 522135477), - Triple("-702594-10-20T10:13:53.212104714Z", -22233867083167, 212104714), - Triple("-442579-11-22T01:35:44.591216727Z", -14028583357456, 591216727), - Triple("-849915-06-25T01:28:27.625015449Z", -26882878833093, 625015449), - Triple("-481897-08-13T05:44:47.077814711Z", -15269348340913, 77814711), - Triple("+295919-02-07T15:47:37.850981753Z", 9276137682457, 850981753), - Triple("+967334-01-15T15:08:10.235167075Z", 30463946694490, 235167075), - Triple("+774237-04-30T16:00:32.810606451Z", 24370403011232, 810606451), - Triple("+792959-05-03T08:18:31.616194572Z", 24961212490711, 616194572), - Triple("-261823-02-16T03:17:35.085815500Z", -8324498983345, 85815500), - Triple("+931062-03-22T17:04:54.135075640Z", 29319318637494, 135075640), - Triple("+623320-01-26T03:08:05.121769356Z", 19607914264085, 121769356), - Triple("+322804-03-06T11:31:24.788006817Z", 10124548774284, 788006817), - Triple("-784322-04-03T21:25:19.666588404Z", -24812970806081, 666588404), - Triple("+403293-01-07T05:59:41.601460200Z", 12664531288781, 601460200), - Triple("-835821-06-01T00:52:15.782852248Z", -26438117296065, 782852248), - Triple("+222483-07-15T08:29:55.019931345Z", 6958735086595, 19931345), - Triple("-663595-09-05T04:36:24.110433196Z", -21003181356216, 110433196), - Triple("+166626-02-15T22:16:34.070665743Z", 5196045449794, 70665743), - Triple("-517158-01-02T22:52:24.155574933Z", -16382097162456, 155574933), - Triple("+850155-01-02T10:25:31.349473798Z", 26766133467931, 349473798), - Triple("-967697-04-25T20:43:33.328060156Z", -30599725115787, 328060156), - Triple("+437131-04-26T07:32:58.134219875Z", 13732364705578, 134219875), - Triple("+372920-11-25T13:38:22.852562723Z", 11706079786702, 852562723), - Triple("+169255-09-07T11:28:18.481625778Z", 5279026303698, 481625778), - Triple("-980786-08-18T17:05:22.581779094Z", -31012764044078, 581779094), - Triple("+182945-05-25T20:39:24.545585221Z", 5711031952764, 545585221), - Triple("+300811-12-15T02:53:38.676752671Z", 9430541175218, 676752671), - Triple("-807816-01-18T18:04:26.291749218Z", -25554376389334, 291749218), - Triple("-53033-12-30T22:02:01.398533618Z", -1735695568679, 398533618), - Triple("-354903-06-14T10:08:46.111648055Z", -11261809864274, 111648055), - Triple("+842009-03-11T23:58:06.537554993Z", 26509076495886, 537554993), - Triple("-391976-11-09T04:16:17.862484469Z", -12431707962223, 862484469), - Triple("-733019-10-28T17:07:13.450343935Z", -23193986539967, 450343935), - Triple("+595280-03-05T23:36:27.765851400Z", 18723060833787, 765851400), - Triple("-930296-07-17T03:33:33.094509320Z", -29419456335987, 94509320), - Triple("+609508-02-29T10:58:02.703241053Z", 19172052557882, 703241053), - Triple("+996233-06-25T06:01:55.647461964Z", 31375924927315, 647461964), - Triple("-93200-12-06T21:29:56.140938343Z", -3003245692204, 140938343), - Triple("+794143-07-02T09:49:35.585085194Z", 24998581100975, 585085194), - Triple("-783550-12-31T17:10:16.577723428Z", -24788585371784, 577723428), - Triple("-240168-11-03T17:22:09.108424624Z", -7641110702271, 108424624), - Triple("+613419-02-15T12:00:07.012460989Z", 19295470641607, 12460989), - Triple("-521405-03-25T02:03:46.552711998Z", -16516112536574, 552711998), - Triple("-938829-01-22T16:48:43.582709371Z", -29688747030677, 582709371), - Triple("+916785-05-16T21:54:45.983221956Z", 28868784818085, 983221956), - Triple("+482425-06-09T04:24:32.683186155Z", 15161709183872, 683186155), - Triple("+622585-08-20T05:45:52.555088343Z", 19584737819152, 555088343), - Triple("-451048-11-02T01:49:29.076392891Z", -14295840847831, 76392891), - Triple("+721083-09-17T00:31:34.648020241Z", 22693036811494, 648020241), - Triple("+235979-10-28T12:07:33.706273641Z", 7384636728453, 706273641), - Triple("+285234-04-12T18:30:25.215363003Z", 8938957285825, 215363003), - Triple("-917176-03-10T10:03:25.943265324Z", -29005440213395, 943265324), - Triple("-381932-09-05T02:47:17.004960541Z", -12114755529163, 4960541), - Triple("-52158-11-11T09:38:45.489915403Z", -1708087530075, 489915403), - Triple("-584290-11-15T20:15:24.377620606Z", -18500551127076, 377620606), - Triple("-645616-05-05T17:36:59.941608628Z", -20435829488581, 941608628), - Triple("+794405-06-22T21:08:20.853641989Z", 25006848239300, 853641989), - Triple("+986590-08-01T05:15:25.827177433Z", 31071624470125, 827177433), - Triple("+527158-02-06T12:34:35.088546391Z", 16573335654875, 88546391), - Triple("-513116-05-01T07:28:44.448204123Z", -16254533665876, 448204123), - Triple("+397065-10-19T21:59:05.831855226Z", 12468019211945, 831855226), - Triple("+312769-04-26T11:33:07.802217284Z", 9807879123187, 802217284), - Triple("+682473-04-14T01:00:38.067076018Z", 21474609498038, 67076018), - Triple("+731560-02-15T02:15:06.599802467Z", 23023640456106, 599802467), - Triple("-877354-10-27T22:55:02.723751549Z", -27748759338298, 723751549), - Triple("-746193-01-02T07:19:56.258497483Z", -23609743807204, 258497483), - Triple("-822112-07-28T08:55:19.319285417Z", -26005498038281, 319285417), - Triple("-400365-04-30T00:05:51.210582736Z", -12696455980449, 210582736), - Triple("+436254-07-11T18:08:06.937065549Z", 13704695921286, 937065549), - Triple("-340854-01-07T03:17:32.367173472Z", -10818479997748, 367173472), - Triple("-985221-04-25T22:57:01.511559459Z", -31152729085379, 511559459), - Triple("+859861-09-01T02:21:20.289341591Z", 27072446149280, 289341591), - Triple("-0131-07-16T10:47:54.756333457Z", -66284140326, 756333457), - Triple("-327041-11-18T22:55:21.885337272Z", -10382556503079, 885337272), - Triple("-268616-05-06T10:27:54.420166505Z", -8538858480726, 420166505), - Triple("-228012-05-16T15:26:54.680432991Z", -7257519160386, 680432991), - Triple("+857168-09-12T13:29:36.945689251Z", 26987464272576, 945689251), - Triple("-974181-04-12T08:47:35.627678735Z", -30804341526745, 627678735), - Triple("-435700-10-20T22:33:13.897477229Z", -13811505874007, 897477229), - Triple("-507467-01-19T23:06:05.156792267Z", -16076277276835, 156792267), - Triple("-382257-11-19T08:00:10.407963305Z", -12125005142390, 407963305), - Triple("+83082-01-04T20:18:56.409867424Z", 2559647852336, 409867424), - Triple("-916839-09-12T22:45:39.091941363Z", -28994789466861, 91941363), - Triple("-147771-05-07T08:31:34.950238979Z", -4725358615706, 950238979), - // random enormous values queried from java.time - Triple("+639727757-10-17T17:26:30.359003681Z", 20187795978609990, 359003681), - Triple("-375448814-11-11T03:04:48.637504595Z", -11848082341899312, 637504595), - Triple("+99162559-10-16T11:21:05.057717803Z", 3129205972303265, 57717803), - Triple("-826813174-05-22T21:35:39.018693830Z", -26091765799784661, 18693830), - Triple("+254125623-04-28T11:42:22.659957596Z", 8019367929949342, 659957596), - Triple("+540978343-01-03T20:31:36.945404442Z", 17071565436102696, 945404442), - Triple("+988277529-06-06T13:25:41.942771350Z", 31186964391657941, 942771350), - Triple("+566487909-09-04T08:09:46.007490076Z", 17876569606963786, 7490076), - Triple("+442225124-02-16T18:23:29.975096773Z", 13955214848034209, 975096773), - Triple("-399250586-02-19T22:58:29.585918917Z", -12599193741267691, 585918917), - Triple("-190217791-04-28T00:49:29.270751921Z", -6002755857213031, 270751921), - Triple("-716173704-05-24T22:05:33.639928802Z", -22600321355469267, 639928802), - Triple("+910504788-10-26T05:23:43.517192887Z", 28732693749312223, 517192887), - Triple("+515896807-08-17T06:36:25.012343642Z", 16280068627982185, 12343642), - Triple("-623794742-03-20T17:09:07.396143995Z", -19685122891483853, 396143995), - Triple("-416781718-10-06T01:41:32.866307162Z", -13152422812544308, 866307162), - Triple("+287346593-09-30T23:30:55.109337183Z", 9067720499134255, 109337183), - Triple("-819839065-09-06T07:25:58.953784983Z", -25871684167672442, 953784983), - Triple("+673467211-06-05T02:15:40.712392732Z", 21252510297310540, 712392732), - Triple("+982441727-04-13T12:12:06.776817565Z", 31002804263391126, 776817565), - )) { - val instant = Instant.parse(str) - assertEquals( - Instant.fromEpochSeconds(seconds, nanos), instant, - "Parsed $instant from $str, with Unix time = `$seconds + 10^-9 * $nanos`" - ) - assertEquals(str, instant.toString()) - } - // non-canonical strings are parsed as well, but formatted differently - for ((str, seconds, nanos) in arrayOf( - // upper, lower case, trailing zeros - Triple("2024-07-15T14:06:29.461245000z", 1721052389, 461245000), - Triple("2024-07-15t14:06:29.4612450z", 1721052389, 461245000), - // current time - Triple("2024-07-15T16:06:29.461245691+02:00", 1721052389, 461245691), - )) { - val instant = Instant.parse(str) - assertEquals( - seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds(), - "Parsed $instant from $str, with Unix time = `$seconds + 10^-9 * $nanos`" - ) - } - } - - @Test - fun nonParseableInstantStrings() { - for (nonIsoString in listOf( - // empty string - "", - // a non-empty but clearly unsuitable string - "x", - // something other than a sign at the beginning - " 1970-01-01T00:00:00Z", - // too many digits for the year - "+1234567890-01-01T00:00:00Z", - "-1234567890-01-01T00:00:00Z", - // not enough padding for the year - "003-01-01T00:00:00Z", - "-003-01-01T00:00:00Z", - // a plus sign even though there is only 4 digits - "+1970-01-01T00:00:00Z", - // too many digits without padding - "11970-01-01T00:00:00Z", - // incorrect separators between the components - "1970/01-01T00:00:00Z", - "1970-01/01T00:00:00Z", - "1970-01-01 00:00:00Z", - "1970-01-01T00-00:00Z", - "1970-01-01T00:00-00Z", - // non-digits where digits are expected - "1970-X1-01T00:00:00Z", - "1970-1X-01T00:00:00Z", - "1970-11-X1T00:00:00Z", - "1970-11-1XT00:00:00Z", - "1970-11-10TX0:00:00Z", - "1970-11-10T0X:00:00Z", - "1970-11-10T00:X0:00Z", - "1970-11-10T00:0X:00Z", - "1970-11-10T00:00:X0Z", - "1970-11-10T00:00:0XZ", - // a non-ascii digit - "1970-11-10T00:00:0٩Z", - // not enough components - "1970-11-10T00:00Z", - // not enough components, even if the length is sufficient - "1970-11-10T00:00+01:15", - // a dot without any fraction of the second following it - "1970-11-10T00:00:00.Z", - // too many digits in the fraction of the second - "1970-11-10T00:00:00.1234567890Z", - // out-of-range values - "1970-00-10T00:00:00Z", - "1970-13-10T00:00:00Z", - "1970-01-32T00:00:00Z", - "1970-02-29T00:00:00Z", - "1972-02-30T00:00:00Z", - "2000-02-30T00:00:00Z", - "2100-02-29T00:00:00Z", - "2004-02-30T00:00:00Z", - "2005-02-29T00:00:00Z", - "2005-04-31T00:00:00Z", - "2005-04-01T24:00:00Z", - "2005-04-01T00:60:00Z", - "2005-04-01T00:00:60Z", - // leap second - "1970-01-01T23:59:60Z", - // lack of padding - "1970-1-10T00:00:00+05:00", - "1970-10-1T00:00:00+05:00", - "1970-10-10T0:00:00+05:00", - "1970-10-10T00:0:00+05:00", - "1970-10-10T00:00:0+05:00", - // no offset - "1970-02-03T04:05:06.123456789", - // some invalid single-character offsets - "1970-02-03T04:05:06.123456789A", - "1970-02-03T04:05:06.123456789+", - "1970-02-03T04:05:06.123456789-", - // too many components in the offset - "1970-02-03T04:05:06.123456789+03:02:01:00", - "1970-02-03T04:05:06.123456789+03:02:01.02", - // single-digit offset - "1970-02-03T04:05:06.123456789+3", - // incorrect sign in the offset - "1970-02-03T04:05:06.123456789 03", - // non-digits in the offset - "1970-02-03T04:05:06.123456789+X3", - "1970-02-03T04:05:06.123456789+1X", - "1970-02-03T04:05:06.123456789+X3:12", - "1970-02-03T04:05:06.123456789+1X:12", - "1970-02-03T04:05:06.123456789+X3:12", - "1970-02-03T04:05:06.123456789+13:X2", - "1970-02-03T04:05:06.123456789+13:1X", - "1970-02-03T04:05:06.123456789+X3:12:59", - "1970-02-03T04:05:06.123456789+1X:12:59", - "1970-02-03T04:05:06.123456789+X3:12:59", - "1970-02-03T04:05:06.123456789+13:X2:59", - "1970-02-03T04:05:06.123456789+13:1X:59", - "1970-02-03T04:05:06.123456789+13:12:X9", - "1970-02-03T04:05:06.123456789+13:12:5X", - // incorrect separators in the offset - "1970-02-03T04:05:06.123456789+13/12", - "1970-02-03T04:05:06.123456789+13/12:59", - "1970-02-03T04:05:06.123456789+13:12/59", - "1970-02-03T04:05:06.123456789+0130", - "1970-02-03T04:05:06.123456789-0130", - // incorrect field length - "1970-02-03T04:05:06.123456789-18:001", - // out-of-range offsets - "1970-02-03T04:05:06.123456789+18:12:59", - "1970-02-03T04:05:06.123456789-18:12:59", - "1970-02-03T04:05:06.123456789+18:00:01", - "1970-02-03T04:05:06.123456789-18:00:01", - "1970-02-03T04:05:06.123456789+18:01", - "1970-02-03T04:05:06.123456789-18:01", - "1970-02-03T04:05:06.123456789+19", - "1970-02-03T04:05:06.123456789-19", - // out-of-range fields of the offset - "1970-02-03T04:05:06.123456789+01:12:60", - "1970-02-03T04:05:06.123456789-01:12:60", - "1970-02-03T04:05:06.123456789+01:60", - "1970-02-03T04:05:06.123456789-01:60", - // lack of padding in the offset - "1970-02-03T04:05:06.123456789+1:12:50", - "1970-02-03T04:05:06.123456789+01:2:60", - "1970-02-03T04:05:06.123456789+01:12:6", - )) { - assertInvalidFormat(nonIsoString) { Instant.parse(nonIsoString) } - } - // this string represents an Instant that is currently larger than Instant.MAX any of the implementations: - assertInvalidFormat { Instant.parse("+1000000001-12-31T23:59:59.000000000Z") } - } - - @Test - fun parseStringsWithOffsets() { - val strings = arrayOf( - Pair("2020-01-01T00:01:01.02+18:00", "2019-12-31T06:01:01.020Z"), - Pair("2020-01-01T00:01:01.123456789-17:59:59", "2020-01-01T18:01:00.123456789Z"), - Pair("2020-01-01T00:01:01.010203040+17:59:59", "2019-12-31T06:01:02.010203040Z"), - Pair("2020-01-01T00:01:01.010203040+17:59", "2019-12-31T06:02:01.010203040Z"), - Pair("2020-01-01T00:01:01+00", "2020-01-01T00:01:01Z"), - ) - strings.forEach { (str, strInZ) -> - val instant = Instant.parse(str) - assertEquals(Instant.parse(strInZ), instant, str) - assertEquals(strInZ, instant.toString(), str) - } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+18:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+1801") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+0") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01") } - assertInvalidFormat { Instant.parse("2020-01-01T00:01:01+000000") } - - val instants = listOf( - Instant.DISTANT_FUTURE, - Instant.DISTANT_PAST, - Instant.fromEpochSeconds(0, 0), - Instant.parse("2020-01-02T03:04:05.6789Z"), - Instant.MAX, - Instant.MIN, - ) - - val offsets = listOf( - 0 to "Z", - 3 * 3600 + 12 * 60 + 14 to "+03:12:14", - - 3 * 3600 - 12 * 60 - 14 to "-03:12:14", - 2 * 3600 + 35 * 60 to "+02:35", - - 2 * 3600 - 35 * 60 to "-02:35", - 4 * 3600 to "+04", - - 4 * 3600 to "-04", - ) - - for (instant in instants) { - for ((offsetSeconds, offsetString) in offsets) { - if (instant == Instant.MAX && offsetSeconds < 0 || - instant == Instant.MIN && offsetSeconds > 0 - ) continue - val newInstant = Instant.parse("${instant.toString().dropLast(1)}$offsetString") - assertEquals(newInstant, instant.minus(offsetSeconds.seconds)) - } - } - } - -} diff --git a/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt b/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt deleted file mode 100644 index b56e18152..000000000 --- a/fake-kotlinx-time/common/test/ThreeTenBpInstantTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -/* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - -package kotlinx.time.test - -import kotlinx.time.* -import kotlin.test.* - -class ThreeTenBpInstantTest { - - @Test - fun instantComparisons() { - 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(true, test5a == test5a) - assertEquals(true, test5a == test5b) - assertEquals(false, test5a == test5n) - assertEquals(false, test5a == test6) - assertEquals(true, test5b == test5a) - assertEquals(true, test5b == test5b) - assertEquals(false, test5b == test5n) - assertEquals(false, test5b == test6) - assertEquals(false, test5n == test5a) - assertEquals(false, test5n == test5b) - assertEquals(true, test5n == test5n) - assertEquals(false, test5n == test6) - assertEquals(false, test6 == test5a) - assertEquals(false, test6 == test5b) - assertEquals(false, test6 == test5n) - assertEquals(true, 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) - } -} diff --git a/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt b/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt deleted file mode 100644 index a698f88df..000000000 --- a/fake-kotlinx-time/common/test/ThreeTenBpUtilTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019-2020 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -/* Based on the ThreeTenBp project. - * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos - */ - -package kotlinx.time.test - -import kotlinx.time.* -import kotlin.test.* - -class ThreeTenBpUtilTest { - @Test - fun isLeap() { - 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)) - } -} diff --git a/fake-kotlinx-time/common/test/assertions.kt b/fake-kotlinx-time/common/test/assertions.kt deleted file mode 100644 index 368f4c6b4..000000000 --- a/fake-kotlinx-time/common/test/assertions.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2019-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -package kotlinx.time.test - -import kotlin.test.assertFailsWith -import kotlin.test.fail - -inline fun assertInvalidFormat(message: String? = null, f: () -> T) { - assertFailsWith(message) { - val result = f() - fail(result.toString()) - } -} - -/** - * The number of iterations to perform in nondeterministic tests. - */ -const val STRESS_TEST_ITERATIONS = 1000 diff --git a/fake-kotlinx-time/common/test/samples/ClockSamples.kt b/fake-kotlinx-time/common/test/samples/ClockSamples.kt deleted file mode 100644 index d799e6bf1..000000000 --- a/fake-kotlinx-time/common/test/samples/ClockSamples.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.test.samples - -import kotlinx.time.* -import kotlin.test.* - -class ClockSamples { - @Test - fun system() { - // Getting the current date and time - val currentInstant = Clock.System.now() - currentInstant.toEpochMilliseconds() // the number of milliseconds since the Unix epoch - } - - @Test - fun dependencyInjection() { - fun formatCurrentTime(clock: Clock): String = - clock.now().toString() - - // In the production code: - val currentTimeInProduction = formatCurrentTime(Clock.System) - // Testing this value is tricky because it changes all the time. - - // In the test code: - val testClock = object: Clock { - override fun now(): Instant = Instant.parse("2023-01-02T22:35:01Z") - } - // Then, one can write a completely deterministic test: - val currentTimeForTests = formatCurrentTime(testClock) - check(currentTimeForTests == "2023-01-02T22:35:01Z") - } -} diff --git a/fake-kotlinx-time/common/test/samples/InstantSamples.kt b/fake-kotlinx-time/common/test/samples/InstantSamples.kt deleted file mode 100644 index af141866e..000000000 --- a/fake-kotlinx-time/common/test/samples/InstantSamples.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.datetime.test.samples - -import kotlinx.time.* -import kotlin.random.* -import kotlin.test.* -import kotlin.time.Duration.Companion.hours - -class InstantSamples { - - @Test - fun epochSeconds() { - // Getting the number of whole seconds that passed since the Unix epoch - val instant1 = Instant.fromEpochSeconds(999_999, nanosecondAdjustment = 123_456_789) - check(instant1.epochSeconds == 999_999L) - val instant2 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = 100_123_456_789) - check(instant2.epochSeconds == 1_000_000 + 100L) - val instant3 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = -100_876_543_211) - check(instant3.epochSeconds == 1_000_000 - 101L) - } - - @Test - fun nanosecondsOfSecond() { - // Getting the number of nanoseconds that passed since the start of the second - val instant1 = Instant.fromEpochSeconds(999_999, nanosecondAdjustment = 123_456_789) - check(instant1.nanosecondsOfSecond == 123_456_789) - val instant2 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = 100_123_456_789) - check(instant2.nanosecondsOfSecond == 123_456_789) - val instant3 = Instant.fromEpochSeconds(1_000_000, nanosecondAdjustment = -100_876_543_211) - check(instant3.nanosecondsOfSecond == 123_456_789) - } - - @Test - fun toEpochMilliseconds() { - // Converting an Instant to the number of milliseconds since the Unix epoch - check(Instant.fromEpochMilliseconds(0).toEpochMilliseconds() == 0L) - check(Instant.fromEpochMilliseconds(1_000_000_000_123).toEpochMilliseconds() == 1_000_000_000_123L) - check(Instant.fromEpochSeconds(1_000_000_000, nanosecondAdjustment = 123_999_999) - .toEpochMilliseconds() == 1_000_000_000_123L) - } - - @Test - fun plusDuration() { - // Finding a moment that's later than the starting point by the given amount of real time - val instant = Instant.fromEpochSeconds(7 * 60 * 60, nanosecondAdjustment = 123_456_789) - val fiveHoursLater = instant + 5.hours - check(fiveHoursLater.epochSeconds == 12 * 60 * 60L) - check(fiveHoursLater.nanosecondsOfSecond == 123_456_789) - } - - @Test - fun minusDuration() { - // Finding a moment that's earlier than the starting point by the given amount of real time - val instant = Instant.fromEpochSeconds(7 * 60 * 60, nanosecondAdjustment = 123_456_789) - val fiveHoursEarlier = instant - 5.hours - check(fiveHoursEarlier.epochSeconds == 2 * 60 * 60L) - check(fiveHoursEarlier.nanosecondsOfSecond == 123_456_789) - } - - @Test - fun minusInstant() { - // Finding the difference between two instants in terms of elapsed time - check(Instant.fromEpochSeconds(0) - Instant.fromEpochSeconds(epochSeconds = 7 * 60 * 60) == (-7).hours) - } - - @Test - fun toStringSample() { - // Converting an Instant to a string - check(Instant.fromEpochSeconds(0).toString() == "1970-01-01T00:00:00Z") - } - - @Test - fun fromEpochMilliseconds() { - // Constructing an Instant from the number of milliseconds since the Unix epoch - check(Instant.fromEpochMilliseconds(epochMilliseconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) - check(Instant.fromEpochMilliseconds(epochMilliseconds = 1_000_000_000_123) - == Instant.parse("2001-09-09T01:46:40.123Z")) - } - - @Test - fun fromEpochSeconds() { - // Constructing an Instant from the number of seconds and nanoseconds since the Unix epoch - check(Instant.fromEpochSeconds(epochSeconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) - check(Instant.fromEpochSeconds(epochSeconds = 1_000_001_234, nanosecondAdjustment = -1_234_000_000_001) - == Instant.parse("2001-09-09T01:46:39.999999999Z")) - } - - @Test - fun fromEpochSecondsIntNanos() { - // Constructing an Instant from the number of seconds and nanoseconds since the Unix epoch - check(Instant.fromEpochSeconds(epochSeconds = 0) == Instant.parse("1970-01-01T00:00:00Z")) - check(Instant.fromEpochSeconds(epochSeconds = 1_000_000_000, nanosecondAdjustment = -1) == Instant.parse("2001-09-09T01:46:39.999999999Z")) - } - - @Test - fun parsing() { - // Parsing an Instant from a string - check(Instant.parse("1970-01-01T00:00:00Z") == Instant.fromEpochSeconds(0)) - } - - @Test - fun isDistantPast() { - // Checking if an instant is so far in the past that it's probably irrelevant - val currentInstant = Clock.System.now() - val tenThousandYearsAgo = currentInstant.minus(24.hours * 365 * 1_000) - check(!tenThousandYearsAgo.isDistantPast) - check(Instant.DISTANT_PAST.isDistantPast) - } - - @Test - fun isDistantFuture() { - // Checking if an instant is so far in the future that it's probably irrelevant - val currentInstant = Clock.System.now() - val tenThousandYearsLater = currentInstant.plus(24.hours * 365 * 10_000) - check(!tenThousandYearsLater.isDistantFuture) - check(Instant.DISTANT_FUTURE.isDistantFuture) - } -} diff --git a/fake-kotlinx-time/js/src/Converters.kt b/fake-kotlinx-time/js/src/Converters.kt deleted file mode 100644 index 5e74ab188..000000000 --- a/fake-kotlinx-time/js/src/Converters.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2019-2022 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -import kotlin.js.* - -/** - * Converts the [Instant] to an instance of JS [Date]. - * - * The conversion is lossy: JS uses millisecond precision to represent dates, and [Instant] allows for nanosecond - * resolution. - */ -public fun Instant.toJSDate(): Date = Date(milliseconds = toEpochMilliseconds().toDouble()) - -/** - * Converts the JS [Date] to the corresponding [Instant]. - */ -public fun Date.toKotlinInstant(): Instant = Instant.fromEpochMilliseconds(getTime().toLong()) diff --git a/fake-kotlinx-time/js/src/Platform.kt b/fake-kotlinx-time/js/src/Platform.kt deleted file mode 100644 index 52a3cbd31..000000000 --- a/fake-kotlinx-time/js/src/Platform.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -import kotlin.js.Date - -internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) diff --git a/fake-kotlinx-time/js/test/ConvertersTest.kt b/fake-kotlinx-time/js/test/ConvertersTest.kt deleted file mode 100644 index 2d067b258..000000000 --- a/fake-kotlinx-time/js/test/ConvertersTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2019-2022 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time.test - -import kotlinx.time.* -import kotlin.js.Date -import kotlin.test.* - -class ConvertersTest { - @Test - fun toJSDateTest() { - val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") - val releaseDate = releaseInstant.toJSDate() - assertEquals(2016, releaseDate.getUTCFullYear()) - assertEquals(1, releaseDate.getUTCMonth()) - assertEquals(15, releaseDate.getUTCDate()) - } - - @Test - fun toInstantTest() { - val kotlinReleaseEpochMilliseconds = 1455494400000 - val releaseDate = Date(milliseconds = kotlinReleaseEpochMilliseconds) - val releaseInstant = Instant.parse("2016-02-15T00:00:00Z") - assertEquals(releaseInstant, releaseDate.toKotlinInstant()) - } -} diff --git a/fake-kotlinx-time/jvm/src/Converters.kt b/fake-kotlinx-time/jvm/src/Converters.kt deleted file mode 100644 index 3774d4b37..000000000 --- a/fake-kotlinx-time/jvm/src/Converters.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2019-2022 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -/** - * Converts this [kotlin.time.Instant][Instant] value to a [java.time.Instant][java.time.Instant] value. - */ -public fun Instant.toJavaInstant(): java.time.Instant = - java.time.Instant.ofEpochSecond(epochSeconds, nanosecondsOfSecond.toLong()) - -/** - * Converts this [java.time.Instant][java.time.Instant] value to a [kotlin.time.Instant][Instant] value. - */ -public fun java.time.Instant.toKotlinInstant(): Instant = Instant.fromEpochSeconds(epochSecond, nano) diff --git a/fake-kotlinx-time/jvm/src/Platform.kt b/fake-kotlinx-time/jvm/src/Platform.kt deleted file mode 100644 index 96031eab0..000000000 --- a/fake-kotlinx-time/jvm/src/Platform.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -internal actual fun currentTime(): Instant = if (javaTimeAvailable) { - java.time.Instant.now().toKotlinInstant() -} else { - /* After experimenting in Android Studio, it seems like on Android with API < 24, only millisecond precision - is available in `Instant.now()` with core library desugaring enabled. Because of that, `currentTimeMillis` - is good enough + suggesting that our users enable core library desugaring isn't going to bring any benefits, - so the KDoc for [Clock] does not mention any of this. */ - Instant.fromEpochMilliseconds(System.currentTimeMillis()) -} - -/** - * `false` for Android devices with API level < 24, where java.time is not available. - */ -private val javaTimeAvailable = try { - java.time.Instant.now() - true -} catch (e: NoClassDefFoundError) { - false -} diff --git a/fake-kotlinx-time/jvm/test/ConvertersTest.kt b/fake-kotlinx-time/jvm/test/ConvertersTest.kt deleted file mode 100644 index 33b3018b7..000000000 --- a/fake-kotlinx-time/jvm/test/ConvertersTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2019-2022 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ -package kotlinx.time.test - -import kotlinx.time.* -import kotlin.test.* -import kotlin.random.* -import java.time.Instant as JTInstant - -class ConvertersTest { - - @Test - fun instant() { - fun test(seconds: Long, nanosecond: Int) { - val ktInstant = Instant.fromEpochSeconds(seconds, nanosecond.toLong()) - val jtInstant = JTInstant.ofEpochSecond(seconds, nanosecond.toLong()) - - assertEquals(ktInstant, jtInstant.toKotlinInstant()) - assertEquals(jtInstant, ktInstant.toJavaInstant()) - - assertEquals(ktInstant, jtInstant.toString().let(Instant::parse)) - assertEquals(jtInstant, ktInstant.toString().let(JTInstant::parse)) - } - - repeat(STRESS_TEST_ITERATIONS) { - val seconds = Random.nextLong(1_000_000_000_000) - val nanos = Random.nextInt() - test(seconds, nanos) - } - } - -} diff --git a/fake-kotlinx-time/native/src/Platform.kt b/fake-kotlinx-time/native/src/Platform.kt deleted file mode 100644 index 8e036c159..000000000 --- a/fake-kotlinx-time/native/src/Platform.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time -import kotlinx.cinterop.* -import platform.posix.* - -@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) -internal actual fun currentTime(): Instant = memScoped { - val tm = alloc() - val error = clock_gettime(CLOCK_REALTIME.convert(), tm.ptr) - check(error == 0) { "Error when reading the system clock: ${strerror(errno)?.toKString() ?: "Unknown error"}" } - try { - require(tm.tv_nsec in 0 until NANOS_PER_ONE) - Instant(tm.tv_sec.convert(), tm.tv_nsec.convert()) - } catch (e: IllegalArgumentException) { - throw IllegalStateException("The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant") - } -} diff --git a/fake-kotlinx-time/wasmJs/src/Platform.kt b/fake-kotlinx-time/wasmJs/src/Platform.kt deleted file mode 100644 index f8c26688b..000000000 --- a/fake-kotlinx-time/wasmJs/src/Platform.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -internal actual fun currentTime(): Instant = Instant.fromEpochMilliseconds(Date().getTime().toLong()) - -private external class Date { - fun getTime(): Double -} diff --git a/fake-kotlinx-time/wasmWasi/src/Platform.kt b/fake-kotlinx-time/wasmWasi/src/Platform.kt deleted file mode 100644 index 012e08c18..000000000 --- a/fake-kotlinx-time/wasmWasi/src/Platform.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2019-2024 JetBrains s.r.o. and contributors. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package kotlinx.time - -import kotlin.wasm.WasmImport -import kotlin.wasm.unsafe.UnsafeWasmMemoryApi -import kotlin.wasm.unsafe.withScopedMemoryAllocator - -/** - * Return the time value of a clock. Note: This is similar to `clock_gettime` in POSIX. - */ -@WasmImport("wasi_snapshot_preview1", "clock_time_get") -private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int - -private const val CLOCKID_REALTIME = 0 - -@OptIn(UnsafeWasmMemoryApi::class) -private fun clockTimeGet(): Long = withScopedMemoryAllocator { allocator -> - val rp0 = allocator.allocate(8) - val ret = wasiRawClockTimeGet( - clockId = CLOCKID_REALTIME, - precision = 1, - resultPtr = rp0.address.toInt() - ) - if (ret == 0) { - rp0.loadLong() - } else { - error("WASI call failed with $ret") - } -} - -internal actual fun currentTime(): Instant = clockTimeGet().let { time -> - // Instant.MAX and Instant.MIN are never going to be exceeded using just the Long number of nanoseconds - Instant(time.floorDiv(NANOS_PER_ONE.toLong()), time.mod(NANOS_PER_ONE.toLong()).toInt()) -} diff --git a/gradle.properties b/gradle.properties index ac136ccf0..37fd4168d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ versionSuffix=SNAPSHOT tzdbVersion=2025a -defaultKotlinVersion=2.1.0 +defaultKotlinVersion=2.1.20-RC dokkaVersion=1.9.20 serializationVersion=1.6.2 benchmarksVersion=0.7.2 diff --git a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt index 7124792ba..c496881f5 100644 --- a/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt +++ b/js-without-timezones/common/test/TimezonesWithoutDatabaseTest.kt @@ -7,9 +7,10 @@ package kotlinx.datetime.test import kotlinx.datetime.* import kotlin.test.* -import kotlinx.time.Instant -import kotlinx.time.Clock +import kotlin.time.Instant +import kotlin.time.Clock +@OptIn(kotlin.time.ExperimentalTime::class) class TimezonesWithoutDatabaseTest { @Test fun system() { diff --git a/settings.gradle.kts b/settings.gradle.kts index c1f6e2823..a1dad22f8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,7 @@ pluginManagement { repositories { maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlinx/maven") + mavenLocal() mavenCentral() gradlePluginPortal() } @@ -25,5 +26,3 @@ project(":serialization").name = "kotlinx-datetime-serialization" include(":js-without-timezones") project(":js-without-timezones").name = "kotlinx-datetime-js-test-without-timezones" include(":benchmarks") - -include(":fake-kotlinx-time")