@@ -447,7 +447,37 @@ public fun String.toInstant(): Instant = Instant.parse(this)
447
447
* [LocalDateTime].
448
448
* @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod
449
449
*/
450
- public expect fun Instant.plus (period : DateTimePeriod , timeZone : TimeZone ): Instant
450
+ public fun Instant.plus (period : DateTimePeriod , timeZone : TimeZone ): Instant = try {
451
+ with (period) {
452
+ val initialOffset = offsetIn(timeZone)
453
+ val initialLdt = toLocalDateTimeFailing(initialOffset)
454
+ val instantAfterMonths: Instant
455
+ val offsetAfterMonths: UtcOffset
456
+ val ldtAfterMonths: LocalDateTime
457
+ if (totalMonths != 0L ) {
458
+ val unresolvedLdtWithMonths = initialLdt.plus(totalMonths, DateTimeUnit .MONTH )
459
+ instantAfterMonths = localDateTimeToInstant(unresolvedLdtWithMonths, timeZone, preferred = initialOffset)
460
+ offsetAfterMonths = instantAfterMonths.offsetIn(timeZone)
461
+ ldtAfterMonths = instantAfterMonths.toLocalDateTime(offsetAfterMonths)
462
+ } else {
463
+ instantAfterMonths = this @plus
464
+ offsetAfterMonths = initialOffset
465
+ ldtAfterMonths = initialLdt
466
+ }
467
+ val instantAfterMonthsAndDays = if (days != 0 ) {
468
+ val unresolvedLdtWithDays = ldtAfterMonths.plus(days, DateTimeUnit .DAY )
469
+ localDateTimeToInstant(unresolvedLdtWithDays, timeZone, preferred = offsetAfterMonths)
470
+ } else {
471
+ instantAfterMonths
472
+ }
473
+ instantAfterMonthsAndDays
474
+ .run { if (totalNanoseconds != 0L ) plus(0 , totalNanoseconds).check(timeZone) else this }
475
+ }.check(timeZone)
476
+ } catch (e: ArithmeticException ) {
477
+ throw DateTimeArithmeticException (" Arithmetic overflow when adding CalendarPeriod to an Instant" , e)
478
+ } catch (e: IllegalArgumentException ) {
479
+ throw DateTimeArithmeticException (" Boundaries of Instant exceeded when adding CalendarPeriod" , e)
480
+ }
451
481
452
482
/* *
453
483
* Returns an instant that is the result of subtracting components of [DateTimePeriod] from this instant. The components
@@ -489,7 +519,25 @@ public fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant =
489
519
* @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
490
520
* @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil
491
521
*/
492
- public expect fun Instant.periodUntil (other : Instant , timeZone : TimeZone ): DateTimePeriod
522
+ public fun Instant.periodUntil (other : Instant , timeZone : TimeZone ): DateTimePeriod {
523
+ val initialOffset = offsetIn(timeZone)
524
+ val initialLdt = toLocalDateTimeFailing(initialOffset)
525
+ val otherLdt = other.toLocalDateTimeFailing(other.offsetIn(timeZone))
526
+
527
+ val months = initialLdt.until(otherLdt, DateTimeUnit .MONTH ) // `until` on dates never fails
528
+ val unresolvedLdtWithMonths = initialLdt.plus(months, DateTimeUnit .MONTH )
529
+ // won't throw: thisLdt + months <= otherLdt, which is known to be valid
530
+ val instantWithMonths = localDateTimeToInstant(unresolvedLdtWithMonths, timeZone, preferred = initialOffset)
531
+ val offsetWithMonths = instantWithMonths.offsetIn(timeZone)
532
+ val ldtWithMonths = instantWithMonths.toLocalDateTime(offsetWithMonths)
533
+ val days = ldtWithMonths.until(otherLdt, DateTimeUnit .DAY ) // `until` on dates never fails
534
+ val unresolvedLdtWithDays = ldtWithMonths.plus(days, DateTimeUnit .DAY )
535
+ val newInstant = localDateTimeToInstant(unresolvedLdtWithDays, timeZone, preferred = initialOffset)
536
+ // won't throw: thisLdt + days <= otherLdt
537
+ val nanoseconds = newInstant.until(other, DateTimeUnit .NANOSECOND ) // |otherLdt - thisLdt| < 24h
538
+
539
+ return buildDateTimePeriod(months, days.toInt(), nanoseconds)
540
+ }
493
541
494
542
/* *
495
543
* Returns the whole number of the specified date or time [units][unit] between `this` and [other] instants
@@ -505,7 +553,15 @@ public expect fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT
505
553
* @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
506
554
* @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit
507
555
*/
508
- public expect fun Instant.until (other : Instant , unit : DateTimeUnit , timeZone : TimeZone ): Long
556
+ public fun Instant.until (other : Instant , unit : DateTimeUnit , timeZone : TimeZone ): Long =
557
+ when (unit) {
558
+ is DateTimeUnit .DateBased ->
559
+ toLocalDateTimeFailing(offsetIn(timeZone)).until(other.toLocalDateTimeFailing(other.offsetIn(timeZone)), unit)
560
+ is DateTimeUnit .TimeBased -> {
561
+ check(timeZone); other.check(timeZone)
562
+ until(other, unit)
563
+ }
564
+ }
509
565
510
566
/* *
511
567
* Returns the whole number of the specified time [units][unit] between `this` and [other] instants.
@@ -592,7 +648,8 @@ public fun Instant.minus(other: Instant, timeZone: TimeZone): DateTimePeriod =
592
648
* @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
593
649
*/
594
650
@Deprecated(" Use the plus overload with an explicit number of units" , ReplaceWith (" this.plus(1, unit, timeZone)" ))
595
- public expect fun Instant.plus (unit : DateTimeUnit , timeZone : TimeZone ): Instant
651
+ public fun Instant.plus (unit : DateTimeUnit , timeZone : TimeZone ): Instant =
652
+ plus(1L , unit, timeZone)
596
653
597
654
/* *
598
655
* Returns an instant that is the result of subtracting one [unit] from this instant
@@ -641,7 +698,8 @@ public fun Instant.minus(unit: DateTimeUnit.TimeBased): Instant =
641
698
* @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
642
699
* @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit
643
700
*/
644
- public expect fun Instant.plus (value : Int , unit : DateTimeUnit , timeZone : TimeZone ): Instant
701
+ public fun Instant.plus (value : Int , unit : DateTimeUnit , timeZone : TimeZone ): Instant =
702
+ plus(value.toLong(), unit, timeZone)
645
703
646
704
/* *
647
705
* Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant
@@ -659,7 +717,8 @@ public expect fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZon
659
717
* @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
660
718
* @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit
661
719
*/
662
- public expect fun Instant.minus (value : Int , unit : DateTimeUnit , timeZone : TimeZone ): Instant
720
+ public fun Instant.minus (value : Int , unit : DateTimeUnit , timeZone : TimeZone ): Instant =
721
+ plus(- value.toLong(), unit, timeZone)
663
722
664
723
/* *
665
724
* Returns an instant that is the result of adding the [value] number of the specified [unit] to this instant.
@@ -700,7 +759,21 @@ public fun Instant.minus(value: Int, unit: DateTimeUnit.TimeBased): Instant =
700
759
* @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
701
760
* @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit
702
761
*/
703
- public expect fun Instant.plus (value : Long , unit : DateTimeUnit , timeZone : TimeZone ): Instant
762
+ public fun Instant.plus (value : Long , unit : DateTimeUnit , timeZone : TimeZone ): Instant = try {
763
+ when (unit) {
764
+ is DateTimeUnit .DateBased -> {
765
+ val initialOffset = offsetIn(timeZone)
766
+ val initialLdt = toLocalDateTimeFailing(initialOffset)
767
+ localDateTimeToInstant(initialLdt.plus(value, unit), timeZone, preferred = initialOffset)
768
+ }
769
+ is DateTimeUnit .TimeBased ->
770
+ check(timeZone).plus(value, unit).check(timeZone)
771
+ }
772
+ } catch (e: ArithmeticException ) {
773
+ throw DateTimeArithmeticException (" Arithmetic overflow when adding to an Instant" , e)
774
+ } catch (e: IllegalArgumentException ) {
775
+ throw DateTimeArithmeticException (" Boundaries of Instant exceeded when adding a value" , e)
776
+ }
704
777
705
778
/* *
706
779
* Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant
@@ -732,7 +805,17 @@ public fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): I
732
805
*
733
806
* @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit
734
807
*/
735
- public expect fun Instant.plus (value : Long , unit : DateTimeUnit .TimeBased ): Instant
808
+ public fun Instant.plus (value : Long , unit : DateTimeUnit .TimeBased ): Instant =
809
+ try {
810
+ multiplyAndDivide(value, unit.nanoseconds, NANOS_PER_ONE .toLong()).let { (seconds, nanoseconds) ->
811
+ plus(seconds, nanoseconds)
812
+ }
813
+ } catch (_: ArithmeticException ) {
814
+ if (value > 0 ) Instant .MAX else Instant .MIN
815
+ } catch (_: IllegalArgumentException ) {
816
+ if (value > 0 ) Instant .MAX else Instant .MIN
817
+ }
818
+
736
819
737
820
/* *
738
821
* Returns an instant that is the result of subtracting the [value] number of the specified [unit] from this instant.
@@ -800,3 +883,43 @@ public fun Instant.format(format: DateTimeFormat<DateTimeComponents>, offset: Ut
800
883
801
884
internal const val DISTANT_PAST_SECONDS = - 3217862419201
802
885
internal const val DISTANT_FUTURE_SECONDS = 3093527980800
886
+
887
+ private fun Instant.toLocalDateTimeFailing (offset : UtcOffset ): LocalDateTime = try {
888
+ toLocalDateTime(offset)
889
+ } catch (e: IllegalArgumentException ) {
890
+ throw DateTimeArithmeticException (" Can not convert instant $this to LocalDateTime to perform computations" , e)
891
+ }
892
+
893
+ /* * Check that [Instant] fits in [LocalDateTime].
894
+ * This is done on the results of computations for consistency with other platforms.
895
+ */
896
+ private fun Instant.check (zone : TimeZone ): Instant = this @check.also {
897
+ toLocalDateTimeFailing(offsetIn(zone))
898
+ }
899
+
900
+ private fun LocalDateTime.plus (value : Long , unit : DateTimeUnit .DateBased ) =
901
+ date.plus(value, unit).atTime(time)
902
+
903
+ private fun LocalDateTime.plus (value : Int , unit : DateTimeUnit .DateBased ) =
904
+ date.plus(value, unit).atTime(time)
905
+
906
+ /* *
907
+ * @throws ArithmeticException if arithmetic overflow occurs
908
+ * @throws IllegalArgumentException if the boundaries of Instant are overflown
909
+ */
910
+ internal expect fun Instant.plus (secondsToAdd : Long , nanosToAdd : Long ): Instant
911
+
912
+ // org.threeten.bp.LocalDateTime#until
913
+ internal fun LocalDateTime.until (other : LocalDateTime , unit : DateTimeUnit .DateBased ): Long {
914
+ val otherDate = other.date
915
+ val delta = when {
916
+ otherDate > date && other.time < time -> - 1 // addition won't throw: endDate - date >= 1
917
+ otherDate < date && other.time > time -> 1 // addition won't throw: date - endDate >= 1
918
+ else -> 0
919
+ }
920
+ val endDate = otherDate.plus(delta, DateTimeUnit .DAY )
921
+ return when (unit) {
922
+ is DateTimeUnit .MonthBased -> date.until(endDate, DateTimeUnit .MONTH ) / unit.months
923
+ is DateTimeUnit .DayBased -> date.until(endDate, DateTimeUnit .DAY ) / unit.days
924
+ }
925
+ }
0 commit comments