@@ -14,7 +14,9 @@ import kotlinx.datetime.internal.JSJoda.Instant as jtInstant
14
14
import kotlinx.datetime.internal.JSJoda.Duration as jtDuration
15
15
import kotlinx.datetime.internal.JSJoda.Clock as jtClock
16
16
import kotlinx.datetime.internal.JSJoda.ChronoUnit
17
- import kotlinx.datetime.internal.JSJoda.ZoneId
17
+ import kotlinx.datetime.internal.JSJoda.LocalTime
18
+ import kotlin.math.nextTowards
19
+ import kotlin.math.truncate
18
20
19
21
@OptIn(kotlin.time.ExperimentalTime ::class )
20
22
public actual class Instant internal constructor(internal val value : jtInstant) : Comparable<Instant> {
@@ -24,10 +26,23 @@ public actual class Instant internal constructor(internal val value: jtInstant)
24
26
actual val nanosecondsOfSecond: Int
25
27
get() = value.nano().toInt()
26
28
27
- public actual fun toEpochMilliseconds (): Long = value.toEpochMilli().toLong()
29
+ public actual fun toEpochMilliseconds (): Long = epochSeconds * 1000 + nanosecondsOfSecond / 1_000_000
30
+
31
+ actual operator fun plus (duration : Duration ): Instant {
32
+ val addSeconds = truncate(duration.inSeconds)
33
+ val addNanos = (duration.inNanoseconds % 1e9).toInt()
34
+ return try {
35
+ Instant (plusFix(addSeconds, addNanos))
36
+ } catch (e: Throwable ) {
37
+ if (! e.isJodaDateTimeException()) throw e
38
+ if (addSeconds > 0 ) MAX else MIN
39
+ }
40
+ }
28
41
29
- actual operator fun plus (duration : Duration ): Instant = duration.toComponents { seconds, nanoseconds ->
30
- Instant (value.plusSeconds(seconds).plusNanos(nanoseconds.toLong()))
42
+ internal fun plusFix (seconds : Double , nanos : Int ): jtInstant {
43
+ val newSeconds = value.epochSecond().toDouble() + seconds
44
+ val newNanos = value.nano().toDouble() + nanos
45
+ return jtInstant.ofEpochSecond(newSeconds, newNanos)
31
46
}
32
47
33
48
actual operator fun minus (duration : Duration ): Instant = plus(- duration)
@@ -51,50 +66,98 @@ public actual class Instant internal constructor(internal val value: jtInstant)
51
66
actual fun now (): Instant =
52
67
Instant (jtClock.systemUTC().instant())
53
68
54
- actual fun fromEpochMilliseconds (epochMilliseconds : Long ): Instant =
55
- Instant (jtInstant.ofEpochMilli(epochMilliseconds.toDouble()))
56
-
57
- actual fun parse (isoString : String ): Instant =
58
- Instant (jtInstant.parse(isoString))
59
-
60
- actual fun fromEpochSeconds (epochSeconds : Long , nanosecondAdjustment : Long ): Instant =
61
- Instant (jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment))
69
+ actual fun fromEpochMilliseconds (epochMilliseconds : Long ): Instant = try {
70
+ fromEpochSeconds(epochMilliseconds / 1000 , epochMilliseconds % 1000 * 1000_000 )
71
+ } catch (e: Throwable ) {
72
+ if (! e.isJodaDateTimeException()) throw e
73
+ if (epochMilliseconds > 0 ) MAX else MIN
74
+ }
75
+
76
+ actual fun parse (isoString : String ): Instant = try {
77
+ Instant (jtInstant.parse(isoString))
78
+ } catch (e: Throwable ) {
79
+ if (e.isJodaDateTimeParseException()) throw DateTimeFormatException (e)
80
+ throw e
81
+ }
82
+
83
+ actual fun fromEpochSeconds (epochSeconds : Long , nanosecondAdjustment : Long ): Instant = try {
84
+ Instant (jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment))
85
+ } catch (e: Throwable ) {
86
+ if (! e.isJodaDateTimeException()) throw e
87
+ if (epochSeconds > 0 ) MAX else MIN
88
+ }
62
89
63
90
internal actual val MIN : Instant = Instant (jtInstant.MIN )
64
91
internal actual val MAX : Instant = Instant (jtInstant.MAX )
65
92
}
66
93
}
67
94
68
95
69
- public actual fun Instant.plus (period : DateTimePeriod , zone : TimeZone ): Instant {
96
+ public actual fun Instant.plus (period : DateTimePeriod , zone : TimeZone ): Instant = try {
70
97
val thisZdt = this .value.atZone(zone.zoneId)
71
- return with (period) {
98
+ with (period) {
72
99
thisZdt
73
100
.run { if (years != 0 && months == 0 ) plusYears(years) else this }
74
101
.run { if (months != 0 ) plusMonths(years * 12.0 + months) else this }
75
102
.run { if (days != 0 ) plusDays(days) as ZonedDateTime else this }
76
103
.run { if (hours != 0 ) plusHours(hours) else this }
77
104
.run { if (minutes != 0 ) plusMinutes(minutes) else this }
78
- .run { if (seconds != 0L ) plusSeconds(seconds.toDouble()) else this }
79
- .run { if (nanoseconds != 0L ) plusNanos(nanoseconds.toDouble()) else this }
105
+ .run { plusSecondsFix (seconds) }
106
+ .run { plusNanosFix (nanoseconds) }
80
107
}.toInstant().let (::Instant )
108
+ } catch (e: Throwable ) {
109
+ if (e.isJodaDateTimeException()) throw DateTimeArithmeticException (e)
110
+ throw e
81
111
}
82
112
83
- internal actual fun Instant.plus (value : Long , unit : CalendarUnit , zone : TimeZone ): Instant =
84
- when (unit) {
85
- CalendarUnit .YEAR -> this .value.atZone(zone.zoneId).plusYears(value).toInstant()
86
- CalendarUnit .MONTH -> this .value.atZone(zone.zoneId).plusMonths(value).toInstant()
87
- CalendarUnit .DAY -> this .value.atZone(zone.zoneId).plusDays(value).let { it as ZonedDateTime }.toInstant()
88
- CalendarUnit .HOUR -> this .value.atZone(zone.zoneId).plusHours(value).toInstant()
89
- CalendarUnit .MINUTE -> this .value.atZone(zone.zoneId).plusMinutes(value).toInstant()
90
- CalendarUnit .SECOND -> this .value.plusSeconds(value)
91
- CalendarUnit .MILLISECOND -> this .value.plusMillis(value)
92
- CalendarUnit .MICROSECOND -> this .value.plusSeconds(value / 1_000_000 ).plusNanos((value % 1_000_000 ).toInt() * 1000 )
93
- CalendarUnit .NANOSECOND -> this .value.plusNanos(value)
94
- }.let (::Instant )
113
+ // workaround for https://github.com/js-joda/js-joda/issues/431
114
+ private fun ZonedDateTime.plusSecondsFix (seconds : Long ): ZonedDateTime {
115
+ val value = seconds.toDouble()
116
+ return when {
117
+ value == 0.0 -> this
118
+ (value.unsafeCast<Int >() or 0 ) != 0 -> plusSeconds(value)
119
+ else -> {
120
+ val valueLittleLess = value.nextTowards(0.0 )
121
+ plusSeconds(valueLittleLess).plusSeconds(value - valueLittleLess)
122
+ }
123
+ }
124
+ }
125
+
126
+ // workaround for https://github.com/js-joda/js-joda/issues/431
127
+ private fun ZonedDateTime.plusNanosFix (nanoseconds : Long ): ZonedDateTime {
128
+ val value = nanoseconds.toDouble()
129
+ return when {
130
+ value == 0.0 -> this
131
+ (value.unsafeCast<Int >() or 0 ) != 0 -> plusNanos(value)
132
+ else -> {
133
+ val valueLittleLess = value.nextTowards(0.0 )
134
+ plusNanos(valueLittleLess).plusNanos(value - valueLittleLess)
135
+ }
136
+ }
137
+ }
138
+
139
+ private fun jtInstant.atZone (zone : TimeZone ): ZonedDateTime = atZone(zone.zoneId)
140
+
141
+ internal actual fun Instant.plus (value : Long , unit : CalendarUnit , zone : TimeZone ): Instant = try {
142
+ val thisZdt = this .value.atZone(zone)
143
+ when (unit) {
144
+ CalendarUnit .YEAR -> thisZdt.plusYears(value).toInstant()
145
+ CalendarUnit .MONTH -> thisZdt.plusMonths(value).toInstant()
146
+ CalendarUnit .DAY -> thisZdt.plusDays(value).let { it as ZonedDateTime }.toInstant()
147
+ CalendarUnit .HOUR -> thisZdt.plusHours(value).toInstant()
148
+ CalendarUnit .MINUTE -> thisZdt.plusMinutes(value).toInstant()
149
+ CalendarUnit .SECOND -> this .plusFix(value.toDouble(), 0 )
150
+ CalendarUnit .MILLISECOND -> this .plusFix((value / 1_000 ).toDouble(), (value % 1_000 ).toInt() * 1_000_000 ).also { it.atZone(zone) }
151
+ CalendarUnit .MICROSECOND -> this .plusFix((value / 1_000_000 ).toDouble(), (value % 1_000_000 ).toInt() * 1000 ).also { it.atZone(zone) }
152
+ CalendarUnit .NANOSECOND -> this .plusFix((value / 1_000_000_000 ).toDouble(), (value % 1_000_000_000 ).toInt()).also { it.atZone(zone) }
153
+ }.let (::Instant )
154
+ } catch (e: Throwable ) {
155
+ if (e.isJodaDateTimeException()) throw DateTimeArithmeticException (e)
156
+ throw e
157
+ }
95
158
96
159
@OptIn(ExperimentalTime ::class )
97
- public actual fun Instant.periodUntil (other : Instant , zone : TimeZone ): DateTimePeriod {
160
+ public actual fun Instant.periodUntil (other : Instant , zone : TimeZone ): DateTimePeriod = try {
98
161
var thisZdt = this .value.atZone(zone.zoneId)
99
162
val otherZdt = other.value.atZone(zone.zoneId)
100
163
@@ -105,21 +168,36 @@ public actual fun Instant.periodUntil(other: Instant, zone: TimeZone): DateTimeP
105
168
time.toComponents { hours, minutes, seconds, nanoseconds ->
106
169
return DateTimePeriod ((months / 12 ).toInt(), (months % 12 ).toInt(), days.toInt(), hours, minutes, seconds.toLong(), nanoseconds.toLong())
107
170
}
171
+ } catch (e: Throwable ) {
172
+ if (e.isJodaDateTimeException()) throw DateTimeArithmeticException (e) else throw e
173
+ }
174
+
175
+ public actual fun Instant.until (other : Instant , unit : DateTimeUnit , zone : TimeZone ): Long = try {
176
+ when (unit) {
177
+ is DateTimeUnit .DateBased ->
178
+ this .value.atZone(zone).until(other.value.atZone(zone), unit.calendarUnit.toChronoUnit()).toLong() / unit.calendarScale
179
+ is DateTimeUnit .TimeBased -> {
180
+ this .value.atZone(zone)
181
+ other.value.atZone(zone)
182
+ try {
183
+ // TODO: use fused multiplyAddDivide
184
+ safeAdd(
185
+ safeMultiply(other.epochSeconds - this .epochSeconds, LocalTime .NANOS_PER_SECOND .toLong()),
186
+ (other.nanosecondsOfSecond - this .nanosecondsOfSecond).toLong()
187
+ ) / unit.nanoseconds
188
+ } catch (e: ArithmeticException ) {
189
+ if (this < other) Long .MAX_VALUE else Long .MIN_VALUE
190
+ }
191
+ }
192
+ }
193
+ } catch (e: Throwable ) {
194
+ if (e.isJodaDateTimeException()) throw DateTimeArithmeticException (e) else throw e
108
195
}
109
- public actual fun Instant.until (other : Instant , unit : DateTimeUnit , zone : TimeZone ): Long =
110
- until(other, unit.calendarUnit.toChronoUnit(), zone.zoneId) / unit.calendarScale
111
196
112
- private fun Instant.until (other : Instant , unit : ChronoUnit , zone : ZoneId ): Long =
113
- this .value.atZone(zone).until(other.value.atZone(zone), unit).toLong()
114
197
115
198
private fun CalendarUnit.toChronoUnit (): ChronoUnit = when (this ) {
116
199
CalendarUnit .YEAR -> ChronoUnit .YEARS
117
200
CalendarUnit .MONTH -> ChronoUnit .MONTHS
118
201
CalendarUnit .DAY -> ChronoUnit .DAYS
119
- CalendarUnit .HOUR -> ChronoUnit .HOURS
120
- CalendarUnit .MINUTE -> ChronoUnit .MINUTES
121
- CalendarUnit .SECOND -> ChronoUnit .SECONDS
122
- CalendarUnit .MILLISECOND -> ChronoUnit .MILLIS
123
- CalendarUnit .MICROSECOND -> ChronoUnit .MICROS
124
- CalendarUnit .NANOSECOND -> ChronoUnit .NANOS
202
+ else -> error(" CalendarUnit $this should not be used" )
125
203
}
0 commit comments