@@ -15,6 +15,7 @@ import kotlinx.serialization.Serializable
15
15
import kotlin.time.*
16
16
import kotlin.time.Duration.Companion.nanoseconds
17
17
import kotlin.time.Duration.Companion.seconds
18
+ import kotlin.math.absoluteValue
18
19
19
20
public actual enum class DayOfWeek {
20
21
MONDAY ,
@@ -153,6 +154,253 @@ public actual class Instant internal constructor(public actual val epochSeconds:
153
154
154
155
}
155
156
157
+ private class UnboundedLocalDateTime (
158
+ val year : Int ,
159
+ val month : Int ,
160
+ val day : Int ,
161
+ val hour : Int ,
162
+ val minute : Int ,
163
+ val second : Int ,
164
+ val nanosecond : Int ,
165
+ ) {
166
+ fun toInstant (offsetSeconds : Int ): Instant {
167
+ fun isLeapYear (year : Int ): Boolean =
168
+ year and 3 == 0 && (year % 100 != 0 || year % 400 == 0 )
169
+ val epochSeconds = run {
170
+ // org.threeten.bp.LocalDate#toEpochDay
171
+ val epochDays = run {
172
+ val y = year
173
+ var total = 365 * y
174
+ if (y >= 0 ) {
175
+ total + = (y + 3 ) / 4 - (y + 99 ) / 100 + (y + 399 ) / 400
176
+ } else {
177
+ total - = y / - 4 - y / - 100 + y / - 400
178
+ }
179
+ total + = ((367 * month - 362 ) / 12 )
180
+ total + = day - 1
181
+ if (month > 2 ) {
182
+ total--
183
+ if (! isLeapYear(year)) {
184
+ total--
185
+ }
186
+ }
187
+ total - DAYS_0000_TO_1970
188
+ }
189
+ // org.threeten.bp.LocalTime#toSecondOfDay
190
+ val daySeconds = hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second
191
+ // org.threeten.bp.chrono.ChronoLocalDateTime#toEpochSecond
192
+ epochDays * 86400L + daySeconds - offsetSeconds
193
+ }
194
+ return Instant .fromEpochSeconds(epochSeconds, nanosecond)
195
+ }
196
+
197
+ companion object {
198
+ fun fromInstant (instant : Instant , offsetSeconds : Int ): UnboundedLocalDateTime {
199
+ val localSecond: Long = instant.epochSeconds + offsetSeconds
200
+ val epochDays = localSecond.floorDiv(SECONDS_PER_DAY .toLong()).toInt()
201
+ val secsOfDay = localSecond.mod(SECONDS_PER_DAY .toLong()).toInt()
202
+ val year: Int
203
+ val month: Int
204
+ val day: Int
205
+ // org.threeten.bp.LocalDate#toEpochDay
206
+ run {
207
+ var zeroDay = epochDays + DAYS_0000_TO_1970
208
+ // find the march-based year
209
+ zeroDay - = 60 // adjust to 0000-03-01 so leap day is at end of four year cycle
210
+
211
+ var adjust = 0
212
+ if (zeroDay < 0 ) { // adjust negative years to positive for calculation
213
+ val adjustCycles = (zeroDay + 1 ) / DAYS_PER_CYCLE - 1
214
+ adjust = adjustCycles * 400
215
+ zeroDay + = - adjustCycles * DAYS_PER_CYCLE
216
+ }
217
+ var yearEst = ((400 * zeroDay.toLong() + 591 ) / DAYS_PER_CYCLE ).toInt()
218
+ var doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400 )
219
+ if (doyEst < 0 ) { // fix estimate
220
+ yearEst--
221
+ doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400 )
222
+ }
223
+ yearEst + = adjust // reset any negative year
224
+
225
+ val marchDoy0 = doyEst
226
+
227
+ // convert march-based values back to january-based
228
+ val marchMonth0 = (marchDoy0 * 5 + 2 ) / 153
229
+ month = (marchMonth0 + 2 ) % 12 + 1
230
+ day = marchDoy0 - (marchMonth0 * 306 + 5 ) / 10 + 1
231
+ year = yearEst + marchMonth0 / 10
232
+ }
233
+ val hours = (secsOfDay / SECONDS_PER_HOUR )
234
+ val secondWithoutHours = secsOfDay - hours * SECONDS_PER_HOUR
235
+ val minutes = (secondWithoutHours / SECONDS_PER_MINUTE )
236
+ val second = secondWithoutHours - minutes * SECONDS_PER_MINUTE
237
+ return UnboundedLocalDateTime (year, month, day, hours, minutes, second, instant.nanosecondsOfSecond)
238
+ }
239
+ }
240
+ }
241
+
242
+ internal fun parseIso (isoString : String ): Instant {
243
+ val s = isoString
244
+ var i = 0
245
+ if (s.isEmpty()) {
246
+ TODO ()
247
+ }
248
+ val yearSign = when (s[i]) {
249
+ ' +' -> { ++ i; ' +' }
250
+ ' -' -> { ++ i; ' -' }
251
+ else -> ' '
252
+ }
253
+ val yearStart = i
254
+ var absYear = 0
255
+ while (i < s.length && s[i] in ' 0' .. ' 9' ) {
256
+ absYear = absYear * 10 + (s[i] - ' 0' )
257
+ ++ i
258
+ }
259
+ val year = when {
260
+ i - yearStart > 9 -> {
261
+ TODO ()
262
+ }
263
+ i - yearStart < 4 -> {
264
+ TODO ()
265
+ }
266
+ else -> {
267
+ if (yearSign == ' +' && i - yearStart == 4 ) {
268
+ TODO ()
269
+ }
270
+ if (yearSign == ' ' && i - yearStart != 4 ) {
271
+ TODO ()
272
+ }
273
+ if (yearSign == ' -' ) - absYear else absYear
274
+ }
275
+ }
276
+ // reading at least -MM-DDTHH:MM:SSZ
277
+ // 0123456789012345 16 chars
278
+ if (s.length < i + 16 ) {
279
+ TODO ()
280
+ }
281
+ if (s[i] != ' -' ) { TODO () }
282
+ if (s[i + 3 ] != ' -' ) { TODO () }
283
+ if (s[i + 6 ] != ' T' && s[i + 6 ] != ' t' ) { TODO () }
284
+ if (s[i + 9 ] != ' :' ) { TODO () }
285
+ if (s[i + 12 ] != ' :' ) { TODO () }
286
+ for (j in listOf (1 , 2 , 4 , 5 , 7 , 8 , 10 , 11 , 13 , 14 )) {
287
+ if (s[i + j] !in ' 0' .. ' 9' ) {
288
+ TODO ()
289
+ }
290
+ }
291
+ fun twoDigitNumber (index : Int ) = s[index].code * 10 + s[index + 1 ].code - ' 0' .code * 11
292
+ val month = twoDigitNumber(i + 1 )
293
+ val day = twoDigitNumber(i + 4 )
294
+ val hour = twoDigitNumber(i + 7 )
295
+ val minute = twoDigitNumber(i + 10 )
296
+ val second = twoDigitNumber(i + 13 )
297
+ val nanosecond = if (s[i + 15 ] == ' .' ) {
298
+ val fractionStart = i + 16
299
+ i = fractionStart
300
+ var fraction = 0
301
+ while (i < s.length && s[i] in ' 0' .. ' 9' ) {
302
+ fraction = fraction * 10 + (s[i] - ' 0' )
303
+ ++ i
304
+ }
305
+ if (i <= fractionStart + 9 ) {
306
+ fraction * POWERS_OF_TEN [fractionStart + 9 - i]
307
+ } else {
308
+ TODO ()
309
+ }
310
+ } else {
311
+ i + = 15
312
+ 0
313
+ }
314
+ val offsetSeconds = when (val sign = s.getOrNull(i)) {
315
+ null -> {
316
+ TODO ()
317
+ }
318
+ ' z' , ' Z' -> if (s.length == i + 1 ) {
319
+ 0
320
+ } else {
321
+ TODO ()
322
+ }
323
+ ' -' , ' +' -> {
324
+ val offsetStrLength = s.length - i
325
+ if (offsetStrLength % 3 != 0 ) { TODO () }
326
+ if (offsetStrLength > 9 ) { TODO () }
327
+ for (j in listOf (3 , 6 )) {
328
+ if (s.getOrNull(i + j) ? : break != ' :' )
329
+ TODO (" Expected ':' at index ${i + j} , got '${s[i + j]} ' when parsing instant from $s " )
330
+ }
331
+ for (j in listOf (1 , 2 , 4 , 5 , 7 , 8 )) {
332
+ if (s.getOrNull(i + j) ? : break !in ' 0' .. ' 9' )
333
+ TODO (" Expected a digit at index ${i + j} , got '${s[i + j]} ' when parsing instant from $s " )
334
+ }
335
+ val offsetHour = twoDigitNumber(i + 1 )
336
+ val offsetMinute = if (offsetStrLength > 3 ) { twoDigitNumber(i + 4 ) } else { 0 }
337
+ val offsetSecond = if (offsetStrLength > 6 ) { twoDigitNumber(i + 7 ) } else { 0 }
338
+ if (offsetMinute !in 0 .. 59 ) { TODO (" Expected offset-minute-of-hour in 0..59, got $offsetMinute when parsing instant from $s " ) }
339
+ if (offsetSecond !in 0 .. 59 ) { TODO (" Expected offset-second-of-minute in 0..59, got $offsetSecond when parsing instant from $s " ) }
340
+ if (offsetHour !in 0 .. 17 && ! (offsetHour == 18 && offsetMinute == 0 && offsetMinute == 0 )) {
341
+ TODO ()
342
+ }
343
+ (offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == ' -' ) - 1 else 1
344
+ }
345
+ else -> {
346
+ TODO ()
347
+ }
348
+ }
349
+ if (month !in 1 .. 12 ) { TODO () }
350
+ if (day !in 1 .. month.monthLength(isLeapYear(year))) { TODO () }
351
+ if (hour !in 0 .. 23 ) { TODO () }
352
+ if (minute !in 0 .. 59 ) { TODO () }
353
+ if (second !in 0 .. 59 ) { TODO () }
354
+ return UnboundedLocalDateTime (year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds)
355
+ }
356
+
357
+ internal fun formatIso (instant : Instant ): String = buildString {
358
+ val ldt = UnboundedLocalDateTime .fromInstant(instant, 0 )
359
+ fun Appendable.appendTwoDigits (number : Int ) {
360
+ if (number < 10 ) append(' 0' )
361
+ append(number)
362
+ }
363
+ run {
364
+ val number = ldt.year
365
+ when {
366
+ number.absoluteValue < 1_000 -> {
367
+ val innerBuilder = StringBuilder ()
368
+ if (number >= 0 ) {
369
+ innerBuilder.append((number + 10_000 )).deleteAt(0 )
370
+ } else {
371
+ innerBuilder.append((number - 10_000 )).deleteAt(1 )
372
+ }
373
+ append(innerBuilder)
374
+ }
375
+ else -> {
376
+ if (number >= 10_000 ) append(' +' )
377
+ append(number)
378
+ }
379
+ }
380
+ }
381
+ append(' -' )
382
+ appendTwoDigits(ldt.month)
383
+ append(' -' )
384
+ appendTwoDigits(ldt.day)
385
+ append(' T' )
386
+ appendTwoDigits(ldt.hour)
387
+ append(' :' )
388
+ appendTwoDigits(ldt.minute)
389
+ append(' :' )
390
+ appendTwoDigits(ldt.second)
391
+ if (ldt.nanosecond != 0 ) {
392
+ append(' .' )
393
+ var zerosToStrip = 0
394
+ while (ldt.nanosecond % POWERS_OF_TEN [zerosToStrip + 1 ] == 0 ) {
395
+ ++ zerosToStrip
396
+ }
397
+ zerosToStrip - = (zerosToStrip.mod(3 )) // rounding down to a multiple of 3
398
+ val numberToOutput = ldt.nanosecond / POWERS_OF_TEN [zerosToStrip]
399
+ append((numberToOutput + POWERS_OF_TEN [9 - zerosToStrip]).toString().substring(1 ))
400
+ }
401
+ append(' Z' )
402
+ }
403
+
156
404
private fun Instant.toZonedDateTimeFailing (zone : TimeZone ): ZonedDateTime = try {
157
405
toZonedDateTime(zone)
158
406
} catch (e: IllegalArgumentException ) {
0 commit comments