Skip to content

Commit f359d50

Browse files
committed
Address the review
1 parent d387586 commit f359d50

12 files changed

+123
-85
lines changed

core/common/src/Instant.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -535,11 +535,3 @@ public fun Instant.Companion.parse(input: CharSequence, format: DateTimeFormat<D
535535

536536
internal const val DISTANT_PAST_SECONDS = -3217862419201
537537
internal const val DISTANT_FUTURE_SECONDS = 3093527980800
538-
539-
/**
540-
* Displays the given Instant in the given [offset].
541-
*
542-
* Be careful: this function may throw for some values of the [Instant].
543-
*/
544-
internal fun Instant.toStringWithOffset(offset: UtcOffset): String =
545-
format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offset)

core/common/src/format/DateTimeComponents.kt

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import kotlinx.datetime.internal.*
1111
import kotlinx.datetime.internal.format.*
1212
import kotlinx.datetime.internal.format.parser.Copyable
1313
import kotlinx.datetime.internal.safeMultiply
14-
import kotlin.native.concurrent.*
1514
import kotlin.reflect.*
1615

1716
/**
@@ -24,10 +23,10 @@ import kotlin.reflect.*
2423
* Example:
2524
* ```
2625
* val input = "2020-03-16T23:59:59.999999999+03:00"
27-
* val bag = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET.parse(input)
28-
* val localDateTime = bag.toLocalDateTime() // LocalDateTime(2020, 3, 16, 23, 59, 59, 999_999_999)
29-
* val instant = bag.toInstantUsingOffset() // Instant.parse("2020-03-16T20:59:59.999999999Z")
30-
* val offset = bag.toUtcOffset() // UtcOffset(hours = 3)
26+
* val components = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET.parse(input)
27+
* val localDateTime = components.toLocalDateTime() // LocalDateTime(2020, 3, 16, 23, 59, 59, 999_999_999)
28+
* val instant = components.toInstantUsingOffset() // Instant.parse("2020-03-16T20:59:59.999999999Z")
29+
* val offset = components.toUtcOffset() // UtcOffset(hours = 3)
3130
* ```
3231
*
3332
* Another purpose is to support parsing and formatting data with out-of-bounds values. For example, parsing
@@ -168,7 +167,7 @@ public class DateTimeComponents internal constructor(internal val contents: Date
168167

169168
/**
170169
* Writes the contents of the specified [localTime] to this [DateTimeComponents].
171-
* The [localTime] is written to the [hour], [hourOfAmPm], [isPm], [minute], [second] and [nanosecond] fields.
170+
* The [localTime] is written to the [hour], [hourOfAmPm], [amPm], [minute], [second] and [nanosecond] fields.
172171
*
173172
* If any of the fields are already set, they will be overwritten.
174173
*/
@@ -186,7 +185,7 @@ public class DateTimeComponents internal constructor(internal val contents: Date
186185
* Writes the contents of the specified [localDateTime] to this [DateTimeComponents].
187186
* The [localDateTime] is written to the
188187
* [year], [monthNumber], [dayOfMonth], [dayOfWeek],
189-
* [hour], [hourOfAmPm], [isPm], [minute], [second] and [nanosecond] fields.
188+
* [hour], [hourOfAmPm], [amPm], [minute], [second] and [nanosecond] fields.
190189
*
191190
* If any of the fields are already set, they will be overwritten.
192191
*/
@@ -247,7 +246,10 @@ public class DateTimeComponents internal constructor(internal val contents: Date
247246
*/
248247
public var monthNumber: Int? by TwoDigitNumber(contents.date::monthNumber)
249248

250-
/** The month ([Month]) component of the date. */
249+
/**
250+
* The month ([Month]) component of the date.
251+
* @throws IllegalArgumentException during getting if [monthNumber] is outside the `1..12` range.
252+
*/
251253
public var month: Month?
252254
get() = monthNumber?.let { Month(it) }
253255
set(value) {
@@ -270,23 +272,23 @@ public class DateTimeComponents internal constructor(internal val contents: Date
270272
// public var dayOfYear: Int
271273

272274
/**
273-
* The hour-of-day time component.
275+
* The hour-of-day (0..23) time component.
274276
* @throws IllegalArgumentException during assignment if the value is outside the `0..99` range.
275277
*/
276278
public var hour: Int? by TwoDigitNumber(contents.time::hour)
277279

278280
/**
279-
* The 12-hour time component.
281+
* The 12-hour (1..12) time component.
280282
* @throws IllegalArgumentException during assignment if the value is outside the `0..99` range.
281-
* @see isPm
283+
* @see amPm
282284
*/
283285
public var hourOfAmPm: Int? by TwoDigitNumber(contents.time::hourOfAmPm)
284286

285287
/**
286-
* The AM/PM state of the time component: `true` if PM, `false` if `AM`.
288+
* The AM/PM state of the time component.
287289
* @see hourOfAmPm
288290
*/
289-
public var isPm: Boolean? by contents.time::isPm
291+
public var amPm: AmPmMarker? by contents.time::amPm
290292

291293
/**
292294
* The minute-of-hour component.
@@ -368,13 +370,13 @@ public class DateTimeComponents internal constructor(internal val contents: Date
368370
* Builds a [LocalTime] from the fields in this [DateTimeComponents].
369371
*
370372
* This method uses the following fields:
371-
* * [hour], [hourOfAmPm], and [isPm]
373+
* * [hour], [hourOfAmPm], and [amPm]
372374
* * [minute]
373375
* * [second] (default value is 0)
374376
* * [nanosecond] (default value is 0)
375377
*
376378
* @throws IllegalArgumentException if hours or minutes are not present, if any of the fields are invalid, or
377-
* [hourOfAmPm] and [isPm] are inconsistent with [hour].
379+
* [hourOfAmPm] and [amPm] are inconsistent with [hour].
378380
*/
379381
public fun toLocalTime(): LocalTime = contents.time.toLocalTime()
380382

@@ -385,7 +387,7 @@ public class DateTimeComponents internal constructor(internal val contents: Date
385387
* * [year]
386388
* * [monthNumber]
387389
* * [dayOfMonth]
388-
* * [hour], [hourOfAmPm], and [isPm]
390+
* * [hour], [hourOfAmPm], and [amPm]
389391
* * [minute]
390392
* * [second] (default value is 0)
391393
* * [nanosecond] (default value is 0)
@@ -421,7 +423,7 @@ public class DateTimeComponents internal constructor(internal val contents: Date
421423
* * and is a multiple of 400, which is the number of years in a leap cycle, which means that taking the
422424
* remainder of the year after dividing by 40_000 will not change the leap year status of the year.
423425
*/
424-
truncatedDate.year = getParsedField(truncatedDate.year, "year") % 10_000
426+
truncatedDate.year = requireParsedField(truncatedDate.year, "year") % 10_000
425427
val totalSeconds = try {
426428
val secDelta = safeMultiply((year!! / 10_000).toLong(), SECONDS_PER_10000_YEARS)
427429
val epochDays = truncatedDate.toLocalDate().toEpochDays().toLong()
@@ -537,4 +539,4 @@ private class TwoDigitNumber(private val reference: KMutableProperty0<Int?>) {
537539
}
538540
}
539541

540-
private val emptyDateTimeComponentsContents = DateTimeComponentsContents()
542+
private val emptyDateTimeComponentsContents = DateTimeComponentsContents()

core/common/src/format/DateTimeFormat.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package kotlinx.datetime.format
88
import kotlinx.datetime.*
99
import kotlinx.datetime.internal.format.*
1010
import kotlinx.datetime.internal.format.parser.*
11-
import kotlin.native.concurrent.*
1211

1312
/**
1413
* A format for parsing and formatting date-time-related values.
@@ -46,11 +45,8 @@ public sealed interface DateTimeFormat<T> {
4645
* The typical use case for this is to create a [DateTimeFormat] instance using a non-idiomatic approach and
4746
* then convert it to a builder DSL.
4847
*/
49-
public fun formatAsKotlinBuilderDsl(format: DateTimeFormat<*>): String {
50-
when (format) {
51-
is AbstractDateTimeFormat<*, *> -> return format.actualFormat.builderString(allFormatConstants)
52-
else -> error("Unsupported format: $format")
53-
}
48+
public fun formatAsKotlinBuilderDsl(format: DateTimeFormat<*>): String = when (format) {
49+
is AbstractDateTimeFormat<*, *> -> format.actualFormat.builderString(allFormatConstants)
5450
}
5551
}
5652
}

core/common/src/format/DateTimeFormatBuilder.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ public sealed interface DateTimeFormatBuilder {
281281
* When formatting, the decimal fraction will round the number to fit in the specified [maxLength] and will add
282282
* trailing zeroes to the specified [minLength].
283283
*
284-
* Additionally, [grouping] is a list, where the i'th element specifies how many trailing zeros to add during formatting
285-
* when
284+
* Additionally, [grouping] is a list, where the i'th (1-based) element specifies how many trailing zeros to add during
285+
* formatting when the number would have i digits.
286286
*
287287
* When parsing, the parser will require that the fraction is at least [minLength] and at most [maxLength]
288288
* digits long.
@@ -390,15 +390,6 @@ internal interface AbstractDateTimeFormatBuilder<Target, ActualSelf> :
390390

391391
override fun chars(value: String) = actualBuilder.add(ConstantFormatStructure(value))
392392

393-
fun withSharedSignImpl(outputPlus: Boolean, block: ActualSelf.() -> Unit) {
394-
actualBuilder.add(
395-
SignedFormatStructure(
396-
createEmpty().also { block(it) }.actualBuilder.build(),
397-
outputPlus
398-
)
399-
)
400-
}
401-
402393
fun build(): StringFormat<Target> = StringFormat(actualBuilder.build())
403394
}
404395

core/common/src/format/LocalDateFormat.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import kotlinx.datetime.*
99
import kotlinx.datetime.internal.*
1010
import kotlinx.datetime.internal.format.*
1111
import kotlinx.datetime.internal.format.parser.Copyable
12-
import kotlin.native.concurrent.*
1312

1413
/**
1514
* A description of how month names are formatted.
@@ -116,7 +115,7 @@ internal fun DayOfWeekNames.toKotlinCode(): String = when (this.names) {
116115
else -> names.joinToString(", ", "DayOfWeekNames(", ")", transform = String::toKotlinCode)
117116
}
118117

119-
internal fun <T> getParsedField(field: T?, name: String): T {
118+
internal fun <T> requireParsedField(field: T?, name: String): T {
120119
if (field == null) {
121120
throw DateTimeFormatException("Can not create a $name from the given input: the field $name is missing")
122121
}
@@ -148,9 +147,9 @@ internal class IncompleteLocalDate(
148147
) : DateFieldContainer, Copyable<IncompleteLocalDate> {
149148
fun toLocalDate(): LocalDate {
150149
val date = LocalDate(
151-
getParsedField(year, "year"),
152-
getParsedField(monthNumber, "monthNumber"),
153-
getParsedField(dayOfMonth, "dayOfMonth")
150+
requireParsedField(year, "year"),
151+
requireParsedField(monthNumber, "monthNumber"),
152+
requireParsedField(dayOfMonth, "dayOfMonth")
154153
)
155154
isoDayOfWeek?.let {
156155
if (it != date.dayOfWeek.isoDayNumber) {

core/common/src/format/LocalTimeFormat.kt

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@ import kotlinx.datetime.*
99
import kotlinx.datetime.internal.*
1010
import kotlinx.datetime.internal.format.*
1111
import kotlinx.datetime.internal.format.parser.Copyable
12-
import kotlin.native.concurrent.*
12+
13+
/**
14+
* The AM/PM marker that indicates whether the hour in range `1..12` is before or after noon.
15+
*/
16+
public enum class AmPmMarker {
17+
/** The time is before noon. */
18+
AM,
19+
/** The time is after noon. */
20+
PM,
21+
}
1322

1423
internal interface TimeFieldContainer {
1524
var minute: Int?
1625
var second: Int?
1726
var nanosecond: Int?
1827
var hour: Int?
1928
var hourOfAmPm: Int?
20-
var isPm: Boolean?
29+
var amPm: AmPmMarker?
2130

2231
var fractionOfSecond: DecimalFraction?
2332
get() = nanosecond?.let { DecimalFraction(it, 9) }
@@ -31,14 +40,14 @@ private object TimeFields {
3140
val minute = UnsignedFieldSpec(PropertyAccessor(TimeFieldContainer::minute), minValue = 0, maxValue = 59)
3241
val second = UnsignedFieldSpec(PropertyAccessor(TimeFieldContainer::second), minValue = 0, maxValue = 59, defaultValue = 0)
3342
val fractionOfSecond = GenericFieldSpec(PropertyAccessor(TimeFieldContainer::fractionOfSecond), defaultValue = DecimalFraction(0, 9))
34-
val isPm = GenericFieldSpec(PropertyAccessor(TimeFieldContainer::isPm))
43+
val amPm = GenericFieldSpec(PropertyAccessor(TimeFieldContainer::amPm))
3544
val hourOfAmPm = UnsignedFieldSpec(PropertyAccessor(TimeFieldContainer::hourOfAmPm), minValue = 1, maxValue = 12)
3645
}
3746

3847
internal class IncompleteLocalTime(
3948
override var hour: Int? = null,
4049
override var hourOfAmPm: Int? = null,
41-
override var isPm: Boolean? = null,
50+
override var amPm: AmPmMarker? = null,
4251
override var minute: Int? = null,
4352
override var second: Int? = null,
4453
override var nanosecond: Int? = null
@@ -48,20 +57,20 @@ internal class IncompleteLocalTime(
4857
hourOfAmPm?.let {
4958
require((hour + 11) % 12 + 1 == it) { "Inconsistent hour and hour-of-am-pm: hour is $hour, but hour-of-am-pm is $it" }
5059
}
51-
isPm?.let {
52-
require(isPm != null && isPm != (hour >= 12)) {
53-
"Inconsistent hour and the AM/PM marker: hour is $hour, but the AM/PM marker is ${if (it) "PM" else "AM"}"
60+
amPm?.let { amPm ->
61+
require((amPm == AmPmMarker.PM) == (hour >= 12)) {
62+
"Inconsistent hour and the AM/PM marker: hour is $hour, but the AM/PM marker is $amPm"
5463
}
5564
}
5665
hour
5766
} ?: hourOfAmPm?.let { hourOfAmPm ->
58-
isPm?.let { isPm ->
59-
hourOfAmPm.let { if (it == 12) 0 else it } + if (isPm) 12 else 0
67+
amPm?.let { amPm ->
68+
hourOfAmPm.let { if (it == 12) 0 else it } + if (amPm == AmPmMarker.PM) 12 else 0
6069
}
6170
} ?: throw DateTimeFormatException("Incomplete time: missing hour")
6271
return LocalTime(
6372
hour,
64-
getParsedField(minute, "minute"),
73+
requireParsedField(minute, "minute"),
6574
second ?: 0,
6675
nanosecond ?: 0,
6776
)
@@ -70,20 +79,20 @@ internal class IncompleteLocalTime(
7079
fun populateFrom(localTime: LocalTime) {
7180
hour = localTime.hour
7281
hourOfAmPm = (localTime.hour + 11) % 12 + 1
73-
isPm = localTime.hour >= 12
82+
amPm = if (localTime.hour >= 12) AmPmMarker.PM else AmPmMarker.AM
7483
minute = localTime.minute
7584
second = localTime.second
7685
nanosecond = localTime.nanosecond
7786
}
7887

79-
override fun copy(): IncompleteLocalTime = IncompleteLocalTime(hour, hourOfAmPm, isPm, minute, second, nanosecond)
88+
override fun copy(): IncompleteLocalTime = IncompleteLocalTime(hour, hourOfAmPm, amPm, minute, second, nanosecond)
8089

8190
override fun equals(other: Any?): Boolean =
82-
other is IncompleteLocalTime && hour == other.hour && hourOfAmPm == other.hourOfAmPm && isPm == other.isPm &&
91+
other is IncompleteLocalTime && hour == other.hour && hourOfAmPm == other.hourOfAmPm && amPm == other.amPm &&
8392
minute == other.minute && second == other.second && nanosecond == other.nanosecond
8493

8594
override fun hashCode(): Int =
86-
(hour ?: 0) * 31 + (hourOfAmPm ?: 0) * 31 + (isPm?.hashCode() ?: 0) * 31 + (minute ?: 0) * 31 +
95+
(hour ?: 0) * 31 + (hourOfAmPm ?: 0) * 31 + (amPm?.hashCode() ?: 0) * 31 + (minute ?: 0) * 31 +
8796
(second ?: 0) * 31 + (nanosecond ?: 0)
8897

8998
override fun toString(): String =
@@ -145,10 +154,10 @@ private class AmPmHourDirective(private val padding: Padding) :
145154
}
146155

147156
private class AmPmMarkerDirective(private val amString: String, private val pmString: String) :
148-
NamedEnumIntFieldFormatDirective<TimeFieldContainer, Boolean>(
149-
TimeFields.isPm, mapOf(
150-
false to amString,
151-
true to pmString,
157+
NamedEnumIntFieldFormatDirective<TimeFieldContainer, AmPmMarker>(
158+
TimeFields.amPm, mapOf(
159+
AmPmMarker.AM to amString,
160+
AmPmMarker.PM to pmString,
152161
),
153162
"AM/PM marker"
154163
) {

core/common/src/format/migration/Unicode.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlin.native.concurrent.*
99

1010
/**
1111
* Marks declarations in the datetime library that use format strings to define datetime formats.
12+
*
1213
* Format strings are discouraged, because they require gaining proficiency in another tiny language.
1314
* When possible, please use the builder-style Kotlin API instead.
1415
* If the format string is a constant, the corresponding builder-style Kotlin code can be obtained by calling

core/common/test/InstantTest.kt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@
66
package kotlinx.datetime.test
77

88
import kotlinx.datetime.*
9-
import kotlinx.datetime.Clock // currently, requires an explicit import due to a conflict with the deprecated Clock from kotlin.time
109
import kotlinx.datetime.format.*
1110
import kotlinx.datetime.internal.*
1211
import kotlin.random.*
1312
import kotlin.test.*
1413
import kotlin.time.*
15-
import kotlin.time.Duration.Companion.days
1614
import kotlin.time.Duration.Companion.hours
1715
import kotlin.time.Duration.Companion.milliseconds
1816
import kotlin.time.Duration.Companion.nanoseconds
@@ -120,19 +118,24 @@ class InstantTest {
120118
Instant.DISTANT_PAST,
121119
Instant.fromEpochSeconds(0, 0))
122120

123-
val offsets = listOf(
124-
UtcOffset.parse("Z"),
125-
UtcOffset.parse("+03:12:14"),
126-
UtcOffset.parse("-03:12:14"),
127-
UtcOffset.parse("+02:35"),
128-
UtcOffset.parse("-02:35"),
129-
UtcOffset.parse("+04"),
130-
UtcOffset.parse("-04"),
121+
val offsetStrings = listOf(
122+
"Z",
123+
"+03:12:14",
124+
"-03:12:14",
125+
"+02:35",
126+
"-02:35",
127+
"+04",
128+
"-04",
131129
)
132130

131+
val offsets = offsetStrings.map { UtcOffset.parse(it) }
132+
133133
for (instant in instants) {
134-
for (offset in offsets) {
135-
val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offset)
134+
for (offsetIx in offsets.indices) {
135+
val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx])
136+
val offsetString = offsetStrings[offsetIx]
137+
assertEquals(offsetString, offsetString.commonSuffixWith(str))
138+
assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET))
136139
assertEquals(instant, Instant.parse(str))
137140
}
138141
}

0 commit comments

Comments
 (0)