From ae4373e9422142f15ca4a80207ce65b8880a984e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 5 Nov 2024 12:54:39 +0100 Subject: [PATCH] Cleanup --- core/common/src/DateTimePeriod.kt | 17 +++++--- core/common/test/DateTimePeriodTest.kt | 2 +- core/common/test/InstantTest.kt | 10 +++-- core/commonJs/src/PlatformSpecifics.kt | 4 +- core/commonJs/src/internal/Platform.kt | 1 - core/commonJs/test/JsJodaTimezoneTest.kt | 50 ++++++++++++---------- core/commonKotlin/src/Instant.kt | 8 ++-- core/commonKotlin/src/internal/Platform.kt | 1 - js-without-timezones/build.gradle.kts | 21 --------- license/README.md | 14 +++--- 10 files changed, 59 insertions(+), 69 deletions(-) diff --git a/core/common/src/DateTimePeriod.kt b/core/common/src/DateTimePeriod.kt index 25f008b3..3f1abd65 100644 --- a/core/common/src/DateTimePeriod.kt +++ b/core/common/src/DateTimePeriod.kt @@ -13,6 +13,7 @@ import kotlinx.datetime.serializers.DateTimePeriodComponentSerializer import kotlin.math.* import kotlin.time.Duration import kotlinx.serialization.Serializable +import kotlin.text.toLong /** * A difference between two [instants][Instant], decomposed into date and time components. @@ -448,7 +449,8 @@ public class DatePeriod internal constructor( * (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit.DateBased] instead. * For example, instead of `DatePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`. * - * @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int]. + * @throws IllegalArgumentException if the total number of years + * (together with full years in [months]) overflows an [Int]. * @sample kotlinx.datetime.test.samples.DatePeriodSamples.construction */ public constructor(years: Int = 0, months: Int = 0, days: Int = 0): this(totalMonths(years, months), days) @@ -499,7 +501,11 @@ private class DateTimePeriodImpl( override val totalNanoseconds: Long, ) : DateTimePeriod() -private fun totalMonths(years: Int, months: Int): Long = years.toLong() * 12 + months.toLong() +private fun totalMonths(years: Int, months: Int): Long = (years.toLong() * 12 + months.toLong()).also { + require(it / 12 in Int.MIN_VALUE..Int.MAX_VALUE) { + "The total number of years in $years years and $months months overflows an Int" + } +} private fun totalNanoseconds(hours: Int, minutes: Int, seconds: Int, nanoseconds: Long): Long { val totalMinutes: Long = hours.toLong() * 60 + minutes @@ -536,9 +542,10 @@ internal fun buildDateTimePeriod(totalMonths: Long = 0, days: Int = 0, totalNano * (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit] instead. * For example, instead of `DateTimePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`. * - * @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int]. - * @throws IllegalArgumentException if the total number of months in [hours], [minutes], [seconds] and [nanoseconds] - * overflows a [Long]. + * @throws IllegalArgumentException if the total number of years + * (together with full years in [months]) overflows an [Int]. + * @throws IllegalArgumentException if the total number of nanoseconds in + * [hours], [minutes], [seconds] and [nanoseconds] overflows a [Long]. * @sample kotlinx.datetime.test.samples.DateTimePeriodSamples.constructorFunction */ public fun DateTimePeriod( diff --git a/core/common/test/DateTimePeriodTest.kt b/core/common/test/DateTimePeriodTest.kt index 3c93e4a9..85d4345b 100644 --- a/core/common/test/DateTimePeriodTest.kt +++ b/core/common/test/DateTimePeriodTest.kt @@ -114,7 +114,7 @@ class DateTimePeriodTest { assertFailsWith { DateTimePeriod.parse("P") } - // overflow of `Long.MAX_VALUE` months + // overflow of `Int.MAX_VALUE` years assertFailsWith { DateTimePeriod.parse("P768614336404564651Y") } assertFailsWith { DateTimePeriod.parse("P1Y9223372036854775805M") } diff --git a/core/common/test/InstantTest.kt b/core/common/test/InstantTest.kt index 4788e2f6..17179626 100644 --- a/core/common/test/InstantTest.kt +++ b/core/common/test/InstantTest.kt @@ -11,6 +11,7 @@ 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 @@ -268,9 +269,8 @@ class InstantTest { val offset3 = instant3.offsetIn(zone) assertEquals(offset1, offset3) - // TODO: fails on JS - // // without the minus, this test fails on JVM - // (Instant.MAX - (2 * 365).days).offsetIn(zone) + // without the minus, this test fails on JVM + (Instant.MAX - (2 * 365).days).offsetIn(zone) } @Test @@ -313,7 +313,9 @@ class InstantTest { val diff2 = date1.periodUntil(date2) if (diff1 != diff2) - println("start: $instant1, end: $instant2, diff by instants: $diff1, diff by dates: $diff2") + throw AssertionError( + "start: $instant1, end: $instant2, diff by instants: $diff1, diff by dates: $diff2" + ) } } } diff --git a/core/commonJs/src/PlatformSpecifics.kt b/core/commonJs/src/PlatformSpecifics.kt index 2813677d..cc65af61 100644 --- a/core/commonJs/src/PlatformSpecifics.kt +++ b/core/commonJs/src/PlatformSpecifics.kt @@ -12,10 +12,10 @@ public expect interface InteropInterface @OptIn(ExperimentalMultiplatform::class) @Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) +@Target(AnnotationTarget.FILE) @OptionalExpectation public expect annotation class JsNonModule() @Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) +@Target(AnnotationTarget.FILE) public expect annotation class JsModule(val import: String) diff --git a/core/commonJs/src/internal/Platform.kt b/core/commonJs/src/internal/Platform.kt index 2606e1c4..86bd4cf8 100644 --- a/core/commonJs/src/internal/Platform.kt +++ b/core/commonJs/src/internal/Platform.kt @@ -76,7 +76,6 @@ private val tzdb: Result = runCatching { (unpackBase60(it) * SECONDS_PER_MINUTE * MILLIS_PER_ONE).roundToLong() / // minutes to milliseconds MILLIS_PER_ONE // but we only need seconds } - println("Zone ${components[0]}: $offsets with ${components[2]} raw data") zones[components[0]] = TimeZoneRules( transitionEpochSeconds = lengthsOfPeriodsWithOffsets.partialSums().take(indices.size - 1), offsets = indices.map { UtcOffset(null, null, -(offsets[it] * 60).roundToInt()) }, diff --git a/core/commonJs/test/JsJodaTimezoneTest.kt b/core/commonJs/test/JsJodaTimezoneTest.kt index 7ca8f9ec..138ed28a 100644 --- a/core/commonJs/test/JsJodaTimezoneTest.kt +++ b/core/commonJs/test/JsJodaTimezoneTest.kt @@ -10,6 +10,8 @@ import kotlin.math.roundToInt import kotlin.test.* 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 class JsJodaTimezoneTest { @Test @@ -25,33 +27,35 @@ class JsJodaTimezoneTest { fun iterateOverAllTimezones() { for (id in TimeZone.availableZoneIds) { val rules = rulesForId(id) ?: throw AssertionError("No rules for $id") - val jodaZone = kotlinx.datetime.test.JSJoda.ZoneId.of(id) - assertNull(rules.recurringZoneRules) + val jodaZone = jtZoneId.of(id) + assertNull(rules.recurringZoneRules) // js-joda doesn't expose recurring rules fun checkAtInstant(instant: Instant) { - val jodaInstant = kotlinx.datetime.test.JSJoda.Instant.ofEpochMilli(instant.toEpochMilliseconds().toDouble()) - val zdt = jodaInstant.atZone(jodaZone) val offset = rules.infoAtInstant(instant) val ourLdt = instant.toLocalDateTime(offset) - val theirLdt = LocalDateTime( - zdt.year(), - zdt.monthValue(), - zdt.dayOfMonth(), - zdt.hour(), - zdt.minute(), - zdt.second(), - zdt.nano().roundToInt() - ) - if ((ourLdt.toInstant(TimeZone.UTC) - theirLdt.toInstant(TimeZone.UTC)).absoluteValue > 1.seconds) { - // It seems that sometimes, js-joda interprets its data incorrectly by at most one second, - // and we don't want to replicate that. - // Example: America/Noronha at 1914-01-01T02:09:39.998Z: - // - Computed 1913-12-31T23:59:59.998 with offset -02:09:40 - // - 1914-01-01T00:00:00.998-02:09:39[America/Noronha] is js-joda's interpretation - // The raw data representing the offset is `29.E`, which is `2 * 60 + 9 + (ord 'E' - 29) / 60`, - // and `ord 'E'` is 69, so the offset is -2:09:40. - // Thus, we allow a difference of 1 second. - throw AssertionError("Failed for $id at $instant: computed $ourLdt with offset $offset, but $zdt is correct") + val zdt = jtInstant.ofEpochMilli(instant.toEpochMilliseconds().toDouble()).atZone(jodaZone) + val theirLdt = with(zdt) { + LocalDateTime( + year(), + monthValue(), + dayOfMonth(), + hour(), + minute(), + second(), + nano().roundToInt() + ) } + // It seems that sometimes, js-joda interprets its data incorrectly by at most one second, + // and we don't want to replicate that. + // Example: America/Noronha at 1914-01-01T02:09:39.998Z: + // - Computed 1913-12-31T23:59:59.998 with offset -02:09:40 + // - 1914-01-01T00:00:00.998-02:09:39[America/Noronha] is js-joda's interpretation + // The raw data representing the offset is `29.E`, which is `2 * 60 + 9 + (ord 'E' - 29) / 60`, + // and `ord 'E'` is 69, so the offset is -2:09:40. + // Thus, we allow a difference of 1 second. + assertTrue( + (ourLdt.toInstant(TimeZone.UTC) - theirLdt.toInstant(TimeZone.UTC)).absoluteValue <= 1.seconds, + "Failed for $id at $instant: computed $ourLdt with offset $offset, but $zdt is correct" + ) } fun checkTransition(instant: Instant) { checkAtInstant(instant - 2.milliseconds) diff --git a/core/commonKotlin/src/Instant.kt b/core/commonKotlin/src/Instant.kt index 1a779606..343404f0 100644 --- a/core/commonKotlin/src/Instant.kt +++ b/core/commonKotlin/src/Instant.kt @@ -242,12 +242,12 @@ public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT val otherLdt = other.toZonedDateTimeFailing(timeZone) val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH) // `until` on dates never fails - thisLdt = thisLdt.plus(months.toLong(), DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid - val days = thisLdt.until(otherLdt, DateTimeUnit.DAY).toInt() // `until` on dates never fails - thisLdt = thisLdt.plus(days.toLong(), DateTimeUnit.DAY) // won't throw: thisLdt + days <= otherLdt + 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, nanoseconds) + return buildDateTimePeriod(months, days.toInt(), nanoseconds) } public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = diff --git a/core/commonKotlin/src/internal/Platform.kt b/core/commonKotlin/src/internal/Platform.kt index 9f52b0b4..8227af25 100644 --- a/core/commonKotlin/src/internal/Platform.kt +++ b/core/commonKotlin/src/internal/Platform.kt @@ -8,7 +8,6 @@ package kotlinx.datetime.internal import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone -// RegionTimeZone(systemTzdb.rulesForId(zoneId), zoneId) internal expect fun timeZoneById(zoneId: String): TimeZone internal expect fun getAvailableZoneIds(): Set diff --git a/js-without-timezones/build.gradle.kts b/js-without-timezones/build.gradle.kts index 0e2b221f..3593447f 100644 --- a/js-without-timezones/build.gradle.kts +++ b/js-without-timezones/build.gradle.kts @@ -8,27 +8,12 @@ plugins { id("org.jetbrains.kotlinx.kover") } -val mainJavaToolchainVersion: String by project - -java { - toolchain { languageVersion.set(JavaLanguageVersion.of(mainJavaToolchainVersion)) } -} - kotlin { - js { nodejs { } - compilations.all { - kotlinOptions { - sourceMap = true - moduleKind = "umd" - metaInfo = true - } - } } - wasmJs { nodejs { } @@ -42,12 +27,6 @@ kotlin { resources.srcDir("$targetName/${suffix?.let { it + "Resources" } ?: "resources"}") } - targets.withType { - compilations["test"].kotlinOptions { - freeCompilerArgs += listOf("-trw") - } - } - sourceSets { commonMain { dependencies { diff --git a/license/README.md b/license/README.md index 62a8d071..30a4c4c7 100644 --- a/license/README.md +++ b/license/README.md @@ -6,11 +6,11 @@ may apply: - Origin: implementation of date/time calculations is based on ThreeTen backport project. - License: BSD 3-Clause ([license/thirdparty/threetenbp_license.txt][threetenbp]) -- Path: `core/nativeMain/src` +- Path: `core/commonKotlin/src` - Origin: implementation of date/time entities is based on ThreeTen backport project. - License: BSD 3-Clause ([license/thirdparty/threetenbp_license.txt][threetenbp]) -- Path: `core/nativeTest/src` +- Path: `core/commonKotlin/src` - Origin: Derived from tests of ThreeTen backport project - License: BSD 3-Clause ([license/thirdparty/threetenbp_license.txt][threetenbp]) @@ -18,11 +18,7 @@ may apply: - Origin: Some tests are derived from tests of ThreeTen backport project - License: BSD 3-Clause ([license/thirdparty/threetenbp_license.txt][threetenbp]) -- Path: `thirdparty/date` - - Origin: https://github.com/HowardHinnant/date library - - License: MIT ([license/thirdparty/cppdate_license.txt](thirdparty/cppdate_license.txt)) - -- Path: `core/nativeMain/cinterop/public/windows_zones.hpp` +- Path: `core/windows/src/internal/WindowsZoneNames.kt` - Origin: time zone name mappings for Windows are generated from https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml - License: Unicode ([license/thirdparty/unicode_license.txt](thirdparty/unicode_license.txt)) @@ -31,4 +27,8 @@ may apply: - Origin: implementation is based on the bionic project. - License: BSD ([license/thirdparty/bionic_license.txt](thirdparty/bionic_license.txt)) +- Path: `core/commonJs/src` + - Origin: implementation accesses the internals of js-joda. + - License: BSD ([license/thirdparty/js-joda_license.txt](thirdparty/js-joda_license.txt)) + [threetenbp]: thirdparty/threetenbp_license.txt