Skip to content

Commit 498bf5c

Browse files
committed
Split ZoneOffset into UtcOffset and FixedOffsetTimeZone classes
Extract some former native-only ZoneOffset tests into common UtcOffset tests.
1 parent ff43dd6 commit 498bf5c

27 files changed

+562
-370
lines changed

core/common/src/Instant.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,4 +483,4 @@ internal const val DISTANT_FUTURE_SECONDS = 3093527980800
483483
*
484484
* Be careful: this function may throw for some values of the [Instant].
485485
*/
486-
internal expect fun Instant.toStringWithOffset(offset: ZoneOffset): String
486+
internal expect fun Instant.toStringWithOffset(offset: UtcOffset): String

core/common/src/TimeZone.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
package kotlinx.datetime
1010

11-
import kotlinx.datetime.serializers.TimeZoneSerializer
12-
import kotlinx.datetime.serializers.ZoneOffsetSerializer
11+
import kotlinx.datetime.serializers.*
1312
import kotlinx.serialization.Serializable
1413

1514
@Serializable(with = TimeZoneSerializer::class)
@@ -85,18 +84,25 @@ public expect open class TimeZone {
8584
public fun LocalDateTime.toInstant(): Instant
8685
}
8786

88-
@Serializable(with = ZoneOffsetSerializer::class)
89-
public expect class ZoneOffset : TimeZone {
87+
@Serializable(with = FixedOffsetTimeZoneSerializer::class)
88+
public expect class FixedOffsetTimeZone : TimeZone {
89+
public constructor(utcOffset: UtcOffset)
90+
public val utcOffset: UtcOffset
91+
92+
@Deprecated("Use utcOffset.totalSeconds", ReplaceWith("utcOffset.totalSeconds"))
9093
public val totalSeconds: Int
9194
}
9295

96+
@Deprecated("Use FixedOffsetTimeZone of UtcOffset instead", ReplaceWith("FixedOffsetTimeZone"))
97+
public typealias ZoneOffset = FixedOffsetTimeZone
98+
9399
/**
94100
* Finds the offset from UTC this time zone has at the specified [instant] of physical time.
95101
*
96102
* @see Instant.toLocalDateTime
97103
* @see TimeZone.offsetAt
98104
*/
99-
public expect fun TimeZone.offsetAt(instant: Instant): ZoneOffset
105+
public expect fun TimeZone.offsetAt(instant: Instant): UtcOffset
100106

101107
/**
102108
* Return a civil date/time value that this instant has in the specified [timeZone].
@@ -110,13 +116,16 @@ public expect fun TimeZone.offsetAt(instant: Instant): ZoneOffset
110116
*/
111117
public expect fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime
112118

119+
public expect fun Instant.toLocalDateTime(utcOffset: UtcOffset): LocalDateTime
120+
121+
113122
/**
114123
* Finds the offset from UTC the specified [timeZone] has at this instant of physical time.
115124
*
116125
* @see Instant.toLocalDateTime
117126
* @see TimeZone.offsetAt
118127
*/
119-
public fun Instant.offsetIn(timeZone: TimeZone): ZoneOffset =
128+
public fun Instant.offsetIn(timeZone: TimeZone): UtcOffset =
120129
timeZone.offsetAt(this)
121130

122131
/**
@@ -135,6 +144,8 @@ public fun Instant.offsetIn(timeZone: TimeZone): ZoneOffset =
135144
*/
136145
public expect fun LocalDateTime.toInstant(timeZone: TimeZone): Instant
137146

147+
public expect fun LocalDateTime.toInstant(utcOffset: UtcOffset): Instant
148+
138149
/**
139150
* Returns an instant that corresponds to the start of this date in the specified [timeZone].
140151
*

core/common/src/UtcOffset.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2019-2021 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.datetime
7+
8+
public expect class UtcOffset {
9+
public val totalSeconds: Int
10+
11+
public companion object {
12+
public fun parse(offsetString: String): UtcOffset
13+
}
14+
}
15+
16+
public fun UtcOffset.asTimeZone(): FixedOffsetTimeZone = FixedOffsetTimeZone(this)

core/common/src/serializers/TimeZoneSerializers.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
package kotlinx.datetime.serializers
77

8+
import kotlinx.datetime.FixedOffsetTimeZone
89
import kotlinx.datetime.TimeZone
9-
import kotlinx.datetime.ZoneOffset
1010
import kotlinx.serialization.*
1111
import kotlinx.serialization.descriptors.*
1212
import kotlinx.serialization.encoding.*
@@ -23,20 +23,20 @@ public object TimeZoneSerializer: KSerializer<TimeZone> {
2323

2424
}
2525

26-
public object ZoneOffsetSerializer: KSerializer<ZoneOffset> {
26+
public object FixedOffsetTimeZoneSerializer: KSerializer<FixedOffsetTimeZone> {
2727

2828
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ZoneOffset", PrimitiveKind.STRING)
2929

30-
override fun deserialize(decoder: Decoder): ZoneOffset {
30+
override fun deserialize(decoder: Decoder): FixedOffsetTimeZone {
3131
val zone = TimeZone.of(decoder.decodeString())
32-
if (zone is ZoneOffset) {
32+
if (zone is FixedOffsetTimeZone) {
3333
return zone
3434
} else {
3535
throw SerializationException("Timezone identifier '$zone' does not correspond to a fixed-offset timezone")
3636
}
3737
}
3838

39-
override fun serialize(encoder: Encoder, value: ZoneOffset) {
39+
override fun serialize(encoder: Encoder, value: FixedOffsetTimeZone) {
4040
encoder.encodeString(value.id)
4141
}
4242

core/common/test/InstantTest.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@ class InstantTest {
112112
Instant.fromEpochSeconds(0, 0))
113113

114114
val offsets = listOf(
115-
TimeZone.of("Z") as ZoneOffset,
116-
TimeZone.of("+03:12:14") as ZoneOffset,
117-
TimeZone.of("-03:12:14") as ZoneOffset,
118-
TimeZone.of("+02:35") as ZoneOffset,
119-
TimeZone.of("-02:35") as ZoneOffset,
120-
TimeZone.of("+04") as ZoneOffset,
121-
TimeZone.of("-04") as ZoneOffset,
115+
UtcOffset.parse("Z"),
116+
UtcOffset.parse("+03:12:14"),
117+
UtcOffset.parse("-03:12:14"),
118+
UtcOffset.parse("+02:35"),
119+
UtcOffset.parse("-02:35"),
120+
UtcOffset.parse("+04"),
121+
UtcOffset.parse("-04"),
122122
)
123123

124124
for (instant in instants) {

core/common/test/TimeZoneTest.kt

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ class TimeZoneTest {
5555

5656
assertFailsWith<IllegalTimeZoneException> { TimeZone.of("Mars/Standard") }
5757
assertFailsWith<IllegalTimeZoneException> { TimeZone.of("UTC+X") }
58+
}
5859

60+
@Test
61+
fun ofFailsOnInvalidOffset() {
62+
for (v in UtcOffsetTest.invalidUtcOffsetStrings) {
63+
assertFailsWith<IllegalTimeZoneException> { TimeZone.of(v) }
64+
}
5965
}
6066

6167
// from 310bp
@@ -97,69 +103,82 @@ class TimeZoneTest {
97103
@Test
98104
fun newYorkOffset() {
99105
val test = TimeZone.of("America/New_York")
100-
val offset = TimeZone.of("-5")
101-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 1, 1).offsetIn(test))
102-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 2, 1).offsetIn(test))
103-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 3, 1).offsetIn(test))
104-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 4, 1).offsetIn(test))
105-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 5, 1).offsetIn(test))
106-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 6, 1).offsetIn(test))
107-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 7, 1).offsetIn(test))
108-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 8, 1).offsetIn(test))
109-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 9, 1).offsetIn(test))
110-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 10, 1).offsetIn(test))
111-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 11, 1).offsetIn(test))
112-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 12, 1).offsetIn(test))
113-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 1, 28).offsetIn(test))
114-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 2, 28).offsetIn(test))
115-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 28).offsetIn(test))
116-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 4, 28).offsetIn(test))
117-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 5, 28).offsetIn(test))
118-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 6, 28).offsetIn(test))
119-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 7, 28).offsetIn(test))
120-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 8, 28).offsetIn(test))
121-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 9, 28).offsetIn(test))
122-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 10, 28).offsetIn(test))
123-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 28).offsetIn(test))
124-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 12, 28).offsetIn(test))
106+
val offset = UtcOffset.parse("-5")
107+
108+
fun check(expectedOffset: String, dateTime: LocalDateTime) {
109+
assertEquals(UtcOffset.parse(expectedOffset), dateTime.toInstant(offset).offsetIn(test))
110+
}
111+
112+
check("-5", LocalDateTime(2008, 1, 1))
113+
check("-5", LocalDateTime(2008, 2, 1))
114+
check("-5", LocalDateTime(2008, 3, 1))
115+
check("-4", LocalDateTime(2008, 4, 1))
116+
check("-4", LocalDateTime(2008, 5, 1))
117+
check("-4", LocalDateTime(2008, 6, 1))
118+
check("-4", LocalDateTime(2008, 7, 1))
119+
check("-4", LocalDateTime(2008, 8, 1))
120+
check("-4", LocalDateTime(2008, 9, 1))
121+
check("-4", LocalDateTime(2008, 10, 1))
122+
check("-4", LocalDateTime(2008, 11, 1))
123+
check("-5", LocalDateTime(2008, 12, 1))
124+
check("-5", LocalDateTime(2008, 1, 28))
125+
check("-5", LocalDateTime(2008, 2, 28))
126+
check("-4", LocalDateTime(2008, 3, 28))
127+
check("-4", LocalDateTime(2008, 4, 28))
128+
check("-4", LocalDateTime(2008, 5, 28))
129+
check("-4", LocalDateTime(2008, 6, 28))
130+
check("-4", LocalDateTime(2008, 7, 28))
131+
check("-4", LocalDateTime(2008, 8, 28))
132+
check("-4", LocalDateTime(2008, 9, 28))
133+
check("-4", LocalDateTime(2008, 10, 28))
134+
check("-5", LocalDateTime(2008, 11, 28))
135+
check("-5", LocalDateTime(2008, 12, 28))
125136
}
126137

127138
// from 310bp
128139
@Test
129140
fun newYorkOffsetToDST() {
130141
val test = TimeZone.of("America/New_York")
131-
val offset = TimeZone.of("-5")
132-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 3, 8).offsetIn(test))
133-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 3, 9).offsetIn(test))
134-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 10).offsetIn(test))
135-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 11).offsetIn(test))
136-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 12).offsetIn(test))
137-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 13).offsetIn(test))
138-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 14).offsetIn(test))
142+
val offset = UtcOffset.parse("-5")
143+
144+
fun check(expectedOffset: String, dateTime: LocalDateTime) {
145+
assertEquals(UtcOffset.parse(expectedOffset), dateTime.toInstant(offset).offsetIn(test))
146+
}
147+
148+
check("-5", LocalDateTime(2008, 3, 8))
149+
check("-5", LocalDateTime(2008, 3, 9))
150+
check("-4", LocalDateTime(2008, 3, 10))
151+
check("-4", LocalDateTime(2008, 3, 11))
152+
check("-4", LocalDateTime(2008, 3, 12))
153+
check("-4", LocalDateTime(2008, 3, 13))
154+
check("-4", LocalDateTime(2008, 3, 14))
139155
// cutover at 02:00 local
140-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 3, 9, 1, 59, 59, 999999999).offsetIn(test))
141-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 3, 9, 2, 0, 0, 0).offsetIn(test))
156+
check("-5", LocalDateTime(2008, 3, 9, 1, 59, 59, 999999999))
157+
check("-4", LocalDateTime(2008, 3, 9, 2, 0, 0, 0))
142158
}
143159

144160
// from 310bp
145161
@Test
146162
fun newYorkOffsetFromDST() {
147163
val test = TimeZone.of("America/New_York")
148-
val offset = TimeZone.of("-4")
149-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 11, 1).offsetIn(test))
150-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 11, 2).offsetIn(test))
151-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 3).offsetIn(test))
152-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 4).offsetIn(test))
153-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 5).offsetIn(test))
154-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 6).offsetIn(test))
155-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 7).offsetIn(test))
164+
val offset = UtcOffset.parse("-4")
165+
166+
fun check(expectedOffset: String, dateTime: LocalDateTime) {
167+
assertEquals(UtcOffset.parse(expectedOffset), dateTime.toInstant(offset).offsetIn(test))
168+
}
169+
170+
check("-4", LocalDateTime(2008, 11, 1))
171+
check("-4", LocalDateTime(2008, 11, 2))
172+
check("-5", LocalDateTime(2008, 11, 3))
173+
check("-5", LocalDateTime(2008, 11, 4))
174+
check("-5", LocalDateTime(2008, 11, 5))
175+
check("-5", LocalDateTime(2008, 11, 6))
176+
check("-5", LocalDateTime(2008, 11, 7))
156177
// cutover at 02:00 local
157-
assertEquals(TimeZone.of("-4"), createInstant(offset, 2008, 11, 2, 1, 59, 59, 999999999).offsetIn(test))
158-
assertEquals(TimeZone.of("-5"), createInstant(offset, 2008, 11, 2, 2, 0, 0, 0).offsetIn(test))
178+
check("-4", LocalDateTime(2008, 11, 2, 1, 59, 59, 999999999))
179+
check("-5", LocalDateTime(2008, 11, 2, 2, 0, 0, 0))
159180
}
160181

161-
// from 310bp
162-
private fun createInstant(offset: TimeZone, year: Int, month: Int, day: Int, hour: Int = 0, min: Int = 0,
163-
sec: Int = 0, nano: Int = 0): Instant =
164-
LocalDateTime(year, month, day, hour, min, sec, nano).toInstant(offset)
182+
private fun LocalDateTime(year: Int, monthNumber: Int, dayOfMonth: Int) = LocalDateTime(year, monthNumber, dayOfMonth, 0, 0)
183+
165184
}

0 commit comments

Comments
 (0)