Skip to content

Commit d06c37b

Browse files
committed
Represent normalizable time zones with FixedOffsetTimeZone
1 parent a35ae66 commit d06c37b

File tree

5 files changed

+54
-21
lines changed

5 files changed

+54
-21
lines changed

core/common/test/TimeZoneTest.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ class TimeZoneTest {
106106
@Test
107107
fun utcOffsetNormalization() {
108108
val sameOffsetTZs = listOf("+04", "+04:00", "UTC+4", "UT+04", "GMT+04:00:00").map { TimeZone.of(it) }
109-
val instant = Instant.fromEpochSeconds(0)
110-
val offsets = sameOffsetTZs.map { it.offsetAt(instant) }
109+
for (tz in sameOffsetTZs) {
110+
assertIs<FixedOffsetTimeZone>(tz)
111+
}
112+
val offsets = sameOffsetTZs.map { (it as FixedOffsetTimeZone).offset }
113+
val zoneIds = sameOffsetTZs.map { it.id }
111114

112115
assertTrue(offsets.distinct().size == 1, "Expected all offsets to be equal: $offsets")
113116
assertTrue(offsets.map { it.toString() }.distinct().size == 1, "Expected all offsets to have the same string representation: $offsets")
117+
118+
assertTrue(zoneIds.distinct().size > 1, "Expected some fixed offset zones to have different ids: $zoneIds")
114119
}
115120

116121
// from 310bp

core/js/src/TimeZone.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,38 @@ public actual open class TimeZone internal constructor(internal val zoneId: Zone
2626
override fun toString(): String = zoneId.toString()
2727

2828
public actual companion object {
29-
public actual fun currentSystemDefault(): TimeZone = ZoneId.systemDefault().let(::TimeZone)
29+
public actual fun currentSystemDefault(): TimeZone = ofZone(ZoneId.systemDefault())
3030
public actual val UTC: FixedOffsetTimeZone = UtcOffset(jtZoneOffset.UTC).asTimeZone()
3131

3232
public actual fun of(zoneId: String): TimeZone = try {
33-
val zone = ZoneId.of(zoneId)
34-
if (zone is jtZoneOffset) {
35-
FixedOffsetTimeZone(UtcOffset(zone))
36-
} else {
37-
TimeZone(zone)
38-
}
33+
ofZone(ZoneId.of(zoneId))
3934
} catch (e: Throwable) {
4035
if (e.isJodaDateTimeException()) throw IllegalTimeZoneException(e)
4136
throw e
4237
}
4338

39+
private fun ofZone(zoneId: ZoneId): TimeZone = when {
40+
zoneId is jtZoneOffset ->
41+
FixedOffsetTimeZone(UtcOffset(zoneId))
42+
zoneId.rules().isFixedOffset() ->
43+
FixedOffsetTimeZone(UtcOffset(zoneId.normalized() as jtZoneOffset), zoneId)
44+
else ->
45+
TimeZone(zoneId)
46+
}
47+
48+
4449
public actual val availableZoneIds: Set<String> get() = ZoneId.getAvailableZoneIds().toSet()
4550
}
4651
}
4752

4853
@Serializable(with = FixedOffsetTimeZoneSerializer::class)
49-
public actual class FixedOffsetTimeZone actual constructor(public actual val offset: UtcOffset): TimeZone(offset.zoneOffset) {
50-
private val zoneOffset get() = zoneId as jtZoneOffset
54+
public actual class FixedOffsetTimeZone
55+
internal constructor(public actual val offset: UtcOffset, zoneId: ZoneId): TimeZone(zoneId) {
56+
57+
public actual constructor(offset: UtcOffset) : this(offset, offset.zoneOffset)
5158

5259
@Deprecated("Use offset.totalSeconds", ReplaceWith("offset.totalSeconds"))
53-
public actual val totalSeconds: Int get() = zoneOffset.totalSeconds().toInt()
60+
public actual val totalSeconds: Int get() = offset.totalSeconds
5461
}
5562

5663

core/jvm/src/Converters.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public fun TimeZone.toJavaZoneId(): java.time.ZoneId = this.zoneId
5757
/**
5858
* Converts this [java.time.ZoneId][java.time.ZoneId] value to a [kotlinx.datetime.TimeZone][TimeZone] value.
5959
*/
60-
public fun java.time.ZoneId.toKotlinTimeZone(): TimeZone = TimeZone(this)
60+
public fun java.time.ZoneId.toKotlinTimeZone(): TimeZone = TimeZone.ofZone(this)
6161

6262

6363
/**

core/jvm/src/TimeZoneJvm.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,35 @@ public actual open class TimeZone internal constructor(internal val zoneId: Zone
3131
override fun toString(): String = zoneId.toString()
3232

3333
public actual companion object {
34-
public actual fun currentSystemDefault(): TimeZone = ZoneId.systemDefault().let(::TimeZone)
34+
public actual fun currentSystemDefault(): TimeZone = ofZone(ZoneId.systemDefault())
3535
public actual val UTC: FixedOffsetTimeZone = UtcOffset(jtZoneOffset.UTC).asTimeZone()
3636

3737
public actual fun of(zoneId: String): TimeZone = try {
38-
val zone = ZoneId.of(zoneId)
39-
if (zone is jtZoneOffset) {
40-
FixedOffsetTimeZone(UtcOffset(zone))
41-
} else {
42-
TimeZone(zone)
43-
}
38+
ofZone(ZoneId.of(zoneId))
4439
} catch (e: Exception) {
4540
if (e is DateTimeException) throw IllegalTimeZoneException(e)
4641
throw e
4742
}
4843

44+
internal fun ofZone(zoneId: ZoneId): TimeZone = when {
45+
zoneId is jtZoneOffset ->
46+
FixedOffsetTimeZone(UtcOffset(zoneId))
47+
zoneId.rules.isFixedOffset ->
48+
FixedOffsetTimeZone(UtcOffset(zoneId.normalized() as jtZoneOffset), zoneId)
49+
else ->
50+
TimeZone(zoneId)
51+
}
52+
4953
public actual val availableZoneIds: Set<String> get() = ZoneId.getAvailableZoneIds()
5054
}
5155
}
5256

5357
@Serializable(with = FixedOffsetTimeZoneSerializer::class)
5458
public actual class FixedOffsetTimeZone
55-
public actual constructor(public actual val offset: UtcOffset): TimeZone(offset.zoneOffset) {
59+
internal constructor(public actual val offset: UtcOffset, zoneId: ZoneId): TimeZone(zoneId) {
60+
61+
public actual constructor(offset: UtcOffset) : this(offset, offset.zoneOffset)
62+
5663
@Deprecated("Use offset.totalSeconds", ReplaceWith("offset.totalSeconds"))
5764
public actual val totalSeconds: Int get() = offset.totalSeconds
5865
}

core/jvm/test/ConvertersTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ class ConvertersTest {
126126
test("Europe/Berlin")
127127
}
128128

129+
@Test
130+
fun fixedOffsetTimeZone() {
131+
val zone = TimeZone.of("UTC") as FixedOffsetTimeZone
132+
133+
val jtZone = zone.toJavaZoneId()
134+
val jtZoneOffset = zone.toJavaZoneOffset()
135+
136+
assertEquals(zone.id, jtZone.id)
137+
assertNotEquals(jtZone, jtZoneOffset)
138+
assertEquals(jtZone.normalized(), jtZoneOffset)
139+
140+
assertIs<FixedOffsetTimeZone>(jtZone.toKotlinTimeZone())
141+
}
142+
129143
@Test
130144
fun zoneOffset() {
131145
fun test(offsetString: String) {

0 commit comments

Comments
 (0)