diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index cacd8d70..6e6daaee 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -485,6 +485,7 @@ public final class kotlinx/datetime/LocalDateTime$Companion { public final class kotlinx/datetime/LocalDateTime$Formats { public static final field INSTANCE Lkotlinx/datetime/LocalDateTime$Formats; public final fun getISO ()Lkotlinx/datetime/format/DateTimeFormat; + public final fun getISO_BASIC ()Lkotlinx/datetime/format/DateTimeFormat; } public final class kotlinx/datetime/LocalDateTimeKt { @@ -530,6 +531,7 @@ public final class kotlinx/datetime/LocalTime$Companion { public final class kotlinx/datetime/LocalTime$Formats { public static final field INSTANCE Lkotlinx/datetime/LocalTime$Formats; public final fun getISO ()Lkotlinx/datetime/format/DateTimeFormat; + public final fun getISO_BASIC ()Lkotlinx/datetime/format/DateTimeFormat; } public final class kotlinx/datetime/LocalTimeKt { diff --git a/core/api/kotlinx-datetime.klib.api b/core/api/kotlinx-datetime.klib.api index 4a20aa64..fff2417f 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -513,6 +513,8 @@ final class kotlinx.datetime/LocalDateTime : kotlin/Comparable(): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDateTime.Formats.ISO.|(){}[0] + final val ISO_BASIC // kotlinx.datetime/LocalDateTime.Formats.ISO_BASIC|{}ISO_BASIC[0] + final fun (): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalDateTime.Formats.ISO_BASIC.|(){}[0] } } @@ -549,6 +551,8 @@ final class kotlinx.datetime/LocalTime : kotlin/Comparable(): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalTime.Formats.ISO.|(){}[0] + final val ISO_BASIC // kotlinx.datetime/LocalTime.Formats.ISO_BASIC|{}ISO_BASIC[0] + final fun (): kotlinx.datetime.format/DateTimeFormat // kotlinx.datetime/LocalTime.Formats.ISO_BASIC.|(){}[0] } } diff --git a/core/common/src/LocalDateTime.kt b/core/common/src/LocalDateTime.kt index d3db077b..5fe605a6 100644 --- a/core/common/src/LocalDateTime.kt +++ b/core/common/src/LocalDateTime.kt @@ -194,6 +194,25 @@ public expect class LocalDateTime : Comparable { * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.Formats.iso */ public val ISO: DateTimeFormat + + /** + * ISO 8601 basic format. + * + * Examples of datetime in ISO 8601 format: + * - `20200830T1843` + * - `+120200830T184300` + * - `00000830T184300.5` + * - `-00010830T184300.123456789` + * + * When formatting, seconds always included. + * Fractional parts of the second are included if non-zero. + * + * See ISO-8601-1:2019, 5.4.2.1a), the version without the offset, together with + * [LocalDate.Formats.ISO_BASIC] and [LocalTime.Formats.ISO_BASIC]. + * + * @sample kotlinx.datetime.test.samples.LocalDateTimeSamples.Formats.basicIso + */ + public val ISO_BASIC: DateTimeFormat } /** diff --git a/core/common/src/LocalTime.kt b/core/common/src/LocalTime.kt index 934a8e1d..33e20267 100644 --- a/core/common/src/LocalTime.kt +++ b/core/common/src/LocalTime.kt @@ -197,6 +197,19 @@ public expect class LocalTime : Comparable { * [kotlinx.datetime.format.DateTimeFormat] for [LocalTime] values. */ public object Formats { + /** + * ISO 8601 basic format. + * + * Examples: `T1234`, `T123456`, `T123456.789`, `T123456.1234`. + * + * When formatting, seconds are always included, even if they are zero. + * Fractional parts of the second are included if non-zero. + * + * @see LocalDateTime.Formats.ISO_BASIC + * @sample kotlinx.datetime.test.samples.LocalTimeSamples.Formats.isoBasic + */ + public val ISO_BASIC: DateTimeFormat + /** * ISO 8601 extended format. * @@ -215,6 +228,7 @@ public expect class LocalTime : Comparable { * We *forbid* using the time designator `T` to allow for a predictable composition of formats: * see the note at the end of rule 5.3.5. * + * @see LocalDateTime.Formats.ISO * @sample kotlinx.datetime.test.samples.LocalTimeSamples.Formats.iso */ public val ISO: DateTimeFormat diff --git a/core/common/src/format/LocalDateTimeFormat.kt b/core/common/src/format/LocalDateTimeFormat.kt index 8e97ea60..51511606 100644 --- a/core/common/src/format/LocalDateTimeFormat.kt +++ b/core/common/src/format/LocalDateTimeFormat.kt @@ -82,4 +82,11 @@ internal val ISO_DATETIME by lazy { } } +internal val ISO_DATETIME_BASIC by lazy { + LocalDateTimeFormat.build { + date(ISO_DATE_BASIC) + time(ISO_TIME_BASIC) + } +} + private val emptyIncompleteLocalDateTime = IncompleteLocalDateTime() diff --git a/core/common/src/format/LocalTimeFormat.kt b/core/common/src/format/LocalTimeFormat.kt index d1854e12..b8df2334 100644 --- a/core/common/src/format/LocalTimeFormat.kt +++ b/core/common/src/format/LocalTimeFormat.kt @@ -279,6 +279,23 @@ internal class LocalTimeFormat(override val actualFormat: CachedFormatStructure< } // these are constants so that the formats are not recreated every time they are used +internal val ISO_TIME_BASIC by lazy { + LocalTimeFormat.build { + alternativeParsing({ char('t') }) { char('T') } + hour() + minute() + alternativeParsing({ + // intentionally empty + }) { + second() + optional { + char('.') + secondFraction(1, 9) + } + } + } +} + internal val ISO_TIME by lazy { LocalTimeFormat.build { hour() diff --git a/core/common/test/format/LocalDateTimeFormatTest.kt b/core/common/test/format/LocalDateTimeFormatTest.kt index 32ab5596..87d47a32 100644 --- a/core/common/test/format/LocalDateTimeFormatTest.kt +++ b/core/common/test/format/LocalDateTimeFormatTest.kt @@ -224,6 +224,41 @@ class LocalDateTimeFormatTest { test(dateTimes, LocalDateTime.Formats.ISO) } + @Test + fun testBasicIso() { + val dateTimes = buildMap>> { + put(LocalDateTime(2008, 7, 5, 0, 0, 0, 0), ("20080705T000000" to setOf("20080705t000000"))) + put(LocalDateTime(2007, 12, 31, 1, 0, 0, 0), ("20071231T010000" to setOf("20071231t010000"))) + put(LocalDateTime(999, 11, 30, 23, 0, 0, 0), ("09991130T230000" to setOf())) + put(LocalDateTime(-1, 1, 2, 0, 1, 0, 0), ("-00010102T000100" to setOf())) + put(LocalDateTime(9999, 10, 31, 12, 30, 0, 0), ("99991031T123000" to setOf())) + put(LocalDateTime(-9999, 9, 30, 23, 59, 0, 0), ("-99990930T235900" to setOf())) + put(LocalDateTime(10000, 8, 1, 0, 0, 1, 0), ("+100000801T000001" to setOf())) + put(LocalDateTime(-10000, 7, 1, 0, 0, 59, 0), ("-100000701T000059" to setOf())) + put(LocalDateTime(123456, 6, 1, 13, 44, 0, 0), ("+1234560601T134400" to setOf())) + put(LocalDateTime(-123456, 5, 1, 13, 44, 0, 0), ("-1234560501T134400" to setOf())) + put(LocalDateTime(123456, 6, 1, 0, 0, 0, 100000000), ("+1234560601T000000.1" to setOf("+1234560601T000000.10", "+1234560601T000000.100"))) + put(LocalDateTime(-123456, 5, 1, 0, 0, 0, 10000000), ("-1234560501T000000.01" to setOf("-1234560501T000000.010"))) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 1000000), ("20220102T000000.001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 100000), ("20220102T000000.0001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 10000), ("20220102T000000.00001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 1000), ("20220102T000000.000001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 100), ("20220102T000000.0000001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 10), ("20220102T000000.00000001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 1), ("20220102T000000.000000001" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 999999999), ("20220102T000000.999999999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 99999999), ("20220102T000000.099999999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 9999999), ("20220102T000000.009999999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 999999), ("20220102T000000.000999999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 99999), ("20220102T000000.000099999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 9999), ("20220102T000000.000009999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 999), ("20220102T000000.000000999" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 99), ("20220102T000000.000000099" to setOf())) + put(LocalDateTime(2022, 1, 2, 0, 0, 0, 9), ("20220102T000000.000000009" to setOf())) + } + test(dateTimes, LocalDateTime.Formats.ISO_BASIC) + } + @Test fun testDoc() { val dateTime = LocalDateTime(2020, 8, 30, 18, 43, 13, 0) diff --git a/core/common/test/samples/LocalTimeSamples.kt b/core/common/test/samples/LocalTimeSamples.kt index 3bbb19e8..28b5fdbe 100644 --- a/core/common/test/samples/LocalTimeSamples.kt +++ b/core/common/test/samples/LocalTimeSamples.kt @@ -280,6 +280,20 @@ class LocalTimeSamples { } class Formats { + @Test + fun isoBasic() { + // Parsing and formatting LocalTime values using the ISO_BASIC format + val timeWithNanoseconds = LocalTime(hour = 8, minute = 30, second = 15, nanosecond = 160_000_000) + val timeWithSeconds = LocalTime(hour = 8, minute = 30, second = 15) + val timeWithoutSeconds = LocalTime(hour = 8, minute = 30) + check(LocalTime.Formats.ISO_BASIC.parse("T083015.16") == timeWithNanoseconds) + check(LocalTime.Formats.ISO_BASIC.parse("T083015") == timeWithSeconds) + check(LocalTime.Formats.ISO_BASIC.parse("T0830") == timeWithoutSeconds) + check(LocalTime.Formats.ISO_BASIC.format(timeWithNanoseconds) == "T083015.16") + check(LocalTime.Formats.ISO_BASIC.format(timeWithSeconds) == "T083015") + check(LocalTime.Formats.ISO_BASIC.format(timeWithoutSeconds) == "T083000") + } + @Test fun iso() { // Parsing and formatting LocalTime values using the ISO format diff --git a/core/commonKotlin/src/LocalDateTime.kt b/core/commonKotlin/src/LocalDateTime.kt index 4a7e2385..3ec1c59f 100644 --- a/core/commonKotlin/src/LocalDateTime.kt +++ b/core/commonKotlin/src/LocalDateTime.kt @@ -33,6 +33,7 @@ public actual constructor(public actual val date: LocalDate, public actual val t public actual object Formats { public actual val ISO: DateTimeFormat = ISO_DATETIME + public actual val ISO_BASIC: DateTimeFormat = ISO_DATETIME_BASIC } public actual constructor(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) : diff --git a/core/commonKotlin/src/LocalTime.kt b/core/commonKotlin/src/LocalTime.kt index 42f5c017..5fdf4872 100644 --- a/core/commonKotlin/src/LocalTime.kt +++ b/core/commonKotlin/src/LocalTime.kt @@ -84,6 +84,7 @@ public actual class LocalTime actual constructor( } public actual object Formats { + public actual val ISO_BASIC: DateTimeFormat get() = ISO_TIME_BASIC public actual val ISO: DateTimeFormat get() = ISO_TIME } diff --git a/core/jvm/src/LocalDateTimeJvm.kt b/core/jvm/src/LocalDateTimeJvm.kt index adf0c6ac..565a33d6 100644 --- a/core/jvm/src/LocalDateTimeJvm.kt +++ b/core/jvm/src/LocalDateTimeJvm.kt @@ -114,6 +114,7 @@ public actual class LocalDateTime internal constructor( public actual object Formats { public actual val ISO: DateTimeFormat = ISO_DATETIME + public actual val ISO_BASIC: DateTimeFormat = ISO_DATETIME_BASIC } private fun readObject(ois: java.io.ObjectInputStream): Unit = throw java.io.InvalidObjectException( diff --git a/core/jvm/src/LocalTimeJvm.kt b/core/jvm/src/LocalTimeJvm.kt index fbd54838..833d468a 100644 --- a/core/jvm/src/LocalTimeJvm.kt +++ b/core/jvm/src/LocalTimeJvm.kt @@ -92,6 +92,7 @@ public actual class LocalTime internal constructor( } public actual object Formats { + public actual val ISO_BASIC: DateTimeFormat get() = ISO_TIME_BASIC public actual val ISO: DateTimeFormat get() = ISO_TIME }