8
8
9
9
package kotlinx.datetime
10
10
11
+ import kotlinx.datetime.format.*
11
12
import kotlinx.datetime.internal.*
12
13
import kotlinx.datetime.serializers.InstantIso8601Serializer
13
14
import kotlinx.serialization.Serializable
14
- import kotlin.math.*
15
15
import kotlin.time.*
16
16
import kotlin.time.Duration.Companion.nanoseconds
17
17
import kotlin.time.Duration.Companion.seconds
@@ -26,95 +26,6 @@ public actual enum class DayOfWeek {
26
26
SUNDAY ;
27
27
}
28
28
29
- /* * A parser for the string representation of [ZoneOffset] as seen in `OffsetDateTime`.
30
- *
31
- * We can't just reuse the parsing logic of [ZoneOffset.of], as that version is more lenient: here, strings like
32
- * "0330" are not considered valid zone offsets, whereas [ZoneOffset.of] sees treats the example above as "03:30". */
33
- private val zoneOffsetParser: Parser <UtcOffset >
34
- get() = (concreteCharParser(' z' ).or (concreteCharParser(' Z' )).map { UtcOffset .ZERO })
35
- .or (
36
- concreteCharParser(' +' ).or (concreteCharParser(' -' ))
37
- .chain(intParser(2 , 2 ))
38
- .chain(
39
- optional(
40
- // minutes
41
- concreteCharParser(' :' ).chainSkipping(intParser(2 , 2 ))
42
- .chain(optional(
43
- // seconds
44
- concreteCharParser(' :' ).chainSkipping(intParser(2 , 2 ))
45
- ))))
46
- .map {
47
- val (signHours, minutesSeconds) = it
48
- val (sign, hours) = signHours
49
- val minutes: Int
50
- val seconds: Int
51
- if (minutesSeconds == null ) {
52
- minutes = 0
53
- seconds = 0
54
- } else {
55
- minutes = minutesSeconds.first
56
- seconds = minutesSeconds.second ? : 0
57
- }
58
- try {
59
- if (sign == ' -' )
60
- UtcOffset .ofHoursMinutesSeconds(- hours, - minutes, - seconds)
61
- else
62
- UtcOffset .ofHoursMinutesSeconds(hours, minutes, seconds)
63
- } catch (e: IllegalArgumentException ) {
64
- throw DateTimeFormatException (e)
65
- }
66
- }
67
- )
68
-
69
- // This is a function and not a value due to https://github.com/Kotlin/kotlinx-datetime/issues/5
70
- // org.threeten.bp.format.DateTimeFormatter#ISO_OFFSET_DATE_TIME
71
- private val instantParser: Parser <Instant >
72
- get() = localDateParser
73
- .chainIgnoring(concreteCharParser(' T' ).or (concreteCharParser(' t' )))
74
- .chain(intParser(2 , 2 )) // hour
75
- .chainIgnoring(concreteCharParser(' :' ))
76
- .chain(intParser(2 , 2 )) // minute
77
- .chainIgnoring(concreteCharParser(' :' ))
78
- .chain(intParser(2 , 2 )) // second
79
- .chain(optional(
80
- concreteCharParser(' .' )
81
- .chainSkipping(fractionParser(0 , 9 , 9 )) // nanos
82
- ))
83
- .chain(zoneOffsetParser)
84
- .map {
85
- val (localDateTime, offset) = it
86
- val (dateHourMinuteSecond, nanosVal) = localDateTime
87
- val (dateHourMinute, secondsVal) = dateHourMinuteSecond
88
- val (dateHour, minutesVal) = dateHourMinute
89
- val (dateVal, hoursVal) = dateHour
90
-
91
- val nano = nanosVal ? : 0
92
- val (days, hours, min, seconds) = if (hoursVal == 24 && minutesVal == 0 && secondsVal == 0 && nano == 0 ) {
93
- listOf (1 , 0 , 0 , 0 )
94
- } else if (hoursVal == 23 && minutesVal == 59 && secondsVal == 60 ) {
95
- // TODO: throw an error on leap seconds to match what the other platforms do
96
- listOf (0 , 23 , 59 , 59 )
97
- } else {
98
- listOf (0 , hoursVal, minutesVal, secondsVal)
99
- }
100
-
101
- // never fails: 9_999 years are always supported
102
- val localDate = dateVal.withYear(dateVal.year % 10000 ).plus(days, DateTimeUnit .DAY )
103
- val localTime = LocalTime .of(hours, min, seconds, 0 )
104
- val secDelta: Long = try {
105
- safeMultiply((dateVal.year / 10000 ).toLong(), SECONDS_PER_10000_YEARS )
106
- } catch (e: ArithmeticException ) {
107
- throw DateTimeFormatException (e)
108
- }
109
- val epochDay = localDate.toEpochDays().toLong()
110
- val instantSecs = epochDay * 86400 - offset.totalSeconds + localTime.toSecondOfDay() + secDelta
111
- try {
112
- Instant (instantSecs, nano)
113
- } catch (e: IllegalArgumentException ) {
114
- throw DateTimeFormatException (e)
115
- }
116
- }
117
-
118
29
/* *
119
30
* The minimum supported epoch second.
120
31
*/
@@ -184,7 +95,6 @@ public actual class Instant internal constructor(public actual val epochSeconds:
184
95
override fun hashCode (): Int =
185
96
(epochSeconds xor (epochSeconds ushr 32 )).toInt() + 51 * nanosecondsOfSecond
186
97
187
- // org.threeten.bp.format.DateTimeFormatterBuilder.InstantPrinterParser#print
188
98
actual override fun toString (): String = toStringWithOffset(UtcOffset .ZERO )
189
99
190
100
public actual companion object {
@@ -226,8 +136,11 @@ public actual class Instant internal constructor(public actual val epochSeconds:
226
136
public actual fun fromEpochSeconds (epochSeconds : Long , nanosecondAdjustment : Int ): Instant =
227
137
fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong())
228
138
229
- public actual fun parse (isoString : String ): Instant =
230
- instantParser.parse(isoString)
139
+ public actual fun parse (isoString : String ): Instant = try {
140
+ DateTimeComponents .parse(isoString, DateTimeComponents .Formats .ISO_DATE_TIME_OFFSET ).toInstantUsingOffset()
141
+ } catch (e: IllegalArgumentException ) {
142
+ throw DateTimeFormatException (" Failed to parse an instant from '$isoString '" , e)
143
+ }
231
144
232
145
public actual val DISTANT_PAST : Instant = fromEpochSeconds(DISTANT_PAST_SECONDS , 999_999_999 )
233
146
@@ -329,65 +242,31 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti
329
242
}
330
243
}
331
244
332
- internal actual fun Instant.toStringWithOffset (offset : UtcOffset ): String {
333
- val buf = StringBuilder ()
334
- val inNano: Int = nanosecondsOfSecond
335
- val seconds = epochSeconds + offset.totalSeconds
336
- if (seconds >= - SECONDS_0000_TO_1970 ) { // current era
337
- val zeroSecs: Long = seconds - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970
338
- val hi: Long = zeroSecs.floorDiv(SECONDS_PER_10000_YEARS ) + 1
339
- val lo: Long = zeroSecs.mod(SECONDS_PER_10000_YEARS )
340
- val ldt: LocalDateTime = Instant (lo - SECONDS_0000_TO_1970 , 0 )
341
- .toLocalDateTime(TimeZone .UTC )
342
- if (hi > 0 ) {
343
- buf.append(' +' ).append(hi)
344
- }
345
- buf.append(ldt)
346
- if (ldt.second == 0 ) {
347
- buf.append(" :00" )
348
- }
349
- } else { // before current era
350
- val zeroSecs: Long = seconds + SECONDS_0000_TO_1970
351
- val hi: Long = zeroSecs / SECONDS_PER_10000_YEARS
352
- val lo: Long = zeroSecs % SECONDS_PER_10000_YEARS
353
- val ldt: LocalDateTime = Instant (lo - SECONDS_0000_TO_1970 , 0 )
354
- .toLocalDateTime(TimeZone .UTC )
355
- val pos = buf.length
356
- buf.append(ldt)
357
- if (ldt.second == 0 ) {
358
- buf.append(" :00" )
359
- }
360
- if (hi < 0 ) {
361
- when {
362
- ldt.year == - 10000 -> {
363
- buf.deleteAt(pos)
364
- buf.deleteAt(pos)
365
- buf.insert(pos, (hi - 1 ).toString())
366
- }
367
- lo == 0L -> {
368
- buf.insert(pos, hi)
369
- }
370
- else -> {
371
- buf.insert(pos + 1 , abs(hi))
372
- }
373
- }
374
- }
245
+ internal actual fun Instant.toStringWithOffset (offset : UtcOffset ): String =
246
+ ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS .format {
247
+ setDateTimeOffset(this @toStringWithOffset, offset)
375
248
}
376
- // fraction
377
- if (inNano != 0 ) {
378
- buf.append(' .' )
379
- when {
380
- inNano % 1000000 == 0 -> {
381
- buf.append((inNano / 1000000 + 1000 ).toString().substring(1 ))
382
- }
383
- inNano % 1000 == 0 -> {
384
- buf.append((inNano / 1000 + 1000000 ).toString().substring(1 ))
385
- }
386
- else -> {
387
- buf.append((inNano + 1000000000 ).toString().substring(1 ))
388
- }
389
- }
249
+
250
+ private val ISO_DATE_TIME_OFFSET_WITH_TRAILING_ZEROS = DateTimeComponents .Format {
251
+ date(ISO_DATE )
252
+ alternativeParsing({
253
+ char(' t' )
254
+ }) {
255
+ char(' T' )
256
+ }
257
+ hour()
258
+ char(' :' )
259
+ minute()
260
+ char(' :' )
261
+ second()
262
+ optional {
263
+ char(' .' )
264
+ secondFractionInternal(1 , 9 , FractionalSecondDirective .GROUP_BY_THREE )
390
265
}
391
- buf.append(offset)
392
- return buf.toString()
266
+ isoOffset(
267
+ zOnZero = true ,
268
+ useSeparator = true ,
269
+ outputMinute = WhenToOutput .IF_NONZERO ,
270
+ outputSecond = WhenToOutput .IF_NONZERO
271
+ )
393
272
}
0 commit comments