diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala index 06ab4b603f028..58a52475b624f 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala @@ -765,6 +765,9 @@ object IntervalUtils { new CalendarInterval(totalMonths, totalDays, micros) } + // The amount of seconds that can cause overflow in the conversion to microseconds + private final val minDurationSeconds = Math.floorDiv(Long.MinValue, MICROS_PER_SECOND) + /** * Converts this duration to the total length in microseconds. *

@@ -779,9 +782,18 @@ object IntervalUtils { * @throws ArithmeticException If numeric overflow occurs */ def durationToMicros(duration: Duration): Long = { - val us = Math.multiplyExact(duration.getSeconds, MICROS_PER_SECOND) - val result = Math.addExact(us, duration.getNano / NANOS_PER_MICROS) - result + val seconds = duration.getSeconds + if (seconds == minDurationSeconds) { + val microsInSeconds = (minDurationSeconds + 1) * MICROS_PER_SECOND + val nanoAdjustment = duration.getNano + assert(0 <= nanoAdjustment && nanoAdjustment < NANOS_PER_SECOND, + "Duration.getNano() must return the adjustment to the seconds field " + + "in the range from 0 to 999999999 nanoseconds, inclusive.") + Math.addExact(microsInSeconds, (nanoAdjustment - NANOS_PER_SECOND) / NANOS_PER_MICROS) + } else { + val microsInSeconds = Math.multiplyExact(seconds, MICROS_PER_SECOND) + Math.addExact(microsInSeconds, duration.getNano / NANOS_PER_MICROS) + } } /** diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala index df2656fb7aa87..607337383a78a 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala @@ -425,4 +425,20 @@ class IntervalUtilsSuite extends SparkFunSuite with SQLHelper { assert(monthsToPeriod(Int.MaxValue) === Period.ofYears(178956970).withMonths(7)) assert(monthsToPeriod(Int.MinValue) === Period.ofYears(-178956970).withMonths(-8)) } + + test("SPARK-34695: round trip conversion of micros -> duration -> micros") { + Seq( + 0, + MICROS_PER_SECOND - 1, + -MICROS_PER_SECOND + 1, + MICROS_PER_SECOND, + -MICROS_PER_SECOND, + Long.MaxValue - MICROS_PER_SECOND, + Long.MinValue + MICROS_PER_SECOND, + Long.MaxValue, + Long.MinValue).foreach { micros => + val duration = microsToDuration(micros) + assert(durationToMicros(duration) === micros) + } + } }