Skip to content

Commit d23960a

Browse files
committed
Redo the model of numeric signs to support multi-field values
Note: this fails on JS for some reason.
1 parent b4e9839 commit d23960a

File tree

7 files changed

+150
-98
lines changed

7 files changed

+150
-98
lines changed

core/common/src/format/UtcOffsetFormat.kt

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import kotlinx.datetime.*
99
import kotlinx.datetime.internal.*
1010
import kotlinx.datetime.internal.format.*
1111
import kotlinx.datetime.internal.format.parser.*
12+
import kotlin.math.*
13+
import kotlin.reflect.*
1214

1315
internal interface UtcOffsetFieldContainer {
14-
var totalHours: Int?
16+
var isNegative: Boolean?
17+
var totalHoursAbs: Int?
1518
var minutesOfHour: Int?
1619
var secondsOfMinute: Int?
1720
}
@@ -87,51 +90,78 @@ public fun UtcOffset.Companion.parse(input: String, formatString: String): UtcOf
8790
public fun UtcOffset.Companion.parse(input: String, format: UtcOffsetFormat): UtcOffset = format.parse(input)
8891

8992
internal fun UtcOffset.toIncompleteUtcOffset(): IncompleteUtcOffset =
90-
IncompleteUtcOffset(totalSeconds / 3600, (totalSeconds / 60) % 60, totalSeconds % 60)
93+
IncompleteUtcOffset(
94+
totalSeconds < 0,
95+
totalSeconds.absoluteValue / 3600,
96+
(totalSeconds.absoluteValue / 60) % 60,
97+
totalSeconds.absoluteValue % 60
98+
)
9199

92100
internal object OffsetFields {
93-
val totalHours = SignedFieldSpec(
94-
UtcOffsetFieldContainer::totalHours,
101+
private val sign = object: FieldSign<UtcOffsetFieldContainer> {
102+
override val isNegative = UtcOffsetFieldContainer::isNegative
103+
override fun isZero(obj: UtcOffsetFieldContainer): Boolean =
104+
(obj.totalHoursAbs ?: 0) == 0 && (obj.minutesOfHour ?: 0) == 0 && (obj.secondsOfMinute ?: 0) == 0
105+
}
106+
val totalHoursAbs = UnsignedFieldSpec(
107+
UtcOffsetFieldContainer::totalHoursAbs,
95108
defaultValue = 0,
96-
maxAbsoluteValue = 18,
109+
minValue = 0,
110+
maxValue = 18,
111+
sign = sign,
97112
)
98-
val minutesOfHour = SignedFieldSpec(
113+
val minutesOfHour = UnsignedFieldSpec(
99114
UtcOffsetFieldContainer::minutesOfHour,
100115
defaultValue = 0,
101-
maxAbsoluteValue = 59,
116+
minValue = 0,
117+
maxValue = 59,
118+
sign = sign,
102119
)
103-
val secondsOfMinute = SignedFieldSpec(
120+
val secondsOfMinute = UnsignedFieldSpec(
104121
UtcOffsetFieldContainer::secondsOfMinute,
105122
defaultValue = 0,
106-
maxAbsoluteValue = 59,
123+
minValue = 0,
124+
maxValue = 59,
125+
sign = sign,
107126
)
108127
}
109128

110129
internal class IncompleteUtcOffset(
111-
override var totalHours: Int? = null,
130+
override var isNegative: Boolean? = null,
131+
override var totalHoursAbs: Int? = null,
112132
override var minutesOfHour: Int? = null,
113133
override var secondsOfMinute: Int? = null,
114134
) : UtcOffsetFieldContainer, Copyable<IncompleteUtcOffset> {
115-
fun toUtcOffset(): UtcOffset = UtcOffset(totalHours, minutesOfHour, secondsOfMinute)
135+
136+
fun toUtcOffset(): UtcOffset {
137+
val sign = if (isNegative == true) -1 else 1
138+
return UtcOffset(
139+
totalHoursAbs?.let { it * sign }, minutesOfHour?.let { it * sign }, secondsOfMinute?.let { it * sign }
140+
)
141+
}
116142

117143
override fun equals(other: Any?): Boolean =
118-
other is IncompleteUtcOffset && totalHours == other.totalHours &&
144+
other is IncompleteUtcOffset && isNegative == other.isNegative && totalHoursAbs == other.totalHoursAbs &&
119145
minutesOfHour == other.minutesOfHour && secondsOfMinute == other.secondsOfMinute
120146

121147
override fun hashCode(): Int =
122-
totalHours.hashCode() * 31 + minutesOfHour.hashCode() * 31 + secondsOfMinute.hashCode()
148+
isNegative.hashCode() + totalHoursAbs.hashCode() + minutesOfHour.hashCode() + secondsOfMinute.hashCode()
149+
150+
override fun copy(): IncompleteUtcOffset =
151+
IncompleteUtcOffset(isNegative, totalHoursAbs, minutesOfHour, secondsOfMinute)
123152

124-
override fun copy(): IncompleteUtcOffset = IncompleteUtcOffset(totalHours, minutesOfHour, secondsOfMinute)
153+
override fun toString(): String =
154+
"${isNegative?.let { if (it) "-" else "+" } ?: " " }${totalHoursAbs ?: "??"}:${minutesOfHour ?: "??"}:${secondsOfMinute ?: "??"}"
125155
}
126156

127157
internal class UtcOffsetWholeHoursDirective(minDigits: Int) :
128-
SignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.totalHours, minDigits)
158+
UnsignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.totalHoursAbs, minDigits)
129159

130160
internal class UtcOffsetMinuteOfHourDirective(minDigits: Int) :
131-
SignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.minutesOfHour, minDigits)
161+
UnsignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.minutesOfHour, minDigits)
132162

133163
internal class UtcOffsetSecondOfMinuteDirective(minDigits: Int) :
134-
SignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.secondsOfMinute, minDigits)
164+
UnsignedIntFieldFormatDirective<UtcOffsetFieldContainer>(OffsetFields.secondsOfMinute, minDigits)
135165

136166
internal object UtcOffsetFormatBuilderSpec: BuilderSpec<UtcOffsetFieldContainer>(
137167
mapOf(

core/common/src/format/ValueBagFormat.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlinx.datetime.internal.*
1111
import kotlinx.datetime.internal.format.*
1212
import kotlinx.datetime.internal.format.parser.*
1313
import kotlinx.datetime.internal.safeMultiply
14+
import kotlin.math.*
1415

1516
/**
1617
* A collection of date-time fields.
@@ -69,9 +70,11 @@ public class ValueBag internal constructor(internal val contents: ValueBagConten
6970
* If any of the fields are already set, they will be overwritten.
7071
*/
7172
public fun populateFrom(utcOffset: UtcOffset) {
72-
offsetTotalHours = utcOffset.totalSeconds / 3600
73-
offsetMinutesOfHour = (utcOffset.totalSeconds % 3600) / 60
74-
offsetSecondsOfMinute = utcOffset.totalSeconds % 60
73+
offsetIsNegative = utcOffset.totalSeconds < 0
74+
val seconds = utcOffset.totalSeconds.absoluteValue
75+
offsetTotalHours = seconds / 3600
76+
offsetMinutesOfHour = (seconds % 3600) / 60
77+
offsetSecondsOfMinute = seconds % 60
7578
}
7679

7780
/**
@@ -123,11 +126,13 @@ public class ValueBag internal constructor(internal val contents: ValueBagConten
123126
/** Returns the nanosecond-of-second time component of this date/time value. */
124127
public var nanosecond: Int? by contents.time::nanosecond
125128

126-
/** The total amount of full hours in the UTC offset. */
127-
public var offsetTotalHours: Int? by contents.offset::totalHours
128-
/** The amount of minutes that don't add to a whole hour in the UTC offset. */
129+
/** True if the offset is negative. */
130+
public var offsetIsNegative: Boolean? by contents.offset::isNegative
131+
/** The total amount of full hours in the UTC offset, in the range [0; 18]. */
132+
public var offsetTotalHours: Int? by contents.offset::totalHoursAbs
133+
/** The amount of minutes that don't add to a whole hour in the UTC offset, in the range [0; 59]. */
129134
public var offsetMinutesOfHour: Int? by contents.offset::minutesOfHour
130-
/** The amount of seconds that don't add to a whole minute in the UTC offset. */
135+
/** The amount of seconds that don't add to a whole minute in the UTC offset, in the range [0; 59]. */
131136
public var offsetSecondsOfMinute: Int? by contents.offset::secondsOfMinute
132137

133138
public var timeZoneId: String? by contents::timeZoneId

core/common/src/internal/format/FieldFormatDirective.kt

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ internal interface FieldFormatDirective<in Target> {
1919
*/
2020
val field: FieldSpec<Target, *>
2121

22-
/**
23-
* For numeric signed values, the way to check if the field is negative. For everything else, `null`.
24-
*/
25-
val signGetter: ((Target) -> Int)?
26-
2722
/**
2823
* The formatter operation that formats the field.
2924
*/
@@ -32,7 +27,7 @@ internal interface FieldFormatDirective<in Target> {
3227
/**
3328
* The parser structure that parses the field.
3429
*/
35-
fun parser(signsInverted: Boolean): ParserStructure<Target>
30+
fun parser(): ParserStructure<Target>
3631
}
3732

3833
/**
@@ -45,8 +40,6 @@ internal abstract class UnsignedIntFieldFormatDirective<in Target>(
4540
private val minDigits: Int,
4641
) : FieldFormatDirective<Target> {
4742

48-
final override val signGetter: ((Target) -> Int)? = null
49-
5043
private val maxDigits: Int = field.maxDigits
5144

5245
init {
@@ -62,7 +55,7 @@ internal abstract class UnsignedIntFieldFormatDirective<in Target>(
6255
zeroPadding = minDigits,
6356
)
6457

65-
override fun parser(signsInverted: Boolean): ParserStructure<Target> =
58+
override fun parser(): ParserStructure<Target> =
6659
ParserStructure(
6760
listOf(
6861
NumberSpanParserOperation(
@@ -94,8 +87,6 @@ internal abstract class NamedUnsignedIntFieldFormatDirective<in Target>(
9487
}
9588
}
9689

97-
final override val signGetter: ((Target) -> Int)? = null
98-
9990
private fun getStringValue(target: Target): String = values[field.getNotNull(target) - field.minValue]
10091

10192
private fun setStringValue(target: Target, value: String) {
@@ -105,7 +96,7 @@ internal abstract class NamedUnsignedIntFieldFormatDirective<in Target>(
10596
override fun formatter(): FormatterOperation<Target> =
10697
StringFormatterOperation(::getStringValue)
10798

108-
override fun parser(signsInverted: Boolean): ParserStructure<Target> =
99+
override fun parser(): ParserStructure<Target> =
109100
ParserStructure(listOf(
110101
StringSetParserOperation(values, ::setStringValue, "One of $values for ${field.name}")
111102
), emptyList())
@@ -119,8 +110,6 @@ internal abstract class NamedEnumIntFieldFormatDirective<in Target, Type>(
119110
private val mapping: Map<Type, String>,
120111
) : FieldFormatDirective<Target> {
121112

122-
final override val signGetter: ((Target) -> Int)? = null
123-
124113
private val reverseMapping = mapping.entries.associate { it.value to it.key }
125114

126115
private fun getStringValue(target: Target): String = mapping[field.getNotNull(target)]
@@ -136,7 +125,7 @@ internal abstract class NamedEnumIntFieldFormatDirective<in Target, Type>(
136125
override fun formatter(): FormatterOperation<Target> =
137126
StringFormatterOperation(::getStringValue)
138127

139-
override fun parser(signsInverted: Boolean): ParserStructure<Target> =
128+
override fun parser(): ParserStructure<Target> =
140129
ParserStructure(listOf(
141130
StringSetParserOperation(mapping.values, ::setStringValue, "One of ${mapping.values} for ${field.name}")
142131
), emptyList())
@@ -147,16 +136,14 @@ internal abstract class StringFieldFormatDirective<in Target>(
147136
private val acceptedStrings: Set<String>,
148137
) : FieldFormatDirective<Target> {
149138

150-
final override val signGetter: ((Target) -> Int)? = null
151-
152139
init {
153140
require(acceptedStrings.isNotEmpty())
154141
}
155142

156143
override fun formatter(): FormatterOperation<Target> =
157144
StringFormatterOperation(field::getNotNull)
158145

159-
override fun parser(signsInverted: Boolean): ParserStructure<Target> =
146+
override fun parser(): ParserStructure<Target> =
160147
ParserStructure(
161148
listOf(StringSetParserOperation(acceptedStrings, field::setWithoutReassigning, field.name)),
162149
emptyList()
@@ -170,9 +157,6 @@ internal abstract class SignedIntFieldFormatDirective<in Target>(
170157
private val outputPlusOnExceededPadding: Boolean = false,
171158
) : FieldFormatDirective<Target> {
172159

173-
final override val signGetter: ((Target) -> Int) = ::signGetterImpl
174-
private fun signGetterImpl(target: Target): Int = (field.accessor.get(target) ?: 0).sign
175-
176160
init {
177161
require(minDigits == null || minDigits >= 0)
178162
require(maxDigits == null || minDigits == null || maxDigits >= minDigits)
@@ -185,14 +169,13 @@ internal abstract class SignedIntFieldFormatDirective<in Target>(
185169
outputPlusOnExceedsPad = outputPlusOnExceededPadding,
186170
)
187171

188-
override fun parser(signsInverted: Boolean): ParserStructure<Target> =
172+
override fun parser(): ParserStructure<Target> =
189173
SignedIntParser(
190174
minDigits = minDigits,
191175
maxDigits = maxDigits,
192176
field::setWithoutReassigning,
193177
field.name,
194178
plusOnExceedsPad = outputPlusOnExceededPadding,
195-
signsInverted = signsInverted
196179
)
197180
}
198181

@@ -201,12 +184,10 @@ internal abstract class DecimalFractionFieldFormatDirective<in Target>(
201184
private val minDigits: Int?,
202185
private val maxDigits: Int?,
203186
) : FieldFormatDirective<Target> {
204-
override val signGetter: ((Target) -> Int)? = null
205-
206187
override fun formatter(): FormatterOperation<Target> =
207188
DecimalFractionFormatterOperation(field::getNotNull, minDigits, maxDigits)
208189

209-
override fun parser(signsInverted: Boolean): ParserStructure<Target> = ParserStructure(
190+
override fun parser(): ParserStructure<Target> = ParserStructure(
210191
listOf(
211192
NumberSpanParserOperation(
212193
listOf(FractionPartConsumer(minDigits, maxDigits, field::setWithoutReassigning, field.name))

core/common/src/internal/format/FieldSpec.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import kotlin.reflect.*
99

1010
private typealias Accessor<Object, Field> = KMutableProperty1<Object, Field?>
1111

12+
internal interface FieldSign<in Target> {
13+
/**
14+
* The field that is `true` if the value of the field is known to be negative, and `false` otherwise.
15+
*/
16+
val isNegative: Accessor<in Target, Boolean>
17+
fun isZero(obj: Target): Boolean
18+
}
19+
1220
/**
1321
* A specification of a field.
1422
*
@@ -33,6 +41,11 @@ internal interface FieldSpec<in Target, Type> {
3341
* The name of the field.
3442
*/
3543
val name: String
44+
45+
/**
46+
* The sign corresponding to the field value, or `null` if the field has none.
47+
*/
48+
val sign: FieldSign<Target>?
3649
}
3750

3851
internal abstract class AbstractFieldSpec<in Target, Type>: FieldSpec<Target, Type> {
@@ -76,6 +89,7 @@ internal class GenericFieldSpec<in Target, Type>(
7689
override val accessor: Accessor<in Target, Type>,
7790
override val name: String = accessor.name,
7891
override val defaultValue: Type? = null,
92+
override val sign: FieldSign<Target>? = null,
7993
) : AbstractFieldSpec<Target, Type>()
8094

8195
/**
@@ -93,6 +107,7 @@ internal class UnsignedFieldSpec<in Target>(
93107
val maxValue: Int,
94108
override val name: String = accessor.name,
95109
override val defaultValue: Int? = null,
110+
override val sign: FieldSign<Target>? = null,
96111
) : AbstractFieldSpec<Target, Int>() {
97112
/**
98113
* The maximum length of the field when represented as a decimal number.
@@ -110,6 +125,7 @@ internal class SignedFieldSpec<in Target>(
110125
val maxAbsoluteValue: Int?,
111126
override val name: String = accessor.name,
112127
override val defaultValue: Int? = null,
128+
override val sign: FieldSign<Target>? = null,
113129
) : AbstractFieldSpec<Target, Int>() {
114130
val maxDigits: Int? = when {
115131
maxAbsoluteValue == null -> null

0 commit comments

Comments
 (0)