From e367c5057168ce86bc88ad3e4cb8f81abff7f33d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 2 Apr 2025 12:02:50 +0200 Subject: [PATCH 1/6] Add LocalDate.createOrNull --- core/api/kotlinx-datetime.api | 2 + core/api/kotlinx-datetime.klib.api | 2 + core/common/src/LocalDate.kt | 29 ++++++++++ core/common/test/LocalDateTest.kt | 60 +++++++++++++++++--- core/common/test/samples/LocalDateSamples.kt | 20 +++++++ core/commonKotlin/src/LocalDate.kt | 11 ++++ core/jvm/src/LocalDate.kt | 14 +++++ 7 files changed, 129 insertions(+), 9 deletions(-) diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index 1f554210..a42f02a1 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -297,6 +297,8 @@ public final class kotlinx/datetime/LocalDate : java/io/Serializable, java/lang/ public final class kotlinx/datetime/LocalDate$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; + public final fun createOrNull (III)Lkotlinx/datetime/LocalDate; + public final fun createOrNull (ILkotlinx/datetime/Month;I)Lkotlinx/datetime/LocalDate; public final fun fromEpochDays (I)Lkotlinx/datetime/LocalDate; public final fun fromEpochDays (J)Lkotlinx/datetime/LocalDate; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/LocalDate; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 129b736d..386f872d 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -360,6 +360,8 @@ final class kotlinx.datetime/LocalDate : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDate.Companion.Format|Format(kotlin.Function1){}[0] + final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun createOrNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.createOrNull|createOrNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] final fun fromEpochDays(kotlin/Int): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.fromEpochDays|fromEpochDays(kotlin.Int){}[0] final fun fromEpochDays(kotlin/Long): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.fromEpochDays|fromEpochDays(kotlin.Long){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 5225f2be..40b913a4 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -68,6 +68,35 @@ import kotlin.internal.* @Serializable(with = LocalDateIso8601Serializer::class) public expect class LocalDate : Comparable { public companion object { + /** + * Constructs a [LocalDate] instance from the given date components + * or returns `null` if a value is out of range or invalid. + * + * The components [month] and [day] are 1-based. + * + * The supported ranges of components: + * - [year] the range is at least enough to represent dates of all instants between + * [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE] + * - [month] `1..12` + * - [day] `1..31`, the upper bound can be less, depending on the month + * + * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNullMonthNumber + */ + public fun createOrNull(year: Int, month: Int, day: Int): LocalDate? + + /** + * Constructs a [LocalDate] instance from the given date components + * or returns `null` if a value is out of range or invalid. + * + * The supported ranges of components: + * - [year] the range at least is enough to represent dates of all instants between + * [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE] + * - [month] all values of the [Month] enum + * - [day] `1..31`, the upper bound can be less, depending on the month + * + * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNull + */ + public fun createOrNull(year: Int, month: Month, day: Int): LocalDate? /** * A shortcut for calling [DateTimeFormat.parse]. * diff --git a/core/common/test/LocalDateTest.kt b/core/common/test/LocalDateTest.kt index f6ded203..9cca8733 100644 --- a/core/common/test/LocalDateTest.kt +++ b/core/common/test/LocalDateTest.kt @@ -230,6 +230,21 @@ class LocalDateTest { } } + @Test + fun createOrNull() { + validDates.forEach { (year, month, day) -> + val expected = LocalDate(year, month, day) + assertEquals(expected, LocalDate.createOrNull(year, month, day)) + assertEquals(expected, LocalDate.createOrNull(year, Month(month), day)) + } + invalidDates.forEach { (year, month, day) -> + assertNull(LocalDate.createOrNull(year, month, day)) + runCatching { Month(month) }.onSuccess { monthEnum -> + assertNull(LocalDate.createOrNull(year, monthEnum, day)) + } + } + } + @Test fun fromEpochDays() { /** This test uses [LocalDate.next] and [LocalDate.previous] and not [LocalDate.plus] because, on Native, @@ -285,17 +300,44 @@ class LocalDateTest { } fun checkInvalidDate(constructor: (year: Int, month: Int, day: Int) -> LocalDate) { - assertFailsWith { constructor(2007, 2, 29) } - assertEquals(29, constructor(2008, 2, 29).day) - assertFailsWith { constructor(2007, 4, 31) } - assertFailsWith { constructor(2007, 1, 0) } - assertFailsWith { constructor(2007,1, 32) } - assertFailsWith { constructor(Int.MIN_VALUE, 1, 1) } - assertFailsWith { constructor(2007, 1, 32) } - assertFailsWith { constructor(2007, 0, 1) } - assertFailsWith { constructor(2007, 13, 1) } + invalidDates.forEach { (year, month, day) -> + assertFailsWith { constructor(year, month, day) } + } + validDates.forEach { (year, month, day) -> + val date = constructor(year, month, day) + assertEquals(year, date.year) + assertEquals(month, date.month.number) + assertEquals(day, date.day) + } } +val invalidDates = listOf( + Triple(2007, 2, 29), + Triple(2007, 4, 31), + Triple(2007, 1, 0), + Triple(2007, 1, 32), + Triple(Int.MIN_VALUE, 1, 1), + Triple(2007, 1, 32), + Triple(2007, 0, 1), + Triple(2007, 13, 1), +) + +val validDates = listOf( + Triple(2007, 1, 1), + Triple(2007, 2, 28), + Triple(2008, 2, 29), + Triple(2007, 3, 31), + Triple(2007, 4, 30), + Triple(2007, 5, 31), + Triple(2007, 6, 30), + Triple(2007, 7, 31), + Triple(2007, 8, 31), + Triple(2007, 9, 30), + Triple(2007, 10, 31), + Triple(2007, 11, 30), + Triple(2007, 12, 31), +) + private val LocalDate.next: LocalDate get() = if (day != month.number.monthLength(isLeapYear(year))) { LocalDate(year, month.number, day + 1) diff --git a/core/common/test/samples/LocalDateSamples.kt b/core/common/test/samples/LocalDateSamples.kt index 5beaf929..a6ce07e9 100644 --- a/core/common/test/samples/LocalDateSamples.kt +++ b/core/common/test/samples/LocalDateSamples.kt @@ -69,6 +69,26 @@ class LocalDateSamples { check(date.day == 16) } + @Test + fun createOrNullMonthNumber() { + // Constructing a LocalDate value using `createOrNull` + val date = LocalDate.createOrNull(2024, 4, 16) + // For valid values, `createOrNull` is equivalent to the constructor + check(date == LocalDate(2024, 4, 16)) + // If a value can not be constructed, null is returned + check(LocalDate.createOrNull(2024, 1, 99) == null) + } + + @Test + fun createOrNull() { + // Constructing a LocalDate value using `createOrNull` + val date = LocalDate.createOrNull(2024, Month.APRIL, 16) + // For valid values, `createOrNull` is equivalent to the constructor + check(date == LocalDate(2024, Month.APRIL, 16)) + // If a value can not be constructed, null is returned + check(LocalDate.createOrNull(2024, Month.FEBRUARY, 31) == null) + } + @Test fun year() { // Getting the year diff --git a/core/commonKotlin/src/LocalDate.kt b/core/commonKotlin/src/LocalDate.kt index 147b89cd..a1efa3fc 100644 --- a/core/commonKotlin/src/LocalDate.kt +++ b/core/commonKotlin/src/LocalDate.kt @@ -53,6 +53,17 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) public fun parse(isoString: String): LocalDate = parse(input = isoString) + public actual fun createOrNull(year: Int, month: Int, day: Int): LocalDate? = + if (!isValidYear(year) || month !in 1..12 || day !in 1..31 || + (day > 28 && day > month.monthLength(isLeapYear(year)))) { + null + } else { + LocalDate(year, month, day) + } + + public actual fun createOrNull(year: Int, month: Month, day: Int): LocalDate? = + createOrNull(year, month.number, day) + // org.threeten.bp.LocalDate#toEpochDay public actual fun fromEpochDays(epochDays: Long): LocalDate { // LocalDate(-999_999_999, 1, 1).toEpochDay(), LocalDate(999_999_999, 12, 31).toEpochDay() diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index 7e7cda70..92809d36 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -22,6 +22,20 @@ public actual class LocalDate internal constructor( internal val value: jtLocalDate ) : Comparable, java.io.Serializable { public actual companion object { + public actual fun createOrNull(year: Int, month: Int, day: Int): LocalDate? = + try { + LocalDate(year, month, day) + } catch (e: IllegalArgumentException) { + null + } + + public actual fun createOrNull(year: Int, month: Month, day: Int): LocalDate? = + try { + LocalDate(year, month, day) + } catch (e: IllegalArgumentException) { + null + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDate = if (format === Formats.ISO) { try { From b682faab3c6580a85e3d54ac26a32d388af6721c Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 2 Apr 2025 15:06:05 +0200 Subject: [PATCH 2/6] Generate `createOrNull` implementations using AI Prompts: """ In the last commit, `LocalDate.createOrNull` was introduced, along with a usage sample, and with the existing tests modified to also check `createOrNull`. You should introduce `LocalTime.createOrNull` in the same way. *** Now, similarly, introduce `LocalDateTime.createOrNull` with tests and a usage sample. *** Now also introduce `UtcOffset.createOrNull` (with tests and a usage sample), except that since the constructor for `UtcOffset` is internal, use the behavior from `fun UtcOffset`. """ --- core/common/src/LocalDateTime.kt | 62 +++++++++++++++++++ core/common/src/LocalTime.kt | 14 +++++ core/common/src/UtcOffset.kt | 20 ++++++ core/common/test/LocalDateTimeTest.kt | 59 ++++++++++++++++++ core/common/test/LocalTimeTest.kt | 35 +++++++++++ core/common/test/UtcOffsetTest.kt | 40 ++++++++++++ .../test/samples/LocalDateTimeSamples.kt | 39 ++++++++++++ core/common/test/samples/LocalTimeSamples.kt | 13 ++++ core/common/test/samples/UtcOffsetSamples.kt | 18 ++++++ core/commonKotlin/src/LocalDateTime.kt | 33 ++++++++++ core/commonKotlin/src/LocalTime.kt | 20 ++++++ core/commonKotlin/src/UtcOffset.kt | 16 +++++ core/jvm/src/LocalDateTimeJvm.kt | 29 +++++++++ core/jvm/src/LocalTimeJvm.kt | 6 ++ core/jvm/src/UtcOffsetJvm.kt | 16 +++++ 15 files changed, 420 insertions(+) diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 8e5085ca..6a4146fa 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -114,6 +114,68 @@ import kotlin.jvm.JvmName public expect class LocalDateTime : Comparable { public companion object { + /** + * Constructs a [LocalDateTime] instance from the given date and time components + * or returns `null` if a value is out of range. + * + * The components [month] and [day] are 1-based. + * + * The supported ranges of components: + * - [year] the range is platform-dependent, but at least is enough to represent dates of all instants between + * [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE] + * - [month] `1..12` + * - [day] `1..31`, the upper bound can be less, depending on the month + * - [hour] `0..23` + * - [minute] `0..59` + * - [second] `0..59` + * - [nanosecond] `0..999_999_999` + * + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNull + */ + public fun createOrNull( + year: Int, + month: Int, + day: Int, + hour: Int, + minute: Int, + second: Int = 0, + nanosecond: Int = 0 + ): LocalDateTime? + + /** + * Constructs a [LocalDateTime] instance from the given date and time components + * or returns `null` if a value is out of range. + * + * The supported ranges of components: + * - [year] the range is platform-dependent, but at least is enough to represent dates of all instants between + * [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE] + * - [month] all values of the [Month] enum + * - [day] `1..31`, the upper bound can be less, depending on the month + * - [hour] `0..23` + * - [minute] `0..59` + * - [second] `0..59` + * - [nanosecond] `0..999_999_999` + * + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullWithMonth + */ + public fun createOrNull( + year: Int, + month: Month, + day: Int, + hour: Int, + minute: Int, + second: Int = 0, + nanosecond: Int = 0 + ): LocalDateTime? + + /** + * Constructs a [LocalDateTime] instance by combining the given [date] and [time] parts + * or returns `null` if either [date] or [time] is `null`. + * + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullFromDateAndTime + */ + public fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? + /** * A shortcut for calling [DateTimeFormat.parse]. * diff --git a/core/common/src/LocalTime.kt b/core/common/src/LocalTime.kt index 7cf01459..b88fdf2d 100644 --- a/core/common/src/LocalTime.kt +++ b/core/common/src/LocalTime.kt @@ -84,6 +84,20 @@ import kotlin.jvm.JvmName public expect class LocalTime : Comparable { public companion object { + /** + * Constructs a [LocalTime] instance from the given time components + * or returns `null` if a value is out of range. + * + * The supported ranges of components: + * - [hour] `0..23` + * - [minute] `0..59` + * - [second] `0..59` + * - [nanosecond] `0..999_999_999` + * + * @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull + */ + public fun createOrNull(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalTime? + /** * A shortcut for calling [DateTimeFormat.parse]. * diff --git a/core/common/src/UtcOffset.kt b/core/common/src/UtcOffset.kt index 97c5e410..0a959375 100644 --- a/core/common/src/UtcOffset.kt +++ b/core/common/src/UtcOffset.kt @@ -75,6 +75,26 @@ public expect class UtcOffset { */ public val ZERO: UtcOffset + /** + * Constructs a [UtcOffset] from hours, minutes, and seconds components + * or returns `null` if a value is out of range or invalid. + * + * All components must have the same sign. + * Otherwise, `null` will be returned. + * + * The bounds are checked: it is invalid to pass something other than `±[0; 59]` as the number of seconds or minutes; + * `null` will be returned if this rule is violated. + * For example, `UtcOffset.createOrNull(hours = 3, minutes = 61)` returns `null`. + * + * However, the non-null component of the highest order can exceed these bounds, + * for example, `UtcOffset.createOrNull(minutes = 241)` and `UtcOffset.createOrNull(seconds = -3600)` are both valid. + * + * @return the [UtcOffset] with the specified components, or `null` if the components are invalid + * or the resulting `UtcOffset` value is outside of range `±18:00`. + * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.createOrNull + */ + public fun createOrNull(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset? + /** * A shortcut for calling [DateTimeFormat.parse]. * diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index fff490a3..5c428f8f 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -130,6 +130,65 @@ class LocalDateTimeTest { assertFailsWith { localTime(0, 0, 0, 1_000_000_000) } } + @Test + fun createOrNull() { + // Test createOrNull with month number + val validDateTime1 = LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 500_000_000) + assertNotNull(validDateTime1) + assertEquals(2020, validDateTime1!!.year) + assertEquals(1, validDateTime1.month.number) + assertEquals(1, validDateTime1.day) + assertEquals(12, validDateTime1.hour) + assertEquals(30, validDateTime1.minute) + assertEquals(45, validDateTime1.second) + assertEquals(500_000_000, validDateTime1.nanosecond) + + // Test createOrNull with Month enum + val validDateTime2 = LocalDateTime.createOrNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999) + assertNotNull(validDateTime2) + assertEquals(2020, validDateTime2!!.year) + assertEquals(Month.FEBRUARY, validDateTime2.month) + assertEquals(29, validDateTime2.day) + assertEquals(23, validDateTime2.hour) + assertEquals(59, validDateTime2.minute) + assertEquals(59, validDateTime2.second) + assertEquals(999_999_999, validDateTime2.nanosecond) + + // Test createOrNull with LocalDate and LocalTime + val date = LocalDate(2020, 1, 1) + val time = LocalTime(12, 30, 45, 500_000_000) + val validDateTime3 = LocalDateTime.createOrNull(date, time) + assertNotNull(validDateTime3) + assertEquals(date, validDateTime3!!.date) + assertEquals(time, validDateTime3.time) + + // Test invalid date components + assertNull(LocalDateTime.createOrNull(2021, 2, 29, 12, 30)) // Invalid day (not a leap year) + assertNull(LocalDateTime.createOrNull(2020, 13, 1, 12, 30)) // Invalid month + assertNull(LocalDateTime.createOrNull(2020, 0, 1, 12, 30)) // Invalid month + assertNull(LocalDateTime.createOrNull(2020, 1, 32, 12, 30)) // Invalid day + assertNull(LocalDateTime.createOrNull(2020, 1, 0, 12, 30)) // Invalid day + + // Test invalid time components + assertNull(LocalDateTime.createOrNull(2020, 1, 1, -1, 30)) // Invalid hour + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 24, 30)) // Invalid hour + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, -1)) // Invalid minute + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 60)) // Invalid minute + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, -1)) // Invalid second + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 60)) // Invalid second + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, -1)) // Invalid nanosecond + assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 1_000_000_000)) // Invalid nanosecond + + // Test with Month enum + assertNull(LocalDateTime.createOrNull(2021, Month.FEBRUARY, 29, 12, 30)) // Invalid day (not a leap year) + assertNull(LocalDateTime.createOrNull(2020, Month.FEBRUARY, 30, 12, 30)) // Invalid day for February + + // Test with null LocalDate or LocalTime + assertNull(LocalDateTime.createOrNull(null, time)) + assertNull(LocalDateTime.createOrNull(date, null)) + assertNull(LocalDateTime.createOrNull(null, null)) + } + } fun checkComponents(value: LocalDateTime, year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0, dayOfWeek: Int? = null, dayOfYear: Int? = null) { diff --git a/core/common/test/LocalTimeTest.kt b/core/common/test/LocalTimeTest.kt index 09f0f5f6..a679c88a 100644 --- a/core/common/test/LocalTimeTest.kt +++ b/core/common/test/LocalTimeTest.kt @@ -87,6 +87,41 @@ class LocalTimeTest { assertFailsWith { LocalTime(0, 0, 0, 1_000_000_000) } } + @Test + fun createOrNull() { + // Valid times should be created correctly + val validTime1 = LocalTime.createOrNull(12, 30, 45, 500_000_000) + assertNotNull(validTime1) + assertEquals(12, validTime1!!.hour) + assertEquals(30, validTime1.minute) + assertEquals(45, validTime1.second) + assertEquals(500_000_000, validTime1.nanosecond) + + val validTime2 = LocalTime.createOrNull(0, 0) + assertNotNull(validTime2) + assertEquals(0, validTime2!!.hour) + assertEquals(0, validTime2.minute) + assertEquals(0, validTime2.second) + assertEquals(0, validTime2.nanosecond) + + val validTime3 = LocalTime.createOrNull(23, 59, 59, 999_999_999) + assertNotNull(validTime3) + assertEquals(23, validTime3!!.hour) + assertEquals(59, validTime3.minute) + assertEquals(59, validTime3.second) + assertEquals(999_999_999, validTime3.nanosecond) + + // Invalid times should return null + assertNull(LocalTime.createOrNull(-1, 0)) + assertNull(LocalTime.createOrNull(24, 0)) + assertNull(LocalTime.createOrNull(0, -1)) + assertNull(LocalTime.createOrNull(0, 60)) + assertNull(LocalTime.createOrNull(0, 0, -1)) + assertNull(LocalTime.createOrNull(0, 0, 60)) + assertNull(LocalTime.createOrNull(0, 0, 0, -1)) + assertNull(LocalTime.createOrNull(0, 0, 0, 1_000_000_000)) + } + @Test fun fromNanosecondOfDay() { val data = mapOf( diff --git a/core/common/test/UtcOffsetTest.kt b/core/common/test/UtcOffsetTest.kt index c9d488fc..fe7bb56b 100644 --- a/core/common/test/UtcOffsetTest.kt +++ b/core/common/test/UtcOffsetTest.kt @@ -161,4 +161,44 @@ class UtcOffsetTest { assertIs(timeZone) assertEquals(offset, timeZone.offset) } + + @Test + fun createOrNull() { + // Valid cases + for (totalSeconds in offsetSecondsRange) { + val hours = totalSeconds / (60 * 60) + val totalMinutes = totalSeconds / 60 + val minutes = totalMinutes % 60 + val seconds = totalSeconds % 60 + val offset = UtcOffset.createOrNull(hours, minutes, seconds) + val offsetSeconds = UtcOffset.createOrNull(seconds = totalSeconds) + val offsetMinutes = UtcOffset.createOrNull(minutes = totalMinutes, seconds = seconds) + assertNotNull(offset) + assertNotNull(offsetSeconds) + assertNotNull(offsetMinutes) + assertEquals(totalSeconds, offset!!.totalSeconds) + assertEquals(offset, offsetMinutes) + assertEquals(offset, offsetSeconds) + } + + // Invalid cases + assertNull(UtcOffset.createOrNull(hours = -19)) + assertNull(UtcOffset.createOrNull(hours = +19)) + assertNull(UtcOffset.createOrNull(hours = -18, minutes = -1)) + assertNull(UtcOffset.createOrNull(hours = -18, seconds = -1)) + assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1)) + assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1)) + assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.first - 1)) + assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.last + 1)) + assertNull(UtcOffset.createOrNull(hours = 0, minutes = 60)) + assertNull(UtcOffset.createOrNull(hours = 0, seconds = -60)) + assertNull(UtcOffset.createOrNull(minutes = 90, seconds = 90)) + assertNull(UtcOffset.createOrNull(minutes = 0, seconds = 90)) + assertNull(UtcOffset.createOrNull(hours = +1, minutes = -1)) + assertNull(UtcOffset.createOrNull(hours = +1, seconds = -1)) + assertNull(UtcOffset.createOrNull(hours = -1, minutes = +1)) + assertNull(UtcOffset.createOrNull(hours = -1, seconds = +1)) + assertNull(UtcOffset.createOrNull(minutes = +1, seconds = -1)) + assertNull(UtcOffset.createOrNull(minutes = -1, seconds = +1)) + } } diff --git a/core/common/test/samples/LocalDateTimeSamples.kt b/core/common/test/samples/LocalDateTimeSamples.kt index eea04c30..dec0fc05 100644 --- a/core/common/test/samples/LocalDateTimeSamples.kt +++ b/core/common/test/samples/LocalDateTimeSamples.kt @@ -183,6 +183,45 @@ class LocalDateTimeSamples { check(LocalDate(2024, 2, 15).atTime(16, 48, 15, 120_000_000).toString() == "2024-02-15T16:48:15.120") } + @Test + fun createOrNull() { + // Constructing a LocalDateTime value using `createOrNull` + val dateTime = LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 999_999_999) + // For valid values, `createOrNull` is equivalent to the constructor + check(dateTime == LocalDateTime(2024, 2, 15, 16, 48, 59, 999_999_999)) + // If a value can not be constructed, null is returned + check(LocalDateTime.createOrNull(2024, 2, 31, 16, 48) == null) // Invalid day + check(LocalDateTime.createOrNull(2024, 2, 15, 24, 48) == null) // Invalid hour + check(LocalDateTime.createOrNull(2024, 2, 15, 16, 60) == null) // Invalid minute + check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 60) == null) // Invalid second + check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 1_000_000_000) == null) // Invalid nanosecond + } + + @Test + fun createOrNullWithMonth() { + // Constructing a LocalDateTime value using `createOrNull` with Month enum + val dateTime = LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999) + // For valid values, `createOrNull` is equivalent to the constructor + check(dateTime == LocalDateTime(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999)) + // If a value can not be constructed, null is returned + check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 31, 16, 48) == null) // Invalid day + check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 24, 48) == null) // Invalid hour + } + + @Test + fun createOrNullFromDateAndTime() { + // Constructing a LocalDateTime value using `createOrNull` with LocalDate and LocalTime + val date = LocalDate(2024, 2, 15) + val time = LocalTime(16, 48) + val dateTime = LocalDateTime.createOrNull(date, time) + // For valid values, `createOrNull` is equivalent to the constructor + check(dateTime == LocalDateTime(date, time)) + // If either date or time is null, null is returned + check(LocalDateTime.createOrNull(null, time) == null) + check(LocalDateTime.createOrNull(date, null) == null) + check(LocalDateTime.createOrNull(null, null) == null) + } + @Test fun formatting() { // Formatting LocalDateTime values using predefined and custom formats diff --git a/core/common/test/samples/LocalTimeSamples.kt b/core/common/test/samples/LocalTimeSamples.kt index 3bbb19e8..e8aa1364 100644 --- a/core/common/test/samples/LocalTimeSamples.kt +++ b/core/common/test/samples/LocalTimeSamples.kt @@ -133,6 +133,19 @@ class LocalTimeSamples { check(timeWithoutSeconds.nanosecond == 0) } + @Test + fun createOrNull() { + // Constructing a LocalTime value using `createOrNull` + val time = LocalTime.createOrNull(8, 30, 15, 123_456_789) + // For valid values, `createOrNull` is equivalent to the constructor + check(time == LocalTime(8, 30, 15, 123_456_789)) + // If a value can not be constructed, null is returned + check(LocalTime.createOrNull(24, 30) == null) + check(LocalTime.createOrNull(8, 60) == null) + check(LocalTime.createOrNull(8, 30, 60) == null) + check(LocalTime.createOrNull(8, 30, 15, 1_000_000_000) == null) + } + @Test fun hour() { // Getting the number of whole hours shown on the clock diff --git a/core/common/test/samples/UtcOffsetSamples.kt b/core/common/test/samples/UtcOffsetSamples.kt index ec651c71..f70e4b6c 100644 --- a/core/common/test/samples/UtcOffsetSamples.kt +++ b/core/common/test/samples/UtcOffsetSamples.kt @@ -108,6 +108,24 @@ class UtcOffsetSamples { } } + @Test + fun createOrNull() { + // Using the createOrNull function to create UtcOffset values + val validOffset = UtcOffset.createOrNull(hours = 3, minutes = 30) + check(validOffset != null) + check(validOffset!!.totalSeconds == 12600) + + // For valid inputs, createOrNull returns a non-null value + check(UtcOffset.createOrNull(seconds = -3600) == UtcOffset(hours = -1)) + + // For invalid inputs, createOrNull returns null + check(UtcOffset.createOrNull(hours = 1, minutes = 60) == null) + // Since `hours` is positive, `minutes` must also be positive + check(UtcOffset.createOrNull(hours = 1, minutes = -30) == null) + // The total offset must be within the range ±18:00 + check(UtcOffset.createOrNull(hours = 19) == null) + } + class Formats { @Test fun isoBasic() { diff --git a/core/commonKotlin/src/LocalDateTime.kt b/core/commonKotlin/src/LocalDateTime.kt index 9375ae91..bdf9cef0 100644 --- a/core/commonKotlin/src/LocalDateTime.kt +++ b/core/commonKotlin/src/LocalDateTime.kt @@ -17,6 +17,39 @@ import kotlinx.serialization.* public actual class LocalDateTime public actual constructor(public actual val date: LocalDate, public actual val time: LocalTime) : Comparable { public actual companion object { + public actual fun createOrNull( + year: Int, + month: Int, + day: Int, + hour: Int, + minute: Int, + second: Int, + nanosecond: Int + ): LocalDateTime? { + val date = LocalDate.createOrNull(year, month, day) ?: return null + val time = LocalTime.createOrNull(hour, minute, second, nanosecond) ?: return null + return LocalDateTime(date, time) + } + + public actual fun createOrNull( + year: Int, + month: Month, + day: Int, + hour: Int, + minute: Int, + second: Int, + nanosecond: Int + ): LocalDateTime? { + val date = LocalDate.createOrNull(year, month, day) ?: return null + val time = LocalTime.createOrNull(hour, minute, second, nanosecond) ?: return null + return LocalDateTime(date, time) + } + + public actual fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? { + if (date == null || time == null) return null + return LocalDateTime(date, time) + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDateTime = format.parse(input) diff --git a/core/commonKotlin/src/LocalTime.kt b/core/commonKotlin/src/LocalTime.kt index 28ebd78d..e573774c 100644 --- a/core/commonKotlin/src/LocalTime.kt +++ b/core/commonKotlin/src/LocalTime.kt @@ -33,6 +33,26 @@ public actual class LocalTime actual constructor( } public actual companion object { + /** + * Constructs a [LocalTime] instance from the given time components + * or returns `null` if a value is out of range. + * + * The supported ranges of components: + * - [hour] `0..23` + * - [minute] `0..59` + * - [second] `0..59` + * - [nanosecond] `0..999_999_999` + * + * @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull + */ + public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? { + return if (hour !in 0..23 || minute !in 0..59 || second !in 0..59 || nanosecond !in 0 until NANOS_PER_ONE) { + null + } else { + LocalTime(hour, minute, second, nanosecond) + } + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalTime = format.parse(input) @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) diff --git a/core/commonKotlin/src/UtcOffset.kt b/core/commonKotlin/src/UtcOffset.kt index e8329d0c..c6bcceb2 100644 --- a/core/commonKotlin/src/UtcOffset.kt +++ b/core/commonKotlin/src/UtcOffset.kt @@ -23,6 +23,22 @@ public actual class UtcOffset private constructor(public actual val totalSeconds public actual val ZERO: UtcOffset = UtcOffset(totalSeconds = 0) + public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? { + return try { + when { + hours != null -> + ofHoursMinutesSeconds(hours, minutes ?: 0, seconds ?: 0) + minutes != null -> + ofHoursMinutesSeconds(minutes / MINUTES_PER_HOUR, minutes % MINUTES_PER_HOUR, seconds ?: 0) + else -> { + ofSeconds(seconds ?: 0) + } + } + } catch (e: IllegalArgumentException) { + null + } + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): UtcOffset = format.parse(input) @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) diff --git a/core/jvm/src/LocalDateTimeJvm.kt b/core/jvm/src/LocalDateTimeJvm.kt index 235be907..56aacb02 100644 --- a/core/jvm/src/LocalDateTimeJvm.kt +++ b/core/jvm/src/LocalDateTimeJvm.kt @@ -85,6 +85,35 @@ public actual class LocalDateTime internal constructor( actual override fun compareTo(other: LocalDateTime): Int = this.value.compareTo(other.value) public actual companion object { + public actual fun createOrNull( + year: Int, + month: Int, + day: Int, + hour: Int, + minute: Int, + second: Int, + nanosecond: Int + ): LocalDateTime? = try { + jtLocalDateTime.of(year, month, day, hour, minute, second, nanosecond).let(::LocalDateTime) + } catch (e: DateTimeException) { + null + } + + public actual fun createOrNull( + year: Int, + month: Month, + day: Int, + hour: Int, + minute: Int, + second: Int, + nanosecond: Int + ): LocalDateTime? = createOrNull(year, month.number, day, hour, minute, second, nanosecond) + + public actual fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? { + if (date == null || time == null) return null + return LocalDateTime(date, time) + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDateTime = if (format === Formats.ISO) { try { diff --git a/core/jvm/src/LocalTimeJvm.kt b/core/jvm/src/LocalTimeJvm.kt index 98f42011..198d0edc 100644 --- a/core/jvm/src/LocalTimeJvm.kt +++ b/core/jvm/src/LocalTimeJvm.kt @@ -47,6 +47,12 @@ public actual class LocalTime internal constructor( actual override fun compareTo(other: LocalTime): Int = this.value.compareTo(other.value) public actual companion object { + public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = try { + jtLocalTime.of(hour, minute, second, nanosecond).let(::LocalTime) + } catch (e: DateTimeException) { + null + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalTime = if (format === Formats.ISO) { try { diff --git a/core/jvm/src/UtcOffsetJvm.kt b/core/jvm/src/UtcOffsetJvm.kt index 7f9ed703..1b476196 100644 --- a/core/jvm/src/UtcOffsetJvm.kt +++ b/core/jvm/src/UtcOffsetJvm.kt @@ -26,6 +26,22 @@ public actual class UtcOffset( public actual companion object { public actual val ZERO: UtcOffset = UtcOffset(ZoneOffset.UTC) + public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? { + return try { + when { + hours != null -> + UtcOffset(ZoneOffset.ofHoursMinutesSeconds(hours, minutes ?: 0, seconds ?: 0)) + minutes != null -> + UtcOffset(ZoneOffset.ofHoursMinutesSeconds(minutes / 60, minutes % 60, seconds ?: 0)) + else -> { + UtcOffset(ZoneOffset.ofTotalSeconds(seconds ?: 0)) + } + } + } catch (e: DateTimeException) { + null + } + } + public actual fun parse(input: CharSequence, format: DateTimeFormat): UtcOffset = when { format === Formats.ISO -> parseWithFormat(input, isoFormat) format === Formats.ISO_BASIC -> parseWithFormat(input, isoBasicFormat) From cdf9e1edecb33abba6e37d9fa8843869eb9b1c29 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 2 Apr 2025 15:57:45 +0200 Subject: [PATCH 3/6] Fix the output of AI --- core/api/kotlinx-datetime.api | 8 ++ core/api/kotlinx-datetime.klib.api | 4 + core/common/src/LocalDateTime.kt | 7 -- core/common/test/LocalDateTimeTest.kt | 65 +++++--------- core/common/test/LocalTimeTest.kt | 53 +++++------ core/common/test/UtcOffsetTest.kt | 89 +++++++------------ .../test/samples/LocalDateTimeSamples.kt | 13 --- core/common/test/samples/UtcOffsetSamples.kt | 2 +- core/commonKotlin/src/LocalDateTime.kt | 4 - core/commonKotlin/src/LocalTime.kt | 17 +--- core/commonKotlin/src/UtcOffset.kt | 18 +--- core/jvm/src/LocalDateTimeJvm.kt | 4 - core/jvm/src/LocalTimeJvm.kt | 2 +- core/jvm/src/UtcOffsetJvm.kt | 18 +--- 14 files changed, 102 insertions(+), 202 deletions(-) diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index a42f02a1..438e1727 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -377,6 +377,10 @@ public final class kotlinx/datetime/LocalDateTime : java/io/Serializable, java/l public final class kotlinx/datetime/LocalDateTime$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; + public final fun createOrNull (IIIIIII)Lkotlinx/datetime/LocalDateTime; + public final fun createOrNull (ILkotlinx/datetime/Month;IIIII)Lkotlinx/datetime/LocalDateTime; + public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalDateTime$Companion;IIIIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; + public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalDateTime$Companion;ILkotlinx/datetime/Month;IIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/LocalDateTime; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/LocalDateTime; public static synthetic fun parse$default (Lkotlinx/datetime/LocalDateTime$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;ILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; @@ -419,6 +423,8 @@ public final class kotlinx/datetime/LocalTime : java/io/Serializable, java/lang/ public final class kotlinx/datetime/LocalTime$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; + public final fun createOrNull (IIII)Lkotlinx/datetime/LocalTime; + public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalTime$Companion;IIIIILjava/lang/Object;)Lkotlinx/datetime/LocalTime; public final fun fromMillisecondOfDay (I)Lkotlinx/datetime/LocalTime; public final fun fromNanosecondOfDay (J)Lkotlinx/datetime/LocalTime; public final fun fromSecondOfDay (I)Lkotlinx/datetime/LocalTime; @@ -527,6 +533,8 @@ public final class kotlinx/datetime/UtcOffset : java/io/Serializable { public final class kotlinx/datetime/UtcOffset$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; + public final fun createOrNull (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lkotlinx/datetime/UtcOffset; + public static synthetic fun createOrNull$default (Lkotlinx/datetime/UtcOffset$Companion;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lkotlinx/datetime/UtcOffset; public final fun getZERO ()Lkotlinx/datetime/UtcOffset; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/UtcOffset; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/UtcOffset; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 386f872d..a208c0dc 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -416,6 +416,8 @@ final class kotlinx.datetime/LocalDateTime : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDateTime.Companion.Format|Format(kotlin.Function1){}[0] + final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun createOrNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/LocalDateTime.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/LocalDateTime // kotlinx.datetime/LocalDateTime.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/LocalDateTime.Companion.serializer|serializer(){}[0] @@ -449,6 +451,7 @@ final class kotlinx.datetime/LocalTime : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalTime.Companion.Format|Format(kotlin.Function1){}[0] + final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalTime? // kotlinx.datetime/LocalTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun fromMillisecondOfDay(kotlin/Int): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromMillisecondOfDay|fromMillisecondOfDay(kotlin.Int){}[0] final fun fromNanosecondOfDay(kotlin/Long): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromNanosecondOfDay|fromNanosecondOfDay(kotlin.Long){}[0] final fun fromSecondOfDay(kotlin/Int): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromSecondOfDay|fromSecondOfDay(kotlin.Int){}[0] @@ -476,6 +479,7 @@ final class kotlinx.datetime/UtcOffset { // kotlinx.datetime/UtcOffset|null[0] final fun (): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.ZERO.|(){}[0] final fun Format(kotlin/Function1): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/UtcOffset.Companion.Format|Format(kotlin.Function1){}[0] + final fun createOrNull(kotlin/Int? = ..., kotlin/Int? = ..., kotlin/Int? = ...): kotlinx.datetime/UtcOffset? // kotlinx.datetime/UtcOffset.Companion.createOrNull|createOrNull(kotlin.Int?;kotlin.Int?;kotlin.Int?){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/UtcOffset.Companion.serializer|serializer(){}[0] diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 6a4146fa..2451bbc2 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -168,13 +168,6 @@ public expect class LocalDateTime : Comparable { nanosecond: Int = 0 ): LocalDateTime? - /** - * Constructs a [LocalDateTime] instance by combining the given [date] and [time] parts - * or returns `null` if either [date] or [time] is `null`. - * - * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullFromDateAndTime - */ - public fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? /** * A shortcut for calling [DateTimeFormat.parse]. diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index 5c428f8f..a7e56ee3 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -131,62 +131,37 @@ class LocalDateTimeTest { } @Test - fun createOrNull() { - // Test createOrNull with month number - val validDateTime1 = LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 500_000_000) - assertNotNull(validDateTime1) - assertEquals(2020, validDateTime1!!.year) - assertEquals(1, validDateTime1.month.number) - assertEquals(1, validDateTime1.day) - assertEquals(12, validDateTime1.hour) - assertEquals(30, validDateTime1.minute) - assertEquals(45, validDateTime1.second) - assertEquals(500_000_000, validDateTime1.nanosecond) + fun createOrNull() {// Test createOrNull with month number + LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 500_000_000)?.let { + checkComponents(it, 2020, 1, 1, 12, 30, 45, 500_000_000) + } ?: fail("LocalDateTime.createOrNull should not return null") // Test createOrNull with Month enum - val validDateTime2 = LocalDateTime.createOrNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999) - assertNotNull(validDateTime2) - assertEquals(2020, validDateTime2!!.year) - assertEquals(Month.FEBRUARY, validDateTime2.month) - assertEquals(29, validDateTime2.day) - assertEquals(23, validDateTime2.hour) - assertEquals(59, validDateTime2.minute) - assertEquals(59, validDateTime2.second) - assertEquals(999_999_999, validDateTime2.nanosecond) - - // Test createOrNull with LocalDate and LocalTime - val date = LocalDate(2020, 1, 1) - val time = LocalTime(12, 30, 45, 500_000_000) - val validDateTime3 = LocalDateTime.createOrNull(date, time) - assertNotNull(validDateTime3) - assertEquals(date, validDateTime3!!.date) - assertEquals(time, validDateTime3.time) + LocalDateTime.createOrNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999)?.let { + checkComponents(it, 2020, 2, 29, 23, 59, 59, 999_999_999) + } ?: fail("LocalDateTime.createOrNull should not return null") // Test invalid date components - assertNull(LocalDateTime.createOrNull(2021, 2, 29, 12, 30)) // Invalid day (not a leap year) - assertNull(LocalDateTime.createOrNull(2020, 13, 1, 12, 30)) // Invalid month - assertNull(LocalDateTime.createOrNull(2020, 0, 1, 12, 30)) // Invalid month - assertNull(LocalDateTime.createOrNull(2020, 1, 32, 12, 30)) // Invalid day - assertNull(LocalDateTime.createOrNull(2020, 1, 0, 12, 30)) // Invalid day + for ((year, month, day) in invalidDates) { + assertNull(LocalDateTime.createOrNull(year, month, day, 12, 30)) + runCatching { Month(month) }.onSuccess { monthEnum -> + assertNull(LocalDateTime.createOrNull(year, monthEnum, day, 12, 30)) + } + } // Test invalid time components - assertNull(LocalDateTime.createOrNull(2020, 1, 1, -1, 30)) // Invalid hour - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 24, 30)) // Invalid hour - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, -1)) // Invalid minute - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 60)) // Invalid minute - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, -1)) // Invalid second - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 60)) // Invalid second - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, -1)) // Invalid nanosecond - assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 1_000_000_000)) // Invalid nanosecond + for (input in invalidTimes) { + when (input.size) { + 2 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1])) + 3 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1], input[2])) + 4 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1], input[2], input[3])) + } + } // Test with Month enum assertNull(LocalDateTime.createOrNull(2021, Month.FEBRUARY, 29, 12, 30)) // Invalid day (not a leap year) assertNull(LocalDateTime.createOrNull(2020, Month.FEBRUARY, 30, 12, 30)) // Invalid day for February - // Test with null LocalDate or LocalTime - assertNull(LocalDateTime.createOrNull(null, time)) - assertNull(LocalDateTime.createOrNull(date, null)) - assertNull(LocalDateTime.createOrNull(null, null)) } } diff --git a/core/common/test/LocalTimeTest.kt b/core/common/test/LocalTimeTest.kt index a679c88a..170cca10 100644 --- a/core/common/test/LocalTimeTest.kt +++ b/core/common/test/LocalTimeTest.kt @@ -90,36 +90,26 @@ class LocalTimeTest { @Test fun createOrNull() { // Valid times should be created correctly - val validTime1 = LocalTime.createOrNull(12, 30, 45, 500_000_000) - assertNotNull(validTime1) - assertEquals(12, validTime1!!.hour) - assertEquals(30, validTime1.minute) - assertEquals(45, validTime1.second) - assertEquals(500_000_000, validTime1.nanosecond) + LocalTime.createOrNull(12, 30, 45, 500_000_000)?.let { + checkComponents(it, 12, 30, 45, 500_000_000) + } ?: fail("LocalTime.createOrNull should not return null") - val validTime2 = LocalTime.createOrNull(0, 0) - assertNotNull(validTime2) - assertEquals(0, validTime2!!.hour) - assertEquals(0, validTime2.minute) - assertEquals(0, validTime2.second) - assertEquals(0, validTime2.nanosecond) + LocalTime.createOrNull(0, 0)?.let { + checkComponents(it, 0, 0) + } ?: fail("LocalTime.createOrNull should not return null") - val validTime3 = LocalTime.createOrNull(23, 59, 59, 999_999_999) - assertNotNull(validTime3) - assertEquals(23, validTime3!!.hour) - assertEquals(59, validTime3.minute) - assertEquals(59, validTime3.second) - assertEquals(999_999_999, validTime3.nanosecond) + LocalTime.createOrNull(23, 59, 59, 999_999_999)?.let { + checkComponents(it, 23, 59, 59, 999_999_999) + } ?: fail("LocalTime.createOrNull should not return null") // Invalid times should return null - assertNull(LocalTime.createOrNull(-1, 0)) - assertNull(LocalTime.createOrNull(24, 0)) - assertNull(LocalTime.createOrNull(0, -1)) - assertNull(LocalTime.createOrNull(0, 60)) - assertNull(LocalTime.createOrNull(0, 0, -1)) - assertNull(LocalTime.createOrNull(0, 0, 60)) - assertNull(LocalTime.createOrNull(0, 0, 0, -1)) - assertNull(LocalTime.createOrNull(0, 0, 0, 1_000_000_000)) + for (input in invalidTimes) { + when (input.size) { + 2 -> assertNull(LocalTime.createOrNull(input[0], input[1])) + 3 -> assertNull(LocalTime.createOrNull(input[0], input[1], input[2])) + 4 -> assertNull(LocalTime.createOrNull(input[0], input[1], input[2], input[3])) + } + } } @Test @@ -238,6 +228,17 @@ class LocalTimeTest { } } +val invalidTimes = listOf( + listOf(-1, 0), // invalid hour + listOf(24, 0), // invalid hour + listOf(0, -1), // invalid minute + listOf(0, 60), // invalid minute + listOf(0, 0, -1), // invalid second + listOf(0, 0, 60), // invalid second + listOf(0, 0, 0, -1), // invalid nanosecond + listOf(0, 0, 0, 1_000_000_000) // invalid nanosecond +) + fun checkComponents(value: LocalTime, hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0) { assertEquals(hour, value.hour, "hours") assertEquals(minute, value.minute, "minutes") diff --git a/core/common/test/UtcOffsetTest.kt b/core/common/test/UtcOffsetTest.kt index fe7bb56b..9e4dde86 100644 --- a/core/common/test/UtcOffsetTest.kt +++ b/core/common/test/UtcOffsetTest.kt @@ -46,35 +46,48 @@ class UtcOffsetTest { val offset = UtcOffset(hours, minutes, seconds) val offsetSeconds = UtcOffset(seconds = totalSeconds) val offsetMinutes = UtcOffset(minutes = totalMinutes, seconds = seconds) + val offsetOrNull = UtcOffset.createOrNull(hours, minutes, seconds) + val offsetSecondsOrNull = UtcOffset.createOrNull(seconds = totalSeconds) + val offsetMinutesOrNull = UtcOffset.createOrNull(minutes = totalMinutes, seconds = seconds) assertEquals(totalSeconds, offset.totalSeconds) assertEquals(offset, offsetMinutes) assertEquals(offset, offsetSeconds) + assertEquals(offset, offsetOrNull) + assertEquals(offset, offsetSecondsOrNull) + assertEquals(offset, offsetMinutesOrNull) } } @Test fun constructionErrors() { + fun assertInvalidUtcOffset( + hours: Int? = null, + minutes: Int? = null, + seconds: Int? = null, + ) { + assertIllegalArgument { UtcOffset(hours, minutes, seconds) } + assertNull(UtcOffset.createOrNull(hours, minutes, seconds)) + } // total range - assertIllegalArgument { UtcOffset(hours = -19) } - assertIllegalArgument { UtcOffset(hours = +19) } - assertIllegalArgument { UtcOffset(hours = -18, minutes = -1) } - assertIllegalArgument { UtcOffset(hours = -18, seconds = -1) } - assertIllegalArgument { UtcOffset(hours = +18, seconds = +1) } - assertIllegalArgument { UtcOffset(hours = +18, seconds = +1) } - assertIllegalArgument { UtcOffset(seconds = offsetSecondsRange.first - 1) } - assertIllegalArgument { UtcOffset(seconds = offsetSecondsRange.last + 1) } + assertInvalidUtcOffset(hours = -19) + assertInvalidUtcOffset(hours = +19) + assertInvalidUtcOffset(hours = -18, minutes = -1) + assertInvalidUtcOffset(hours = -18, seconds = -1) + assertInvalidUtcOffset(hours = +18, seconds = +1) + assertInvalidUtcOffset(seconds = offsetSecondsRange.first - 1) + assertInvalidUtcOffset(seconds = offsetSecondsRange.last + 1) // component ranges - assertIllegalArgument { UtcOffset(hours = 0, minutes = 60) } - assertIllegalArgument { UtcOffset(hours = 0, seconds = -60) } - assertIllegalArgument { UtcOffset(minutes = 90, seconds = 90) } - assertIllegalArgument { UtcOffset(minutes = 0, seconds = 90) } + assertInvalidUtcOffset(hours = 0, minutes = 60) + assertInvalidUtcOffset(hours = 0, seconds = -60) + assertInvalidUtcOffset(minutes = 90, seconds = 90) + assertInvalidUtcOffset(minutes = 0, seconds = 90) // component signs - assertIllegalArgument { UtcOffset(hours = +1, minutes = -1) } - assertIllegalArgument { UtcOffset(hours = +1, seconds = -1) } - assertIllegalArgument { UtcOffset(hours = -1, minutes = +1) } - assertIllegalArgument { UtcOffset(hours = -1, seconds = +1) } - assertIllegalArgument { UtcOffset(minutes = +1, seconds = -1) } - assertIllegalArgument { UtcOffset(minutes = -1, seconds = +1) } + assertInvalidUtcOffset(hours = +1, minutes = -1) + assertInvalidUtcOffset(hours = +1, seconds = -1) + assertInvalidUtcOffset(hours = -1, minutes = +1) + assertInvalidUtcOffset(hours = -1, seconds = +1) + assertInvalidUtcOffset(minutes = +1, seconds = -1) + assertInvalidUtcOffset(minutes = -1, seconds = +1) } @Test @@ -161,44 +174,4 @@ class UtcOffsetTest { assertIs(timeZone) assertEquals(offset, timeZone.offset) } - - @Test - fun createOrNull() { - // Valid cases - for (totalSeconds in offsetSecondsRange) { - val hours = totalSeconds / (60 * 60) - val totalMinutes = totalSeconds / 60 - val minutes = totalMinutes % 60 - val seconds = totalSeconds % 60 - val offset = UtcOffset.createOrNull(hours, minutes, seconds) - val offsetSeconds = UtcOffset.createOrNull(seconds = totalSeconds) - val offsetMinutes = UtcOffset.createOrNull(minutes = totalMinutes, seconds = seconds) - assertNotNull(offset) - assertNotNull(offsetSeconds) - assertNotNull(offsetMinutes) - assertEquals(totalSeconds, offset!!.totalSeconds) - assertEquals(offset, offsetMinutes) - assertEquals(offset, offsetSeconds) - } - - // Invalid cases - assertNull(UtcOffset.createOrNull(hours = -19)) - assertNull(UtcOffset.createOrNull(hours = +19)) - assertNull(UtcOffset.createOrNull(hours = -18, minutes = -1)) - assertNull(UtcOffset.createOrNull(hours = -18, seconds = -1)) - assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1)) - assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1)) - assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.first - 1)) - assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.last + 1)) - assertNull(UtcOffset.createOrNull(hours = 0, minutes = 60)) - assertNull(UtcOffset.createOrNull(hours = 0, seconds = -60)) - assertNull(UtcOffset.createOrNull(minutes = 90, seconds = 90)) - assertNull(UtcOffset.createOrNull(minutes = 0, seconds = 90)) - assertNull(UtcOffset.createOrNull(hours = +1, minutes = -1)) - assertNull(UtcOffset.createOrNull(hours = +1, seconds = -1)) - assertNull(UtcOffset.createOrNull(hours = -1, minutes = +1)) - assertNull(UtcOffset.createOrNull(hours = -1, seconds = +1)) - assertNull(UtcOffset.createOrNull(minutes = +1, seconds = -1)) - assertNull(UtcOffset.createOrNull(minutes = -1, seconds = +1)) - } } diff --git a/core/common/test/samples/LocalDateTimeSamples.kt b/core/common/test/samples/LocalDateTimeSamples.kt index dec0fc05..e21c4f14 100644 --- a/core/common/test/samples/LocalDateTimeSamples.kt +++ b/core/common/test/samples/LocalDateTimeSamples.kt @@ -208,19 +208,6 @@ class LocalDateTimeSamples { check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 24, 48) == null) // Invalid hour } - @Test - fun createOrNullFromDateAndTime() { - // Constructing a LocalDateTime value using `createOrNull` with LocalDate and LocalTime - val date = LocalDate(2024, 2, 15) - val time = LocalTime(16, 48) - val dateTime = LocalDateTime.createOrNull(date, time) - // For valid values, `createOrNull` is equivalent to the constructor - check(dateTime == LocalDateTime(date, time)) - // If either date or time is null, null is returned - check(LocalDateTime.createOrNull(null, time) == null) - check(LocalDateTime.createOrNull(date, null) == null) - check(LocalDateTime.createOrNull(null, null) == null) - } @Test fun formatting() { diff --git a/core/common/test/samples/UtcOffsetSamples.kt b/core/common/test/samples/UtcOffsetSamples.kt index f70e4b6c..e2689768 100644 --- a/core/common/test/samples/UtcOffsetSamples.kt +++ b/core/common/test/samples/UtcOffsetSamples.kt @@ -113,7 +113,7 @@ class UtcOffsetSamples { // Using the createOrNull function to create UtcOffset values val validOffset = UtcOffset.createOrNull(hours = 3, minutes = 30) check(validOffset != null) - check(validOffset!!.totalSeconds == 12600) + check(validOffset.totalSeconds == 12600) // For valid inputs, createOrNull returns a non-null value check(UtcOffset.createOrNull(seconds = -3600) == UtcOffset(hours = -1)) diff --git a/core/commonKotlin/src/LocalDateTime.kt b/core/commonKotlin/src/LocalDateTime.kt index bdf9cef0..7f05039e 100644 --- a/core/commonKotlin/src/LocalDateTime.kt +++ b/core/commonKotlin/src/LocalDateTime.kt @@ -45,10 +45,6 @@ public actual constructor(public actual val date: LocalDate, public actual val t return LocalDateTime(date, time) } - public actual fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? { - if (date == null || time == null) return null - return LocalDateTime(date, time) - } public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDateTime = format.parse(input) diff --git a/core/commonKotlin/src/LocalTime.kt b/core/commonKotlin/src/LocalTime.kt index e573774c..ff58d4af 100644 --- a/core/commonKotlin/src/LocalTime.kt +++ b/core/commonKotlin/src/LocalTime.kt @@ -33,25 +33,12 @@ public actual class LocalTime actual constructor( } public actual companion object { - /** - * Constructs a [LocalTime] instance from the given time components - * or returns `null` if a value is out of range. - * - * The supported ranges of components: - * - [hour] `0..23` - * - [minute] `0..59` - * - [second] `0..59` - * - [nanosecond] `0..999_999_999` - * - * @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull - */ - public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? { - return if (hour !in 0..23 || minute !in 0..59 || second !in 0..59 || nanosecond !in 0 until NANOS_PER_ONE) { + public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = + if (hour !in 0..23 || minute !in 0..59 || second !in 0..59 || nanosecond !in 0 until NANOS_PER_ONE) { null } else { LocalTime(hour, minute, second, nanosecond) } - } public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalTime = format.parse(input) diff --git a/core/commonKotlin/src/UtcOffset.kt b/core/commonKotlin/src/UtcOffset.kt index c6bcceb2..00c28fe4 100644 --- a/core/commonKotlin/src/UtcOffset.kt +++ b/core/commonKotlin/src/UtcOffset.kt @@ -23,20 +23,10 @@ public actual class UtcOffset private constructor(public actual val totalSeconds public actual val ZERO: UtcOffset = UtcOffset(totalSeconds = 0) - public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? { - return try { - when { - hours != null -> - ofHoursMinutesSeconds(hours, minutes ?: 0, seconds ?: 0) - minutes != null -> - ofHoursMinutesSeconds(minutes / MINUTES_PER_HOUR, minutes % MINUTES_PER_HOUR, seconds ?: 0) - else -> { - ofSeconds(seconds ?: 0) - } - } - } catch (e: IllegalArgumentException) { - null - } + public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { + UtcOffset(hours, minutes, seconds) + } catch (_: IllegalArgumentException) { + null } public actual fun parse(input: CharSequence, format: DateTimeFormat): UtcOffset = format.parse(input) diff --git a/core/jvm/src/LocalDateTimeJvm.kt b/core/jvm/src/LocalDateTimeJvm.kt index 56aacb02..1534f137 100644 --- a/core/jvm/src/LocalDateTimeJvm.kt +++ b/core/jvm/src/LocalDateTimeJvm.kt @@ -109,10 +109,6 @@ public actual class LocalDateTime internal constructor( nanosecond: Int ): LocalDateTime? = createOrNull(year, month.number, day, hour, minute, second, nanosecond) - public actual fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime? { - if (date == null || time == null) return null - return LocalDateTime(date, time) - } public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDateTime = if (format === Formats.ISO) { diff --git a/core/jvm/src/LocalTimeJvm.kt b/core/jvm/src/LocalTimeJvm.kt index 198d0edc..c993c917 100644 --- a/core/jvm/src/LocalTimeJvm.kt +++ b/core/jvm/src/LocalTimeJvm.kt @@ -49,7 +49,7 @@ public actual class LocalTime internal constructor( public actual companion object { public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = try { jtLocalTime.of(hour, minute, second, nanosecond).let(::LocalTime) - } catch (e: DateTimeException) { + } catch (_: DateTimeException) { null } diff --git a/core/jvm/src/UtcOffsetJvm.kt b/core/jvm/src/UtcOffsetJvm.kt index 1b476196..c4344d77 100644 --- a/core/jvm/src/UtcOffsetJvm.kt +++ b/core/jvm/src/UtcOffsetJvm.kt @@ -26,20 +26,10 @@ public actual class UtcOffset( public actual companion object { public actual val ZERO: UtcOffset = UtcOffset(ZoneOffset.UTC) - public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? { - return try { - when { - hours != null -> - UtcOffset(ZoneOffset.ofHoursMinutesSeconds(hours, minutes ?: 0, seconds ?: 0)) - minutes != null -> - UtcOffset(ZoneOffset.ofHoursMinutesSeconds(minutes / 60, minutes % 60, seconds ?: 0)) - else -> { - UtcOffset(ZoneOffset.ofTotalSeconds(seconds ?: 0)) - } - } - } catch (e: DateTimeException) { - null - } + public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { + UtcOffset(hours, minutes, seconds) + } catch (_: IllegalArgumentException) { + null } public actual fun parse(input: CharSequence, format: DateTimeFormat): UtcOffset = when { From 4665f8855c4eb8d7a331413520ced15b1d54be91 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 2 Apr 2025 16:37:13 +0200 Subject: [PATCH 4/6] Update the docs --- core/common/src/LocalDate.kt | 10 ++++++++++ core/common/src/LocalDateTime.kt | 9 +++++++++ core/common/src/LocalTime.kt | 4 ++++ core/common/src/UtcOffset.kt | 6 ++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index 40b913a4..f4b2f78e 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -80,6 +80,9 @@ public expect class LocalDate : Comparable { * - [month] `1..12` * - [day] `1..31`, the upper bound can be less, depending on the month * + * Use `LocalDate(year, month, day) to throw an exception + * instead of returning `null` when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNullMonthNumber */ public fun createOrNull(year: Int, month: Int, day: Int): LocalDate? @@ -94,6 +97,9 @@ public expect class LocalDate : Comparable { * - [month] all values of the [Month] enum * - [day] `1..31`, the upper bound can be less, depending on the month * + * Use `LocalDate(year, month, day) to throw an exception + * instead of returning `null` when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNull */ public fun createOrNull(year: Int, month: Month, day: Int): LocalDate? @@ -205,6 +211,8 @@ public expect class LocalDate : Comparable { * * @throws IllegalArgumentException if any parameter is out of range or if [day] is invalid for the * given [month] and [year]. + * @see createOrNull for a version that returns `null` instead of throwing an exception + * when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateSamples.constructorFunctionMonthNumber */ public constructor(year: Int, month: Int, day: Int) @@ -220,6 +228,8 @@ public expect class LocalDate : Comparable { * * @throws IllegalArgumentException if any parameter is out of range or if [day] is invalid for the * given [month] and [year]. + * @see createOrNull for a version that returns `null` instead of throwing an exception + * when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateSamples.constructorFunction */ public constructor(year: Int, month: Month, day: Int) diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 2451bbc2..5b7f3551 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -130,6 +130,9 @@ public expect class LocalDateTime : Comparable { * - [second] `0..59` * - [nanosecond] `0..999_999_999` * + * Use `LocalDateTime(year, month, day, hour, minute, second, nanosecond)` + * to throw an exception instead of returning `null` when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNull */ public fun createOrNull( @@ -156,6 +159,9 @@ public expect class LocalDateTime : Comparable { * - [second] `0..59` * - [nanosecond] `0..999_999_999` * + * Use `LocalDateTime(year, month, day, hour, minute, second, nanosecond)` + * to throw an exception instead of returning `null` when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullWithMonth */ public fun createOrNull( @@ -268,6 +274,7 @@ public expect class LocalDateTime : Comparable { * * @throws IllegalArgumentException if any parameter is out of range * or if [day] is invalid for the given [monthNumber] and [year]. + * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.constructorFunctionWithMonthNumber */ @@ -296,6 +303,7 @@ public expect class LocalDateTime : Comparable { * * @throws IllegalArgumentException if any parameter is out of range, * or if [day] is invalid for the given [month] and [year]. + * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.constructorFunction */ @@ -312,6 +320,7 @@ public expect class LocalDateTime : Comparable { /** * Constructs a [LocalDateTime] instance by combining the given [date] and [time] parts. * + * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.fromDateAndTime */ public constructor(date: LocalDate, time: LocalTime) diff --git a/core/common/src/LocalTime.kt b/core/common/src/LocalTime.kt index b88fdf2d..16625dc4 100644 --- a/core/common/src/LocalTime.kt +++ b/core/common/src/LocalTime.kt @@ -94,6 +94,9 @@ public expect class LocalTime : Comparable { * - [second] `0..59` * - [nanosecond] `0..999_999_999` * + * Use `LocalTime(hour, minute, second, nanosecond)` + * to throw an exception instead of returning `null` when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull */ public fun createOrNull(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalTime? @@ -244,6 +247,7 @@ public expect class LocalTime : Comparable { * - [nanosecond] `0..999_999_999` * * @throws IllegalArgumentException if any parameter is out of range. + * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalTimeSamples.constructorFunction */ public constructor(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0) diff --git a/core/common/src/UtcOffset.kt b/core/common/src/UtcOffset.kt index 0a959375..0d4c6738 100644 --- a/core/common/src/UtcOffset.kt +++ b/core/common/src/UtcOffset.kt @@ -89,8 +89,9 @@ public expect class UtcOffset { * However, the non-null component of the highest order can exceed these bounds, * for example, `UtcOffset.createOrNull(minutes = 241)` and `UtcOffset.createOrNull(seconds = -3600)` are both valid. * - * @return the [UtcOffset] with the specified components, or `null` if the components are invalid - * or the resulting `UtcOffset` value is outside of range `±18:00`. + * Use `UtcOffset(hours, minutes, seconds)` to throw an exception instead of returning `null` + * when the parameters are invalid. + * * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.createOrNull */ public fun createOrNull(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset? @@ -228,6 +229,7 @@ public fun UtcOffset.format(format: DateTimeFormat): String = format. * @throws IllegalArgumentException if a component exceeds its bounds when a higher order component is specified. * @throws IllegalArgumentException if components have different signs. * @throws IllegalArgumentException if the resulting `UtcOffset` value is outside of range `±18:00`. + * @see UtcOffset.createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.constructorFunction */ public expect fun UtcOffset(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset From b9e110677e66914b43fc3264f88e94221b1080ad Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 17 Apr 2025 11:16:36 +0200 Subject: [PATCH 5/6] Rename createOrNull to orNull --- core/api/kotlinx-datetime.api | 20 ++++++------- core/api/kotlinx-datetime.klib.api | 12 ++++---- core/common/src/LocalDate.kt | 12 ++++---- core/common/src/LocalDateTime.kt | 14 ++++----- core/common/src/LocalTime.kt | 6 ++-- core/common/src/UtcOffset.kt | 10 +++---- core/common/test/LocalDateTest.kt | 10 +++---- core/common/test/LocalDateTimeTest.kt | 26 ++++++++-------- core/common/test/LocalTimeTest.kt | 20 ++++++------- core/common/test/UtcOffsetTest.kt | 8 ++--- core/common/test/samples/LocalDateSamples.kt | 20 ++++++------- .../test/samples/LocalDateTimeSamples.kt | 30 +++++++++---------- core/common/test/samples/LocalTimeSamples.kt | 16 +++++----- core/common/test/samples/UtcOffsetSamples.kt | 18 +++++------ core/commonKotlin/src/LocalDate.kt | 6 ++-- core/commonKotlin/src/LocalDateTime.kt | 12 ++++---- core/commonKotlin/src/LocalTime.kt | 2 +- core/commonKotlin/src/UtcOffset.kt | 2 +- core/jvm/src/LocalDate.kt | 4 +-- core/jvm/src/LocalDateTimeJvm.kt | 6 ++-- core/jvm/src/LocalTimeJvm.kt | 2 +- core/jvm/src/UtcOffsetJvm.kt | 2 +- 22 files changed, 129 insertions(+), 129 deletions(-) diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index 438e1727..8b4d7e50 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -297,10 +297,10 @@ public final class kotlinx/datetime/LocalDate : java/io/Serializable, java/lang/ public final class kotlinx/datetime/LocalDate$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; - public final fun createOrNull (III)Lkotlinx/datetime/LocalDate; - public final fun createOrNull (ILkotlinx/datetime/Month;I)Lkotlinx/datetime/LocalDate; public final fun fromEpochDays (I)Lkotlinx/datetime/LocalDate; public final fun fromEpochDays (J)Lkotlinx/datetime/LocalDate; + public final fun orNull (III)Lkotlinx/datetime/LocalDate; + public final fun orNull (ILkotlinx/datetime/Month;I)Lkotlinx/datetime/LocalDate; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/LocalDate; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/LocalDate; public static synthetic fun parse$default (Lkotlinx/datetime/LocalDate$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;ILjava/lang/Object;)Lkotlinx/datetime/LocalDate; @@ -377,10 +377,10 @@ public final class kotlinx/datetime/LocalDateTime : java/io/Serializable, java/l public final class kotlinx/datetime/LocalDateTime$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; - public final fun createOrNull (IIIIIII)Lkotlinx/datetime/LocalDateTime; - public final fun createOrNull (ILkotlinx/datetime/Month;IIIII)Lkotlinx/datetime/LocalDateTime; - public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalDateTime$Companion;IIIIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; - public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalDateTime$Companion;ILkotlinx/datetime/Month;IIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; + public final fun orNull (IIIIIII)Lkotlinx/datetime/LocalDateTime; + public final fun orNull (ILkotlinx/datetime/Month;IIIII)Lkotlinx/datetime/LocalDateTime; + public static synthetic fun orNull$default (Lkotlinx/datetime/LocalDateTime$Companion;IIIIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; + public static synthetic fun orNull$default (Lkotlinx/datetime/LocalDateTime$Companion;ILkotlinx/datetime/Month;IIIIIILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/LocalDateTime; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/LocalDateTime; public static synthetic fun parse$default (Lkotlinx/datetime/LocalDateTime$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;ILjava/lang/Object;)Lkotlinx/datetime/LocalDateTime; @@ -423,11 +423,11 @@ public final class kotlinx/datetime/LocalTime : java/io/Serializable, java/lang/ public final class kotlinx/datetime/LocalTime$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; - public final fun createOrNull (IIII)Lkotlinx/datetime/LocalTime; - public static synthetic fun createOrNull$default (Lkotlinx/datetime/LocalTime$Companion;IIIIILjava/lang/Object;)Lkotlinx/datetime/LocalTime; public final fun fromMillisecondOfDay (I)Lkotlinx/datetime/LocalTime; public final fun fromNanosecondOfDay (J)Lkotlinx/datetime/LocalTime; public final fun fromSecondOfDay (I)Lkotlinx/datetime/LocalTime; + public final fun orNull (IIII)Lkotlinx/datetime/LocalTime; + public static synthetic fun orNull$default (Lkotlinx/datetime/LocalTime$Companion;IIIIILjava/lang/Object;)Lkotlinx/datetime/LocalTime; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/LocalTime; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/LocalTime; public static synthetic fun parse$default (Lkotlinx/datetime/LocalTime$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;ILjava/lang/Object;)Lkotlinx/datetime/LocalTime; @@ -533,9 +533,9 @@ public final class kotlinx/datetime/UtcOffset : java/io/Serializable { public final class kotlinx/datetime/UtcOffset$Companion { public final fun Format (Lkotlin/jvm/functions/Function1;)Lkotlinx/datetime/format/DateTimeFormat; - public final fun createOrNull (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lkotlinx/datetime/UtcOffset; - public static synthetic fun createOrNull$default (Lkotlinx/datetime/UtcOffset$Companion;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lkotlinx/datetime/UtcOffset; public final fun getZERO ()Lkotlinx/datetime/UtcOffset; + public final fun orNull (Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lkotlinx/datetime/UtcOffset; + public static synthetic fun orNull$default (Lkotlinx/datetime/UtcOffset$Companion;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lkotlinx/datetime/UtcOffset; public final fun parse (Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;)Lkotlinx/datetime/UtcOffset; public final synthetic fun parse (Ljava/lang/String;)Lkotlinx/datetime/UtcOffset; public static synthetic fun parse$default (Lkotlinx/datetime/UtcOffset$Companion;Ljava/lang/CharSequence;Lkotlinx/datetime/format/DateTimeFormat;ILjava/lang/Object;)Lkotlinx/datetime/UtcOffset; diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index a208c0dc..7a57a29d 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -360,10 +360,10 @@ final class kotlinx.datetime/LocalDate : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDate.Companion.Format|Format(kotlin.Function1){}[0] - final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int){}[0] - final fun createOrNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.createOrNull|createOrNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] final fun fromEpochDays(kotlin/Int): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.fromEpochDays|fromEpochDays(kotlin.Int){}[0] final fun fromEpochDays(kotlin/Long): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.fromEpochDays|fromEpochDays(kotlin.Long){}[0] + final fun orNull(kotlin/Int, kotlin/Int, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.orNull|orNull(kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun orNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int): kotlinx.datetime/LocalDate? // kotlinx.datetime/LocalDate.Companion.orNull|orNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/LocalDate // kotlinx.datetime/LocalDate.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/LocalDate.Companion.serializer|serializer(){}[0] @@ -416,8 +416,8 @@ final class kotlinx.datetime/LocalDateTime : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDateTime.Companion.Format|Format(kotlin.Function1){}[0] - final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] - final fun createOrNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun orNull(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.orNull|orNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun orNull(kotlin/Int, kotlinx.datetime/Month, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalDateTime? // kotlinx.datetime/LocalDateTime.Companion.orNull|orNull(kotlin.Int;kotlinx.datetime.Month;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/LocalDateTime // kotlinx.datetime/LocalDateTime.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/LocalDateTime // kotlinx.datetime/LocalDateTime.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/LocalDateTime.Companion.serializer|serializer(){}[0] @@ -451,10 +451,10 @@ final class kotlinx.datetime/LocalTime : kotlin/Comparable): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalTime.Companion.Format|Format(kotlin.Function1){}[0] - final fun createOrNull(kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalTime? // kotlinx.datetime/LocalTime.Companion.createOrNull|createOrNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun fromMillisecondOfDay(kotlin/Int): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromMillisecondOfDay|fromMillisecondOfDay(kotlin.Int){}[0] final fun fromNanosecondOfDay(kotlin/Long): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromNanosecondOfDay|fromNanosecondOfDay(kotlin.Long){}[0] final fun fromSecondOfDay(kotlin/Int): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.fromSecondOfDay|fromSecondOfDay(kotlin.Int){}[0] + final fun orNull(kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Int = ...): kotlinx.datetime/LocalTime? // kotlinx.datetime/LocalTime.Companion.orNull|orNull(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/LocalTime // kotlinx.datetime/LocalTime.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/LocalTime.Companion.serializer|serializer(){}[0] @@ -479,7 +479,7 @@ final class kotlinx.datetime/UtcOffset { // kotlinx.datetime/UtcOffset|null[0] final fun (): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.ZERO.|(){}[0] final fun Format(kotlin/Function1): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/UtcOffset.Companion.Format|Format(kotlin.Function1){}[0] - final fun createOrNull(kotlin/Int? = ..., kotlin/Int? = ..., kotlin/Int? = ...): kotlinx.datetime/UtcOffset? // kotlinx.datetime/UtcOffset.Companion.createOrNull|createOrNull(kotlin.Int?;kotlin.Int?;kotlin.Int?){}[0] + final fun orNull(kotlin/Int? = ..., kotlin/Int? = ..., kotlin/Int? = ...): kotlinx.datetime/UtcOffset? // kotlinx.datetime/UtcOffset.Companion.orNull|orNull(kotlin.Int?;kotlin.Int?;kotlin.Int?){}[0] final fun parse(kotlin/CharSequence, kotlinx.datetime.format/DateTimeFormat = ...): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.parse|parse(kotlin.CharSequence;kotlinx.datetime.format.DateTimeFormat){}[0] final fun parse(kotlin/String): kotlinx.datetime/UtcOffset // kotlinx.datetime/UtcOffset.Companion.parse|parse(kotlin.String){}[0] final fun serializer(): kotlinx.serialization/KSerializer // kotlinx.datetime/UtcOffset.Companion.serializer|serializer(){}[0] diff --git a/core/common/src/LocalDate.kt b/core/common/src/LocalDate.kt index f4b2f78e..6d3fe4ad 100644 --- a/core/common/src/LocalDate.kt +++ b/core/common/src/LocalDate.kt @@ -83,9 +83,9 @@ public expect class LocalDate : Comparable { * Use `LocalDate(year, month, day) to throw an exception * instead of returning `null` when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNullMonthNumber + * @sample kotlinx.datetime.test.samples.LocalDateSamples.orNullMonthNumber */ - public fun createOrNull(year: Int, month: Int, day: Int): LocalDate? + public fun orNull(year: Int, month: Int, day: Int): LocalDate? /** * Constructs a [LocalDate] instance from the given date components @@ -100,9 +100,9 @@ public expect class LocalDate : Comparable { * Use `LocalDate(year, month, day) to throw an exception * instead of returning `null` when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.LocalDateSamples.createOrNull + * @sample kotlinx.datetime.test.samples.LocalDateSamples.orNull */ - public fun createOrNull(year: Int, month: Month, day: Int): LocalDate? + public fun orNull(year: Int, month: Month, day: Int): LocalDate? /** * A shortcut for calling [DateTimeFormat.parse]. * @@ -211,7 +211,7 @@ public expect class LocalDate : Comparable { * * @throws IllegalArgumentException if any parameter is out of range or if [day] is invalid for the * given [month] and [year]. - * @see createOrNull for a version that returns `null` instead of throwing an exception + * @see orNull for a version that returns `null` instead of throwing an exception * when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateSamples.constructorFunctionMonthNumber */ @@ -228,7 +228,7 @@ public expect class LocalDate : Comparable { * * @throws IllegalArgumentException if any parameter is out of range or if [day] is invalid for the * given [month] and [year]. - * @see createOrNull for a version that returns `null` instead of throwing an exception + * @see orNull for a version that returns `null` instead of throwing an exception * when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateSamples.constructorFunction */ diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index 5b7f3551..63525982 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -133,9 +133,9 @@ public expect class LocalDateTime : Comparable { * Use `LocalDateTime(year, month, day, hour, minute, second, nanosecond)` * to throw an exception instead of returning `null` when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNull + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.orNull */ - public fun createOrNull( + public fun orNull( year: Int, month: Int, day: Int, @@ -162,9 +162,9 @@ public expect class LocalDateTime : Comparable { * Use `LocalDateTime(year, month, day, hour, minute, second, nanosecond)` * to throw an exception instead of returning `null` when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullWithMonth + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.orNullWithMonth */ - public fun createOrNull( + public fun orNull( year: Int, month: Month, day: Int, @@ -274,7 +274,7 @@ public expect class LocalDateTime : Comparable { * * @throws IllegalArgumentException if any parameter is out of range * or if [day] is invalid for the given [monthNumber] and [year]. - * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. + * @see orNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.constructorFunctionWithMonthNumber */ @@ -303,7 +303,7 @@ public expect class LocalDateTime : Comparable { * * @throws IllegalArgumentException if any parameter is out of range, * or if [day] is invalid for the given [month] and [year]. - * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. + * @see orNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.constructorFunction */ @@ -320,7 +320,7 @@ public expect class LocalDateTime : Comparable { /** * Constructs a [LocalDateTime] instance by combining the given [date] and [time] parts. * - * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. + * @see orNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.fromDateAndTime */ public constructor(date: LocalDate, time: LocalTime) diff --git a/core/common/src/LocalTime.kt b/core/common/src/LocalTime.kt index 16625dc4..f8441c1e 100644 --- a/core/common/src/LocalTime.kt +++ b/core/common/src/LocalTime.kt @@ -97,9 +97,9 @@ public expect class LocalTime : Comparable { * Use `LocalTime(hour, minute, second, nanosecond)` * to throw an exception instead of returning `null` when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull + * @sample kotlinx.datetime.test.samples.LocalTimeSamples.orNull */ - public fun createOrNull(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalTime? + public fun orNull(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalTime? /** * A shortcut for calling [DateTimeFormat.parse]. @@ -247,7 +247,7 @@ public expect class LocalTime : Comparable { * - [nanosecond] `0..999_999_999` * * @throws IllegalArgumentException if any parameter is out of range. - * @see createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. + * @see orNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.LocalTimeSamples.constructorFunction */ public constructor(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0) diff --git a/core/common/src/UtcOffset.kt b/core/common/src/UtcOffset.kt index 0d4c6738..45f27f22 100644 --- a/core/common/src/UtcOffset.kt +++ b/core/common/src/UtcOffset.kt @@ -84,17 +84,17 @@ public expect class UtcOffset { * * The bounds are checked: it is invalid to pass something other than `±[0; 59]` as the number of seconds or minutes; * `null` will be returned if this rule is violated. - * For example, `UtcOffset.createOrNull(hours = 3, minutes = 61)` returns `null`. + * For example, `UtcOffset.orNull(hours = 3, minutes = 61)` returns `null`. * * However, the non-null component of the highest order can exceed these bounds, - * for example, `UtcOffset.createOrNull(minutes = 241)` and `UtcOffset.createOrNull(seconds = -3600)` are both valid. + * for example, `UtcOffset.orNull(minutes = 241)` and `UtcOffset.orNull(seconds = -3600)` are both valid. * * Use `UtcOffset(hours, minutes, seconds)` to throw an exception instead of returning `null` * when the parameters are invalid. * - * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.createOrNull + * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.orNull */ - public fun createOrNull(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset? + public fun orNull(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset? /** * A shortcut for calling [DateTimeFormat.parse]. @@ -229,7 +229,7 @@ public fun UtcOffset.format(format: DateTimeFormat): String = format. * @throws IllegalArgumentException if a component exceeds its bounds when a higher order component is specified. * @throws IllegalArgumentException if components have different signs. * @throws IllegalArgumentException if the resulting `UtcOffset` value is outside of range `±18:00`. - * @see UtcOffset.createOrNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. + * @see UtcOffset.orNull for a version that returns `null` instead of throwing an exception when the parameters are invalid. * @sample kotlinx.datetime.test.samples.UtcOffsetSamples.constructorFunction */ public expect fun UtcOffset(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset diff --git a/core/common/test/LocalDateTest.kt b/core/common/test/LocalDateTest.kt index 9cca8733..95196541 100644 --- a/core/common/test/LocalDateTest.kt +++ b/core/common/test/LocalDateTest.kt @@ -231,16 +231,16 @@ class LocalDateTest { } @Test - fun createOrNull() { + fun orNull() { validDates.forEach { (year, month, day) -> val expected = LocalDate(year, month, day) - assertEquals(expected, LocalDate.createOrNull(year, month, day)) - assertEquals(expected, LocalDate.createOrNull(year, Month(month), day)) + assertEquals(expected, LocalDate.orNull(year, month, day)) + assertEquals(expected, LocalDate.orNull(year, Month(month), day)) } invalidDates.forEach { (year, month, day) -> - assertNull(LocalDate.createOrNull(year, month, day)) + assertNull(LocalDate.orNull(year, month, day)) runCatching { Month(month) }.onSuccess { monthEnum -> - assertNull(LocalDate.createOrNull(year, monthEnum, day)) + assertNull(LocalDate.orNull(year, monthEnum, day)) } } } diff --git a/core/common/test/LocalDateTimeTest.kt b/core/common/test/LocalDateTimeTest.kt index a7e56ee3..62ca25bc 100644 --- a/core/common/test/LocalDateTimeTest.kt +++ b/core/common/test/LocalDateTimeTest.kt @@ -131,36 +131,36 @@ class LocalDateTimeTest { } @Test - fun createOrNull() {// Test createOrNull with month number - LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 500_000_000)?.let { + fun orNull() {// Test orNull with month number + LocalDateTime.orNull(2020, 1, 1, 12, 30, 45, 500_000_000)?.let { checkComponents(it, 2020, 1, 1, 12, 30, 45, 500_000_000) - } ?: fail("LocalDateTime.createOrNull should not return null") + } ?: fail("LocalDateTime.orNull should not return null") - // Test createOrNull with Month enum - LocalDateTime.createOrNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999)?.let { + // Test orNull with Month enum + LocalDateTime.orNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999)?.let { checkComponents(it, 2020, 2, 29, 23, 59, 59, 999_999_999) - } ?: fail("LocalDateTime.createOrNull should not return null") + } ?: fail("LocalDateTime.orNull should not return null") // Test invalid date components for ((year, month, day) in invalidDates) { - assertNull(LocalDateTime.createOrNull(year, month, day, 12, 30)) + assertNull(LocalDateTime.orNull(year, month, day, 12, 30)) runCatching { Month(month) }.onSuccess { monthEnum -> - assertNull(LocalDateTime.createOrNull(year, monthEnum, day, 12, 30)) + assertNull(LocalDateTime.orNull(year, monthEnum, day, 12, 30)) } } // Test invalid time components for (input in invalidTimes) { when (input.size) { - 2 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1])) - 3 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1], input[2])) - 4 -> assertNull(LocalDateTime.createOrNull(2024, 1, 1, input[0], input[1], input[2], input[3])) + 2 -> assertNull(LocalDateTime.orNull(2024, 1, 1, input[0], input[1])) + 3 -> assertNull(LocalDateTime.orNull(2024, 1, 1, input[0], input[1], input[2])) + 4 -> assertNull(LocalDateTime.orNull(2024, 1, 1, input[0], input[1], input[2], input[3])) } } // Test with Month enum - assertNull(LocalDateTime.createOrNull(2021, Month.FEBRUARY, 29, 12, 30)) // Invalid day (not a leap year) - assertNull(LocalDateTime.createOrNull(2020, Month.FEBRUARY, 30, 12, 30)) // Invalid day for February + assertNull(LocalDateTime.orNull(2021, Month.FEBRUARY, 29, 12, 30)) // Invalid day (not a leap year) + assertNull(LocalDateTime.orNull(2020, Month.FEBRUARY, 30, 12, 30)) // Invalid day for February } diff --git a/core/common/test/LocalTimeTest.kt b/core/common/test/LocalTimeTest.kt index 170cca10..d24efcbf 100644 --- a/core/common/test/LocalTimeTest.kt +++ b/core/common/test/LocalTimeTest.kt @@ -88,26 +88,26 @@ class LocalTimeTest { } @Test - fun createOrNull() { + fun orNull() { // Valid times should be created correctly - LocalTime.createOrNull(12, 30, 45, 500_000_000)?.let { + LocalTime.orNull(12, 30, 45, 500_000_000)?.let { checkComponents(it, 12, 30, 45, 500_000_000) - } ?: fail("LocalTime.createOrNull should not return null") + } ?: fail("LocalTime.orNull should not return null") - LocalTime.createOrNull(0, 0)?.let { + LocalTime.orNull(0, 0)?.let { checkComponents(it, 0, 0) - } ?: fail("LocalTime.createOrNull should not return null") + } ?: fail("LocalTime.orNull should not return null") - LocalTime.createOrNull(23, 59, 59, 999_999_999)?.let { + LocalTime.orNull(23, 59, 59, 999_999_999)?.let { checkComponents(it, 23, 59, 59, 999_999_999) - } ?: fail("LocalTime.createOrNull should not return null") + } ?: fail("LocalTime.orNull should not return null") // Invalid times should return null for (input in invalidTimes) { when (input.size) { - 2 -> assertNull(LocalTime.createOrNull(input[0], input[1])) - 3 -> assertNull(LocalTime.createOrNull(input[0], input[1], input[2])) - 4 -> assertNull(LocalTime.createOrNull(input[0], input[1], input[2], input[3])) + 2 -> assertNull(LocalTime.orNull(input[0], input[1])) + 3 -> assertNull(LocalTime.orNull(input[0], input[1], input[2])) + 4 -> assertNull(LocalTime.orNull(input[0], input[1], input[2], input[3])) } } } diff --git a/core/common/test/UtcOffsetTest.kt b/core/common/test/UtcOffsetTest.kt index 9e4dde86..9fbaf096 100644 --- a/core/common/test/UtcOffsetTest.kt +++ b/core/common/test/UtcOffsetTest.kt @@ -46,9 +46,9 @@ class UtcOffsetTest { val offset = UtcOffset(hours, minutes, seconds) val offsetSeconds = UtcOffset(seconds = totalSeconds) val offsetMinutes = UtcOffset(minutes = totalMinutes, seconds = seconds) - val offsetOrNull = UtcOffset.createOrNull(hours, minutes, seconds) - val offsetSecondsOrNull = UtcOffset.createOrNull(seconds = totalSeconds) - val offsetMinutesOrNull = UtcOffset.createOrNull(minutes = totalMinutes, seconds = seconds) + val offsetOrNull = UtcOffset.orNull(hours, minutes, seconds) + val offsetSecondsOrNull = UtcOffset.orNull(seconds = totalSeconds) + val offsetMinutesOrNull = UtcOffset.orNull(minutes = totalMinutes, seconds = seconds) assertEquals(totalSeconds, offset.totalSeconds) assertEquals(offset, offsetMinutes) assertEquals(offset, offsetSeconds) @@ -66,7 +66,7 @@ class UtcOffsetTest { seconds: Int? = null, ) { assertIllegalArgument { UtcOffset(hours, minutes, seconds) } - assertNull(UtcOffset.createOrNull(hours, minutes, seconds)) + assertNull(UtcOffset.orNull(hours, minutes, seconds)) } // total range assertInvalidUtcOffset(hours = -19) diff --git a/core/common/test/samples/LocalDateSamples.kt b/core/common/test/samples/LocalDateSamples.kt index a6ce07e9..e9bc6d1a 100644 --- a/core/common/test/samples/LocalDateSamples.kt +++ b/core/common/test/samples/LocalDateSamples.kt @@ -70,23 +70,23 @@ class LocalDateSamples { } @Test - fun createOrNullMonthNumber() { - // Constructing a LocalDate value using `createOrNull` - val date = LocalDate.createOrNull(2024, 4, 16) - // For valid values, `createOrNull` is equivalent to the constructor + fun orNullMonthNumber() { + // Constructing a LocalDate value using `orNull` + val date = LocalDate.orNull(2024, 4, 16) + // For valid values, `orNull` is equivalent to the constructor check(date == LocalDate(2024, 4, 16)) // If a value can not be constructed, null is returned - check(LocalDate.createOrNull(2024, 1, 99) == null) + check(LocalDate.orNull(2024, 1, 99) == null) } @Test - fun createOrNull() { - // Constructing a LocalDate value using `createOrNull` - val date = LocalDate.createOrNull(2024, Month.APRIL, 16) - // For valid values, `createOrNull` is equivalent to the constructor + fun orNull() { + // Constructing a LocalDate value using `orNull` + val date = LocalDate.orNull(2024, Month.APRIL, 16) + // For valid values, `orNull` is equivalent to the constructor check(date == LocalDate(2024, Month.APRIL, 16)) // If a value can not be constructed, null is returned - check(LocalDate.createOrNull(2024, Month.FEBRUARY, 31) == null) + check(LocalDate.orNull(2024, Month.FEBRUARY, 31) == null) } @Test diff --git a/core/common/test/samples/LocalDateTimeSamples.kt b/core/common/test/samples/LocalDateTimeSamples.kt index e21c4f14..93fad209 100644 --- a/core/common/test/samples/LocalDateTimeSamples.kt +++ b/core/common/test/samples/LocalDateTimeSamples.kt @@ -184,28 +184,28 @@ class LocalDateTimeSamples { } @Test - fun createOrNull() { - // Constructing a LocalDateTime value using `createOrNull` - val dateTime = LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 999_999_999) - // For valid values, `createOrNull` is equivalent to the constructor + fun orNull() { + // Constructing a LocalDateTime value using `orNull` + val dateTime = LocalDateTime.orNull(2024, 2, 15, 16, 48, 59, 999_999_999) + // For valid values, `orNull` is equivalent to the constructor check(dateTime == LocalDateTime(2024, 2, 15, 16, 48, 59, 999_999_999)) // If a value can not be constructed, null is returned - check(LocalDateTime.createOrNull(2024, 2, 31, 16, 48) == null) // Invalid day - check(LocalDateTime.createOrNull(2024, 2, 15, 24, 48) == null) // Invalid hour - check(LocalDateTime.createOrNull(2024, 2, 15, 16, 60) == null) // Invalid minute - check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 60) == null) // Invalid second - check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 1_000_000_000) == null) // Invalid nanosecond + check(LocalDateTime.orNull(2024, 2, 31, 16, 48) == null) // Invalid day + check(LocalDateTime.orNull(2024, 2, 15, 24, 48) == null) // Invalid hour + check(LocalDateTime.orNull(2024, 2, 15, 16, 60) == null) // Invalid minute + check(LocalDateTime.orNull(2024, 2, 15, 16, 48, 60) == null) // Invalid second + check(LocalDateTime.orNull(2024, 2, 15, 16, 48, 59, 1_000_000_000) == null) // Invalid nanosecond } @Test - fun createOrNullWithMonth() { - // Constructing a LocalDateTime value using `createOrNull` with Month enum - val dateTime = LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999) - // For valid values, `createOrNull` is equivalent to the constructor + fun orNullWithMonth() { + // Constructing a LocalDateTime value using `orNull` with Month enum + val dateTime = LocalDateTime.orNull(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999) + // For valid values, `orNull` is equivalent to the constructor check(dateTime == LocalDateTime(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999)) // If a value can not be constructed, null is returned - check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 31, 16, 48) == null) // Invalid day - check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 24, 48) == null) // Invalid hour + check(LocalDateTime.orNull(2024, Month.FEBRUARY, 31, 16, 48) == null) // Invalid day + check(LocalDateTime.orNull(2024, Month.FEBRUARY, 15, 24, 48) == null) // Invalid hour } diff --git a/core/common/test/samples/LocalTimeSamples.kt b/core/common/test/samples/LocalTimeSamples.kt index e8aa1364..1680dd4d 100644 --- a/core/common/test/samples/LocalTimeSamples.kt +++ b/core/common/test/samples/LocalTimeSamples.kt @@ -134,16 +134,16 @@ class LocalTimeSamples { } @Test - fun createOrNull() { - // Constructing a LocalTime value using `createOrNull` - val time = LocalTime.createOrNull(8, 30, 15, 123_456_789) - // For valid values, `createOrNull` is equivalent to the constructor + fun orNull() { + // Constructing a LocalTime value using `orNull` + val time = LocalTime.orNull(8, 30, 15, 123_456_789) + // For valid values, `orNull` is equivalent to the constructor check(time == LocalTime(8, 30, 15, 123_456_789)) // If a value can not be constructed, null is returned - check(LocalTime.createOrNull(24, 30) == null) - check(LocalTime.createOrNull(8, 60) == null) - check(LocalTime.createOrNull(8, 30, 60) == null) - check(LocalTime.createOrNull(8, 30, 15, 1_000_000_000) == null) + check(LocalTime.orNull(24, 30) == null) + check(LocalTime.orNull(8, 60) == null) + check(LocalTime.orNull(8, 30, 60) == null) + check(LocalTime.orNull(8, 30, 15, 1_000_000_000) == null) } @Test diff --git a/core/common/test/samples/UtcOffsetSamples.kt b/core/common/test/samples/UtcOffsetSamples.kt index e2689768..7c91b4c1 100644 --- a/core/common/test/samples/UtcOffsetSamples.kt +++ b/core/common/test/samples/UtcOffsetSamples.kt @@ -109,21 +109,21 @@ class UtcOffsetSamples { } @Test - fun createOrNull() { - // Using the createOrNull function to create UtcOffset values - val validOffset = UtcOffset.createOrNull(hours = 3, minutes = 30) + fun orNull() { + // Using the orNull function to create UtcOffset values + val validOffset = UtcOffset.orNull(hours = 3, minutes = 30) check(validOffset != null) check(validOffset.totalSeconds == 12600) - // For valid inputs, createOrNull returns a non-null value - check(UtcOffset.createOrNull(seconds = -3600) == UtcOffset(hours = -1)) + // For valid inputs, orNull returns a non-null value + check(UtcOffset.orNull(seconds = -3600) == UtcOffset(hours = -1)) - // For invalid inputs, createOrNull returns null - check(UtcOffset.createOrNull(hours = 1, minutes = 60) == null) + // For invalid inputs, orNull returns null + check(UtcOffset.orNull(hours = 1, minutes = 60) == null) // Since `hours` is positive, `minutes` must also be positive - check(UtcOffset.createOrNull(hours = 1, minutes = -30) == null) + check(UtcOffset.orNull(hours = 1, minutes = -30) == null) // The total offset must be within the range ±18:00 - check(UtcOffset.createOrNull(hours = 19) == null) + check(UtcOffset.orNull(hours = 19) == null) } class Formats { diff --git a/core/commonKotlin/src/LocalDate.kt b/core/commonKotlin/src/LocalDate.kt index a1efa3fc..3d244459 100644 --- a/core/commonKotlin/src/LocalDate.kt +++ b/core/commonKotlin/src/LocalDate.kt @@ -53,7 +53,7 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) public fun parse(isoString: String): LocalDate = parse(input = isoString) - public actual fun createOrNull(year: Int, month: Int, day: Int): LocalDate? = + public actual fun orNull(year: Int, month: Int, day: Int): LocalDate? = if (!isValidYear(year) || month !in 1..12 || day !in 1..31 || (day > 28 && day > month.monthLength(isLeapYear(year)))) { null @@ -61,8 +61,8 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo LocalDate(year, month, day) } - public actual fun createOrNull(year: Int, month: Month, day: Int): LocalDate? = - createOrNull(year, month.number, day) + public actual fun orNull(year: Int, month: Month, day: Int): LocalDate? = + orNull(year, month.number, day) // org.threeten.bp.LocalDate#toEpochDay public actual fun fromEpochDays(epochDays: Long): LocalDate { diff --git a/core/commonKotlin/src/LocalDateTime.kt b/core/commonKotlin/src/LocalDateTime.kt index 7f05039e..f281701b 100644 --- a/core/commonKotlin/src/LocalDateTime.kt +++ b/core/commonKotlin/src/LocalDateTime.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.* public actual class LocalDateTime public actual constructor(public actual val date: LocalDate, public actual val time: LocalTime) : Comparable { public actual companion object { - public actual fun createOrNull( + public actual fun orNull( year: Int, month: Int, day: Int, @@ -26,12 +26,12 @@ public actual constructor(public actual val date: LocalDate, public actual val t second: Int, nanosecond: Int ): LocalDateTime? { - val date = LocalDate.createOrNull(year, month, day) ?: return null - val time = LocalTime.createOrNull(hour, minute, second, nanosecond) ?: return null + val date = LocalDate.orNull(year, month, day) ?: return null + val time = LocalTime.orNull(hour, minute, second, nanosecond) ?: return null return LocalDateTime(date, time) } - public actual fun createOrNull( + public actual fun orNull( year: Int, month: Month, day: Int, @@ -40,8 +40,8 @@ public actual constructor(public actual val date: LocalDate, public actual val t second: Int, nanosecond: Int ): LocalDateTime? { - val date = LocalDate.createOrNull(year, month, day) ?: return null - val time = LocalTime.createOrNull(hour, minute, second, nanosecond) ?: return null + val date = LocalDate.orNull(year, month, day) ?: return null + val time = LocalTime.orNull(hour, minute, second, nanosecond) ?: return null return LocalDateTime(date, time) } diff --git a/core/commonKotlin/src/LocalTime.kt b/core/commonKotlin/src/LocalTime.kt index ff58d4af..5d9c89c5 100644 --- a/core/commonKotlin/src/LocalTime.kt +++ b/core/commonKotlin/src/LocalTime.kt @@ -33,7 +33,7 @@ public actual class LocalTime actual constructor( } public actual companion object { - public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = + public actual fun orNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = if (hour !in 0..23 || minute !in 0..59 || second !in 0..59 || nanosecond !in 0 until NANOS_PER_ONE) { null } else { diff --git a/core/commonKotlin/src/UtcOffset.kt b/core/commonKotlin/src/UtcOffset.kt index 00c28fe4..dcd7e52e 100644 --- a/core/commonKotlin/src/UtcOffset.kt +++ b/core/commonKotlin/src/UtcOffset.kt @@ -23,7 +23,7 @@ public actual class UtcOffset private constructor(public actual val totalSeconds public actual val ZERO: UtcOffset = UtcOffset(totalSeconds = 0) - public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { + public actual fun orNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { UtcOffset(hours, minutes, seconds) } catch (_: IllegalArgumentException) { null diff --git a/core/jvm/src/LocalDate.kt b/core/jvm/src/LocalDate.kt index 92809d36..b56b6e48 100644 --- a/core/jvm/src/LocalDate.kt +++ b/core/jvm/src/LocalDate.kt @@ -22,14 +22,14 @@ public actual class LocalDate internal constructor( internal val value: jtLocalDate ) : Comparable, java.io.Serializable { public actual companion object { - public actual fun createOrNull(year: Int, month: Int, day: Int): LocalDate? = + public actual fun orNull(year: Int, month: Int, day: Int): LocalDate? = try { LocalDate(year, month, day) } catch (e: IllegalArgumentException) { null } - public actual fun createOrNull(year: Int, month: Month, day: Int): LocalDate? = + public actual fun orNull(year: Int, month: Month, day: Int): LocalDate? = try { LocalDate(year, month, day) } catch (e: IllegalArgumentException) { diff --git a/core/jvm/src/LocalDateTimeJvm.kt b/core/jvm/src/LocalDateTimeJvm.kt index 1534f137..225c0693 100644 --- a/core/jvm/src/LocalDateTimeJvm.kt +++ b/core/jvm/src/LocalDateTimeJvm.kt @@ -85,7 +85,7 @@ public actual class LocalDateTime internal constructor( actual override fun compareTo(other: LocalDateTime): Int = this.value.compareTo(other.value) public actual companion object { - public actual fun createOrNull( + public actual fun orNull( year: Int, month: Int, day: Int, @@ -99,7 +99,7 @@ public actual class LocalDateTime internal constructor( null } - public actual fun createOrNull( + public actual fun orNull( year: Int, month: Month, day: Int, @@ -107,7 +107,7 @@ public actual class LocalDateTime internal constructor( minute: Int, second: Int, nanosecond: Int - ): LocalDateTime? = createOrNull(year, month.number, day, hour, minute, second, nanosecond) + ): LocalDateTime? = orNull(year, month.number, day, hour, minute, second, nanosecond) public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDateTime = diff --git a/core/jvm/src/LocalTimeJvm.kt b/core/jvm/src/LocalTimeJvm.kt index c993c917..5d921f47 100644 --- a/core/jvm/src/LocalTimeJvm.kt +++ b/core/jvm/src/LocalTimeJvm.kt @@ -47,7 +47,7 @@ public actual class LocalTime internal constructor( actual override fun compareTo(other: LocalTime): Int = this.value.compareTo(other.value) public actual companion object { - public actual fun createOrNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = try { + public actual fun orNull(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime? = try { jtLocalTime.of(hour, minute, second, nanosecond).let(::LocalTime) } catch (_: DateTimeException) { null diff --git a/core/jvm/src/UtcOffsetJvm.kt b/core/jvm/src/UtcOffsetJvm.kt index c4344d77..af05f915 100644 --- a/core/jvm/src/UtcOffsetJvm.kt +++ b/core/jvm/src/UtcOffsetJvm.kt @@ -26,7 +26,7 @@ public actual class UtcOffset( public actual companion object { public actual val ZERO: UtcOffset = UtcOffset(ZoneOffset.UTC) - public actual fun createOrNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { + public actual fun orNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { UtcOffset(hours, minutes, seconds) } catch (_: IllegalArgumentException) { null From b70a8b6904ff94ad65c58e56505c5f150b2ffd9e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 17 Apr 2025 14:52:24 +0200 Subject: [PATCH 6/6] Avoid extraneous range checks when constructors receive safe values --- core/common/src/Month.kt | 4 +- core/commonKotlin/src/LocalDate.kt | 29 +++++++++---- core/commonKotlin/src/LocalDateTime.kt | 6 +-- core/commonKotlin/src/LocalTime.kt | 35 +++++++++------ core/commonKotlin/src/UtcOffset.kt | 59 +++++++++++++++++++------- 5 files changed, 91 insertions(+), 42 deletions(-) diff --git a/core/common/src/Month.kt b/core/common/src/Month.kt index 9163d668..4b9d8646 100644 --- a/core/common/src/Month.kt +++ b/core/common/src/Month.kt @@ -72,6 +72,8 @@ public val Month.number: Int get() = ordinal + 1 * @sample kotlinx.datetime.test.samples.MonthSamples.constructorFunction */ public fun Month(number: Int): Month { - require(number in 1..12) + require(number in 1..12) { + "Month must be a number between 1 and 12, got $number" + } return Month.entries[number - 1] } diff --git a/core/commonKotlin/src/LocalDate.kt b/core/commonKotlin/src/LocalDate.kt index 3d244459..5c142e13 100644 --- a/core/commonKotlin/src/LocalDate.kt +++ b/core/commonKotlin/src/LocalDate.kt @@ -23,7 +23,14 @@ private fun isValidYear(year: Int): Boolean = year >= YEAR_MIN && year <= YEAR_MAX @Serializable(with = LocalDateIso8601Serializer::class) -public actual class LocalDate actual constructor(public actual val year: Int, month: Int, public actual val day: Int) : Comparable { +public actual class LocalDate private constructor( + public actual val year: Int, month: Int, public actual val day: Int, unit: Unit +) : Comparable { + + public actual constructor(year: Int, month: Int, day: Int): this(year, month, day, Unit) { + require(_month in 1..12) { "Invalid date: month must be a number between 1 and 12, got $_month" } + validateYearAndDay() + } private val _month: Int = month @Deprecated("Use the 'month' property instead", ReplaceWith("this.month.number"), level = DeprecationLevel.WARNING) @@ -31,22 +38,23 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo @Deprecated("Use the 'day' property instead", ReplaceWith("this.day"), level = DeprecationLevel.WARNING) public actual val dayOfMonth: Int get() = day - init { + public actual constructor(year: Int, month: Month, day: Int) : this(year, month.number, day, Unit) { + validateYearAndDay() + } + + private fun validateYearAndDay() { // org.threeten.bp.LocalDate#create require(isValidYear(year)) { "Invalid date: the year is out of range" } - require(_month in 1..12) { "Invalid date: month must be a number between 1 and 12, got $_month" } require(day in 1..31) { "Invalid date: day of month must be a number between 1 and 31, got $day" } if (day > 28 && day > _month.monthLength(isLeapYear(year))) { if (day == 29) { throw IllegalArgumentException("Invalid date 'February 29' as '$year' is not a leap year") } else { - throw IllegalArgumentException("Invalid date '${Month(month)} $day'") + throw IllegalArgumentException("Invalid date '$month $day'") } } } - public actual constructor(year: Int, month: Month, day: Int) : this(year, month.number, day) - public actual companion object { public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalDate = format.parse(input) @@ -58,7 +66,7 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo (day > 28 && day > month.monthLength(isLeapYear(year)))) { null } else { - LocalDate(year, month, day) + LocalDate(year, month, day, Unit) } public actual fun orNull(year: Int, month: Month, day: Int): LocalDate? = @@ -98,7 +106,8 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo val dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1 yearEst += marchMonth0 / 10 - return LocalDate(yearEst.toInt(), month, dom) + // The range of valid values was checked in the beginning + return LocalDate(yearEst.toInt(), month, dom, Unit) } public actual fun fromEpochDays(epochDays: Int): LocalDate = fromEpochDays(epochDays.toLong()) @@ -171,11 +180,13 @@ public actual class LocalDate actual constructor(public actual val year: Int, mo // org.threeten.bp.LocalDate#resolvePreviousValid /** + * May only be called if the year and month are valid. + * * @throws IllegalArgumentException if the result exceeds the boundaries */ private fun resolvePreviousValid(year: Int, month: Int, day: Int): LocalDate { val newDay = min(day, month.monthLength(isLeapYear(year))) - return LocalDate(year, month, newDay) + return LocalDate(year, month, newDay, Unit) } // org.threeten.bp.LocalDate#plusMonths diff --git a/core/commonKotlin/src/LocalDateTime.kt b/core/commonKotlin/src/LocalDateTime.kt index f281701b..17fe148a 100644 --- a/core/commonKotlin/src/LocalDateTime.kt +++ b/core/commonKotlin/src/LocalDateTime.kt @@ -65,10 +65,10 @@ public actual constructor(public actual val date: LocalDate, public actual val t } public actual constructor(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : - this(LocalDate(year, month, day), LocalTime.of(hour, minute, second, nanosecond)) + this(LocalDate(year, month, day), LocalTime(hour, minute, second, nanosecond)) public actual constructor(year: Int, month: Month, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : - this(LocalDate(year, month, day), LocalTime.of(hour, minute, second, nanosecond)) + this(LocalDate(year, month, day), LocalTime(hour, minute, second, nanosecond)) public actual val year: Int get() = date.year @Deprecated("Use the 'month' property instead", ReplaceWith("this.month.number"), level = DeprecationLevel.WARNING) @@ -106,7 +106,7 @@ public actual constructor(public actual val date: LocalDate, public actual val t // org.threeten.bp.chrono.ChronoLocalDateTime#toEpochSecond internal fun toEpochSecond(offset: UtcOffset): Long { - val epochDay = date.toEpochDays().toLong() + val epochDay = date.toEpochDays() var secs: Long = epochDay * 86400 + time.toSecondOfDay() secs -= offset.totalSeconds return secs diff --git a/core/commonKotlin/src/LocalTime.kt b/core/commonKotlin/src/LocalTime.kt index 5d9c89c5..b7055fd0 100644 --- a/core/commonKotlin/src/LocalTime.kt +++ b/core/commonKotlin/src/LocalTime.kt @@ -14,14 +14,17 @@ import kotlinx.datetime.serializers.LocalTimeIso8601Serializer import kotlinx.serialization.Serializable @Serializable(LocalTimeIso8601Serializer::class) -public actual class LocalTime actual constructor( +public actual class LocalTime private constructor( public actual val hour: Int, public actual val minute: Int, public actual val second: Int, - public actual val nanosecond: Int - ) : Comparable { + public actual val nanosecond: Int, + unit: Unit, +) : Comparable { - init { + public actual constructor( + hour: Int, minute: Int, second: Int, nanosecond: Int + ) : this(hour, minute, second, nanosecond, Unit) { fun check(value: Int, lower: Int, upper: Int, str: String) = require(value in lower..upper) { "Invalid time: $str must be a number between $lower and $upper, got $value" @@ -37,7 +40,7 @@ public actual class LocalTime actual constructor( if (hour !in 0..23 || minute !in 0..59 || second !in 0..59 || nanosecond !in 0 until NANOS_PER_ONE) { null } else { - LocalTime(hour, minute, second, nanosecond) + LocalTime(hour, minute, second, nanosecond, Unit) } public actual fun parse(input: CharSequence, format: DateTimeFormat): LocalTime = format.parse(input) @@ -56,22 +59,25 @@ public actual class LocalTime actual constructor( // org.threeten.bp.LocalTime#ofSecondOfDay(long, int) internal fun ofSecondOfDay(secondOfDay: Int, nanoOfSecond: Int): LocalTime { - require(secondOfDay in 0 until SECONDS_PER_DAY) - require(nanoOfSecond in 0 until NANOS_PER_ONE) + require(secondOfDay in 0 until SECONDS_PER_DAY) { + "Invalid time: secondOfDay must be between 0 and $SECONDS_PER_DAY, got $secondOfDay" + } + require(nanoOfSecond in 0 until NANOS_PER_ONE) { + "Invalid time: nanosecondOfSecond must be between 0 and $NANOS_PER_ONE, got $nanoOfSecond" + } val hours = (secondOfDay / SECONDS_PER_HOUR) val secondWithoutHours = secondOfDay - hours * SECONDS_PER_HOUR val minutes = (secondWithoutHours / SECONDS_PER_MINUTE) val second = secondWithoutHours - minutes * SECONDS_PER_MINUTE - return LocalTime(hours, minutes, second, nanoOfSecond) - } - - internal fun of(hour: Int, minute: Int, second: Int, nanosecond: Int): LocalTime { - return LocalTime(hour, minute, second, nanosecond) + // The range of valid values was checked in the require statements above + return LocalTime(hours, minutes, second, nanoOfSecond, Unit) } // org.threeten.bp.LocalTime#ofNanoOfDay internal fun ofNanoOfDay(nanoOfDay: Long): LocalTime { - require(nanoOfDay >= 0 && nanoOfDay < SECONDS_PER_DAY.toLong() * NANOS_PER_ONE) + require(nanoOfDay >= 0 && nanoOfDay < SECONDS_PER_DAY.toLong() * NANOS_PER_ONE) { + "Invalid time: nanosecondOfDay must be between 0 and 86_400_000_000_000, got $nanoOfDay" + } var newNanoOfDay = nanoOfDay val hours = (newNanoOfDay / NANOS_PER_HOUR).toInt() newNanoOfDay -= hours * NANOS_PER_HOUR @@ -79,7 +85,8 @@ public actual class LocalTime actual constructor( newNanoOfDay -= minutes * NANOS_PER_MINUTE val seconds = (newNanoOfDay / NANOS_PER_ONE).toInt() newNanoOfDay -= seconds * NANOS_PER_ONE - return LocalTime(hours, minutes, seconds, newNanoOfDay.toInt()) + // The range of valid values was checked in the require statement + return LocalTime(hours, minutes, seconds, newNanoOfDay.toInt(), Unit) } internal actual val MIN: LocalTime = LocalTime(0, 0, 0, 0) diff --git a/core/commonKotlin/src/UtcOffset.kt b/core/commonKotlin/src/UtcOffset.kt index dcd7e52e..c54cce2a 100644 --- a/core/commonKotlin/src/UtcOffset.kt +++ b/core/commonKotlin/src/UtcOffset.kt @@ -23,10 +23,13 @@ public actual class UtcOffset private constructor(public actual val totalSeconds public actual val ZERO: UtcOffset = UtcOffset(totalSeconds = 0) - public actual fun orNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = try { - UtcOffset(hours, minutes, seconds) - } catch (_: IllegalArgumentException) { - null + public actual fun orNull(hours: Int?, minutes: Int?, seconds: Int?): UtcOffset? = when { + hours != null -> + ofHoursMinutesSecondsOrNull(hours, minutes ?: 0, seconds ?: 0) + minutes != null -> + ofHoursMinutesSecondsOrNull(minutes / MINUTES_PER_HOUR, minutes % MINUTES_PER_HOUR, seconds ?: 0) + else -> + ofSecondsOrNull(seconds ?: 0) } public actual fun parse(input: CharSequence, format: DateTimeFormat): UtcOffset = format.parse(input) @@ -34,10 +37,11 @@ public actual class UtcOffset private constructor(public actual val totalSeconds @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) public fun parse(offsetString: String): UtcOffset = parse(input = offsetString) + private fun totalSecondsValid(totalSeconds: Int): Boolean = + totalSeconds in -18 * SECONDS_PER_HOUR..18 * SECONDS_PER_HOUR + private fun validateTotal(totalSeconds: Int) { - if (totalSeconds !in -18 * SECONDS_PER_HOUR .. 18 * SECONDS_PER_HOUR) { - throw IllegalArgumentException("Total seconds value is out of range: $totalSeconds") - } + require(totalSecondsValid(totalSeconds)) { "Total seconds value is out of range: $totalSeconds" } } // org.threeten.bp.ZoneOffset#validate @@ -70,23 +74,48 @@ public actual class UtcOffset private constructor(public actual val totalSeconds } } + private fun hoursMinutesSecondsValid(hours: Int, minutes: Int, seconds: Int): Boolean = + // valid range for hours, minutes and seconds + hours in -18..18 && minutes in -59..59 && seconds in -59..59 + // same sign for all components + && !(hours > 0 && (minutes < 0 || seconds < 0)) + && !(hours < 0 && (minutes > 0 || seconds > 0)) + // same sign for minutes and seconds + && !(minutes > 0 && seconds < 0 || minutes < 0 && seconds > 0) + // valid range for total seconds + && !(abs(hours) == 18 && (abs(minutes) > 0 || abs(seconds) > 0)) + // org.threeten.bp.ZoneOffset#ofHoursMinutesSeconds + internal fun ofHoursMinutesSecondsUnsafe(hours: Int, minutes: Int, seconds: Int): UtcOffset = + if (hours == 0 && minutes == 0 && seconds == 0) ZERO + else ofSeconds(hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds) + internal fun ofHoursMinutesSeconds(hours: Int, minutes: Int, seconds: Int): UtcOffset { validate(hours, minutes, seconds) - return if (hours == 0 && minutes == 0 && seconds == 0) ZERO - else ofSeconds(hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds) + return ofHoursMinutesSecondsUnsafe(hours, minutes, seconds) } - // org.threeten.bp.ZoneOffset#ofTotalSeconds - internal fun ofSeconds(seconds: Int): UtcOffset { - validateTotal(seconds) - return if (seconds % (15 * SECONDS_PER_MINUTE) == 0) { - utcOffsetCache[seconds] ?: UtcOffset(totalSeconds = seconds).also { utcOffsetCache[seconds] = it } + internal fun ofHoursMinutesSecondsOrNull(hours: Int, minutes: Int, seconds: Int): UtcOffset? = + if (hoursMinutesSecondsValid(hours, minutes, seconds)) { + ofHoursMinutesSecondsUnsafe(hours, minutes, seconds) } else { - UtcOffset(totalSeconds = seconds) + null } + + // org.threeten.bp.ZoneOffset#ofTotalSeconds + private fun ofSecondsUnsafe(seconds: Int): UtcOffset = if (seconds % (15 * SECONDS_PER_MINUTE) == 0) { + utcOffsetCache[seconds] ?: UtcOffset(totalSeconds = seconds).also { utcOffsetCache[seconds] = it } + } else { + UtcOffset(totalSeconds = seconds) } + internal fun ofSeconds(seconds: Int): UtcOffset = validateTotal(seconds).let { + ofSecondsUnsafe(seconds) + } + + internal fun ofSecondsOrNull(seconds: Int): UtcOffset? = + if (totalSecondsValid(seconds)) ofSecondsUnsafe(seconds) else null + @Suppress("FunctionName") public actual fun Format(block: DateTimeFormatBuilder.WithUtcOffset.() -> Unit): DateTimeFormat = UtcOffsetFormat.build(block)