Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refer to DateTimeComponents field names in errors for null values #472

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions core/common/src/format/DateTimeComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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".
Expand Down
18 changes: 9 additions & 9 deletions core/common/src/format/LocalDateFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,15 @@ internal interface DateFieldContainer {
var year: Int?
var monthNumber: Int?
var dayOfMonth: Int?
var isoDayOfWeek: Int?
var dayOfWeek: Int?
var dayOfYear: Int?
}

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)
}

Expand All @@ -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<IncompleteLocalDate> {
fun toLocalDate(): LocalDate {
Expand Down Expand Up @@ -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: " +
Expand All @@ -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) :
Expand Down
2 changes: 1 addition & 1 deletion core/common/src/format/LocalTimeFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
48 changes: 24 additions & 24 deletions core/common/src/format/UtcOffsetFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -127,26 +127,26 @@ internal fun DateTimeFormatBuilder.WithUtcOffset.isoOffset(

private object OffsetFields {
private val sign = object : FieldSign<UtcOffsetFieldContainer> {
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,
Expand All @@ -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<IncompleteUtcOffset> {

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) :
Expand Down
7 changes: 4 additions & 3 deletions core/common/src/internal/format/FieldSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ internal interface Accessor<in Object, Field>: AssignableField<Object, Field> {
/**
* An implementation of [Accessor] for a mutable property of an object.
*/
internal class PropertyAccessor<Object, Field>(private val property: KMutableProperty1<Object, Field?>): Accessor<Object, Field> {
override val name: String get() = property.name

internal class PropertyAccessor<Object, Field>(
private val property: KMutableProperty1<Object, Field?>,
override val name: String = property.name
): Accessor<Object, Field> {
override fun trySetWithoutReassigning(container: Object, newValue: Field): Field? {
val oldValue = property.get(container)
return when {
Expand Down
53 changes: 52 additions & 1 deletion core/common/test/format/DateTimeComponentsFormatTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -198,6 +199,56 @@ class DateTimeComponentsFormatTest {
assertNull(bagWithAlternative.nanosecond)
}

@Test
fun testFormattingWithUnsetFields() {
class PropertyAndItsValue<Target, Value>(val property: KMutableProperty1<Target, Value>, val value: Value) {
fun set(target: Target) {
property.set(target, value)
}
}
val fields = listOf<PropertyAndItsValue<DateTimeComponents, *>>(
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() {
Expand Down