Skip to content

Commit 13fd441

Browse files
committed
Fix Instant.parse allowing one to omit seconds on the JVM and JS
Fixes #369
1 parent cff3fdd commit 13fd441

File tree

4 files changed

+23
-33
lines changed

4 files changed

+23
-33
lines changed

core/common/test/InstantTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class InstantTest {
8686

8787
assertInvalidFormat { Instant.parse("1970-01-01T23:59:60Z")}
8888
assertInvalidFormat { Instant.parse("1970-01-01T24:00:00Z")}
89+
assertInvalidFormat { Instant.parse("1970-01-01T23:59Z")}
8990
assertInvalidFormat { Instant.parse("x") }
9091
assertInvalidFormat { Instant.parse("12020-12-31T23:59:59.000000000Z") }
9192
// this string represents an Instant that is currently larger than Instant.MAX any of the implementations:

core/commonJs/src/Instant.kt

+6-15
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,12 @@ public actual class Instant internal constructor(internal val value: jtInstant)
7474
if (epochMilliseconds > 0) MAX else MIN
7575
}
7676

77-
public actual fun parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): Instant =
78-
if (format === DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET) {
79-
try {
80-
Instant(jsTry { jtOffsetDateTime.parse(fixOffsetRepresentation(input.toString())) }.toInstant())
81-
} catch (e: Throwable) {
82-
if (e.isJodaDateTimeParseException()) throw DateTimeFormatException(e)
83-
throw e
84-
}
85-
} else {
86-
try {
87-
format.parse(input).toInstantUsingOffset()
88-
} catch (e: IllegalArgumentException) {
89-
throw DateTimeFormatException("Failed to parse an instant from '$input'", e)
90-
}
91-
}
77+
public actual fun parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): Instant = try {
78+
// This format is not supported properly by Joda-Time, so we can't delegate to it.
79+
format.parse(input).toInstantUsingOffset()
80+
} catch (e: IllegalArgumentException) {
81+
throw DateTimeFormatException("Failed to parse an instant from '$input'", e)
82+
}
9283

9384
@Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN)
9485
public fun parse(isoString: String): Instant = parse(input = isoString)

core/js/test/JsConverterTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import kotlin.test.*
1212
class JsConverterTest {
1313
@Test
1414
fun toJSDateTest() {
15-
val releaseInstant = "2016-02-15T00:00Z".toInstant()
15+
val releaseInstant = Instant.parse("2016-02-15T00:00:00Z")
1616
val releaseDate = releaseInstant.toJSDate()
1717
assertEquals(2016, releaseDate.getUTCFullYear())
1818
assertEquals(1, releaseDate.getUTCMonth())
@@ -23,7 +23,7 @@ class JsConverterTest {
2323
fun toInstantTest() {
2424
val kotlinReleaseEpochMilliseconds = 1455494400000
2525
val releaseDate = Date(milliseconds = kotlinReleaseEpochMilliseconds)
26-
val releaseInstant = "2016-02-15T00:00Z".toInstant()
26+
val releaseInstant = Instant.parse("2016-02-15T00:00:00Z")
2727
assertEquals(releaseInstant, releaseDate.toKotlinInstant())
2828
}
2929
}

core/jvm/src/Instant.kt

+14-16
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import kotlinx.datetime.internal.*
1212
import kotlinx.datetime.serializers.InstantIso8601Serializer
1313
import kotlinx.serialization.Serializable
1414
import java.time.DateTimeException
15-
import java.time.format.DateTimeParseException
16-
import java.time.temporal.ChronoUnit
15+
import java.time.format.*
16+
import java.time.temporal.*
1717
import kotlin.time.*
1818
import kotlin.time.Duration.Companion.nanoseconds
1919
import kotlin.time.Duration.Companion.seconds
@@ -67,20 +67,18 @@ public actual class Instant internal constructor(internal val value: jtInstant)
6767
public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant =
6868
Instant(jtInstant.ofEpochMilli(epochMilliseconds))
6969

70-
public actual fun parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): Instant =
71-
if (format === DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET) {
72-
try {
73-
Instant(jtOffsetDateTime.parse(fixOffsetRepresentation(input)).toInstant())
74-
} catch (e: DateTimeParseException) {
75-
throw DateTimeFormatException(e)
76-
}
77-
} else {
78-
try {
79-
format.parse(input).toInstantUsingOffset()
80-
} catch (e: IllegalArgumentException) {
81-
throw DateTimeFormatException("Failed to parse an instant from '$input'", e)
82-
}
83-
}
70+
public actual fun parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): Instant = try {
71+
/**
72+
* Can't use built-in Java Time's handling of `Instant.parse` because it supports 24:00:00 and
73+
* 23:59:60, and also doesn't support non-`Z` UTC offsets on older JDKs.
74+
* Can't use custom Java Time's formats because Java 8 doesn't support the UTC offset format with
75+
* optional minutes and seconds and `:` between them:
76+
* https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendOffset-java.lang.String-java.lang.String-
77+
*/
78+
format.parse(input).toInstantUsingOffset()
79+
} catch (e: IllegalArgumentException) {
80+
throw DateTimeFormatException("Failed to parse an instant from '$input'", e)
81+
}
8482

8583
@Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN)
8684
public fun parse(isoString: String): Instant = parse(input = isoString)

0 commit comments

Comments
 (0)