commonTest.io.islandtime.ZonedDateTimeTest.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-metadata Show documentation
Show all versions of core-metadata Show documentation
A multiplatform library for working with dates and times
The newest version!
package io.islandtime
import io.islandtime.measures.*
import io.islandtime.parser.DateTimeParseException
import io.islandtime.parser.DateTimeParsers
import io.islandtime.test.AbstractIslandTimeTest
import io.islandtime.zone.TimeZoneRulesException
import kotlin.test.*
class ZonedDateTimeTest : AbstractIslandTimeTest() {
private val nyZone = TimeZone("America/New_York")
private val denverZone = TimeZone("America/Denver")
@Test
fun `throws an exception when constructed with a TimeZone that has no rules`() {
assertFailsWith {
ZonedDateTime(
DateTime(2019, 5, 30, 18, 0),
TimeZone("America/Boston")
)
}
}
@Test
fun `when constructed from a DateTime that falls in an overlap, the earlier offset is used by default`() {
val actual = ZonedDateTime(
DateTime(2019, 11, 3, 1, 0),
nyZone
)
assertEquals(DateTime(2019, 11, 3, 1, 0), actual.dateTime)
assertEquals(nyZone, actual.zone)
assertEquals(UtcOffset((-4).hours), actual.offset)
}
@Test
fun `when constructed from a DateTime that falls in an overlap, a preferred offset may be provided`() {
val actual = ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 0),
nyZone,
UtcOffset((-5).hours)
)
assertEquals(DateTime(2019, 11, 3, 1, 0), actual.dateTime)
assertEquals(nyZone, actual.zone)
assertEquals(UtcOffset((-5).hours), actual.offset)
}
@Test
fun `when constructed from a DateTime that falls in an overlap, an invalid preferred offset is ignored`() {
val actual = ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 0),
nyZone,
UtcOffset((-8).hours)
)
assertEquals(DateTime(2019, 11, 3, 1, 0), actual.dateTime)
assertEquals(nyZone, actual.zone)
assertEquals(UtcOffset((-4).hours), actual.offset)
}
@Test
fun `when constructed from a DateTime that doesn't fall in an overlap, the preferred offset is ignored`() {
val actual = ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 2, 0),
nyZone,
UtcOffset((-4).hours)
)
assertEquals(DateTime(2019, 11, 3, 2, 0), actual.dateTime)
assertEquals(nyZone, actual.zone)
assertEquals(UtcOffset((-5).hours), actual.offset)
}
@Test
fun `when constructed from a DateTime that falls during a gap, the DateTime is adjusted by the gap's length`() {
val actual = ZonedDateTime(
DateTime(2019, 3, 10, 2, 30),
nyZone
)
assertEquals(DateTime(2019, 3, 10, 3, 30), actual.dateTime)
assertEquals(nyZone, actual.zone)
assertEquals(UtcOffset((-4).hours), actual.offset)
}
@Test
fun `can be constructed with day of year`() {
ZonedDateTime(2019, 18, 1, 2, 3, 4, nyZone).run {
assertEquals(2019, year)
assertEquals(18, dayOfYear)
assertEquals(1, hour)
assertEquals(2, minute)
assertEquals(3, second)
assertEquals(4, nanosecond)
assertEquals((-5).hours.asUtcOffset(), offset)
assertEquals(nyZone, zone)
}
}
@Test
fun `can be constructed from seconds since unix epoch`() {
ZonedDateTime.fromSecondsSinceUnixEpoch((-1L).seconds, zone = TimeZone.UTC).run {
assertEquals(1969, year)
assertEquals(365, dayOfYear)
assertEquals(23, hour)
assertEquals(59, minute)
assertEquals(59, second)
assertEquals(0, nanosecond)
assertEquals(UtcOffset.ZERO, offset)
}
ZonedDateTime.fromSecondsSinceUnixEpoch((-1L).seconds, 1.nanoseconds, TimeZone.UTC).run {
assertEquals(1969, year)
assertEquals(365, dayOfYear)
assertEquals(23, hour)
assertEquals(59, minute)
assertEquals(59, second)
assertEquals(1, nanosecond)
assertEquals(UtcOffset.ZERO, offset)
}
}
@Test
fun `can be constructed from milliseconds since unix epoch`() {
ZonedDateTime.fromMillisecondsSinceUnixEpoch(1L.milliseconds, (-1).hours.asUtcOffset().asTimeZone()).run {
assertEquals(1969, year)
assertEquals(365, dayOfYear)
assertEquals(23, hour)
assertEquals(0, minute)
assertEquals(0, second)
assertEquals(1_000_000, nanosecond)
assertEquals((-1).hours.asUtcOffset(), offset)
}
}
@Test
fun `can be constructed from second of unix epoch`() {
ZonedDateTime.fromSecondOfUnixEpoch(-1L, zone = TimeZone.UTC).run {
assertEquals(1969, year)
assertEquals(365, dayOfYear)
assertEquals(23, hour)
assertEquals(59, minute)
assertEquals(59, second)
assertEquals(0, nanosecond)
assertEquals(UtcOffset.ZERO, offset)
}
ZonedDateTime.fromSecondOfUnixEpoch(0L, 1, TimeZone.UTC).run {
assertEquals(1970, year)
assertEquals(1, dayOfYear)
assertEquals(0, hour)
assertEquals(0, minute)
assertEquals(0, second)
assertEquals(1, nanosecond)
assertEquals(UtcOffset.ZERO, offset)
}
}
@Test
fun `can be constructed from millisecond of unix epoch`() {
ZonedDateTime.fromMillisecondOfUnixEpoch(1L, TimeZone.UTC).run {
assertEquals(1970, year)
assertEquals(1, dayOfYear)
assertEquals(0, hour)
assertEquals(0, minute)
assertEquals(0, second)
assertEquals(1_000_000, nanosecond)
assertEquals(UtcOffset.ZERO, offset)
}
}
@Test
fun `equality is based on date-time, time zone, and offset`() {
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 0),
UtcOffset((-4).hours),
nyZone
),
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 0),
UtcOffset((-4).hours),
nyZone
)
)
assertNotEquals(
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 0),
nyZone,
UtcOffset((-4).hours)
),
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 0),
nyZone,
UtcOffset((-5).hours)
)
)
assertNotEquals(
ZonedDateTime(
DateTime(2019, 11, 3, 5, 0),
denverZone
),
ZonedDateTime(
DateTime(2019, 11, 3, 7, 0),
nyZone
)
)
}
@Test
fun `DEFAULT_SORT_ORDER compares based on instant, then date and time, and then zone`() {
assertTrue {
ZonedDateTime.DEFAULT_SORT_ORDER.compare(
Date(1969, 365) at Time(23, 0) at TimeZone("America/Chicago"),
Date(1970, 1) at Time(0, 0) at nyZone
) < 0
}
assertTrue {
ZonedDateTime.DEFAULT_SORT_ORDER.compare(
Date(1970, 1) at Time(0, 0) at TimeZone("Etc/GMT+5"),
Date(1970, 1) at Time(0, 0) at nyZone
) > 0
}
assertTrue {
ZonedDateTime.DEFAULT_SORT_ORDER.compare(
Date(1969, 365) at Time(23, 0) at TimeZone("Etc/GMT+5"),
Date(1969, 365) at Time(23, 0) at TimeZone("Etc/GMT+5")
) == 0
}
}
@Test
fun `TIMELINE_ORDER compare based on instant only`() {
assertTrue {
ZonedDateTime.TIMELINE_ORDER.compare(
Date(1969, 365) at Time(23, 0) at UtcOffset((-1).hours),
Date(1970, 1) at Time(0, 0) at UtcOffset.ZERO
) == 0
}
}
@Test
fun `compareTo() compares based on instant only`() {
assertTrue {
Date(1969, 365) at Time(22, 0) at UtcOffset((-1).hours) <
Date(1970, 1) at Time(0, 0) at UtcOffset.ZERO
}
}
@Test
fun `properties have expected values`() {
val zonedDateTime = DateTime(2019, 3, 3, 7, 0) at denverZone
assertEquals(2019, zonedDateTime.year)
assertEquals(Month.MARCH, zonedDateTime.month)
assertEquals(3, zonedDateTime.monthNumber)
assertEquals(3, zonedDateTime.dayOfMonth)
assertEquals(Date(2019, 3, 3), zonedDateTime.date)
assertEquals(Time(7, 0), zonedDateTime.time)
assertEquals(DateTime(2019, 3, 3, 7, 0), zonedDateTime.dateTime)
}
@Test
fun `copy() ignores changes to the offset if it isn't valid for the time zone`() {
assertEquals(
ZonedDateTime.create(
DateTime(2019, 3, 3, 7, 0),
UtcOffset((-7).hours),
denverZone
),
ZonedDateTime(
DateTime(2019, 11, 3, 7, 0),
nyZone
).copy(
monthNumber = 3,
offset = (-4).hours.asUtcOffset(),
zone = denverZone
)
)
}
@Test
fun `copy() adjusts components forward when rendered invalid due to gaps`() {
assertEquals(
ZonedDateTime.create(
DateTime(2019, 3, 10, 3, 3),
UtcOffset((-4).hours),
nyZone
),
ZonedDateTime(
DateTime(2019, 3, 10, 7, 0),
nyZone
).copy(hour = 2, minute = 3)
)
}
@Test
fun `copy() replaces components directly with new values when it's possible to do so`() {
assertEquals(
ZonedDateTime.create(
DateTime(2018, 3, 10, 3, 0),
UtcOffset((-5).hours),
nyZone
),
ZonedDateTime(
DateTime(2019, 3, 10, 7, 5),
nyZone
).copy(hour = 3, minute = 0, year = 2018)
)
}
@Test
fun `withEarlierOffsetAtOverlap() returns the same DateTime with the earlier offset when there's a DST overlap`() {
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-4).hours),
nyZone
),
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 30),
nyZone,
UtcOffset((-5).hours)
).withEarlierOffsetAtOverlap()
)
}
@Test
fun `withEarlierOffsetAtOverlap() returns the same ZonedDateTime when there's no overlap`() {
assertEquals(
ZonedDateTime(
DateTime(2019, 11, 3, 2, 30),
nyZone
),
ZonedDateTime(
DateTime(2019, 11, 3, 2, 30),
nyZone
).withEarlierOffsetAtOverlap()
)
}
@Test
fun `withLaterOffsetAtOverlap() returns the same DateTime with the later offset when there's a DST overlap`() {
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-5).hours),
nyZone
),
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-4).hours),
nyZone
).withLaterOffsetAtOverlap()
)
}
@Test
fun `withLaterOffsetAtOverlap() returns the same ZonedDateTime when there's no overlap`() {
assertEquals(
ZonedDateTime(
DateTime(2019, 11, 3, 2, 30),
nyZone
),
ZonedDateTime(
DateTime(2019, 11, 3, 2, 30),
nyZone
).withLaterOffsetAtOverlap()
)
}
@Test
fun `withFixedOffsetZone() returns the same ZonedDateTime if it already has a fixed zone`() {
assertEquals(
ZonedDateTime(
DateTime(2019, 11, 4, 8, 30),
UtcOffset((-5).hours).asTimeZone()
),
ZonedDateTime(
DateTime(2019, 11, 4, 8, 30),
UtcOffset((-5).hours).asTimeZone()
).withFixedOffsetZone()
)
}
@Test
fun `withFixedOffsetZone() returns a ZonedDateTime with a fixed offset zone when region-based`() {
assertEquals(
ZonedDateTime(
DateTime(2019, 11, 4, 8, 30),
UtcOffset((-5).hours).asTimeZone()
),
ZonedDateTime(
DateTime(2019, 11, 4, 8, 30),
nyZone
).withFixedOffsetZone()
)
}
@Test
fun `adjustedTo() converts to a different time zone while preserving the instant during overlap`() {
// New York in overlap, Denver not in overlap
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 0, 30),
UtcOffset((-6).hours),
denverZone
),
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 1, 30),
nyZone,
UtcOffset((-5).hours)
).adjustedTo(denverZone)
)
// New York no longer in overlap, Denver in earlier offset at overlap
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-6).hours),
denverZone
),
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 2, 30),
nyZone,
UtcOffset((-5).hours)
).adjustedTo(denverZone)
)
// New York not in overlap, Denver in later offset at overlap
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-7).hours),
denverZone
),
ZonedDateTime.fromLocal(
DateTime(2019, 11, 3, 3, 30),
nyZone,
UtcOffset((-5).hours)
).adjustedTo(denverZone)
)
}
@Test
fun `adjustedTo() converts to a different time zone while preserving the instant during gaps`() {
// New York in DST, Denver not yet
assertEquals(
ZonedDateTime.create(
DateTime(2019, 3, 10, 1, 30),
UtcOffset((-7).hours),
denverZone
),
ZonedDateTime(
DateTime(2019, 3, 10, 4, 30),
nyZone
).adjustedTo(denverZone)
)
// New York and Denver both in DST
assertEquals(
ZonedDateTime.create(
DateTime(2019, 3, 10, 3, 30),
UtcOffset((-6).hours),
denverZone
),
ZonedDateTime(
DateTime(2019, 3, 10, 5, 30),
nyZone
).adjustedTo(denverZone)
)
}
@Test
fun `add period of zero`() {
val zonedDateTime = DateTime(2016, Month.FEBRUARY, 29, 13, 0) at nyZone
assertEquals(zonedDateTime, zonedDateTime + Period.ZERO)
}
@Test
fun `adding a period first adds years, then months, then days`() {
assertEquals(
DateTime(2017, Month.MARCH, 29, 9, 0) at nyZone,
(DateTime(2016, Month.FEBRUARY, 29, 9, 0) at nyZone) +
periodOf(1.years, 1.months, 1.days)
)
assertEquals(
DateTime(2015, Month.JANUARY, 27, 9, 0) at nyZone,
(DateTime(2016, Month.FEBRUARY, 29, 9, 0) at nyZone) +
periodOf((-1).years, (-1).months, (-1).days)
)
}
@Test
fun `subtract period of zero`() {
val zonedDateTime = DateTime(2016, Month.FEBRUARY, 29, 13, 0) at nyZone
assertEquals(zonedDateTime, zonedDateTime - Period.ZERO)
}
@Test
fun `subtracting a period first subtracts years, then months, then days`() {
assertEquals(
DateTime(2017, Month.MARCH, 29, 9, 0) at nyZone,
(DateTime(2016, Month.FEBRUARY, 29, 9, 0) at nyZone) -
periodOf((-1).years, (-1).months, (-1).days)
)
assertEquals(
DateTime(2015, Month.JANUARY, 27, 9, 0) at nyZone,
(DateTime(2016, Month.FEBRUARY, 29, 9, 0) at nyZone) -
periodOf(1.years, 1.months, 1.days)
)
}
@Test
fun `toString() returns an ISO-8601 extended offset date-time along with a non-standard region ID`() {
assertEquals(
"2019-11-03T01:30Z[Etc/UTC]",
ZonedDateTime(
DateTime(2019, 11, 3, 1, 30),
TimeZone("Etc/UTC")
).toString()
)
assertEquals(
"2019-11-03T01:30-05:00[America/New_York]",
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 30),
UtcOffset((-5).hours),
nyZone
).toString()
)
}
@Test
fun `String_toZonedDateTime() throws an exception when the string is empty`() {
assertFailsWith { "".toZonedDateTime() }
}
@Test
fun `String_toZonedDateTime() throws an exception when the format is unexpected`() {
listOf(
"2019-12-05T12:00+01:00[America/New_York ]",
"2019-12-05T12:00+01:00America/New_York",
"2019-12-05T12:00+01:00[America/New_York",
"2019-12-05T12:00+01:00[]",
"2019-12-05T12:00+01:00[America/New_York/one_more/characters/than_supported]",
"2019-12-05T12:00+01:00:00[America/New_York ]",
"2019-12-05T12:00+01:00:00America/New_York",
"2019-12-05T12:00+01:00:00[America/New_York",
"2019-12-05T12:00+01:00:00[]",
"2019-12-05T12:00+01:00:00[America/New_York/one_more/characters/than_supported]"
).forEach {
assertFailsWith { it.toZonedDateTime() }
}
}
@Test
fun `String_toZonedDateTime() throws an exception when fields are out of range`() {
listOf(
"2000-01-01T24:00Z[Etc/Utc]",
"2000-01-01T08:60-01:00[GMT+1]",
"2000-13-01T08:59-01:00[GMT+1]",
"2000-01-32T08:59-01:00[GMT+1]"
).forEach {
assertFailsWith { it.toZonedDateTime() }
}
}
@Test
fun `String_toZonedDateTime() throws an exception if the parsed zone isn't valid`() {
listOf(
"2000-01-01T23:00+01:00[America/Boston]",
"2000-01-01T23:00+01:00[Etc/GMT-20]"
).forEach {
assertFailsWith { it.toZonedDateTime() }
}
}
@Test
fun `String_toZonedDateTime() parses ISO-8601 calendar date time strings in extended format by default`() {
assertEquals(
ZonedDateTime.create(
DateTime(Date(2019, Month.MAY, 5), Time.NOON),
UtcOffset((-4).hours),
nyZone
),
"2019-05-05T12:00-04:00[America/New_York]".toZonedDateTime()
)
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 0),
UtcOffset((-5).hours),
nyZone
),
"2019-11-03T01:00-05:00[America/New_York]".toZonedDateTime()
)
assertEquals(
ZonedDateTime.create(
DateTime(2019, 11, 3, 1, 0),
UtcOffset((-5).hours),
UtcOffset((-5).hours).asTimeZone()
),
"2019-11-03T01:00-05:00".toZonedDateTime()
)
}
@Test
fun `String_toZonedDateTime() parses valid ISO-8601 strings with explicit parser`() {
assertEquals(
ZonedDateTime.create(
DateTime(Date(2019, Month.MAY, 5), Time.NOON),
UtcOffset((-4).hours),
nyZone
),
"20190505 1200-04[America/New_York]".toZonedDateTime(DateTimeParsers.Iso.ZONED_DATE_TIME)
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy