Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package javatimefun.localdatetime.extensions

import javatimefun.ZoneIds
import java.time.DayOfWeek
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId

fun LocalDateTime.atStartOfDay(): LocalDateTime = this.withLocalTime(LocalTime.MIN)

Expand Down Expand Up @@ -40,4 +42,18 @@ fun LocalDateTime.getNext(dayOfWeek: DayOfWeek, countingInThisDay: Boolean = fal
nextLocalDate = nextLocalDate.plusDays(1)
}
return nextLocalDate
}
}

fun LocalDateTime.fromZoneToZone(fromZoneId: ZoneId, toZoneId: ZoneId): LocalDateTime {
if (fromZoneId == toZoneId) return this
return this
.atZone(fromZoneId)
.withZoneSameInstant(toZoneId)
.toLocalDateTime()
}

fun LocalDateTime.fromUtcToZone(toZoneId: ZoneId): LocalDateTime =
this.fromZoneToZone(ZoneIds.UTC, toZoneId)

fun LocalDateTime.fromZoneToUtc(fromZoneId: ZoneId): LocalDateTime =
this.fromZoneToZone(fromZoneId, ZoneIds.UTC)
92 changes: 92 additions & 0 deletions src/test/kotlin/localdatetime/LocalDateTimeMutatingTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package localdatetime

import javatimefun.ZoneIds
import javatimefun.localdatetime.extensions.fromUtcToZone
import javatimefun.localdatetime.extensions.fromZoneToUtc
import javatimefun.localdatetime.extensions.fromZoneToZone
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.time.LocalDateTime

class LocalDateTimeMutatingTest {

@Test
fun `fromZoneToZone returns same time if zones are identical`() {
val time = LocalDateTime.of(2025, 6, 15, 12, 0)
val zone = ZoneIds.AFRICA_CAIRO

val converted = time.fromZoneToZone(zone, zone)
assertEquals(time, converted, "When fromZoneId and toZoneId are the same, time should not change")
}

@Test
fun `fromZoneToZone converts correctly between different zones`() {
val timeInTokyo = LocalDateTime.of(2025, 6, 15, 12, 0) // Noon in Tokyo
val fromZone = ZoneIds.AFRICA_CAIRO
val toZone = ZoneIds.AMERICA_ARGENTINA_Catamarca

val converted = timeInTokyo.fromZoneToZone(fromZone, toZone)

// Convert via ZonedDateTime for expected result
val expected = timeInTokyo.atZone(fromZone)
.withZoneSameInstant(toZone)
.toLocalDateTime()

assertEquals(expected, converted)
}

@Test
fun `fromUtcToZone converts correctly`() {
val timeUtc = LocalDateTime.of(2025, 6, 15, 12, 0)
val toZone = ZoneIds.AFRICA_CAIRO

val converted = timeUtc.fromUtcToZone(toZone)
val expected = timeUtc.atZone(ZoneIds.UTC)
.withZoneSameInstant(toZone)
.toLocalDateTime()

assertEquals(expected, converted)
}

@Test
fun `fromZoneToUtc converts correctly`() {
val timeInTokyo = LocalDateTime.of(2025, 6, 15, 12, 0)
val fromZone = ZoneIds.AFRICA_CAIRO

val converted = timeInTokyo.fromZoneToUtc(fromZone)
val expected = timeInTokyo.atZone(fromZone)
.withZoneSameInstant(ZoneIds.UTC)
.toLocalDateTime()

assertEquals(expected, converted)
}

@Test
fun `conversion handles DST transition correctly`() {
// Example: US Eastern DST ends on Nov 2, 2014 at 2:00 am
val fromZone = ZoneIds.AMERICA_NEW_YORK
val toZone = ZoneIds.EUROPE_LONDON

// 1:30 AM EDT (before DST ends)
val timeBeforeDSTEnd = LocalDateTime.of(2014, 11, 2, 1, 30)

val convertedBefore = timeBeforeDSTEnd.fromZoneToZone(fromZone, toZone)

val expectedBefore = timeBeforeDSTEnd.atZone(fromZone)
.withZoneSameInstant(toZone)
.toLocalDateTime()

assertEquals(expectedBefore, convertedBefore)

// 2:30 AM EST (after DST ends - clocks moved back 1 hour)
val timeAfterDSTEnd = LocalDateTime.of(2014, 11, 2, 2, 30)

val convertedAfter = timeAfterDSTEnd.fromZoneToZone(fromZone, toZone)

val expectedAfter = timeAfterDSTEnd.atZone(fromZone)
.withZoneSameInstant(toZone)
.toLocalDateTime()

assertEquals(expectedAfter, convertedAfter)
}
}