Skip to content

Commit 66a0bd0

Browse files
committed
Smoke check some historic data for timezones
1 parent 9c60347 commit 66a0bd0

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

core/common/test/TimeZoneTest.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,103 @@ class TimeZoneTest {
205205
check("-5", LocalDateTime(2008, 11, 2, 2, 0, 0, 0))
206206
}
207207

208+
@Test
209+
fun checkKnownTimezoneDatabaseRecords() {
210+
with(TimeZone.of("America/New_York")) {
211+
checkRegular(this, LocalDateTime(2019, 3, 8, 23, 0), UtcOffset(hours = -5))
212+
checkGap(this, LocalDateTime(2019, 3, 10, 2, 0))
213+
checkRegular(this, LocalDateTime(2019, 6, 2, 23, 0), UtcOffset(hours = -4))
214+
checkOverlap(this, LocalDateTime(2019, 11, 3, 2, 0))
215+
checkRegular(this, LocalDateTime(2019, 12, 5, 23, 0), UtcOffset(hours = -5))
216+
}
217+
with(TimeZone.of("Europe/Berlin")) {
218+
checkRegular(this, LocalDateTime(2019, 1, 31, 1, 0), UtcOffset(hours = 1))
219+
checkGap(this, LocalDateTime(2019, 3, 31, 2, 0))
220+
checkRegular(this, LocalDateTime(2019, 6, 27, 1, 0), UtcOffset(hours = 2))
221+
checkOverlap(this, LocalDateTime(2019, 10, 27, 3, 0))
222+
checkRegular(this, LocalDateTime(2019, 12, 5, 23, 0), UtcOffset(hours = 1))
223+
}
224+
with(TimeZone.of("Europe/Moscow")) {
225+
checkRegular(this, LocalDateTime(2019, 1, 31, 1, 0), UtcOffset(hours = 3))
226+
checkRegular(this, LocalDateTime(2011, 1, 31, 1, 0), UtcOffset(hours = 3))
227+
checkGap(this, LocalDateTime(2011, 3, 27, 2, 0))
228+
checkRegular(this, LocalDateTime(2011, 5, 3, 1, 0), UtcOffset(hours = 4))
229+
}
230+
with(TimeZone.of("Australia/Sydney")) {
231+
checkRegular(this, LocalDateTime(2019, 1, 31, 1, 0), UtcOffset(hours = 11))
232+
checkOverlap(this, LocalDateTime(2019, 4, 7, 3, 0))
233+
checkRegular(this, LocalDateTime(2019, 10, 6, 1, 0), UtcOffset(hours = 10))
234+
checkGap(this, LocalDateTime(2019, 10, 6, 2, 0))
235+
checkRegular(this, LocalDateTime(2019, 12, 5, 23, 0), UtcOffset(hours = 11))
236+
}
237+
}
238+
208239
private fun LocalDateTime(year: Int, monthNumber: Int, dayOfMonth: Int) = LocalDateTime(year, monthNumber, dayOfMonth, 0, 0)
209240

210241
}
242+
243+
/**
244+
* [gapStart] is the first non-existent moment.
245+
*/
246+
private fun checkGap(timeZone: TimeZone, gapStart: LocalDateTime) {
247+
val instant = gapStart.toInstant(timeZone)
248+
/** the first [LocalDateTime] after the gap */
249+
val adjusted = instant.toLocalDateTime(timeZone)
250+
try {
251+
// there is at least a one-second gap
252+
assertNotEquals(gapStart, adjusted)
253+
// the offsets before the gap are equal
254+
assertEquals(
255+
instant.offsetIn(timeZone),
256+
instant.plus(1, DateTimeUnit.SECOND).offsetIn(timeZone))
257+
// the offsets after the gap are equal
258+
assertEquals(
259+
instant.minus(1, DateTimeUnit.SECOND).offsetIn(timeZone),
260+
instant.minus(2, DateTimeUnit.SECOND).offsetIn(timeZone)
261+
)
262+
} catch (e: Throwable) {
263+
throw Exception("Didn't find a gap at $gapStart for $timeZone", e)
264+
}
265+
}
266+
267+
/**
268+
* [overlapStart] is the first non-ambiguous date-time.
269+
*/
270+
private fun checkOverlap(timeZone: TimeZone, overlapStart: LocalDateTime) {
271+
// the earlier occurrence of the overlap
272+
val instantStart = overlapStart.plusNominalSeconds(-1).toInstant(timeZone).plus(1, DateTimeUnit.SECOND)
273+
// the later occurrence of the overlap
274+
val instantEnd = overlapStart.toInstant(timeZone)
275+
try {
276+
// there is at least a one-second overlap
277+
assertNotEquals(instantStart, instantEnd)
278+
// the offsets before the overlap are equal
279+
assertEquals(
280+
instantStart.minus(1, DateTimeUnit.SECOND).offsetIn(timeZone),
281+
instantStart.minus(2, DateTimeUnit.SECOND).offsetIn(timeZone)
282+
)
283+
// the offsets after the overlap are equal
284+
assertEquals(
285+
instantStart.offsetIn(timeZone),
286+
instantEnd.offsetIn(timeZone)
287+
)
288+
} catch (e: Throwable) {
289+
throw Exception("Didn't find an overlap at $overlapStart for $timeZone", e)
290+
}
291+
}
292+
293+
private fun checkRegular(timeZone: TimeZone, dateTime: LocalDateTime, offset: UtcOffset) {
294+
val instant = dateTime.toInstant(timeZone)
295+
assertEquals(offset, instant.offsetIn(timeZone))
296+
try {
297+
// not a gap:
298+
assertEquals(dateTime, instant.toLocalDateTime(timeZone))
299+
// not an overlap, or an overlap longer than one hour:
300+
assertTrue(dateTime.plusNominalSeconds(3600) <= instant.plus(1, DateTimeUnit.HOUR).toLocalDateTime(timeZone))
301+
} catch (e: Throwable) {
302+
throw Exception("The date-time at $dateTime for $timeZone was in a gap or overlap", e)
303+
}
304+
}
305+
306+
private fun LocalDateTime.plusNominalSeconds(seconds: Int): LocalDateTime =
307+
toInstant(UtcOffset.ZERO).plus(seconds, DateTimeUnit.SECOND).toLocalDateTime(UtcOffset.ZERO)

0 commit comments

Comments
 (0)