From 2fb942e4230b97c120e3efe31481847a2223c848 Mon Sep 17 00:00:00 2001 From: Erik Christensen <40830816+erikc5000@users.noreply.github.com> Date: Wed, 5 Aug 2020 10:56:00 -0400 Subject: [PATCH] Re-work clock implementation (#118) * Re-work the clock implementation on JVM * Re-work new clock implementation to handle Darwin * Minor clean up --- .../kotlin/io/islandtime/Instant.kt | 5 + .../kotlin/io/islandtime/clock/Clock.kt | 287 +++++++----------- .../kotlin/io/islandtime/clock/FixedClock.kt | 159 ++++++++++ .../kotlin/io/islandtime/clock/Now.kt | 116 +++---- .../kotlin/io/islandtime/clock/SystemClock.kt | 42 +++ .../io/islandtime/clock/internal/NowImpl.kt | 11 + .../clock/internal/SystemClockImpl.kt | 6 + .../io/islandtime/internal/Deprecation.kt | 3 + .../kotlin/io/islandtime/internal/Instants.kt | 26 ++ .../kotlin/io/islandtime/internal/Platform.kt | 5 - .../io/islandtime/internal/PlatformImpl.kt | 10 + .../io/islandtime/zone/TimeZoneRules.kt | 32 +- .../kotlin/io/islandtime/clock/ClockTest.kt | 12 +- .../kotlin/io/islandtime/clock/NowTest.kt | 26 +- .../kotlin/io/islandtime/PlatformInstant.kt | 5 + .../islandtime/clock/PlatformSystemClock.kt | 20 -- .../io/islandtime/clock/internal/NowImpl.kt | 41 +++ .../clock/internal/SystemClockImpl.kt | 38 +++ .../kotlin/io/islandtime/internal/Platform.kt | 8 - .../io/islandtime/internal/PlatformImpl.kt | 15 + .../io/islandtime/zone/TimeZoneRules.kt | 13 +- .../kotlin/io/islandtime/PlatformInstant.kt | 3 + .../islandtime/clock/PlatformSystemClock.kt | 7 - .../islandtime/clock/internal/Conversions.kt | 35 +++ .../io/islandtime/clock/internal/NowImpl.kt | 28 ++ .../clock/internal/SystemClockImpl.kt | 21 ++ .../kotlin/io/islandtime/clock/jvm/Now.kt | 64 ++++ .../kotlin/io/islandtime/internal/Platform.kt | 18 -- .../io/islandtime/internal/PlatformImpl.kt | 34 +++ .../kotlin/io/islandtime/jvm/Conversions.kt | 21 +- .../io/islandtime/zone/TimeZoneRules.kt | 19 +- .../io/islandtime/clock/jvm/JvmNowTest.kt | 103 +++++++ .../{PlatformTest.kt => PlatformImplTest.kt} | 12 +- .../io/islandtime/jvm/ConversionsTest.kt | 24 ++ 34 files changed, 900 insertions(+), 369 deletions(-) create mode 100644 core/src/commonMain/kotlin/io/islandtime/clock/FixedClock.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/clock/SystemClock.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/clock/internal/NowImpl.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/internal/Deprecation.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/internal/Instants.kt delete mode 100644 core/src/commonMain/kotlin/io/islandtime/internal/Platform.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/internal/PlatformImpl.kt create mode 100644 core/src/darwinMain/kotlin/io/islandtime/PlatformInstant.kt delete mode 100644 core/src/darwinMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt create mode 100644 core/src/darwinMain/kotlin/io/islandtime/clock/internal/NowImpl.kt create mode 100644 core/src/darwinMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt delete mode 100644 core/src/darwinMain/kotlin/io/islandtime/internal/Platform.kt create mode 100644 core/src/darwinMain/kotlin/io/islandtime/internal/PlatformImpl.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/PlatformInstant.kt delete mode 100644 core/src/jvmMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/clock/internal/Conversions.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/clock/internal/NowImpl.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/clock/jvm/Now.kt delete mode 100644 core/src/jvmMain/kotlin/io/islandtime/internal/Platform.kt create mode 100644 core/src/jvmMain/kotlin/io/islandtime/internal/PlatformImpl.kt create mode 100644 core/src/jvmTest/kotlin/io/islandtime/clock/jvm/JvmNowTest.kt rename core/src/jvmTest/kotlin/io/islandtime/internal/{PlatformTest.kt => PlatformImplTest.kt} (79%) diff --git a/core/src/commonMain/kotlin/io/islandtime/Instant.kt b/core/src/commonMain/kotlin/io/islandtime/Instant.kt index 892eeb0bb..6b86e3113 100644 --- a/core/src/commonMain/kotlin/io/islandtime/Instant.kt +++ b/core/src/commonMain/kotlin/io/islandtime/Instant.kt @@ -10,6 +10,11 @@ import io.islandtime.measures.internal.plusWithOverflow import io.islandtime.parser.* import io.islandtime.ranges.InstantInterval +/** + * A platform-specific representation of an instant in time. + */ +expect class PlatformInstant + /** * An instant in time with nanosecond precision. */ diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt b/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt index 80cf54e54..3e94f6b98 100644 --- a/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt +++ b/core/src/commonMain/kotlin/io/islandtime/clock/Clock.kt @@ -1,11 +1,21 @@ +@file:Suppress("FunctionName", "UNUSED_PARAMETER") + package io.islandtime.clock import io.islandtime.Instant +import io.islandtime.PlatformInstant import io.islandtime.TimeZone +import io.islandtime.internal.deprecatedToError import io.islandtime.measures.* /** * A time source. + * + * For an implementation that uses the system's clock, see [SystemClock]. [FixedClock] is also available for testing + * purposes. + * + * @see SystemClock + * @see FixedClock */ interface Clock { /** @@ -14,190 +24,107 @@ interface Clock { val zone: TimeZone /** - * Get the current number of milliseconds since the Unix epoch of 1970-01-01T00:00 in UTC. + * Reads the current number of milliseconds that have elapsed since the Unix epoch of `1970-01-01T00:00` in UTC. */ - fun read(): LongMilliseconds + fun readMilliseconds(): LongMilliseconds /** - * Get the current [Instant]. + * Reads the current [Instant]. */ - fun instant(): Instant = Instant(read()) -} - -/** - * A clock that provides the time from the current system. - * - * The time zone is treated as an immutable property of the clock, set when it is created. If you wish to follow - * changes to the system clock's configured time zone, you must create a new [SystemClock] in response to any time zone - * changes. - * - * @constructor Create a [SystemClock] with a specific time zone, defaulting to the system's current zone. - * @see currentZone - */ -class SystemClock(override val zone: TimeZone = TimeZone.systemDefault()) : Clock { - override fun read() = PlatformSystemClock.read() - - override fun equals(other: Any?): Boolean { - return other is SystemClock && zone == other.zone - } - - override fun hashCode(): Int = zone.hashCode() + 1 - override fun toString(): String = "SystemClock[$zone]" - - companion object { - /** - * A system clock in the UTC time zone. - */ - val UTC = SystemClock(TimeZone.UTC) - - /** - * Get the current system time zone. - */ - @Deprecated( - "Use TimeZone.systemDefault() instead.", - ReplaceWith("TimeZone.systemDefault()"), - DeprecationLevel.WARNING - ) - fun currentZone(): TimeZone = TimeZone.systemDefault() - } -} - -/** - * Platform system clock implementation - */ -internal expect object PlatformSystemClock { - fun read(): LongMilliseconds -} - -/** - * A clock with fixed time, suitable for testing. - */ -class FixedClock( - private var millisecondsSinceUnixEpoch: LongMilliseconds = 0L.milliseconds, - override val zone: TimeZone = TimeZone.UTC -) : Clock { - - operator fun plusAssign(days: LongDays) { - millisecondsSinceUnixEpoch += days - } - - operator fun plusAssign(days: IntDays) { - millisecondsSinceUnixEpoch += days - } - - operator fun plusAssign(hours: LongHours) { - millisecondsSinceUnixEpoch += hours - } - - operator fun plusAssign(hours: IntHours) { - millisecondsSinceUnixEpoch += hours - } - - operator fun plusAssign(minutes: LongMinutes) { - millisecondsSinceUnixEpoch += minutes - } - - operator fun plusAssign(minutes: IntMinutes) { - millisecondsSinceUnixEpoch += minutes - } - - operator fun plusAssign(seconds: LongSeconds) { - millisecondsSinceUnixEpoch += seconds - } + fun readInstant(): Instant - operator fun plusAssign(seconds: IntSeconds) { - millisecondsSinceUnixEpoch += seconds - } - - operator fun plusAssign(milliseconds: LongMilliseconds) { - millisecondsSinceUnixEpoch += milliseconds - } - - operator fun plusAssign(milliseconds: IntMilliseconds) { - millisecondsSinceUnixEpoch += milliseconds - } - - operator fun minusAssign(days: LongDays) { - millisecondsSinceUnixEpoch -= days - } - - operator fun minusAssign(days: IntDays) { - millisecondsSinceUnixEpoch -= days - } - - operator fun minusAssign(hours: LongHours) { - millisecondsSinceUnixEpoch -= hours - } - - operator fun minusAssign(hours: IntHours) { - millisecondsSinceUnixEpoch -= hours - } - - operator fun minusAssign(minutes: LongMinutes) { - millisecondsSinceUnixEpoch -= minutes - } - - operator fun minusAssign(minutes: IntMinutes) { - millisecondsSinceUnixEpoch -= minutes - } - - operator fun minusAssign(seconds: LongSeconds) { - millisecondsSinceUnixEpoch -= seconds - } - - operator fun minusAssign(seconds: IntSeconds) { - millisecondsSinceUnixEpoch -= seconds - } - - operator fun minusAssign(milliseconds: LongMilliseconds) { - millisecondsSinceUnixEpoch -= milliseconds - } - - operator fun minusAssign(milliseconds: IntMilliseconds) { - millisecondsSinceUnixEpoch -= milliseconds - } - - override fun read(): LongMilliseconds { - return millisecondsSinceUnixEpoch - } - - override fun equals(other: Any?): Boolean { - return other is FixedClock && - millisecondsSinceUnixEpoch == other.millisecondsSinceUnixEpoch && - zone == other.zone - } - - override fun hashCode(): Int { - return 31 * millisecondsSinceUnixEpoch.hashCode() + zone.hashCode() - } - - override fun toString(): String = "FixedClock[${instant()}, $zone]" + /** + * Reads the current [PlatformInstant]. + */ + fun readPlatformInstant(): PlatformInstant } -@Suppress("FunctionName") -fun FixedClock(days: LongDays, zone: TimeZone = TimeZone.UTC) = FixedClock(days.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(days: IntDays, zone: TimeZone = TimeZone.UTC) = FixedClock(days.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(hours: LongHours, zone: TimeZone = TimeZone.UTC) = FixedClock(hours.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(hours: IntHours, zone: TimeZone = TimeZone.UTC) = FixedClock(hours.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(minutes: LongMinutes, zone: TimeZone = TimeZone.UTC) = FixedClock(minutes.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(minutes: IntMinutes, zone: TimeZone = TimeZone.UTC) = FixedClock(minutes.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(seconds: LongSeconds, zone: TimeZone = TimeZone.UTC) = FixedClock(seconds.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(seconds: IntSeconds, zone: TimeZone = TimeZone.UTC) = FixedClock(seconds.inMilliseconds, zone) - -@Suppress("FunctionName") -fun FixedClock(milliseconds: IntMilliseconds, zone: TimeZone = TimeZone.UTC) = - FixedClock(milliseconds.toLongMilliseconds(), zone) \ No newline at end of file +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(days.inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(days: LongDays, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(days.toLong().inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(days: IntDays, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(hours.inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(hours: LongHours, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(hours.toLong().inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(hours: IntHours, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(minutes.inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(minutes: LongMinutes, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(minutes.toLong().inSeconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(minutes: IntMinutes, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(seconds), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(seconds: LongSeconds, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(seconds.toLongSeconds()), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(seconds: IntSeconds, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() + +@Deprecated( + message = "Specify the instant explicitly.", + replaceWith = ReplaceWith( + "FixedClock(Instant(milliseconds.toLongMilliseconds()), zone)", + "io.islandtime.Instant" + ), + level = DeprecationLevel.ERROR +) +fun FixedClock(milliseconds: IntMilliseconds, zone: TimeZone = TimeZone.UTC): FixedClock = deprecatedToError() \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/FixedClock.kt b/core/src/commonMain/kotlin/io/islandtime/clock/FixedClock.kt new file mode 100644 index 000000000..2e946b565 --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/clock/FixedClock.kt @@ -0,0 +1,159 @@ +package io.islandtime.clock + +import io.islandtime.Instant +import io.islandtime.PlatformInstant +import io.islandtime.TimeZone +import io.islandtime.internal.toPlatformInstant +import io.islandtime.measures.* + +/** + * A clock with a fixed time, suitable for testing. + * + * @param instant the initial instant that the clock should be set to + * @param zone the time zone + */ +class FixedClock( + private var instant: Instant, + override val zone: TimeZone +) : Clock { + + fun setTo(instant: Instant) { + this.instant = instant + } + + operator fun plusAssign(days: LongDays) { + instant += days + } + + operator fun plusAssign(days: IntDays) { + instant += days + } + + operator fun plusAssign(hours: LongHours) { + instant += hours + } + + operator fun plusAssign(hours: IntHours) { + instant += hours + } + + operator fun plusAssign(minutes: LongMinutes) { + instant += minutes + } + + operator fun plusAssign(minutes: IntMinutes) { + instant += minutes + } + + operator fun plusAssign(seconds: LongSeconds) { + instant += seconds + } + + operator fun plusAssign(seconds: IntSeconds) { + instant += seconds + } + + operator fun plusAssign(milliseconds: LongMilliseconds) { + instant += milliseconds + } + + operator fun plusAssign(milliseconds: IntMilliseconds) { + instant += milliseconds + } + + operator fun plusAssign(microseconds: LongMicroseconds) { + instant += microseconds + } + + operator fun plusAssign(microseconds: IntMicroseconds) { + instant += microseconds + } + + operator fun plusAssign(nanoseconds: LongNanoseconds) { + instant += nanoseconds + } + + operator fun plusAssign(nanoseconds: IntNanoseconds) { + instant += nanoseconds + } + + operator fun minusAssign(days: LongDays) { + instant -= days + } + + operator fun minusAssign(days: IntDays) { + instant -= days + } + + operator fun minusAssign(hours: LongHours) { + instant -= hours + } + + operator fun minusAssign(hours: IntHours) { + instant -= hours + } + + operator fun minusAssign(minutes: LongMinutes) { + instant -= minutes + } + + operator fun minusAssign(minutes: IntMinutes) { + instant -= minutes + } + + operator fun minusAssign(seconds: LongSeconds) { + instant -= seconds + } + + operator fun minusAssign(seconds: IntSeconds) { + instant -= seconds + } + + operator fun minusAssign(milliseconds: LongMilliseconds) { + instant -= milliseconds + } + + operator fun minusAssign(milliseconds: IntMilliseconds) { + instant -= milliseconds + } + + operator fun minusAssign(microseconds: LongMicroseconds) { + instant -= microseconds + } + + operator fun minusAssign(microseconds: IntMicroseconds) { + instant -= microseconds + } + + operator fun minusAssign(nanoseconds: LongNanoseconds) { + instant -= nanoseconds + } + + operator fun minusAssign(nanoseconds: IntNanoseconds) { + instant -= nanoseconds + } + + override fun readMilliseconds(): LongMilliseconds { + return instant.millisecondsSinceUnixEpoch + } + + override fun readInstant(): Instant { + return instant + } + + override fun readPlatformInstant(): PlatformInstant { + return instant.toPlatformInstant() + } + + override fun equals(other: Any?): Boolean { + return other is FixedClock && + instant == other.instant && + zone == other.zone + } + + override fun hashCode(): Int { + return 31 * instant.hashCode() + zone.hashCode() + } + + override fun toString(): String = "FixedClock[$instant, $zone]" +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/Now.kt b/core/src/commonMain/kotlin/io/islandtime/clock/Now.kt index c11a11881..6d2f5a946 100644 --- a/core/src/commonMain/kotlin/io/islandtime/clock/Now.kt +++ b/core/src/commonMain/kotlin/io/islandtime/clock/Now.kt @@ -1,139 +1,103 @@ package io.islandtime.clock import io.islandtime.* -import io.islandtime.internal.* -import io.islandtime.internal.MILLISECONDS_PER_DAY -import io.islandtime.internal.MILLISECONDS_PER_SECOND -import io.islandtime.internal.SECONDS_PER_DAY -import io.islandtime.internal.floorDiv -import io.islandtime.measures.nanoseconds +import io.islandtime.clock.internal.nowImpl +import io.islandtime.internal.deprecatedToError /** - * Get the current [Instant] from the system clock. + * Gets the current [Instant] from the system clock. */ -fun Instant.Companion.now() = now(SystemClock.UTC) +fun Instant.Companion.now(): Instant = now(SystemClock.UTC) /** - * Get the current [Instant] from the specified clock. + * Gets the current [Instant] from the provided [clock]. */ -fun Instant.Companion.now(clock: Clock) = clock.instant() +fun Instant.Companion.now(clock: Clock): Instant = clock.readInstant() /** - * Get the current [Year] from the system clock. + * Gets the current [Year] from the system clock. */ -fun Year.Companion.now() = now(SystemClock()) +fun Year.Companion.now(): Year = now(SystemClock()) /** - * Get the current [Year] from the specified clock. + * Gets the current [Year] from the provided [clock]. */ -fun Year.Companion.now(clock: Clock) = Date.now(clock).toYear() +fun Year.Companion.now(clock: Clock): Year = Date.now(clock).toYear() /** - * Get the current [YearMonth] from the system clock. + * Gets the current [YearMonth] from the system clock. */ -fun YearMonth.Companion.now() = now(SystemClock()) +fun YearMonth.Companion.now(): YearMonth = now(SystemClock()) /** - * Get the current [YearMonth] from the specified clock. + * Gets the current [YearMonth] from the provided [clock]. */ -fun YearMonth.Companion.now(clock: Clock) = Date.now(clock).toYearMonth() +fun YearMonth.Companion.now(clock: Clock): YearMonth = Date.now(clock).toYearMonth() /** - * Get the current [Date] from the system clock. + * Gets the current [Date] from the system clock. */ -fun Date.Companion.now() = now(SystemClock()) +fun Date.Companion.now(): Date = nowImpl(SystemClock()) /** - * Get the current [Date] from the specified clock. + * Gets the current [Date] from the provided [clock]. */ -fun Date.Companion.now(clock: Clock): Date { - val milliseconds = clock.read() - val offset = clock.zone.rules.offsetAt(milliseconds) - val unixEpochSecond = (milliseconds.value floorDiv MILLISECONDS_PER_SECOND) + offset.totalSeconds.value - val unixEpochDay = unixEpochSecond floorDiv SECONDS_PER_DAY - return fromDayOfUnixEpoch(unixEpochDay) -} +fun Date.Companion.now(clock: Clock): Date = nowImpl(clock) /** - * Get the current [DateTime] from the system clock. + * Gets the current [DateTime] from the system clock. */ -fun DateTime.Companion.now() = now(SystemClock()) +fun DateTime.Companion.now(): DateTime = nowImpl(SystemClock()) /** - * Get the current [DateTime] from the specified clock. + * Gets the current [DateTime] from the provided [clock]. */ -fun DateTime.Companion.now(clock: Clock): DateTime { - val milliseconds = clock.read() - val offset = clock.zone.rules.offsetAt(milliseconds) - return fromMillisecondsSinceUnixEpoch(milliseconds, offset) -} +fun DateTime.Companion.now(clock: Clock): DateTime = nowImpl(clock) /** - * Get the current [OffsetDateTime] from the system clock. + * Gets the current [OffsetDateTime] from the system clock. */ -fun OffsetDateTime.Companion.now() = now(SystemClock()) +fun OffsetDateTime.Companion.now(): OffsetDateTime = nowImpl(SystemClock()) /** - * Get the current [OffsetDateTime] from the specified clock. + * Gets the current [OffsetDateTime] from the provided [clock]. */ -fun OffsetDateTime.Companion.now(clock: Clock): OffsetDateTime { - val milliseconds = clock.read() - val offset = clock.zone.rules.offsetAt(milliseconds) - return DateTime.fromMillisecondsSinceUnixEpoch(milliseconds, offset) at offset -} +fun OffsetDateTime.Companion.now(clock: Clock): OffsetDateTime = nowImpl(clock) /** - * Get the current [OffsetTime] from the system clock. + * Gets the current [ZonedDateTime] from the system clock. */ -fun OffsetTime.Companion.now() = now(SystemClock()) +fun ZonedDateTime.Companion.now(): ZonedDateTime = nowImpl(SystemClock()) /** - * Get the current [OffsetTime] from the specified clock. + * Gets the current [ZonedDateTime] from the provided [clock]. */ -fun OffsetTime.Companion.now(clock: Clock): OffsetTime { - val milliseconds = clock.read() - val offset = clock.zone.rules.offsetAt(milliseconds) - - val nanosecondsSinceStartOfDay = ((milliseconds % MILLISECONDS_PER_DAY).inNanoseconds + - offset.totalSeconds + NANOSECONDS_PER_DAY.nanoseconds) % NANOSECONDS_PER_DAY - - return Time.fromNanosecondOfDay(nanosecondsSinceStartOfDay.value) at offset -} +fun ZonedDateTime.Companion.now(clock: Clock): ZonedDateTime = nowImpl(clock) /** - * Get the current [Time] from the system clock. + * Gets the current [Time] from the system clock. */ -fun Time.Companion.now() = now(SystemClock()) +fun Time.Companion.now(): Time = nowImpl(SystemClock()) /** - * Get the current [Time] from the specified clock. + * Gets the current [Time] from the provided [clock]. */ -fun Time.Companion.now(clock: Clock): Time { - val milliseconds = clock.read() - val offset = clock.zone.rules.offsetAt(milliseconds) - - val nanosecondsSinceStartOfDay = ((milliseconds % MILLISECONDS_PER_DAY).inNanoseconds + - offset.totalSeconds + NANOSECONDS_PER_DAY.nanoseconds) % NANOSECONDS_PER_DAY - - return fromNanosecondOfDay(nanosecondsSinceStartOfDay.value) -} +fun Time.Companion.now(clock: Clock): Time = nowImpl(clock) /** - * Get the current [ZonedDateTime] from the system clock. + * Gets the current [OffsetTime] from the system clock. */ -fun ZonedDateTime.Companion.now() = now(SystemClock()) +fun OffsetTime.Companion.now(): OffsetTime = nowImpl(SystemClock()) /** - * Get the current [ZonedDateTime] from the specified clock. + * Gets the current [OffsetTime] from the provided [clock]. */ -fun ZonedDateTime.Companion.now(clock: Clock): ZonedDateTime { - return fromMillisecondsSinceUnixEpoch(clock.read(), clock.zone) -} +fun OffsetTime.Companion.now(clock: Clock): OffsetTime = nowImpl(clock) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") @Deprecated( "Moved to TimeZone companion object.", ReplaceWith("systemDefault()"), - DeprecationLevel.WARNING + DeprecationLevel.ERROR ) -fun TimeZone.Companion.systemDefault() = systemDefault() \ No newline at end of file +fun TimeZone.Companion.systemDefault(): TimeZone = deprecatedToError() \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/SystemClock.kt b/core/src/commonMain/kotlin/io/islandtime/clock/SystemClock.kt new file mode 100644 index 000000000..142474543 --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/clock/SystemClock.kt @@ -0,0 +1,42 @@ +@file:Suppress("FunctionName") + +package io.islandtime.clock + +import io.islandtime.TimeZone +import io.islandtime.clock.internal.createSystemClock +import io.islandtime.internal.deprecatedToError + +/** + * A clock that provides the time from the current system. + * + * The time zone is treated as an immutable property of the clock, set when it is created. If you wish to follow + * changes to the system clock's configured time zone, you must create a new [SystemClock] in response to any time zone + * changes. + */ +abstract class SystemClock protected constructor() : Clock { + override fun equals(other: Any?): Boolean { + return other is SystemClock && zone == other.zone + } + + override fun hashCode(): Int = zone.hashCode() + 1 + override fun toString(): String = "SystemClock[$zone]" + + companion object { + /** + * A system clock in the UTC time zone. + */ + val UTC: SystemClock = createSystemClock(TimeZone.UTC) + + @Deprecated( + "Use TimeZone.systemDefault() instead.", + ReplaceWith("TimeZone.systemDefault()"), + DeprecationLevel.ERROR + ) + fun currentZone(): TimeZone = deprecatedToError() + } +} + +/** + * Creates a [SystemClock], optionally overriding the system's default time zone with another [zone]. + */ +fun SystemClock(zone: TimeZone = TimeZone.systemDefault()): SystemClock = createSystemClock(zone) \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/internal/NowImpl.kt b/core/src/commonMain/kotlin/io/islandtime/clock/internal/NowImpl.kt new file mode 100644 index 000000000..7c25cf93c --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/clock/internal/NowImpl.kt @@ -0,0 +1,11 @@ +package io.islandtime.clock.internal + +import io.islandtime.* +import io.islandtime.clock.Clock + +expect fun Date.Companion.nowImpl(clock: Clock): Date +expect fun DateTime.Companion.nowImpl(clock: Clock): DateTime +expect fun OffsetDateTime.Companion.nowImpl(clock: Clock): OffsetDateTime +expect fun ZonedDateTime.Companion.nowImpl(clock: Clock): ZonedDateTime +expect fun Time.Companion.nowImpl(clock: Clock): Time +expect fun OffsetTime.Companion.nowImpl(clock: Clock): OffsetTime \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt b/core/src/commonMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt new file mode 100644 index 000000000..80ec08363 --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt @@ -0,0 +1,6 @@ +package io.islandtime.clock.internal + +import io.islandtime.TimeZone +import io.islandtime.clock.SystemClock + +internal expect fun createSystemClock(zone: TimeZone): SystemClock \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/internal/Deprecation.kt b/core/src/commonMain/kotlin/io/islandtime/internal/Deprecation.kt new file mode 100644 index 000000000..73c18298b --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/internal/Deprecation.kt @@ -0,0 +1,3 @@ +package io.islandtime.internal + +internal fun deprecatedToError(): Nothing = throw NotImplementedError("Deprecation level is ERROR") \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/internal/Instants.kt b/core/src/commonMain/kotlin/io/islandtime/internal/Instants.kt new file mode 100644 index 000000000..aa34e0016 --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/internal/Instants.kt @@ -0,0 +1,26 @@ +package io.islandtime.internal + +import io.islandtime.* + +internal fun toTimeAt(offset: UtcOffset, secondOfUnixEpoch: Long, nanosecond: Int): Time { + val nanosecondOfDay = ((secondOfUnixEpoch % SECONDS_PER_DAY) * NANOSECONDS_PER_SECOND + + nanosecond + offset.totalSeconds.inNanoseconds.value + NANOSECONDS_PER_DAY) % NANOSECONDS_PER_DAY + return Time.fromNanosecondOfDay(nanosecondOfDay) +} + +internal fun toOffsetTimeAt(offset: UtcOffset, secondOfUnixEpoch: Long, nanosecond: Int): OffsetTime { + return toTimeAt(offset, secondOfUnixEpoch, nanosecond) at offset +} + +internal fun toDateAt(offset: UtcOffset, secondOfUnixEpoch: Long): Date { + val dayOfUnixEpoch = (secondOfUnixEpoch + offset.totalSeconds.value) floorDiv SECONDS_PER_DAY + return Date.fromDayOfUnixEpoch(dayOfUnixEpoch) +} + +internal fun toDateTimeAt(offset: UtcOffset, secondOfUnixEpoch: Long, nanosecond: Int): DateTime { + return DateTime.fromSecondOfUnixEpoch(secondOfUnixEpoch, nanosecond, offset) +} + +internal fun toOffsetDateTimeAt(offset: UtcOffset, secondOfUnixEpoch: Long, nanosecond: Int): OffsetDateTime { + return OffsetDateTime.fromSecondOfUnixEpoch(secondOfUnixEpoch, nanosecond, offset) +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/internal/Platform.kt b/core/src/commonMain/kotlin/io/islandtime/internal/Platform.kt deleted file mode 100644 index 48c65c7b0..000000000 --- a/core/src/commonMain/kotlin/io/islandtime/internal/Platform.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.islandtime.internal - -import io.islandtime.TimeZone - -internal expect fun systemDefaultTimeZone(): TimeZone \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/internal/PlatformImpl.kt b/core/src/commonMain/kotlin/io/islandtime/internal/PlatformImpl.kt new file mode 100644 index 000000000..7b62d15fc --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/internal/PlatformImpl.kt @@ -0,0 +1,10 @@ +package io.islandtime.internal + +import io.islandtime.Instant +import io.islandtime.PlatformInstant +import io.islandtime.TimeZone + +internal expect fun systemDefaultTimeZone(): TimeZone + +internal expect fun PlatformInstant.toIslandInstant(): Instant +internal expect fun Instant.toPlatformInstant(): PlatformInstant \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/islandtime/zone/TimeZoneRules.kt b/core/src/commonMain/kotlin/io/islandtime/zone/TimeZoneRules.kt index 1c2e5ceb2..8acec027b 100644 --- a/core/src/commonMain/kotlin/io/islandtime/zone/TimeZoneRules.kt +++ b/core/src/commonMain/kotlin/io/islandtime/zone/TimeZoneRules.kt @@ -64,12 +64,12 @@ interface TimeZoneOffsetTransition { val dateTimeAfter: DateTime /** - * Check if this is a gap, meaning that there are clock times that go "missing". + * Checks if this is a gap, meaning that there are clock times that go "missing". */ val isGap: Boolean /** - * Check if this is an overlap, meaning that there are clock times that exist twice. + * Checks if this is an overlap, meaning that there are clock times that exist twice. */ val isOverlap: Boolean @@ -89,7 +89,7 @@ interface TimeZoneOffsetTransition { val duration: IntSeconds /** - * Get a list of the valid offsets during this transition. If this is gap, the list will be empty. If this is an + * Gets a list of the valid offsets during this transition. If this is gap, the list will be empty. If this is an * overlap, the list will contain both the earlier and later offsets. */ val validOffsets: List @@ -101,52 +101,57 @@ interface TimeZoneOffsetTransition { */ interface TimeZoneRules { /** - * Check if the time zone has a fixed offset from UTC. + * Checks if the time zone has a fixed offset from UTC. */ val hasFixedOffset: Boolean /** - * Get the offset in effect at a certain number of milliseconds since the Unix epoch. + * Gets the offset in effect at a certain number of milliseconds since the Unix epoch. */ fun offsetAt(millisecondsSinceUnixEpoch: LongMilliseconds): UtcOffset /** - * Get the offset in effect at a certain number of seconds since the Unix epoch. + * Gets the offset in effect at a certain number of seconds since the Unix epoch. */ fun offsetAt(secondsSinceUnixEpoch: LongSeconds, nanoOfSeconds: IntNanoseconds): UtcOffset /** - * Get the offset in effect at a particular instant. + * Gets the offset in effect at a particular instant. */ fun offsetAt(instant: Instant): UtcOffset /** - * Get the offset in effect at a particular date and time. + * Gets the offset in effect at a particular instant. + */ + fun offsetAt(instant: PlatformInstant): UtcOffset + + /** + * Gets the offset in effect at a particular date and time. */ fun offsetAt(dateTime: DateTime): UtcOffset /** - * Get a list of the valid offsets at a particular date and time. + * Gets a list of the valid offsets at a particular date and time. */ fun validOffsetsAt(dateTime: DateTime): List /** - * Get the transition at a particular date and time, if one exists. + * Gets the transition at a particular date and time, if one exists. */ fun transitionAt(dateTime: DateTime): TimeZoneOffsetTransition? /** - * Check if [offset] is valid at particular date and time. + * Checks if [offset] is valid at particular date and time. */ fun isValidOffset(dateTime: DateTime, offset: UtcOffset): Boolean = validOffsetsAt(dateTime).contains(offset) /** - * Check if daylight savings time is in effect at a particular instant. + * Checks if daylight savings time is in effect at a particular instant. */ fun isDaylightSavingsAt(instant: Instant): Boolean /** - * Get the amount of daylight savings time in effect at a particular instant. This is the amount of time added to + * Gets the amount of daylight savings time in effect at a particular instant. This is the amount of time added to * the standard offset. */ fun daylightSavingsAt(instant: Instant): IntSeconds @@ -165,6 +170,7 @@ internal class FixedTimeZoneRules(private val offset: UtcOffset) : TimeZoneRules } override fun offsetAt(instant: Instant) = offset + override fun offsetAt(instant: PlatformInstant) = offset override fun offsetAt(dateTime: DateTime) = offset override fun validOffsetsAt(dateTime: DateTime) = listOf(offset) override fun transitionAt(dateTime: DateTime): TimeZoneOffsetTransition? = null diff --git a/core/src/commonTest/kotlin/io/islandtime/clock/ClockTest.kt b/core/src/commonTest/kotlin/io/islandtime/clock/ClockTest.kt index bf76dba8f..8a478cbe9 100644 --- a/core/src/commonTest/kotlin/io/islandtime/clock/ClockTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/clock/ClockTest.kt @@ -13,8 +13,8 @@ class ClockTest { fun `UTC SystemClock`() { val clock = SystemClock.UTC - assertTrue { clock.read() > 0L.milliseconds } - assertTrue { clock.instant() > Instant.UNIX_EPOCH } + assertTrue { clock.readMilliseconds() > 0L.milliseconds } + assertTrue { clock.readInstant() > Instant.UNIX_EPOCH } assertEquals(TimeZone.UTC, clock.zone) } @@ -22,8 +22,8 @@ class ClockTest { fun `SystemClock() without zone`() { val clock = SystemClock() - assertTrue { clock.read() > 0L.milliseconds } - assertTrue { clock.instant() > Instant.UNIX_EPOCH } + assertTrue { clock.readMilliseconds() > 0L.milliseconds } + assertTrue { clock.readInstant() > Instant.UNIX_EPOCH } assertNotEquals("", clock.zone.id) } @@ -31,8 +31,8 @@ class ClockTest { fun `SystemClock() with zone`() { val clock = SystemClock(TimeZone("America/Denver")) - assertTrue { clock.read() > 0L.milliseconds } - assertTrue { clock.instant() > Instant.UNIX_EPOCH } + assertTrue { clock.readMilliseconds() > 0L.milliseconds } + assertTrue { clock.readInstant() > Instant.UNIX_EPOCH } assertEquals(TimeZone("America/Denver"), clock.zone) } } \ No newline at end of file diff --git a/core/src/commonTest/kotlin/io/islandtime/clock/NowTest.kt b/core/src/commonTest/kotlin/io/islandtime/clock/NowTest.kt index 6d1f6dae3..907575265 100644 --- a/core/src/commonTest/kotlin/io/islandtime/clock/NowTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/clock/NowTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertEquals class NowTest : AbstractIslandTimeTest() { @Test fun `Year_now()`() { - val clock = FixedClock((-1L).milliseconds) + val clock = FixedClock(Instant((-1L).milliseconds), TimeZone.UTC) assertEquals(Year(1969), Year.now(clock)) clock += 1.milliseconds @@ -20,7 +20,7 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `YearMonth_now()`() { - val clock = FixedClock((-1L).milliseconds) + val clock = FixedClock(Instant((-1L).milliseconds), TimeZone.UTC) assertEquals(YearMonth(1969, Month.DECEMBER), YearMonth.now(clock)) clock += 1.milliseconds @@ -29,7 +29,7 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `Date_now() in UTC`() { - val clock = FixedClock((-1L).milliseconds) + val clock = FixedClock(Instant((-1L).milliseconds), TimeZone.UTC) assertEquals(Date(1969, Month.DECEMBER, 31), Date.now(clock)) clock += 1.milliseconds @@ -38,13 +38,13 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `Date_now() with offset`() { - val clock = FixedClock((-1).days, TimeZone("Etc/GMT+1")) + val clock = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+1")) assertEquals(Date(1969, Month.DECEMBER, 30), Date.now(clock)) } @Test fun `DateTime_now() in UTC`() { - val clock = FixedClock((-1L).milliseconds) + val clock = FixedClock(Instant((-1L).milliseconds), TimeZone.UTC) assertEquals( DateTime(1969, Month.DECEMBER, 31, 23, 59, 59, 999_000_000), DateTime.now(clock) @@ -65,7 +65,7 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `DateTime_now() with offset`() { - val clock = FixedClock((-1).days, TimeZone("Etc/GMT+1")) + val clock = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+1")) assertEquals( DateTime(1969, Month.DECEMBER, 30, 23, 0), DateTime.now(clock) @@ -74,7 +74,7 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `Time_now() in UTC`() { - val clock = FixedClock((-1).days) + val clock = FixedClock(Instant((-1L).days.inSeconds), TimeZone.UTC) assertEquals(Time(0, 0), Time.now(clock)) clock += 1.milliseconds @@ -92,25 +92,25 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `Time_now() with offset`() { - val clock1 = FixedClock((-1).days, TimeZone("Etc/GMT+4")) + val clock1 = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+4")) assertEquals(Time(20, 0), Time.now(clock1)) - val clock2 = FixedClock((-1).days, TimeZone("Etc/GMT-4")) + val clock2 = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT-4")) assertEquals(Time(4, 0), Time.now(clock2)) } @Test fun `OffsetTime_now()`() { - val clock1 = FixedClock((-1).days, TimeZone("Etc/GMT+4")) + val clock1 = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+4")) assertEquals(OffsetTime(Time(20, 0), (-4).hours.asUtcOffset()), OffsetTime.now(clock1)) - val clock2 = FixedClock((-1).days, TimeZone("Etc/GMT-4")) + val clock2 = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT-4")) assertEquals(OffsetTime(Time(4, 0), 4.hours.asUtcOffset()), OffsetTime.now(clock2)) } @Test fun `OffsetDateTime_now()`() { - val clock = FixedClock((-1).days, TimeZone("Etc/GMT+1")) + val clock = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+1")) assertEquals( OffsetDateTime( DateTime(1969, Month.DECEMBER, 30, 23, 0), @@ -131,7 +131,7 @@ class NowTest : AbstractIslandTimeTest() { @Test fun `ZonedDateTime_now()`() { - val clock = FixedClock((-1).days, TimeZone("Etc/GMT+1")) + val clock = FixedClock(Instant((-1L).days.inSeconds), TimeZone("Etc/GMT+1")) assertEquals( ZonedDateTime( DateTime(1969, Month.DECEMBER, 30, 23, 0), diff --git a/core/src/darwinMain/kotlin/io/islandtime/PlatformInstant.kt b/core/src/darwinMain/kotlin/io/islandtime/PlatformInstant.kt new file mode 100644 index 000000000..ba1f33177 --- /dev/null +++ b/core/src/darwinMain/kotlin/io/islandtime/PlatformInstant.kt @@ -0,0 +1,5 @@ +package io.islandtime + +import platform.Foundation.NSDate + +actual typealias PlatformInstant = NSDate \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt b/core/src/darwinMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt deleted file mode 100644 index a6247b381..000000000 --- a/core/src/darwinMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.islandtime.clock - -import io.islandtime.darwin.toIslandTimeZone -import io.islandtime.measures.LongMilliseconds -import io.islandtime.measures.microseconds -import io.islandtime.measures.seconds -import kotlinx.cinterop.* -import platform.Foundation.* -import platform.posix.gettimeofday -import platform.posix.timeval - -internal actual object PlatformSystemClock { - actual fun read(): LongMilliseconds { - return memScoped { - val posixTime = alloc() - gettimeofday(posixTime.ptr, null) - posixTime.tv_sec.seconds + posixTime.tv_usec.microseconds.inMilliseconds - } - } -} \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/clock/internal/NowImpl.kt b/core/src/darwinMain/kotlin/io/islandtime/clock/internal/NowImpl.kt new file mode 100644 index 000000000..b4506b769 --- /dev/null +++ b/core/src/darwinMain/kotlin/io/islandtime/clock/internal/NowImpl.kt @@ -0,0 +1,41 @@ +package io.islandtime.clock.internal + +import io.islandtime.* +import io.islandtime.clock.Clock +import io.islandtime.internal.* + +actual fun Date.Companion.nowImpl(clock: Clock): Date { + return with(clock) { readInstant().toDateAt(zone) } +} + +actual fun DateTime.Companion.nowImpl(clock: Clock): DateTime { + return with(clock) { readInstant().toDateTimeAt(zone) } +} + +actual fun OffsetDateTime.Companion.nowImpl(clock: Clock): OffsetDateTime { + return with(clock) { readInstant().toOffsetDateTimeAt(zone) } +} + +actual fun ZonedDateTime.Companion.nowImpl(clock: Clock): ZonedDateTime { + return with(clock) { readInstant() at zone } +} + +actual fun Time.Companion.nowImpl(clock: Clock): Time { + return with(clock) { readInstant().toTimeAt(zone) } +} + +actual fun OffsetTime.Companion.nowImpl(clock: Clock): OffsetTime { + return with(clock) { readInstant().toOffsetTimeAt(zone) } +} + +private fun Instant.toOffsetDateTimeAt(zone: TimeZone): OffsetDateTime { + return this at zone.rules.offsetAt(this) +} + +private fun Instant.toTimeAt(offset: UtcOffset): Time = toTimeAt(offset, secondOfUnixEpoch, nanosecond) +private fun Instant.toTimeAt(zone: TimeZone): Time = toTimeAt(zone.rules.offsetAt(this)) + +private fun Instant.toOffsetTimeAt(offset: UtcOffset): OffsetTime = + toOffsetTimeAt(offset, secondOfUnixEpoch, nanosecond) + +private fun Instant.toOffsetTimeAt(zone: TimeZone): OffsetTime = toOffsetTimeAt(zone.rules.offsetAt(this)) \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt b/core/src/darwinMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt new file mode 100644 index 000000000..6a59126c9 --- /dev/null +++ b/core/src/darwinMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt @@ -0,0 +1,38 @@ +package io.islandtime.clock.internal + +import io.islandtime.Instant +import io.islandtime.PlatformInstant +import io.islandtime.TimeZone +import io.islandtime.clock.SystemClock +import io.islandtime.measures.* +import kotlinx.cinterop.alloc +import kotlinx.cinterop.convert +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import platform.Foundation.NSDate +import platform.posix.gettimeofday +import platform.posix.timeval + +internal actual fun createSystemClock(zone: TimeZone): SystemClock { + return object : SystemClock() { + override val zone: TimeZone = zone + + override fun readMilliseconds(): LongMilliseconds { + return readSystemTime { seconds, microseconds -> seconds + microseconds.inMilliseconds } + } + + override fun readInstant(): Instant { + return readSystemTime { seconds, microseconds -> Instant(seconds, microseconds.inNanoseconds) } + } + + override fun readPlatformInstant(): PlatformInstant = NSDate() + } +} + +private inline fun readSystemTime(action: (seconds: LongSeconds, microseconds: IntMicroseconds) -> T): T { + return memScoped { + val posixTime = alloc() + gettimeofday(posixTime.ptr, null) + action(posixTime.tv_sec.convert().seconds, posixTime.tv_usec.microseconds) + } +} \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/internal/Platform.kt b/core/src/darwinMain/kotlin/io/islandtime/internal/Platform.kt deleted file mode 100644 index 76b0935de..000000000 --- a/core/src/darwinMain/kotlin/io/islandtime/internal/Platform.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.islandtime.internal - -import io.islandtime.TimeZone -import io.islandtime.darwin.toIslandTimeZone -import platform.Foundation.NSTimeZone -import platform.Foundation.localTimeZone - -internal actual fun systemDefaultTimeZone(): TimeZone = NSTimeZone.localTimeZone.toIslandTimeZone() \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/internal/PlatformImpl.kt b/core/src/darwinMain/kotlin/io/islandtime/internal/PlatformImpl.kt new file mode 100644 index 000000000..e69df20be --- /dev/null +++ b/core/src/darwinMain/kotlin/io/islandtime/internal/PlatformImpl.kt @@ -0,0 +1,15 @@ +package io.islandtime.internal + +import io.islandtime.Instant +import io.islandtime.PlatformInstant +import io.islandtime.TimeZone +import io.islandtime.darwin.toIslandInstant +import io.islandtime.darwin.toIslandTimeZone +import io.islandtime.darwin.toNSDate +import platform.Foundation.NSTimeZone +import platform.Foundation.localTimeZone + +internal actual fun systemDefaultTimeZone(): TimeZone = NSTimeZone.localTimeZone.toIslandTimeZone() + +internal actual fun PlatformInstant.toIslandInstant(): Instant = toIslandInstant() +internal actual fun Instant.toPlatformInstant(): PlatformInstant = toNSDate() \ No newline at end of file diff --git a/core/src/darwinMain/kotlin/io/islandtime/zone/TimeZoneRules.kt b/core/src/darwinMain/kotlin/io/islandtime/zone/TimeZoneRules.kt index 26f3c111f..684036454 100644 --- a/core/src/darwinMain/kotlin/io/islandtime/zone/TimeZoneRules.kt +++ b/core/src/darwinMain/kotlin/io/islandtime/zone/TimeZoneRules.kt @@ -1,9 +1,6 @@ package io.islandtime.zone -import io.islandtime.DateTime -import io.islandtime.Instant -import io.islandtime.UtcOffset -import io.islandtime.asUtcOffset +import io.islandtime.* import io.islandtime.darwin.toIslandDateTimeAt import io.islandtime.darwin.toNSDate import io.islandtime.darwin.toNSDateComponents @@ -69,6 +66,10 @@ private class DarwinTimeZoneRules(timeZone: NSTimeZone) : TimeZoneRules { override fun offsetAt(instant: Instant): UtcOffset = offsetAt(instant.toNSDate()) + override fun offsetAt(instant: PlatformInstant): UtcOffset { + return timeZone.secondsFromGMTForDate(instant).convert().seconds.asUtcOffset() + } + override fun transitionAt(dateTime: DateTime): TimeZoneOffsetTransition? { return transitionsInYear.use { map -> map.getOrPut(dateTime.year) { @@ -98,10 +99,6 @@ private class DarwinTimeZoneRules(timeZone: NSTimeZone) : TimeZoneRules { override val hasFixedOffset: Boolean get() = timeZone.nextDaylightSavingTimeTransitionAfterDate(NSDate.distantPast) == null - private fun offsetAt(date: NSDate): UtcOffset { - return timeZone.secondsFromGMTForDate(date).convert().seconds.asUtcOffset() - } - @OptIn(ExperimentalStdlibApi::class) private fun findTransitionsIn(year: Int): List { val startDateComponents = NSDateComponents().also { diff --git a/core/src/jvmMain/kotlin/io/islandtime/PlatformInstant.kt b/core/src/jvmMain/kotlin/io/islandtime/PlatformInstant.kt new file mode 100644 index 000000000..ca16e4bb3 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/PlatformInstant.kt @@ -0,0 +1,3 @@ +package io.islandtime + +actual typealias PlatformInstant = java.time.Instant \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt b/core/src/jvmMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt deleted file mode 100644 index b6d61b040..000000000 --- a/core/src/jvmMain/kotlin/io/islandtime/clock/PlatformSystemClock.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.islandtime.clock - -import io.islandtime.measures.milliseconds - -internal actual object PlatformSystemClock { - actual fun read() = System.currentTimeMillis().milliseconds -} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/clock/internal/Conversions.kt b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/Conversions.kt new file mode 100644 index 000000000..18059e92d --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/Conversions.kt @@ -0,0 +1,35 @@ +package io.islandtime.clock.internal + +import io.islandtime.* +import io.islandtime.internal.* + +internal fun java.time.Instant.toTimeAt(offset: UtcOffset): Time = toTimeAt(offset, epochSecond, nano) +internal fun java.time.Instant.toTimeAt(zone: TimeZone): Time = toTimeAt(zone.rules.offsetAt(this)) + +internal fun java.time.Instant.toOffsetTimeAt(offset: UtcOffset): OffsetTime { + return toOffsetTimeAt(offset, epochSecond, nano) +} + +internal fun java.time.Instant.toOffsetTimeAt(zone: TimeZone): OffsetTime { + return toOffsetTimeAt(zone.rules.offsetAt(this)) +} + +internal fun java.time.Instant.toDateAt(offset: UtcOffset): Date = toDateAt(offset, epochSecond) +internal fun java.time.Instant.toDateAt(zone: TimeZone): Date = toDateAt(zone.rules.offsetAt(this)) + +internal fun java.time.Instant.toDateTimeAt(offset: UtcOffset): DateTime = toDateTimeAt(offset, epochSecond, nano) +internal fun java.time.Instant.toDateTimeAt(zone: TimeZone): DateTime = toDateTimeAt(zone.rules.offsetAt(this)) + +internal fun java.time.Instant.toOffsetDateTimeAt(offset: UtcOffset): OffsetDateTime { + return toOffsetDateTimeAt(offset, epochSecond, nano) +} + +internal fun java.time.Instant.toOffsetDateTimeAt(zone: TimeZone): OffsetDateTime { + return toOffsetDateTimeAt(zone.rules.offsetAt(this)) +} + +internal fun java.time.Instant.toZonedDateTimeAt(zone: TimeZone): ZonedDateTime { + val offset = zone.rules.offsetAt(this) + val dateTime = DateTime.fromSecondOfUnixEpoch(epochSecond, nano, offset) + return ZonedDateTime.create(dateTime, offset, zone) +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/clock/internal/NowImpl.kt b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/NowImpl.kt new file mode 100644 index 000000000..881f81cd3 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/NowImpl.kt @@ -0,0 +1,28 @@ +package io.islandtime.clock.internal + +import io.islandtime.* +import io.islandtime.clock.Clock + +actual fun Date.Companion.nowImpl(clock: Clock): Date { + return with(clock) { readPlatformInstant().toDateAt(zone) } +} + +actual fun DateTime.Companion.nowImpl(clock: Clock): DateTime { + return with(clock) { readPlatformInstant().toDateTimeAt(zone) } +} + +actual fun OffsetDateTime.Companion.nowImpl(clock: Clock): OffsetDateTime { + return with(clock) { readPlatformInstant().toOffsetDateTimeAt(zone) } +} + +actual fun ZonedDateTime.Companion.nowImpl(clock: Clock): ZonedDateTime { + return with(clock) { readPlatformInstant().toZonedDateTimeAt(zone) } +} + +actual fun Time.Companion.nowImpl(clock: Clock): Time { + return with(clock) { readPlatformInstant().toTimeAt(zone) } +} + +actual fun OffsetTime.Companion.nowImpl(clock: Clock): OffsetTime { + return with(clock) { readPlatformInstant().toOffsetTimeAt(zone) } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt new file mode 100644 index 000000000..8328f6c03 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/clock/internal/SystemClockImpl.kt @@ -0,0 +1,21 @@ +package io.islandtime.clock.internal + +import io.islandtime.Instant +import io.islandtime.PlatformInstant +import io.islandtime.TimeZone +import io.islandtime.clock.SystemClock +import io.islandtime.jvm.toIslandInstant +import io.islandtime.measures.LongMilliseconds +import io.islandtime.measures.milliseconds +import java.time.Clock as JavaClock + +private val javaClock = JavaClock.systemUTC() + +internal actual fun createSystemClock(zone: TimeZone): SystemClock { + return object : SystemClock() { + override val zone: TimeZone = zone + override fun readMilliseconds(): LongMilliseconds = System.currentTimeMillis().milliseconds + override fun readInstant(): Instant = readPlatformInstant().toIslandInstant() + override fun readPlatformInstant(): PlatformInstant = javaClock.instant() + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/clock/jvm/Now.kt b/core/src/jvmMain/kotlin/io/islandtime/clock/jvm/Now.kt new file mode 100644 index 000000000..928852ed9 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/clock/jvm/Now.kt @@ -0,0 +1,64 @@ +package io.islandtime.clock.jvm + +import io.islandtime.* +import io.islandtime.clock.internal.* +import io.islandtime.internal.toIslandInstant +import io.islandtime.jvm.toIslandTimeZone +import java.time.Clock as JavaClock + +/** + * Gets the current [Instant] from the provided [clock]. + */ +fun Instant.Companion.now(clock: JavaClock): Instant = clock.instant().toIslandInstant() + +/** + * Gets the current [Year] from the provided [clock]. + */ +fun Year.Companion.now(clock: JavaClock): Year = Date.now(clock).toYear() + +/** + * Gets the current [YearMonth] from the provided [clock]. + */ +fun YearMonth.Companion.now(clock: JavaClock): YearMonth = Date.now(clock).toYearMonth() + +/** + * Gets the current [Date] from the provided [clock]. + */ +fun Date.Companion.now(clock: JavaClock): Date { + return with(clock) { instant().toDateAt(zone.toIslandTimeZone()) } +} + +/** + * Gets the current [DateTime] from the provided [clock]. + */ +fun DateTime.Companion.now(clock: JavaClock): DateTime { + return with(clock) { instant().toDateTimeAt(zone.toIslandTimeZone()) } +} + +/** + * Gets the current [OffsetDateTime] from the provided [clock]. + */ +fun OffsetDateTime.Companion.now(clock: JavaClock): OffsetDateTime { + return with(clock) { instant().toOffsetDateTimeAt(zone.toIslandTimeZone()) } +} + +/** + * Gets the current [ZonedDateTime] from the provided [clock]. + */ +fun ZonedDateTime.Companion.now(clock: JavaClock): ZonedDateTime { + return with(clock) { instant().toZonedDateTimeAt(zone.toIslandTimeZone()) } +} + +/** + * Gets the current [Time] from the provided [clock]. + */ +fun Time.Companion.now(clock: JavaClock): Time { + return with(clock) { instant().toTimeAt(zone.toIslandTimeZone()) } +} + +/** + * Gets the current [OffsetTime] from the provided [clock]. + */ +fun OffsetTime.Companion.now(clock: JavaClock): OffsetTime { + return with(clock) { instant().toOffsetTimeAt(zone.toIslandTimeZone()) } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/internal/Platform.kt b/core/src/jvmMain/kotlin/io/islandtime/internal/Platform.kt deleted file mode 100644 index 0384354e3..000000000 --- a/core/src/jvmMain/kotlin/io/islandtime/internal/Platform.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.islandtime.internal - -import io.islandtime.TimeZone - -// TODO: Benchmark and see if using a ConcurrentHashMap would be faster than parsing the ID each time -internal actual fun systemDefaultTimeZone(): TimeZone = java.util.TimeZone.getDefault().toIslandTimeZone() - -internal fun java.util.TimeZone.toIslandTimeZone(): TimeZone { - return if (id.startsWith("GMT")) { - if (id.length == 3) { - TimeZone.Region(id) - } else { - TimeZone.FixedOffset(id.substring(3)) - } - } else { - TimeZone.Region(id) - } -} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/internal/PlatformImpl.kt b/core/src/jvmMain/kotlin/io/islandtime/internal/PlatformImpl.kt new file mode 100644 index 000000000..f87d63e76 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/islandtime/internal/PlatformImpl.kt @@ -0,0 +1,34 @@ +package io.islandtime.internal + +import io.islandtime.* +import io.islandtime.jvm.toIslandInstant +import io.islandtime.jvm.toJavaInstant + +internal actual fun systemDefaultTimeZone(): TimeZone = java.util.TimeZone.getDefault().toIslandTimeZone() + +internal actual fun PlatformInstant.toIslandInstant(): Instant = this.toIslandInstant() +internal actual fun Instant.toPlatformInstant(): PlatformInstant = this.toJavaInstant() + +private var lastDefaultTimeZone: Pair? = null + +internal fun java.util.TimeZone.toIslandTimeZone(): TimeZone { + lastDefaultTimeZone?.let { (javaZone, islandZone) -> + if (this == javaZone) { + return islandZone + } + } + + val id = id + val islandZone = if (id.startsWith("GMT")) { + if (id.length == 3) { + TimeZone.Region(id) + } else { + TimeZone.FixedOffset(id.substring(3)) + } + } else { + TimeZone.Region(id) + } + + lastDefaultTimeZone = this to islandZone + return islandZone +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/jvm/Conversions.kt b/core/src/jvmMain/kotlin/io/islandtime/jvm/Conversions.kt index 59cf8c061..c94c71fb7 100644 --- a/core/src/jvmMain/kotlin/io/islandtime/jvm/Conversions.kt +++ b/core/src/jvmMain/kotlin/io/islandtime/jvm/Conversions.kt @@ -3,6 +3,7 @@ package io.islandtime.jvm import io.islandtime.* +import io.islandtime.clock.Clock import io.islandtime.internal.MICROSECONDS_PER_SECOND import io.islandtime.measures.* @@ -349,4 +350,22 @@ fun IntNanoseconds.toJavaDuration(): java.time.Duration = java.time.Duration.ofN /** * Convert to an equivalent Java `Duration`. */ -fun LongNanoseconds.toJavaDuration(): java.time.Duration = java.time.Duration.ofNanos(value) \ No newline at end of file +fun LongNanoseconds.toJavaDuration(): java.time.Duration = java.time.Duration.ofNanos(value) + +/** + * Makes this clock compatible with Island Time's [Clock] interface. + */ +fun java.time.Clock.asIslandClock(): Clock = WrappedJavaClock(clock = this) + +private class WrappedJavaClock(private val clock: java.time.Clock) : Clock { + override fun equals(other: Any?): Boolean { + return other is WrappedJavaClock && other.clock == clock + } + + override fun hashCode(): Int = clock.hashCode() + override fun toString(): String = "$clock (Java)" + override val zone: TimeZone get() = clock.zone.toIslandTimeZone() + override fun readPlatformInstant(): PlatformInstant = clock.instant() + override fun readInstant(): Instant = readPlatformInstant().toIslandInstant() + override fun readMilliseconds(): LongMilliseconds = clock.millis().milliseconds +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/islandtime/zone/TimeZoneRules.kt b/core/src/jvmMain/kotlin/io/islandtime/zone/TimeZoneRules.kt index c371c5178..4e8d6be82 100644 --- a/core/src/jvmMain/kotlin/io/islandtime/zone/TimeZoneRules.kt +++ b/core/src/jvmMain/kotlin/io/islandtime/zone/TimeZoneRules.kt @@ -2,6 +2,7 @@ package io.islandtime.zone import io.islandtime.DateTime import io.islandtime.Instant +import io.islandtime.PlatformInstant import io.islandtime.UtcOffset import io.islandtime.jvm.* import io.islandtime.measures.* @@ -58,6 +59,15 @@ private class JavaTimeZoneRules(private val javaZoneRules: ZoneRules) : TimeZone return with(instant) { offsetAt(secondsSinceUnixEpoch, additionalNanosecondsSinceUnixEpoch) } } + override fun offsetAt(instant: PlatformInstant): UtcOffset { + return try { + javaZoneRules.getOffset(instant).toIslandUtcOffset() + } catch (e: ArithmeticException) { + // Workaround for Android desugaring issue (https://issuetracker.google.com/issues/153773237) + UtcOffset.ZERO + } + } + override fun offsetAt(dateTime: DateTime): UtcOffset { val offset = javaZoneRules.getOffset(dateTime.toJavaLocalDateTime()) return offset.toIslandUtcOffset() @@ -87,15 +97,6 @@ private class JavaTimeZoneRules(private val javaZoneRules: ZoneRules) : TimeZone override fun daylightSavingsAt(instant: Instant): IntSeconds { return javaZoneRules.getDaylightSavings(instant.toJavaInstant()).seconds.toInt().seconds } - - private fun offsetAt(javaInstant: JavaInstant): UtcOffset { - return try { - javaZoneRules.getOffset(javaInstant).toIslandUtcOffset() - } catch (e: ArithmeticException) { - // Workaround for Android desugaring issue (https://issuetracker.google.com/issues/153773237) - UtcOffset.ZERO - } - } } private class JavaTimeZoneOffsetTransition( diff --git a/core/src/jvmTest/kotlin/io/islandtime/clock/jvm/JvmNowTest.kt b/core/src/jvmTest/kotlin/io/islandtime/clock/jvm/JvmNowTest.kt new file mode 100644 index 000000000..a029d6072 --- /dev/null +++ b/core/src/jvmTest/kotlin/io/islandtime/clock/jvm/JvmNowTest.kt @@ -0,0 +1,103 @@ +package io.islandtime.clock.jvm + +import io.islandtime.* +import io.islandtime.measures.hours +import io.islandtime.test.AbstractIslandTimeTest +import org.junit.Test +import kotlin.test.assertEquals +import java.time.Clock as JavaClock +import java.time.Instant as JavaInstant +import java.time.ZoneId as JavaZoneId +import java.time.ZoneOffset as JavaZoneOffset + +class JvmNowTest : AbstractIslandTimeTest() { + @Test + fun `Year_now()`() { + val clock = JavaClock.fixed(JavaInstant.ofEpochMilli(-1L), JavaZoneOffset.UTC) + assertEquals(Year(1969), Year.now(clock)) + } + + @Test + fun `YearMonth_now()`() { + val clock = JavaClock.fixed(JavaInstant.ofEpochMilli(-1L), JavaZoneOffset.UTC) + assertEquals(YearMonth(1969, Month.DECEMBER), YearMonth.now(clock)) + } + + @Test + fun `Date_now() in UTC`() { + val clock = JavaClock.fixed(JavaInstant.ofEpochMilli(-1L), JavaZoneOffset.UTC) + assertEquals(Date(1969, Month.DECEMBER, 31), Date.now(clock)) + } + + @Test + fun `Date_now() with offset`() { + val clock = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+1")) + assertEquals(Date(1969, Month.DECEMBER, 30), Date.now(clock)) + } + + @Test + fun `DateTime_now() in UTC`() { + val clock = JavaClock.fixed(JavaInstant.ofEpochMilli(-1L), JavaZoneOffset.UTC) + assertEquals( + DateTime(1969, Month.DECEMBER, 31, 23, 59, 59, 999_000_000), + DateTime.now(clock) + ) + } + + @Test + fun `DateTime_now() with offset`() { + val clock = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+1")) + assertEquals( + DateTime(1969, Month.DECEMBER, 30, 23, 0), + DateTime.now(clock) + ) + } + + @Test + fun `Time_now() in UTC`() { + val clock = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneOffset.UTC) + assertEquals(Time(0, 0), Time.now(clock)) + } + + @Test + fun `Time_now() with offset`() { + val clock1 = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+4")) + assertEquals(Time(20, 0), Time.now(clock1)) + + val clock2 = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT-4")) + assertEquals(Time(4, 0), Time.now(clock2)) + } + + @Test + fun `OffsetTime_now()`() { + val clock1 = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+4")) + assertEquals(OffsetTime(Time(20, 0), (-4).hours.asUtcOffset()), OffsetTime.now(clock1)) + + val clock2 = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT-4")) + assertEquals(OffsetTime(Time(4, 0), 4.hours.asUtcOffset()), OffsetTime.now(clock2)) + } + + @Test + fun `OffsetDateTime_now()`() { + val clock = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+1")) + assertEquals( + OffsetDateTime( + DateTime(1969, Month.DECEMBER, 30, 23, 0), + (-1).hours.asUtcOffset() + ), + OffsetDateTime.now(clock) + ) + } + + @Test + fun `ZonedDateTime_now()`() { + val clock = JavaClock.fixed(JavaInstant.parse("1969-12-31T00:00:00Z"), JavaZoneId.of("Etc/GMT+1")) + assertEquals( + ZonedDateTime( + DateTime(1969, Month.DECEMBER, 30, 23, 0), + TimeZone("Etc/GMT+1") + ), + ZonedDateTime.now(clock) + ) + } +} \ No newline at end of file diff --git a/core/src/jvmTest/kotlin/io/islandtime/internal/PlatformTest.kt b/core/src/jvmTest/kotlin/io/islandtime/internal/PlatformImplTest.kt similarity index 79% rename from core/src/jvmTest/kotlin/io/islandtime/internal/PlatformTest.kt rename to core/src/jvmTest/kotlin/io/islandtime/internal/PlatformImplTest.kt index eaa4ec896..8007ead9a 100644 --- a/core/src/jvmTest/kotlin/io/islandtime/internal/PlatformTest.kt +++ b/core/src/jvmTest/kotlin/io/islandtime/internal/PlatformImplTest.kt @@ -8,13 +8,15 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue -class PlatformTest : AbstractIslandTimeTest() { +class PlatformImplTest : AbstractIslandTimeTest() { @Test fun `converts java_util_TimeZone with UTC equivalent IDs to Island region-based zones`() { - listOf("GMT", "UTC").forEach { - val tz = java.util.TimeZone.getTimeZone(it).toIslandTimeZone() - assertEquals(it, tz.id) - assertTrue { tz.isValid } + listOf("GMT", "UTC").forEach { id -> + repeat(2) { + val tz = java.util.TimeZone.getTimeZone(id).toIslandTimeZone() + assertEquals(id, tz.id) + assertTrue { tz.isValid } + } } } diff --git a/core/src/jvmTest/kotlin/io/islandtime/jvm/ConversionsTest.kt b/core/src/jvmTest/kotlin/io/islandtime/jvm/ConversionsTest.kt index a08d91482..13992aeba 100644 --- a/core/src/jvmTest/kotlin/io/islandtime/jvm/ConversionsTest.kt +++ b/core/src/jvmTest/kotlin/io/islandtime/jvm/ConversionsTest.kt @@ -296,4 +296,28 @@ class ConversionsTest { assertEquals(1_000_000_000L, 1_000_000_000.nanoseconds.toJavaDuration().toNanos()) assertEquals(-1L, (-1L).nanoseconds.toJavaDuration().toNanos()) } + + @Test + fun `converts a Java Clock to an Island Time Clock`() { + val javaClock = java.time.Clock.fixed( + java.time.Instant.ofEpochSecond(234_678_901L, 123456789), + java.time.ZoneId.of("America/New_York") + ) + + val islandClock = javaClock.asIslandClock() + + assertEquals( + java.time.Instant.ofEpochSecond(234_678_901L, 123456789), + islandClock.readPlatformInstant() + ) + assertEquals( + Instant.fromSecondOfUnixEpoch(234_678_901L, 123456789), + islandClock.readInstant() + ) + assertEquals( + Instant.fromSecondOfUnixEpoch(234_678_901L, 123456789).millisecondsSinceUnixEpoch, + islandClock.readMilliseconds() + ) + assertEquals(TimeZone("America/New_York"), islandClock.zone) + } } \ No newline at end of file