diff --git a/core/common/src/format/DateTimeComponents.kt b/core/common/src/format/DateTimeComponents.kt index 436fa499..79917dee 100644 --- a/core/common/src/format/DateTimeComponents.kt +++ b/core/common/src/format/DateTimeComponents.kt @@ -301,9 +301,9 @@ public class DateTimeComponents internal constructor(internal val contents: Date * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.dayOfWeek */ public var dayOfWeek: DayOfWeek? - get() = contents.date.isoDayOfWeek?.let { DayOfWeek(it) } + get() = contents.date.dayOfWeek?.let { DayOfWeek(it) } set(value) { - contents.date.isoDayOfWeek = value?.isoDayNumber + contents.date.dayOfWeek = value?.isoDayNumber } /** @@ -366,28 +366,28 @@ public class DateTimeComponents internal constructor(internal val contents: Date * True if the offset is negative. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.offset */ - public var offsetIsNegative: Boolean? by contents.offset::isNegative + public var offsetIsNegative: Boolean? by contents.offset::offsetIsNegative /** * The total amount of full hours in the UTC offset, in the range [0; 18]. * @throws IllegalArgumentException during assignment if the value is outside the `0..99` range. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.offset */ - public var offsetHours: Int? by TwoDigitNumber(contents.offset::totalHoursAbs) + public var offsetHours: Int? by TwoDigitNumber(contents.offset::offsetHours) /** * The amount of minutes that don't add to a whole hour in the UTC offset, in the range [0; 59]. * @throws IllegalArgumentException during assignment if the value is outside the `0..99` range. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.offset */ - public var offsetMinutesOfHour: Int? by TwoDigitNumber(contents.offset::minutesOfHour) + public var offsetMinutesOfHour: Int? by TwoDigitNumber(contents.offset::offsetMinutesOfHour) /** * The amount of seconds that don't add to a whole minute in the UTC offset, in the range [0; 59]. * @throws IllegalArgumentException during assignment if the value is outside the `0..99` range. * @sample kotlinx.datetime.test.samples.format.DateTimeComponentsSamples.offset */ - public var offsetSecondsOfMinute: Int? by TwoDigitNumber(contents.offset::secondsOfMinute) + public var offsetSecondsOfMinute: Int? by TwoDigitNumber(contents.offset::offsetSecondsOfMinute) /** * The timezone identifier, for example, "Europe/Berlin". diff --git a/core/common/src/format/LocalDateFormat.kt b/core/common/src/format/LocalDateFormat.kt index 0104f8b7..b7162201 100644 --- a/core/common/src/format/LocalDateFormat.kt +++ b/core/common/src/format/LocalDateFormat.kt @@ -200,7 +200,7 @@ internal interface DateFieldContainer { var year: Int? var monthNumber: Int? var dayOfMonth: Int? - var isoDayOfWeek: Int? + var dayOfWeek: Int? var dayOfYear: Int? } @@ -208,7 +208,7 @@ private object DateFields { val year = GenericFieldSpec(PropertyAccessor(DateFieldContainer::year)) val month = UnsignedFieldSpec(PropertyAccessor(DateFieldContainer::monthNumber), minValue = 1, maxValue = 12) val dayOfMonth = UnsignedFieldSpec(PropertyAccessor(DateFieldContainer::dayOfMonth), minValue = 1, maxValue = 31) - val isoDayOfWeek = UnsignedFieldSpec(PropertyAccessor(DateFieldContainer::isoDayOfWeek), minValue = 1, maxValue = 7) + val isoDayOfWeek = UnsignedFieldSpec(PropertyAccessor(DateFieldContainer::dayOfWeek), minValue = 1, maxValue = 7) val dayOfYear = UnsignedFieldSpec(PropertyAccessor(DateFieldContainer::dayOfYear), minValue = 1, maxValue = 366) } @@ -219,7 +219,7 @@ internal class IncompleteLocalDate( override var year: Int? = null, override var monthNumber: Int? = null, override var dayOfMonth: Int? = null, - override var isoDayOfWeek: Int? = null, + override var dayOfWeek: Int? = null, override var dayOfYear: Int? = null, ) : DateFieldContainer, Copyable { fun toLocalDate(): LocalDate { @@ -253,7 +253,7 @@ internal class IncompleteLocalDate( } } } - isoDayOfWeek?.let { + dayOfWeek?.let { if (it != date.dayOfWeek.isoDayNumber) { throw DateTimeFormatException( "Can not create a LocalDate from the given input: " + @@ -268,25 +268,25 @@ internal class IncompleteLocalDate( year = date.year monthNumber = date.monthNumber dayOfMonth = date.dayOfMonth - isoDayOfWeek = date.dayOfWeek.isoDayNumber + dayOfWeek = date.dayOfWeek.isoDayNumber dayOfYear = date.dayOfYear } override fun copy(): IncompleteLocalDate = - IncompleteLocalDate(year, monthNumber, dayOfMonth, isoDayOfWeek, dayOfYear) + IncompleteLocalDate(year, monthNumber, dayOfMonth, dayOfWeek, dayOfYear) override fun equals(other: Any?): Boolean = other is IncompleteLocalDate && year == other.year && monthNumber == other.monthNumber && - dayOfMonth == other.dayOfMonth && isoDayOfWeek == other.isoDayOfWeek && dayOfYear == other.dayOfYear + dayOfMonth == other.dayOfMonth && dayOfWeek == other.dayOfWeek && dayOfYear == other.dayOfYear override fun hashCode(): Int = year.hashCode() * 923521 + monthNumber.hashCode() * 29791 + dayOfMonth.hashCode() * 961 + - isoDayOfWeek.hashCode() * 31 + + dayOfWeek.hashCode() * 31 + dayOfYear.hashCode() override fun toString(): String = - "${year ?: "??"}-${monthNumber ?: "??"}-${dayOfMonth ?: "??"} (day of week is ${isoDayOfWeek ?: "??"})" + "${year ?: "??"}-${monthNumber ?: "??"}-${dayOfMonth ?: "??"} (day of week is ${dayOfWeek ?: "??"})" } private class YearDirective(private val padding: Padding, private val isYearOfEra: Boolean = false) : diff --git a/core/common/src/format/LocalTimeFormat.kt b/core/common/src/format/LocalTimeFormat.kt index 05c1f0d7..d1854e12 100644 --- a/core/common/src/format/LocalTimeFormat.kt +++ b/core/common/src/format/LocalTimeFormat.kt @@ -43,7 +43,7 @@ private object TimeFields { val second = UnsignedFieldSpec(PropertyAccessor(TimeFieldContainer::second), minValue = 0, maxValue = 59, defaultValue = 0) val fractionOfSecond = - GenericFieldSpec(PropertyAccessor(TimeFieldContainer::fractionOfSecond), defaultValue = DecimalFraction(0, 9)) + GenericFieldSpec(PropertyAccessor(TimeFieldContainer::fractionOfSecond, "nanosecond"), defaultValue = DecimalFraction(0, 9)) val amPm = GenericFieldSpec(PropertyAccessor(TimeFieldContainer::amPm)) val hourOfAmPm = UnsignedFieldSpec(PropertyAccessor(TimeFieldContainer::hourOfAmPm), minValue = 1, maxValue = 12) } diff --git a/core/common/src/format/UtcOffsetFormat.kt b/core/common/src/format/UtcOffsetFormat.kt index 13106267..e8bc4777 100644 --- a/core/common/src/format/UtcOffsetFormat.kt +++ b/core/common/src/format/UtcOffsetFormat.kt @@ -11,10 +11,10 @@ import kotlinx.datetime.internal.format.parser.Copyable import kotlin.math.* internal interface UtcOffsetFieldContainer { - var isNegative: Boolean? - var totalHoursAbs: Int? - var minutesOfHour: Int? - var secondsOfMinute: Int? + var offsetIsNegative: Boolean? + var offsetHours: Int? + var offsetMinutesOfHour: Int? + var offsetSecondsOfMinute: Int? } internal interface AbstractWithOffsetBuilder : DateTimeFormatBuilder.WithUtcOffset { @@ -127,26 +127,26 @@ internal fun DateTimeFormatBuilder.WithUtcOffset.isoOffset( private object OffsetFields { private val sign = object : FieldSign { - override val isNegative = PropertyAccessor(UtcOffsetFieldContainer::isNegative) + override val isNegative = PropertyAccessor(UtcOffsetFieldContainer::offsetIsNegative) override fun isZero(obj: UtcOffsetFieldContainer): Boolean = - (obj.totalHoursAbs ?: 0) == 0 && (obj.minutesOfHour ?: 0) == 0 && (obj.secondsOfMinute ?: 0) == 0 + (obj.offsetHours ?: 0) == 0 && (obj.offsetMinutesOfHour ?: 0) == 0 && (obj.offsetSecondsOfMinute ?: 0) == 0 } val totalHoursAbs = UnsignedFieldSpec( - PropertyAccessor(UtcOffsetFieldContainer::totalHoursAbs), + PropertyAccessor(UtcOffsetFieldContainer::offsetHours), defaultValue = 0, minValue = 0, maxValue = 18, sign = sign, ) val minutesOfHour = UnsignedFieldSpec( - PropertyAccessor(UtcOffsetFieldContainer::minutesOfHour), + PropertyAccessor(UtcOffsetFieldContainer::offsetMinutesOfHour), defaultValue = 0, minValue = 0, maxValue = 59, sign = sign, ) val secondsOfMinute = UnsignedFieldSpec( - PropertyAccessor(UtcOffsetFieldContainer::secondsOfMinute), + PropertyAccessor(UtcOffsetFieldContainer::offsetSecondsOfMinute), defaultValue = 0, minValue = 0, maxValue = 59, @@ -155,39 +155,39 @@ private object OffsetFields { } internal class IncompleteUtcOffset( - override var isNegative: Boolean? = null, - override var totalHoursAbs: Int? = null, - override var minutesOfHour: Int? = null, - override var secondsOfMinute: Int? = null, + override var offsetIsNegative: Boolean? = null, + override var offsetHours: Int? = null, + override var offsetMinutesOfHour: Int? = null, + override var offsetSecondsOfMinute: Int? = null, ) : UtcOffsetFieldContainer, Copyable { fun toUtcOffset(): UtcOffset { - val sign = if (isNegative == true) -1 else 1 + val sign = if (offsetIsNegative == true) -1 else 1 return UtcOffset( - totalHoursAbs?.let { it * sign }, minutesOfHour?.let { it * sign }, secondsOfMinute?.let { it * sign } + offsetHours?.let { it * sign }, offsetMinutesOfHour?.let { it * sign }, offsetSecondsOfMinute?.let { it * sign } ) } fun populateFrom(offset: UtcOffset) { - isNegative = offset.totalSeconds < 0 + offsetIsNegative = offset.totalSeconds < 0 val totalSecondsAbs = offset.totalSeconds.absoluteValue - totalHoursAbs = totalSecondsAbs / 3600 - minutesOfHour = (totalSecondsAbs / 60) % 60 - secondsOfMinute = totalSecondsAbs % 60 + offsetHours = totalSecondsAbs / 3600 + offsetMinutesOfHour = (totalSecondsAbs / 60) % 60 + offsetSecondsOfMinute = totalSecondsAbs % 60 } override fun equals(other: Any?): Boolean = - other is IncompleteUtcOffset && isNegative == other.isNegative && totalHoursAbs == other.totalHoursAbs && - minutesOfHour == other.minutesOfHour && secondsOfMinute == other.secondsOfMinute + other is IncompleteUtcOffset && offsetIsNegative == other.offsetIsNegative && offsetHours == other.offsetHours && + offsetMinutesOfHour == other.offsetMinutesOfHour && offsetSecondsOfMinute == other.offsetSecondsOfMinute override fun hashCode(): Int = - isNegative.hashCode() + totalHoursAbs.hashCode() + minutesOfHour.hashCode() + secondsOfMinute.hashCode() + offsetIsNegative.hashCode() + offsetHours.hashCode() + offsetMinutesOfHour.hashCode() + offsetSecondsOfMinute.hashCode() override fun copy(): IncompleteUtcOffset = - IncompleteUtcOffset(isNegative, totalHoursAbs, minutesOfHour, secondsOfMinute) + IncompleteUtcOffset(offsetIsNegative, offsetHours, offsetMinutesOfHour, offsetSecondsOfMinute) override fun toString(): String = - "${isNegative?.let { if (it) "-" else "+" } ?: " "}${totalHoursAbs ?: "??"}:${minutesOfHour ?: "??"}:${secondsOfMinute ?: "??"}" + "${offsetIsNegative?.let { if (it) "-" else "+" } ?: " "}${offsetHours ?: "??"}:${offsetMinutesOfHour ?: "??"}:${offsetSecondsOfMinute ?: "??"}" } internal class UtcOffsetWholeHoursDirective(private val padding: Padding) : diff --git a/core/common/src/internal/format/FieldSpec.kt b/core/common/src/internal/format/FieldSpec.kt index cbb2d763..30caa717 100644 --- a/core/common/src/internal/format/FieldSpec.kt +++ b/core/common/src/internal/format/FieldSpec.kt @@ -30,9 +30,10 @@ internal interface Accessor: AssignableField { /** * An implementation of [Accessor] for a mutable property of an object. */ -internal class PropertyAccessor(private val property: KMutableProperty1): Accessor { - override val name: String get() = property.name - +internal class PropertyAccessor( + private val property: KMutableProperty1, + override val name: String = property.name +): Accessor { override fun trySetWithoutReassigning(container: Object, newValue: Field): Field? { val oldValue = property.get(container) return when { diff --git a/core/common/test/format/DateTimeComponentsFormatTest.kt b/core/common/test/format/DateTimeComponentsFormatTest.kt index 07973d75..7593f7b2 100644 --- a/core/common/test/format/DateTimeComponentsFormatTest.kt +++ b/core/common/test/format/DateTimeComponentsFormatTest.kt @@ -7,10 +7,11 @@ package kotlinx.datetime.test.format import kotlinx.datetime.* import kotlinx.datetime.format.* +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty import kotlin.test.* class DateTimeComponentsFormatTest { - @Test fun testErrorHandling() { val format = DateTimeComponents.Formats.RFC_1123 @@ -198,6 +199,56 @@ class DateTimeComponentsFormatTest { assertNull(bagWithAlternative.nanosecond) } + @Test + fun testFormattingWithUnsetFields() { + class PropertyAndItsValue(val property: KMutableProperty1, val value: Value) { + fun set(target: Target) { + property.set(target, value) + } + } + val fields = listOf>( + PropertyAndItsValue(DateTimeComponents::timeZoneId, "Europe/Berlin"), + PropertyAndItsValue(DateTimeComponents::year, 2020), + PropertyAndItsValue(DateTimeComponents::monthNumber, 3), + PropertyAndItsValue(DateTimeComponents::dayOfMonth, 16), + PropertyAndItsValue(DateTimeComponents::dayOfWeek, DayOfWeek.MONDAY), + PropertyAndItsValue(DateTimeComponents::dayOfYear, 76), + PropertyAndItsValue(DateTimeComponents::hour, 23), + PropertyAndItsValue(DateTimeComponents::hourOfAmPm, 11), + PropertyAndItsValue(DateTimeComponents::amPm, AmPmMarker.PM), + PropertyAndItsValue(DateTimeComponents::minute, 59), + PropertyAndItsValue(DateTimeComponents::second, 45), + PropertyAndItsValue(DateTimeComponents::nanosecond, 123_456_789), + PropertyAndItsValue(DateTimeComponents::offsetHours, 3), + PropertyAndItsValue(DateTimeComponents::offsetMinutesOfHour, 30), + PropertyAndItsValue(DateTimeComponents::offsetSecondsOfMinute, 15), + ) + val formatWithEverything = DateTimeComponents.Format { + timeZoneId() + dateTime(LocalDateTime.Formats.ISO) + char(' ') + dayOfWeek(DayOfWeekNames.ENGLISH_FULL) + char(' ') + dayOfYear() + char(' ') + amPmHour(); char(' '); amPmMarker("AM", "PM") + char(' ') + offset(UtcOffset.Formats.ISO) + } + for (index in fields.indices) { + try { + formatWithEverything.format { + for (i in fields.indices) { + if (i != index) fields[i].set(this) + } + } + fail("No error if the field ${fields[index].property.name} is unset") + } catch (e: IllegalStateException) { + assertTrue(e.message!!.contains(fields[index].property.name), "Error message '$e' does not contain ${fields[index].property.name}") + } + } + } + @OptIn(FormatStringsInDatetimeFormats::class) @Test fun testByUnicodePatternDoc() {