diff --git a/core/api/kotlinx-datetime.api b/core/api/kotlinx-datetime.api index e5429a44..f950edbe 100644 --- a/core/api/kotlinx-datetime.api +++ b/core/api/kotlinx-datetime.api @@ -449,6 +449,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 { @@ -494,6 +495,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 2fb3de66..ef5ca19e 100644 --- a/core/api/kotlinx-datetime.klib.api +++ b/core/api/kotlinx-datetime.klib.api @@ -444,6 +444,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] } } @@ -480,6 +482,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 8e5085ca..be97f045 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 are included, only if they are non-zero. + * 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 7cf01459..6092b5b5 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..bd281c2c 100644 --- a/core/common/src/format/LocalTimeFormat.kt +++ b/core/common/src/format/LocalTimeFormat.kt @@ -279,6 +279,25 @@ 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 + }) { + optional { + 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..5ab708df 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), ("20080705T0000" to setOf("20080705T0000"))) + put(LocalDateTime(2007, 12, 31, 1, 0, 0, 0), ("20071231T0100" to setOf("20071231t010000"))) + put(LocalDateTime(999, 11, 30, 23, 0, 0, 0), ("09991130T2300" to setOf())) + put(LocalDateTime(-1, 1, 2, 0, 1, 0, 0), ("-00010102T0001" to setOf())) + put(LocalDateTime(9999, 10, 31, 12, 30, 0, 0), ("99991031T1230" to setOf())) + put(LocalDateTime(-9999, 9, 30, 23, 59, 0, 0), ("-99990930T2359" 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), ("+1234560601T1344" to setOf())) + put(LocalDateTime(-123456, 5, 1, 13, 44, 0, 0), ("-1234560501T1344" 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..7780a5b3 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) == "T0830") + } + @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 9375ae91..c23447fb 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 28ebd78d..fe3077e8 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 235be907..c6c419cb 100644 --- a/core/jvm/src/LocalDateTimeJvm.kt +++ b/core/jvm/src/LocalDateTimeJvm.kt @@ -110,6 +110,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 writeReplace(): Any = Ser(Ser.DATE_TIME_TAG, this) diff --git a/core/jvm/src/LocalTimeJvm.kt b/core/jvm/src/LocalTimeJvm.kt index 98f42011..dd11650a 100644 --- a/core/jvm/src/LocalTimeJvm.kt +++ b/core/jvm/src/LocalTimeJvm.kt @@ -88,6 +88,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 }