-
Notifications
You must be signed in to change notification settings - Fork 110
WIP: Added ZonedDateTime #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package kotlinx.datetime | ||
|
||
/** A timezone-aware date-time object. */ | ||
public sealed class ZonedDateTime( | ||
protected val localDateTime: LocalDateTime, | ||
) : Comparable<ZonedDateTime> { | ||
|
||
public abstract val timeZone: TimeZone | ||
|
||
// XXX: If the underlying time zone database can change while the current process is running | ||
// this value could become incorrect. Maybe don't cache at all? Or detect time zone db changes? | ||
private val instant: Instant by lazy { localDateTime.toInstant(timeZone) } | ||
|
||
public val year: Int get() = localDateTime.year | ||
public val monthNumber: Int get() = localDateTime.monthNumber | ||
public val month: Month get() = localDateTime.month | ||
public val dayOfMonth: Int get() = localDateTime.dayOfMonth | ||
public val dayOfWeek: DayOfWeek get() = localDateTime.dayOfWeek | ||
public val dayOfYear: Int get() = localDateTime.dayOfYear | ||
public val hour: Int get() = localDateTime.hour | ||
public val minute: Int get() = localDateTime.minute | ||
public val second: Int get() = localDateTime.second | ||
public val nanosecond: Int get() = localDateTime.nanosecond | ||
|
||
public fun toInstant(): Instant = instant | ||
|
||
public fun toLocalDateTime(): LocalDateTime = localDateTime | ||
|
||
public fun toLocalDateTime(timeZone: TimeZone): LocalDateTime = | ||
toInstant().toLocalDateTime(timeZone) | ||
|
||
public fun toLocalDate(): LocalDate = toLocalDateTime().date | ||
|
||
public fun toLocalDate(timeZone: TimeZone): LocalDate = toLocalDateTime(timeZone).date | ||
|
||
override fun compareTo(other: ZonedDateTime): Int = toInstant().compareTo(other.toInstant()) | ||
|
||
override fun equals(other: Any?): Boolean = | ||
this === other || (other is ZonedDateTime && compareTo(other) == 0) | ||
|
||
override fun hashCode(): Int = localDateTime.hashCode() xor timeZone.hashCode() | ||
|
||
public companion object { | ||
|
||
public fun parse(isoString: String): ZonedDateTime { | ||
TODO() | ||
} | ||
} | ||
} | ||
|
||
/** Constructs a new [ZonedDateTime] from the given [localDateTime] and [timeZone]. */ | ||
public fun ZonedDateTime(localDateTime: LocalDateTime, timeZone: TimeZone): ZonedDateTime = | ||
when (timeZone) { | ||
is FixedOffsetTimeZone -> OffsetDateTime(localDateTime, timeZone) | ||
// TODO: Define a common RegionTimeZone and make TimeZone a sealed class/interface | ||
else -> RegionDateTime(localDateTime, timeZone) | ||
} | ||
|
||
public fun String.toZonedDateTime(): ZonedDateTime = ZonedDateTime.parse(this) | ||
|
||
/** | ||
* A [ZonedDateTime] describing a region-based [TimeZone]. | ||
* | ||
* This class tries to represent how humans think in terms of dates. | ||
* For example, adding one day will result in the same local time even if a DST change happens | ||
* within that day. | ||
* Also, you can safely represent future dates because time zone database changes are taken into | ||
* account. | ||
*/ | ||
public class RegionDateTime( | ||
localDateTime: LocalDateTime, | ||
// TODO: this should be a RegionTimeZone | ||
override val timeZone: TimeZone, | ||
// TODO: Add optional DST offset or at least a UTC offset (should it be part of RegionTimeZone?) | ||
) : ZonedDateTime(localDateTime) { | ||
|
||
public constructor(instant: Instant, timeZone: TimeZone) : | ||
this(instant.toLocalDateTime(timeZone), timeZone) | ||
|
||
// TODO: Should RegionTimeZone.toString() print with surrounding `[]`? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have any standards for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PostgreSQL, Elasticsearch, java.time, JS Temporal and others seem to have settled on this full format: Though, maybe RegionTimeZone itself should just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case I'd settle with the established format |
||
override fun toString(): String = "$localDateTime[$timeZone]" | ||
} | ||
|
||
/** | ||
* A [ZonedDateTime] with a [FixedOffsetTimeZone]. Use this only for representing past events. | ||
* | ||
* Don't use this to represent future dates (e.g. in a calendar) because this fails to work | ||
* correctly under time zone database changes. Use [RegionDateTime] instead. | ||
*/ | ||
public class OffsetDateTime( | ||
localDateTime: LocalDateTime, | ||
override val timeZone: FixedOffsetTimeZone, | ||
) : ZonedDateTime(localDateTime) { | ||
|
||
public constructor(instant: Instant, timeZone: FixedOffsetTimeZone) : | ||
this(instant.toLocalDateTime(timeZone), timeZone) | ||
|
||
override fun toString(): String = "$localDateTime$timeZone" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be in RFC3339 as that's the common serialization and representation everywhere. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package kotlinx.datetime | ||
|
||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertIs | ||
|
||
internal class ZonedDateTimeTest { | ||
@Test | ||
fun parseZonedDateTime() { | ||
val offsetDates = listOf( | ||
"2021-12-29 17:32:01Z", | ||
"2021-12-29T17:32:01Z", | ||
"2021-12-29 17:32:01+03:00", | ||
"2021-12-29 17:32:01-03:00", | ||
) | ||
val regionDates = listOf( | ||
"2021-12-29 17:32:01[Europe/Berlin]", | ||
"2021-12-29 17:32:01+01:00[Europe/Berlin]", | ||
) | ||
for (isoString in offsetDates + regionDates) { | ||
val dateTime = ZonedDateTime.parse(isoString) | ||
assertEquals(isoString.replace(" ", "T"), dateTime.toString()) | ||
if (isoString in offsetDates) { | ||
assertIs<OffsetDateTime>(dateTime) | ||
} else { | ||
assertIs<RegionDateTime>(dateTime) | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.