diff --git a/core/common/test/InstantTest.kt b/core/common/test/InstantTest.kt index 63289a1e..dc41675c 100644 --- a/core/common/test/InstantTest.kt +++ b/core/common/test/InstantTest.kt @@ -445,6 +445,23 @@ class InstantTest { assertFalse(Instant.MIN.isDistantFuture) } + @Test + fun periodUntilDuringDstTransition() { + val tz = TimeZone.of("Europe/Berlin") + + // Test during spring forward (when clocks move forward) + val springStart = LocalDateTime(2025, 3, 29, 2, 30).toInstant(tz) + val springEnd = LocalDateTime(2025, 3, 30, 3, 10).toInstant(tz) + val springPeriod = springStart.periodUntil(springEnd, tz) + assertTrue(springPeriod.minutes >= 0, "Minutes should be non-negative during spring forward") + + // Test during fall back (when clocks move backward) + val fallStart = LocalDateTime(2024, 10, 26, 2, 30).toInstant(tz) + val fallEnd = LocalDateTime(2024, 10, 27, 3, 10).toInstant(tz) + val fallPeriod = fallStart.periodUntil(fallEnd, tz) + assertTrue(fallPeriod.minutes >= 0, "Minutes should be non-negative during fall back") + } + } class InstantRangeTest { diff --git a/core/jvm/src/Instant.kt b/core/jvm/src/Instant.kt index 735ce53d..f9ce2bf5 100644 --- a/core/jvm/src/Instant.kt +++ b/core/jvm/src/Instant.kt @@ -162,11 +162,24 @@ public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT var thisZdt = this.atZone(timeZone) val otherZdt = other.atZone(timeZone) - val months = thisZdt.until(otherZdt, ChronoUnit.MONTHS); thisZdt = thisZdt.plusMonths(months) - val days = thisZdt.until(otherZdt, ChronoUnit.DAYS); thisZdt = thisZdt.plusDays(days) - val nanoseconds = thisZdt.until(otherZdt, ChronoUnit.NANOS) - - return buildDateTimePeriod(months, days.toInt(), nanoseconds) + // Calculate date-based components first + val months = thisZdt.until(otherZdt, ChronoUnit.MONTHS) + thisZdt = thisZdt.plusMonths(months) + val days = thisZdt.until(otherZdt, ChronoUnit.DAYS) + thisZdt = thisZdt.plusDays(days) + + // Calculate time-based components using the actual duration + val remainingDuration = java.time.Duration.between(thisZdt, otherZdt) + val hours = remainingDuration.toHours() + val minutes = remainingDuration.toMinutesPart() + val seconds = remainingDuration.toSecondsPart() + val nanos = remainingDuration.toNanosPart() + + return buildDateTimePeriod( + months, + days.toInt(), + hours * 3600_000_000_000L + minutes * 60_000_000_000L + seconds * 1_000_000_000L + nanos + ) } public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long = try {