Skip to content

Commit 99b5902

Browse files
committed
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`. """
1 parent 8960d11 commit 99b5902

15 files changed

+420
-0
lines changed

core/common/src/LocalDateTime.kt

+62
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,68 @@ import kotlin.jvm.JvmName
114114
public expect class LocalDateTime : Comparable<LocalDateTime> {
115115
public companion object {
116116

117+
/**
118+
* Constructs a [LocalDateTime] instance from the given date and time components
119+
* or returns `null` if a value is out of range.
120+
*
121+
* The components [month] and [day] are 1-based.
122+
*
123+
* The supported ranges of components:
124+
* - [year] the range is platform-dependent, but at least is enough to represent dates of all instants between
125+
* [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE]
126+
* - [month] `1..12`
127+
* - [day] `1..31`, the upper bound can be less, depending on the month
128+
* - [hour] `0..23`
129+
* - [minute] `0..59`
130+
* - [second] `0..59`
131+
* - [nanosecond] `0..999_999_999`
132+
*
133+
* @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNull
134+
*/
135+
public fun createOrNull(
136+
year: Int,
137+
month: Int,
138+
day: Int,
139+
hour: Int,
140+
minute: Int,
141+
second: Int = 0,
142+
nanosecond: Int = 0
143+
): LocalDateTime?
144+
145+
/**
146+
* Constructs a [LocalDateTime] instance from the given date and time components
147+
* or returns `null` if a value is out of range.
148+
*
149+
* The supported ranges of components:
150+
* - [year] the range is platform-dependent, but at least is enough to represent dates of all instants between
151+
* [Instant.DISTANT_PAST] and [Instant.DISTANT_FUTURE]
152+
* - [month] all values of the [Month] enum
153+
* - [day] `1..31`, the upper bound can be less, depending on the month
154+
* - [hour] `0..23`
155+
* - [minute] `0..59`
156+
* - [second] `0..59`
157+
* - [nanosecond] `0..999_999_999`
158+
*
159+
* @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullWithMonth
160+
*/
161+
public fun createOrNull(
162+
year: Int,
163+
month: Month,
164+
day: Int,
165+
hour: Int,
166+
minute: Int,
167+
second: Int = 0,
168+
nanosecond: Int = 0
169+
): LocalDateTime?
170+
171+
/**
172+
* Constructs a [LocalDateTime] instance by combining the given [date] and [time] parts
173+
* or returns `null` if either [date] or [time] is `null`.
174+
*
175+
* @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.createOrNullFromDateAndTime
176+
*/
177+
public fun createOrNull(date: LocalDate?, time: LocalTime?): LocalDateTime?
178+
117179
/**
118180
* A shortcut for calling [DateTimeFormat.parse].
119181
*

core/common/src/LocalTime.kt

+14
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ import kotlin.jvm.JvmName
8484
public expect class LocalTime : Comparable<LocalTime> {
8585
public companion object {
8686

87+
/**
88+
* Constructs a [LocalTime] instance from the given time components
89+
* or returns `null` if a value is out of range.
90+
*
91+
* The supported ranges of components:
92+
* - [hour] `0..23`
93+
* - [minute] `0..59`
94+
* - [second] `0..59`
95+
* - [nanosecond] `0..999_999_999`
96+
*
97+
* @sample kotlinx.datetime.test.samples.LocalTimeSamples.createOrNull
98+
*/
99+
public fun createOrNull(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): LocalTime?
100+
87101
/**
88102
* A shortcut for calling [DateTimeFormat.parse].
89103
*

core/common/src/UtcOffset.kt

+20
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,26 @@ public expect class UtcOffset {
7575
*/
7676
public val ZERO: UtcOffset
7777

78+
/**
79+
* Constructs a [UtcOffset] from hours, minutes, and seconds components
80+
* or returns `null` if a value is out of range or invalid.
81+
*
82+
* All components must have the same sign.
83+
* Otherwise, `null` will be returned.
84+
*
85+
* The bounds are checked: it is invalid to pass something other than `±[0; 59]` as the number of seconds or minutes;
86+
* `null` will be returned if this rule is violated.
87+
* For example, `UtcOffset.createOrNull(hours = 3, minutes = 61)` returns `null`.
88+
*
89+
* However, the non-null component of the highest order can exceed these bounds,
90+
* for example, `UtcOffset.createOrNull(minutes = 241)` and `UtcOffset.createOrNull(seconds = -3600)` are both valid.
91+
*
92+
* @return the [UtcOffset] with the specified components, or `null` if the components are invalid
93+
* or the resulting `UtcOffset` value is outside of range `±18:00`.
94+
* @sample kotlinx.datetime.test.samples.UtcOffsetSamples.createOrNull
95+
*/
96+
public fun createOrNull(hours: Int? = null, minutes: Int? = null, seconds: Int? = null): UtcOffset?
97+
7898
/**
7999
* A shortcut for calling [DateTimeFormat.parse].
80100
*

core/common/test/LocalDateTimeTest.kt

+59
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,65 @@ class LocalDateTimeTest {
130130
assertFailsWith<IllegalArgumentException> { localTime(0, 0, 0, 1_000_000_000) }
131131
}
132132

133+
@Test
134+
fun createOrNull() {
135+
// Test createOrNull with month number
136+
val validDateTime1 = LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 500_000_000)
137+
assertNotNull(validDateTime1)
138+
assertEquals(2020, validDateTime1!!.year)
139+
assertEquals(1, validDateTime1.month.number)
140+
assertEquals(1, validDateTime1.day)
141+
assertEquals(12, validDateTime1.hour)
142+
assertEquals(30, validDateTime1.minute)
143+
assertEquals(45, validDateTime1.second)
144+
assertEquals(500_000_000, validDateTime1.nanosecond)
145+
146+
// Test createOrNull with Month enum
147+
val validDateTime2 = LocalDateTime.createOrNull(2020, Month.FEBRUARY, 29, 23, 59, 59, 999_999_999)
148+
assertNotNull(validDateTime2)
149+
assertEquals(2020, validDateTime2!!.year)
150+
assertEquals(Month.FEBRUARY, validDateTime2.month)
151+
assertEquals(29, validDateTime2.day)
152+
assertEquals(23, validDateTime2.hour)
153+
assertEquals(59, validDateTime2.minute)
154+
assertEquals(59, validDateTime2.second)
155+
assertEquals(999_999_999, validDateTime2.nanosecond)
156+
157+
// Test createOrNull with LocalDate and LocalTime
158+
val date = LocalDate(2020, 1, 1)
159+
val time = LocalTime(12, 30, 45, 500_000_000)
160+
val validDateTime3 = LocalDateTime.createOrNull(date, time)
161+
assertNotNull(validDateTime3)
162+
assertEquals(date, validDateTime3!!.date)
163+
assertEquals(time, validDateTime3.time)
164+
165+
// Test invalid date components
166+
assertNull(LocalDateTime.createOrNull(2021, 2, 29, 12, 30)) // Invalid day (not a leap year)
167+
assertNull(LocalDateTime.createOrNull(2020, 13, 1, 12, 30)) // Invalid month
168+
assertNull(LocalDateTime.createOrNull(2020, 0, 1, 12, 30)) // Invalid month
169+
assertNull(LocalDateTime.createOrNull(2020, 1, 32, 12, 30)) // Invalid day
170+
assertNull(LocalDateTime.createOrNull(2020, 1, 0, 12, 30)) // Invalid day
171+
172+
// Test invalid time components
173+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, -1, 30)) // Invalid hour
174+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 24, 30)) // Invalid hour
175+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, -1)) // Invalid minute
176+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 60)) // Invalid minute
177+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, -1)) // Invalid second
178+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 60)) // Invalid second
179+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, -1)) // Invalid nanosecond
180+
assertNull(LocalDateTime.createOrNull(2020, 1, 1, 12, 30, 45, 1_000_000_000)) // Invalid nanosecond
181+
182+
// Test with Month enum
183+
assertNull(LocalDateTime.createOrNull(2021, Month.FEBRUARY, 29, 12, 30)) // Invalid day (not a leap year)
184+
assertNull(LocalDateTime.createOrNull(2020, Month.FEBRUARY, 30, 12, 30)) // Invalid day for February
185+
186+
// Test with null LocalDate or LocalTime
187+
assertNull(LocalDateTime.createOrNull(null, time))
188+
assertNull(LocalDateTime.createOrNull(date, null))
189+
assertNull(LocalDateTime.createOrNull(null, null))
190+
}
191+
133192
}
134193

135194
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) {

core/common/test/LocalTimeTest.kt

+35
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,41 @@ class LocalTimeTest {
8787
assertFailsWith<IllegalArgumentException> { LocalTime(0, 0, 0, 1_000_000_000) }
8888
}
8989

90+
@Test
91+
fun createOrNull() {
92+
// Valid times should be created correctly
93+
val validTime1 = LocalTime.createOrNull(12, 30, 45, 500_000_000)
94+
assertNotNull(validTime1)
95+
assertEquals(12, validTime1!!.hour)
96+
assertEquals(30, validTime1.minute)
97+
assertEquals(45, validTime1.second)
98+
assertEquals(500_000_000, validTime1.nanosecond)
99+
100+
val validTime2 = LocalTime.createOrNull(0, 0)
101+
assertNotNull(validTime2)
102+
assertEquals(0, validTime2!!.hour)
103+
assertEquals(0, validTime2.minute)
104+
assertEquals(0, validTime2.second)
105+
assertEquals(0, validTime2.nanosecond)
106+
107+
val validTime3 = LocalTime.createOrNull(23, 59, 59, 999_999_999)
108+
assertNotNull(validTime3)
109+
assertEquals(23, validTime3!!.hour)
110+
assertEquals(59, validTime3.minute)
111+
assertEquals(59, validTime3.second)
112+
assertEquals(999_999_999, validTime3.nanosecond)
113+
114+
// Invalid times should return null
115+
assertNull(LocalTime.createOrNull(-1, 0))
116+
assertNull(LocalTime.createOrNull(24, 0))
117+
assertNull(LocalTime.createOrNull(0, -1))
118+
assertNull(LocalTime.createOrNull(0, 60))
119+
assertNull(LocalTime.createOrNull(0, 0, -1))
120+
assertNull(LocalTime.createOrNull(0, 0, 60))
121+
assertNull(LocalTime.createOrNull(0, 0, 0, -1))
122+
assertNull(LocalTime.createOrNull(0, 0, 0, 1_000_000_000))
123+
}
124+
90125
@Test
91126
fun fromNanosecondOfDay() {
92127
val data = mapOf(

core/common/test/UtcOffsetTest.kt

+40
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,44 @@ class UtcOffsetTest {
161161
assertIs<FixedOffsetTimeZone>(timeZone)
162162
assertEquals(offset, timeZone.offset)
163163
}
164+
165+
@Test
166+
fun createOrNull() {
167+
// Valid cases
168+
for (totalSeconds in offsetSecondsRange) {
169+
val hours = totalSeconds / (60 * 60)
170+
val totalMinutes = totalSeconds / 60
171+
val minutes = totalMinutes % 60
172+
val seconds = totalSeconds % 60
173+
val offset = UtcOffset.createOrNull(hours, minutes, seconds)
174+
val offsetSeconds = UtcOffset.createOrNull(seconds = totalSeconds)
175+
val offsetMinutes = UtcOffset.createOrNull(minutes = totalMinutes, seconds = seconds)
176+
assertNotNull(offset)
177+
assertNotNull(offsetSeconds)
178+
assertNotNull(offsetMinutes)
179+
assertEquals(totalSeconds, offset!!.totalSeconds)
180+
assertEquals(offset, offsetMinutes)
181+
assertEquals(offset, offsetSeconds)
182+
}
183+
184+
// Invalid cases
185+
assertNull(UtcOffset.createOrNull(hours = -19))
186+
assertNull(UtcOffset.createOrNull(hours = +19))
187+
assertNull(UtcOffset.createOrNull(hours = -18, minutes = -1))
188+
assertNull(UtcOffset.createOrNull(hours = -18, seconds = -1))
189+
assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1))
190+
assertNull(UtcOffset.createOrNull(hours = +18, seconds = +1))
191+
assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.first - 1))
192+
assertNull(UtcOffset.createOrNull(seconds = offsetSecondsRange.last + 1))
193+
assertNull(UtcOffset.createOrNull(hours = 0, minutes = 60))
194+
assertNull(UtcOffset.createOrNull(hours = 0, seconds = -60))
195+
assertNull(UtcOffset.createOrNull(minutes = 90, seconds = 90))
196+
assertNull(UtcOffset.createOrNull(minutes = 0, seconds = 90))
197+
assertNull(UtcOffset.createOrNull(hours = +1, minutes = -1))
198+
assertNull(UtcOffset.createOrNull(hours = +1, seconds = -1))
199+
assertNull(UtcOffset.createOrNull(hours = -1, minutes = +1))
200+
assertNull(UtcOffset.createOrNull(hours = -1, seconds = +1))
201+
assertNull(UtcOffset.createOrNull(minutes = +1, seconds = -1))
202+
assertNull(UtcOffset.createOrNull(minutes = -1, seconds = +1))
203+
}
164204
}

core/common/test/samples/LocalDateTimeSamples.kt

+39
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,45 @@ class LocalDateTimeSamples {
183183
check(LocalDate(2024, 2, 15).atTime(16, 48, 15, 120_000_000).toString() == "2024-02-15T16:48:15.120")
184184
}
185185

186+
@Test
187+
fun createOrNull() {
188+
// Constructing a LocalDateTime value using `createOrNull`
189+
val dateTime = LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 999_999_999)
190+
// For valid values, `createOrNull` is equivalent to the constructor
191+
check(dateTime == LocalDateTime(2024, 2, 15, 16, 48, 59, 999_999_999))
192+
// If a value can not be constructed, null is returned
193+
check(LocalDateTime.createOrNull(2024, 2, 31, 16, 48) == null) // Invalid day
194+
check(LocalDateTime.createOrNull(2024, 2, 15, 24, 48) == null) // Invalid hour
195+
check(LocalDateTime.createOrNull(2024, 2, 15, 16, 60) == null) // Invalid minute
196+
check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 60) == null) // Invalid second
197+
check(LocalDateTime.createOrNull(2024, 2, 15, 16, 48, 59, 1_000_000_000) == null) // Invalid nanosecond
198+
}
199+
200+
@Test
201+
fun createOrNullWithMonth() {
202+
// Constructing a LocalDateTime value using `createOrNull` with Month enum
203+
val dateTime = LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999)
204+
// For valid values, `createOrNull` is equivalent to the constructor
205+
check(dateTime == LocalDateTime(2024, Month.FEBRUARY, 15, 16, 48, 59, 999_999_999))
206+
// If a value can not be constructed, null is returned
207+
check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 31, 16, 48) == null) // Invalid day
208+
check(LocalDateTime.createOrNull(2024, Month.FEBRUARY, 15, 24, 48) == null) // Invalid hour
209+
}
210+
211+
@Test
212+
fun createOrNullFromDateAndTime() {
213+
// Constructing a LocalDateTime value using `createOrNull` with LocalDate and LocalTime
214+
val date = LocalDate(2024, 2, 15)
215+
val time = LocalTime(16, 48)
216+
val dateTime = LocalDateTime.createOrNull(date, time)
217+
// For valid values, `createOrNull` is equivalent to the constructor
218+
check(dateTime == LocalDateTime(date, time))
219+
// If either date or time is null, null is returned
220+
check(LocalDateTime.createOrNull(null, time) == null)
221+
check(LocalDateTime.createOrNull(date, null) == null)
222+
check(LocalDateTime.createOrNull(null, null) == null)
223+
}
224+
186225
@Test
187226
fun formatting() {
188227
// Formatting LocalDateTime values using predefined and custom formats

core/common/test/samples/LocalTimeSamples.kt

+13
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,19 @@ class LocalTimeSamples {
133133
check(timeWithoutSeconds.nanosecond == 0)
134134
}
135135

136+
@Test
137+
fun createOrNull() {
138+
// Constructing a LocalTime value using `createOrNull`
139+
val time = LocalTime.createOrNull(8, 30, 15, 123_456_789)
140+
// For valid values, `createOrNull` is equivalent to the constructor
141+
check(time == LocalTime(8, 30, 15, 123_456_789))
142+
// If a value can not be constructed, null is returned
143+
check(LocalTime.createOrNull(24, 30) == null)
144+
check(LocalTime.createOrNull(8, 60) == null)
145+
check(LocalTime.createOrNull(8, 30, 60) == null)
146+
check(LocalTime.createOrNull(8, 30, 15, 1_000_000_000) == null)
147+
}
148+
136149
@Test
137150
fun hour() {
138151
// Getting the number of whole hours shown on the clock

core/common/test/samples/UtcOffsetSamples.kt

+18
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@ class UtcOffsetSamples {
108108
}
109109
}
110110

111+
@Test
112+
fun createOrNull() {
113+
// Using the createOrNull function to create UtcOffset values
114+
val validOffset = UtcOffset.createOrNull(hours = 3, minutes = 30)
115+
check(validOffset != null)
116+
check(validOffset!!.totalSeconds == 12600)
117+
118+
// For valid inputs, createOrNull returns a non-null value
119+
check(UtcOffset.createOrNull(seconds = -3600) == UtcOffset(hours = -1))
120+
121+
// For invalid inputs, createOrNull returns null
122+
check(UtcOffset.createOrNull(hours = 1, minutes = 60) == null)
123+
// Since `hours` is positive, `minutes` must also be positive
124+
check(UtcOffset.createOrNull(hours = 1, minutes = -30) == null)
125+
// The total offset must be within the range ±18:00
126+
check(UtcOffset.createOrNull(hours = 19) == null)
127+
}
128+
111129
class Formats {
112130
@Test
113131
fun isoBasic() {

0 commit comments

Comments
 (0)