Skip to content
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

Overhaul conversions between the different date-time and interval types #93

Merged
merged 2 commits into from
Jul 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions core/src/commonMain/kotlin/io/islandtime/Conversions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
@file:JvmMultifileClass
@file:JvmName("DateTimesKt")

package io.islandtime

import io.islandtime.base.TimePoint
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

/**
* Returns this date with the precision reduced to the year.
*/
fun YearMonth.toYear(): Year = Year(year)

/**
* Returns this date with the precision reduced to the year.
*/
fun Date.toYear(): Year = Year(year)

/**
* Returns this date-time with the precision reduced to the year.
*/
fun DateTime.toYear(): Year = date.toYear()

/**
* Returns this date-time with the precision reduced to the year.
*/
fun OffsetDateTime.toYear(): Year = date.toYear()

/**
* Returns this date-time with the precision reduced to the year.
*/
fun ZonedDateTime.toYear(): Year = date.toYear()

/**
* Returns this date with the precision reduced to the year-month.
*/
fun Date.toYearMonth(): YearMonth = YearMonth(year, month)

/**
* Returns this date-time with the precision reduced to the year-month.
*/
fun DateTime.toYearMonth(): YearMonth = date.toYearMonth()

/**
* Returns this date-time with the precision reduced to the year-month.
*/
fun OffsetDateTime.toYearMonth(): YearMonth = dateTime.toYearMonth()

/**
* Returns this date-time with the precision reduced to the year-month.
*/
fun ZonedDateTime.toYearMonth(): YearMonth = dateTime.toYearMonth()

/**
* Returns the combined time and UTC offset.
*/
fun OffsetDateTime.toOffsetTime(): OffsetTime = OffsetTime(time, offset)

/**
* Returns the combined time and UTC offset.
*/
fun ZonedDateTime.toOffsetTime(): OffsetTime = OffsetTime(time, offset)

/**
* Returns the combined date, time, and UTC offset.
*
* While similar to `ZonedDateTime`, an `OffsetDateTime` representation is unaffected by time zone rule changes or
* database differences between systems, making it better suited for use cases involving persistence or network
* transfer.
*/
fun ZonedDateTime.toOffsetDateTime(): OffsetDateTime = OffsetDateTime(dateTime, offset)

/**
* Converts this instant to the corresponding [DateTime] in [zone].
*/
fun Instant.toDateTimeAt(zone: TimeZone): DateTime = toDateTimeAt(zone.rules.offsetAt(this))

/**
* Strategy to use when converting a local date-time accompanied by a [UtcOffset] to a date and time that are valid
* according to the rules of a [TimeZone].
*/
enum class OffsetConversionStrategy {
/**
* Preserve the instant on the timeline, ignoring the local time.
*/
PRESERVE_INSTANT,

/**
* Preserve the local date and time in the new time zone (if possible), adjusting the offset if needed.
*/
PRESERVE_LOCAL_TIME
}

/**
* Converts this [OffsetDateTime] to a [ZonedDateTime] using the specified [strategy] to adjust it to a valid date,
* time, and offset in [zone].
*
* - [OffsetConversionStrategy.PRESERVE_INSTANT] - Preserve the instant captured by the date, time, and offset,
* ignoring the local time.
*
* - [OffsetConversionStrategy.PRESERVE_LOCAL_TIME] - Preserve the local date and time in the new time zone, adjusting
* the offset if needed.
*
* Alternatively, you can use [asZonedDateTime] to convert to a [ZonedDateTime] with an equivalent fixed-offset zone.
* However, this comes with the caveat that a fixed-offset zone lacks knowledge of any region and will not respond to
* daylight savings time changes.
*
* @see asZonedDateTime
*/
fun OffsetDateTime.toZonedDateTime(zone: TimeZone, strategy: OffsetConversionStrategy): ZonedDateTime {
return when (strategy) {
OffsetConversionStrategy.PRESERVE_INSTANT -> ZonedDateTime.fromInstant(dateTime, offset, zone)
OffsetConversionStrategy.PRESERVE_LOCAL_TIME -> ZonedDateTime.fromLocal(dateTime, zone, offset)
}
}

/**
* Converts this date-time to the corresponding [Instant] at [offset].
* @param offset the offset from UTC
*/
fun DateTime.toInstantAt(offset: UtcOffset): Instant {
return Instant.fromSecondOfUnixEpoch(secondOfUnixEpochAt(offset), nanosecond)
}

/**
* Converts this date-time to the [Instant] representing the same time point.
*/
fun OffsetDateTime.toInstant(): Instant = (this as TimePoint<*>).toInstant()

/**
* Converts this date-time to the [Instant] representing the same time point.
*/
fun ZonedDateTime.toInstant(): Instant = (this as TimePoint<*>).toInstant()

internal fun TimePoint<*>.toInstant(): Instant = Instant.fromSecondOfUnixEpoch(secondOfUnixEpoch, nanosecond)
14 changes: 9 additions & 5 deletions core/src/commonMain/kotlin/io/islandtime/Date.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")

package io.islandtime

import io.islandtime.base.DateTimeField
Expand Down Expand Up @@ -90,10 +92,13 @@ class Date(
*/
val lengthOfYear: IntDays get() = lengthOfYear(year)

/**
* The combined year and month.
*/
inline val yearMonth: YearMonth get() = YearMonth(year, month)
@Deprecated(
"Use toYearMonth() instead.",
ReplaceWith("this.toYearMonth()"),
DeprecationLevel.WARNING
)
inline val yearMonth: YearMonth
get() = toYearMonth()

/**
* The number of days away from the Unix epoch (`1970-01-01T00:00Z`) that this date falls.
Expand Down Expand Up @@ -331,7 +336,6 @@ class Date(
* @param dayOfYear the day of the calendar year
* @throws DateTimeException if the year or day of year are invalid
*/
@Suppress("FunctionName")
fun Date(year: Int, dayOfYear: Int): Date {
checkValidYear(year)
checkValidDayOfYear(year, dayOfYear)
Expand Down
25 changes: 13 additions & 12 deletions core/src/commonMain/kotlin/io/islandtime/DateTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,12 @@ class DateTime(
*/
inline val lengthOfYear: IntDays get() = date.lengthOfYear

/**
* The combined year and month.
*/
inline val yearMonth: YearMonth get() = date.yearMonth
@Deprecated(
"Use toYearMonth() instead.",
ReplaceWith("this.toYearMonth()"),
DeprecationLevel.WARNING
)
inline val yearMonth: YearMonth get() = toYearMonth()

/**
* Return a [DateTime] with [period] added to it.
Expand Down Expand Up @@ -579,13 +581,12 @@ class DateTime(
*/
fun millisecondOfUnixEpochAt(offset: UtcOffset): Long = millisecondsSinceUnixEpochAt(offset).value

/**
* The [Instant] represented by this date-time at a particular offset from UTC.
* @param offset the offset from UTC
*/
fun instantAt(offset: UtcOffset): Instant {
return Instant.fromSecondOfUnixEpoch(secondOfUnixEpochAt(offset), nanosecond)
}
@Deprecated(
"Use toInstantAt() instead.",
ReplaceWith("this.toInstantAt(offset)"),
DeprecationLevel.WARNING
)
fun instantAt(offset: UtcOffset): Instant = toInstantAt(offset)

companion object {
/**
Expand Down Expand Up @@ -679,7 +680,7 @@ fun Date.atTime(hour: Int, minute: Int, second: Int = 0, nanosecond: Int = 0): D
}

/**
* Convert to a [DateTime] at a particular offset from UTC.
* Converts this instant to the corresponding [DateTime] at [offset].
*/
fun Instant.toDateTimeAt(offset: UtcOffset): DateTime {
return DateTime.fromSecondOfUnixEpoch(secondOfUnixEpoch, nanosecond, offset)
Expand Down
8 changes: 3 additions & 5 deletions core/src/commonMain/kotlin/io/islandtime/Instant.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")

package io.islandtime

import io.islandtime.base.DateTimeField
Expand Down Expand Up @@ -290,14 +292,12 @@ class Instant private constructor(
/**
* Create the [Instant] represented by a number of seconds relative to the Unix epoch of 1970-01-01T00:00Z.
*/
@Suppress("FunctionName")
fun Instant(secondsSinceUnixEpoch: LongSeconds) = Instant.fromSecondOfUnixEpoch(secondsSinceUnixEpoch.value)

/**
* Create the [Instant] represented by a number of seconds and additional nanoseconds relative to the Unix epoch of
* 1970-01-01T00:00Z.
*/
@Suppress("FunctionName")
fun Instant(secondsSinceUnixEpoch: LongSeconds, nanosecondAdjustment: IntNanoseconds): Instant {
return Instant.fromSecondOfUnixEpoch(secondsSinceUnixEpoch.value, nanosecondAdjustment.value)
}
Expand All @@ -306,15 +306,13 @@ fun Instant(secondsSinceUnixEpoch: LongSeconds, nanosecondAdjustment: IntNanosec
* Create the [Instant] represented by a number of seconds and additional nanoseconds relative to the Unix epoch of
* 1970-01-01T00:00Z.
*/
@Suppress("FunctionName")
fun Instant(secondsSinceUnixEpoch: LongSeconds, nanosecondAdjustment: LongNanoseconds): Instant {
return Instant.fromSecondOfUnixEpoch(secondsSinceUnixEpoch.value, nanosecondAdjustment.value)
}

/**
* Create the [Instant] represented by a number of milliseconds relative to the Unix epoch of 1970-01-01T00:00Z.
*/
@Suppress("FunctionName")
fun Instant(millisecondsSinceUnixEpoch: LongMilliseconds): Instant {
return Instant.fromMillisecondOfUnixEpoch(millisecondsSinceUnixEpoch.value)
}
Expand Down Expand Up @@ -362,7 +360,7 @@ internal fun DateTimeParseResult.toInstant(): Instant? {

return if (dateTime != null && offset != null) {
val secondOfEpoch = dateTime.secondOfUnixEpochAt(offset) +
((parsedYear / 10_000L) timesExact SECONDS_PER_10000_YEARS)
((parsedYear / 10_000L) timesExact SECONDS_PER_10000_YEARS)
Instant.fromSecondOfUnixEpoch(secondOfEpoch, dateTime.nanosecond)
} else {
null
Expand Down
40 changes: 23 additions & 17 deletions core/src/commonMain/kotlin/io/islandtime/OffsetDateTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,26 @@ class OffsetDateTime(
*/
inline val lengthOfYear: IntDays get() = dateTime.lengthOfYear

/**
* The combined year and month.
*/
inline val yearMonth: YearMonth get() = dateTime.yearMonth

/**
* The combined time of day and offset.
*/
inline val offsetTime: OffsetTime get() = OffsetTime(time, offset)

/**
* The [Instant] representing the same time point.
*/
inline val instant: Instant get() = Instant.fromSecondOfUnixEpoch(secondOfUnixEpoch, nanosecond)
@Deprecated(
"Use toYearMonth() instead.",
ReplaceWith("this.toYearMonth()"),
DeprecationLevel.WARNING
)
inline val yearMonth: YearMonth get() = toYearMonth()

@Deprecated(
"Use toOffsetTime() instead.",
ReplaceWith("this.toOffsetTime()"),
DeprecationLevel.WARNING
)
inline val offsetTime: OffsetTime get() = toOffsetTime()

@Deprecated(
"Use toInstant() instead.",
ReplaceWith("this.toInstant()"),
DeprecationLevel.WARNING
)
inline val instant: Instant get() = toInstant()

override val secondsSinceUnixEpoch: LongSeconds
get() = dateTime.secondsSinceUnixEpochAt(offset)
Expand Down Expand Up @@ -410,11 +416,11 @@ infix fun Date.at(offsetTime: OffsetTime) = OffsetDateTime(this, offsetTime.time
infix fun Instant.at(offset: UtcOffset) = OffsetDateTime(this.toDateTimeAt(offset), offset)

@Deprecated(
"Use the 'offsetDateTime' property on ZonedDateTime instead.",
ReplaceWith("this.offsetDateTime"),
"Use 'toOffsetDateTime()' instead.",
ReplaceWith("this.toOffsetDateTime()"),
DeprecationLevel.WARNING
)
fun ZonedDateTime.asOffsetDateTime() = offsetDateTime
fun ZonedDateTime.asOffsetDateTime() = toOffsetDateTime()

/**
* Convert a string to an [OffsetDateTime].
Expand Down
11 changes: 7 additions & 4 deletions core/src/commonMain/kotlin/io/islandtime/TimeZone.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")

package io.islandtime

import io.islandtime.format.TimeZoneTextProvider
Expand Down Expand Up @@ -173,8 +175,10 @@ sealed class TimeZone : Comparable<TimeZone> {
*/
val UTC: TimeZone = FixedOffset(UtcOffset.ZERO)

@Suppress("FunctionName")
fun FixedOffset(id: String): TimeZone {
/**
* Create a fixed-offset [TimeZone] from an identifier in the form of `+01:00`.
*/
fun FixedOffset(id: String): FixedOffset {
return try {
FixedOffset(id.toUtcOffset(FIXED_TIME_ZONE_PARSER))
} catch (e: DateTimeParseException) {
Expand All @@ -187,7 +191,6 @@ sealed class TimeZone : Comparable<TimeZone> {
/**
* Create a [TimeZone] from an identifier.
*/
@Suppress("FunctionName")
fun TimeZone(id: String): TimeZone {
return when {
id == "Z" -> TimeZone.UTC
Expand All @@ -198,7 +201,7 @@ fun TimeZone(id: String): TimeZone {
}

/**
* Convert a UTC offset into a [TimeZone] with a fixed offset.
* Converts this [UtcOffset] into a fixed-offset [TimeZone].
*/
fun UtcOffset.asTimeZone(): TimeZone = TimeZone.FixedOffset(this)

Expand Down
9 changes: 5 additions & 4 deletions core/src/commonMain/kotlin/io/islandtime/UtcOffset.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")

package io.islandtime

import io.islandtime.base.DateTimeField
Expand Down Expand Up @@ -98,7 +100,6 @@ inline class UtcOffset(val totalSeconds: IntSeconds) : Comparable<UtcOffset> {
* @throws DateTimeException if any of the individual components is outside the valid range
* @return a [UtcOffset]
*/
@Suppress("FunctionName")
fun UtcOffset(
hours: IntHours,
minutes: IntMinutes = 0.minutes,
Expand All @@ -109,19 +110,19 @@ fun UtcOffset(
}

/**
* Convert a duration of hours into a UTC time offset of the same length.
* Converts a duration of hours into a UTC time offset of the same length.
* @throws ArithmeticException if overflow occurs
*/
fun IntHours.asUtcOffset() = UtcOffset(this.inSeconds)

/**
* Convert a duration of minutes into a UTC time offset of the same length.
* Converts a duration of minutes into a UTC time offset of the same length.
* @throws ArithmeticException if overflow occurs
*/
fun IntMinutes.asUtcOffset() = UtcOffset(this.inSeconds)

/**
* Convert a duration of seconds into a UTC time offset of the same length.
* Converts a duration of seconds into a UTC time offset of the same length.
*/
fun IntSeconds.asUtcOffset() = UtcOffset(this)

Expand Down
Loading