@@ -447,7 +447,37 @@ public fun String.toInstant(): Instant = Instant.parse(this)
447447 * [LocalDateTime].
448448 * @sample kotlinx.datetime.test.samples.InstantSamples.plusPeriod
449449 */
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+ }
451481
452482/* *
453483 * 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 =
489519 * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
490520 * @sample kotlinx.datetime.test.samples.InstantSamples.periodUntil
491521 */
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+ }
493541
494542/* *
495543 * 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
505553 * @throws DateTimeArithmeticException if `this` or [other] instant is too large to fit in [LocalDateTime].
506554 * @sample kotlinx.datetime.test.samples.InstantSamples.untilAsDateTimeUnit
507555 */
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+ }
509565
510566/* *
511567 * 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 =
592648 * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
593649 */
594650@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)
596653
597654/* *
598655 * 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 =
641698 * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
642699 * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit
643700 */
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)
645703
646704/* *
647705 * 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
659717 * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
660718 * @sample kotlinx.datetime.test.samples.InstantSamples.minusDateTimeUnit
661719 */
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)
663722
664723/* *
665724 * 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 =
700759 * @throws DateTimeArithmeticException if this value or the result is too large to fit in [LocalDateTime].
701760 * @sample kotlinx.datetime.test.samples.InstantSamples.plusDateTimeUnit
702761 */
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+ }
704777
705778/* *
706779 * 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
732805 *
733806 * @sample kotlinx.datetime.test.samples.InstantSamples.plusTimeBasedUnit
734807 */
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+
736819
737820/* *
738821 * 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
800883
801884internal const val DISTANT_PAST_SECONDS = - 3217862419201
802885internal 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