All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.islandtime.OffsetDateTime.kt Maven / Gradle / Ivy

The newest version!
package io.islandtime

import io.islandtime.base.TimePoint
import io.islandtime.measures.*
import io.islandtime.parser.*
import io.islandtime.ranges.OffsetDateTimeInterval

/**
 * A date and time of day with an offset from UTC.
 *
 * `OffsetDateTime` is intended to be used primarily for use cases involving persistence or network transfer where the
 * application of time zone rules may be undesirable. For most applications, [ZonedDateTime] is a better choice since
 * it takes time zone rules into account when performing calendrical calculations.
 *
 * @constructor Creates an [OffsetDateTime] by combining a [DateTime] and [UtcOffset].
 * @param dateTime the local date and time of day
 * @param offset the offset from UTC
 * @throws DateTimeException if the offset is invalid
 */
class OffsetDateTime(
    /** The local date and time of day. */
    val dateTime: DateTime,
    /** The offset from UTC. */
    val offset: UtcOffset
) : TimePoint {

    init {
        offset.validate()
    }

    /**
     * Creates an [OffsetDateTime].
     * @throws DateTimeException if the offset is invalid
     */
    constructor(date: Date, time: Time, offset: UtcOffset) : this(DateTime(date, time), offset)

    /**
     * Creates an [OffsetDateTime].
     * @throws DateTimeException if the date-time or offset is invalid
     */
    constructor(
        year: Int,
        month: Month,
        dayOfMonth: Int,
        hour: Int,
        minute: Int,
        second: Int,
        nanosecond: Int,
        offset: UtcOffset
    ) : this(DateTime(year, month, dayOfMonth, hour, minute, second, nanosecond), offset)

    /**
     * Creates an [OffsetDateTime].
     * @throws DateTimeException if the date-time or offset is invalid
     */
    constructor(
        year: Int,
        monthNumber: Int,
        dayOfMonth: Int,
        hour: Int,
        minute: Int,
        second: Int,
        nanosecond: Int,
        offset: UtcOffset
    ) : this(DateTime(year, monthNumber.toMonth(), dayOfMonth, hour, minute, second, nanosecond), offset)

    /**
     * Creates an [OffsetDateTime].
     * @throws DateTimeException if the date-time or offset is invalid
     */
    constructor(
        year: Int,
        dayOfYear: Int,
        hour: Int,
        minute: Int,
        second: Int,
        nanosecond: Int,
        offset: UtcOffset
    ) : this(DateTime(year, dayOfYear, hour, minute, second, nanosecond), offset)

    /**
     * The local date.
     */
    inline val date: Date get() = dateTime.date

    /**
     * The local time of day.
     */
    inline val time: Time get() = dateTime.time

    /**
     * The hour of the day.
     */
    inline val hour: Int get() = dateTime.hour

    /**
     * The minute of the hour.
     */
    inline val minute: Int get() = dateTime.minute

    /**
     * The second of the minute.
     */
    inline val second: Int get() = dateTime.second

    /**
     * The nanosecond of the second.
     */
    override val nanosecond: Int get() = dateTime.nanosecond

    /**
     * The month of the year.
     */
    inline val month: Month get() = dateTime.month

    /**
     * The ISO month number, from 1-12.
     */
    inline val monthNumber: Int get() = month.number

    /**
     * The day of the week.
     */
    inline val dayOfWeek: DayOfWeek get() = dateTime.dayOfWeek

    /**
     * The day of the month.
     */
    inline val dayOfMonth: Int get() = dateTime.dayOfMonth

    /**
     * The day of the year.
     */
    inline val dayOfYear: Int get() = dateTime.dayOfYear

    /**
     * The year.
     */
    inline val year: Int get() = dateTime.year

    @Deprecated(
        "Use toYearMonth() instead.",
        ReplaceWith("this.toYearMonth()"),
        DeprecationLevel.ERROR
    )
    inline val yearMonth: YearMonth get() = toYearMonth()

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

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

    override val secondsSinceUnixEpoch: LongSeconds
        get() = dateTime.secondsSinceUnixEpochAt(offset)

    override val additionalNanosecondsSinceUnixEpoch: IntNanoseconds
        get() = dateTime.additionalNanosecondsSinceUnixEpoch

    override val millisecondsSinceUnixEpoch: LongMilliseconds
        get() = dateTime.millisecondsSinceUnixEpochAt(offset)

    /**
     * Changes the offset of this [OffsetDateTime], adjusting the date and time components such that the instant
     * represented by it remains the same.
     */
    fun adjustedTo(newOffset: UtcOffset): OffsetDateTime {
        return if (newOffset == offset) {
            this
        } else {
            val newDateTime = dateTime + (newOffset.totalSeconds - offset.totalSeconds)
            OffsetDateTime(newDateTime, newOffset)
        }
    }

    /**
     * Returns this date-time with [period] added to it.
     *
     * Years are added first, then months, then days. If the day exceeds the maximum month length at any step, it will
     * be coerced into the valid range.
     */
    operator fun plus(period: Period) = copy(dateTime = dateTime + period)

    operator fun plus(duration: Duration) = copy(dateTime = dateTime + duration)

    operator fun plus(years: LongYears) = copy(dateTime = dateTime + years)
    operator fun plus(years: IntYears) = copy(dateTime = dateTime + years)
    operator fun plus(months: LongMonths) = copy(dateTime = dateTime + months)
    operator fun plus(months: IntMonths) = copy(dateTime = dateTime + months)
    operator fun plus(weeks: LongWeeks) = copy(dateTime = dateTime + weeks)
    operator fun plus(weeks: IntWeeks) = copy(dateTime = dateTime + weeks)
    operator fun plus(days: LongDays) = copy(dateTime = dateTime + days)
    operator fun plus(days: IntDays) = copy(dateTime = dateTime + days)
    override operator fun plus(hours: LongHours) = copy(dateTime = dateTime + hours)
    override operator fun plus(hours: IntHours) = copy(dateTime = dateTime + hours)
    override operator fun plus(minutes: LongMinutes) = copy(dateTime = dateTime + minutes)
    override operator fun plus(minutes: IntMinutes) = copy(dateTime = dateTime + minutes)
    override operator fun plus(seconds: LongSeconds) = copy(dateTime = dateTime + seconds)
    override operator fun plus(seconds: IntSeconds) = copy(dateTime = dateTime + seconds)
    override operator fun plus(milliseconds: LongMilliseconds) = copy(dateTime = dateTime + milliseconds)
    override operator fun plus(milliseconds: IntMilliseconds) = copy(dateTime = dateTime + milliseconds)
    override operator fun plus(microseconds: LongMicroseconds) = copy(dateTime = dateTime + microseconds)
    override operator fun plus(microseconds: IntMicroseconds) = copy(dateTime = dateTime + microseconds)
    override operator fun plus(nanoseconds: LongNanoseconds) = copy(dateTime = dateTime + nanoseconds)
    override operator fun plus(nanoseconds: IntNanoseconds) = copy(dateTime = dateTime + nanoseconds)

    /**
     * Returns this date-time with [period] subtracted from it.
     *
     * Years are added first, then months, then days. If the day exceeds the maximum month length at any step, it will
     * be coerced into the valid range.
     */
    operator fun minus(period: Period) = copy(dateTime = dateTime - period)

    operator fun minus(duration: Duration) = copy(dateTime = dateTime - duration)

    operator fun minus(years: LongYears) = copy(dateTime = dateTime - years)
    operator fun minus(years: IntYears) = copy(dateTime = dateTime - years)
    operator fun minus(months: LongMonths) = copy(dateTime = dateTime - months)
    operator fun minus(months: IntMonths) = copy(dateTime = dateTime - months)
    operator fun minus(weeks: LongWeeks) = copy(dateTime = dateTime - weeks)
    operator fun minus(weeks: IntWeeks) = copy(dateTime = dateTime - weeks)
    operator fun minus(days: LongDays) = copy(dateTime = dateTime - days)
    operator fun minus(days: IntDays) = copy(dateTime = dateTime - days)
    override operator fun minus(hours: LongHours) = copy(dateTime = dateTime - hours)
    override operator fun minus(hours: IntHours) = copy(dateTime = dateTime - hours)
    override operator fun minus(minutes: LongMinutes) = copy(dateTime = dateTime - minutes)
    override operator fun minus(minutes: IntMinutes) = copy(dateTime = dateTime - minutes)
    override operator fun minus(seconds: LongSeconds) = copy(dateTime = dateTime - seconds)
    override operator fun minus(seconds: IntSeconds) = copy(dateTime = dateTime - seconds)
    override operator fun minus(milliseconds: LongMilliseconds) = copy(dateTime = dateTime - milliseconds)
    override operator fun minus(milliseconds: IntMilliseconds) = copy(dateTime = dateTime - milliseconds)
    override operator fun minus(microseconds: LongMicroseconds) = copy(dateTime = dateTime - microseconds)
    override operator fun minus(microseconds: IntMicroseconds) = copy(dateTime = dateTime - microseconds)
    override operator fun minus(nanoseconds: LongNanoseconds) = copy(dateTime = dateTime - nanoseconds)
    override operator fun minus(nanoseconds: IntNanoseconds) = copy(dateTime = dateTime - nanoseconds)

    operator fun rangeTo(other: OffsetDateTime): OffsetDateTimeInterval =
        OffsetDateTimeInterval.withInclusiveEnd(this, other)

    /**
     * Converts this date-time to a string in ISO-8601 extended format. For example,
     * `2012-04-15T17:31:45.923452091-04:00` or `2020-02-13T02:30Z`.
     */
    override fun toString(): String = buildString(MAX_OFFSET_DATE_TIME_STRING_LENGTH) {
        appendOffsetDateTime(this@OffsetDateTime)
    }

    override fun equals(other: Any?): Boolean {
        return this === other || (other is OffsetDateTime && dateTime == other.dateTime && offset == other.offset)
    }

    override fun hashCode(): Int {
        return 31 * dateTime.hashCode() + offset.hashCode()
    }

    /**
     * Returns a copy of this date-time with the values of any individual components replaced by the new values
     * specified.
     * @throws DateTimeException if the resulting date-time is invalid
     */
    fun copy(
        dateTime: DateTime = this.dateTime,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(dateTime, offset)

    /**
     * Returns a copy of this date-time with the values of any individual components replaced by the new values
     * specified.
     * @throws DateTimeException if the resulting date-time is invalid
     */
    fun copy(
        date: Date = this.date,
        time: Time = this.time,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(date, time, offset)

    /**
     * Returns a copy of this date-time with the values of any individual components replaced by the new values
     * specified.
     * @throws DateTimeException if the resulting date-time is invalid
     */
    fun copy(
        year: Int = this.year,
        dayOfYear: Int = this.dayOfYear,
        hour: Int = this.hour,
        minute: Int = this.minute,
        second: Int = this.second,
        nanosecond: Int = this.nanosecond,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(date.copy(year, dayOfYear), time.copy(hour, minute, second, nanosecond), offset)

    /**
     * Returns a copy of this date-time with the values of any individual components replaced by the new values
     * specified.
     * @throws DateTimeException if the resulting date-time is invalid
     */
    fun copy(
        year: Int = this.year,
        month: Month = this.month,
        dayOfMonth: Int = this.dayOfMonth,
        hour: Int = this.hour,
        minute: Int = this.minute,
        second: Int = this.second,
        nanosecond: Int = this.nanosecond,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(date.copy(year, month, dayOfMonth), time.copy(hour, minute, second, nanosecond), offset)

    companion object {
        /**
         * The earliest supported [OffsetDateTime], which can be used as a "far past" sentinel.
         */
        val MIN = DateTime.MIN at UtcOffset.MAX

        /**
         * The latest supported [OffsetDateTime], which can be used as a "far future" sentinel.
         */
        val MAX = DateTime.MAX at UtcOffset.MIN

        /**
         * A [Comparator] that compares by instant, then date-time. Using this `Comparator` guarantees a deterministic
         * order when sorting.
         */
        val DEFAULT_SORT_ORDER: Comparator = compareBy { it.secondOfUnixEpoch }
            .thenBy { it.nanosecond }
            .thenBy { it.dateTime }

        /**
         * A [Comparator] that compares by timeline order only, ignoring any offset differences.
         */
        val TIMELINE_ORDER: Comparator> get() = TimePoint.TIMELINE_ORDER

        /**
         * Creates an [OffsetDateTime] from a duration of milliseconds relative to the Unix epoch at [offset].
         */
        fun fromMillisecondsSinceUnixEpoch(milliseconds: LongMilliseconds, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(DateTime.fromMillisecondsSinceUnixEpoch(milliseconds, offset), offset)
        }

        /**
         * Creates an [OffsetDateTime] from a duration of seconds relative to the Unix epoch at [offset], optionally,
         * with some number of additional nanoseconds added to it.
         */
        fun fromSecondsSinceUnixEpoch(
            seconds: LongSeconds,
            nanosecondAdjustment: IntNanoseconds = 0.nanoseconds,
            offset: UtcOffset
        ): OffsetDateTime {
            return OffsetDateTime(DateTime.fromSecondsSinceUnixEpoch(seconds, nanosecondAdjustment, offset), offset)
        }

        /**
         * Creates an [OffsetDateTime] from the millisecond of the Unix epoch at [offset].
         */
        fun fromMillisecondOfUnixEpoch(millisecond: Long, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(DateTime.fromMillisecondOfUnixEpoch(millisecond, offset), offset)
        }

        /**
         * Creates an [OffsetDateTime] from the second of the Unix epoch at [offset] and optionally, the nanosecond of
         * the second.
         */
        fun fromSecondOfUnixEpoch(second: Long, nanosecond: Int = 0, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(DateTime.fromSecondOfUnixEpoch(second, nanosecond, offset), offset)
        }

        @Deprecated(
            "Use fromMillisecondOfUnixEpoch() instead.",
            ReplaceWith("OffsetDateTime.fromMillisecondOfUnixEpoch(millisecond, offset)"),
            DeprecationLevel.ERROR
        )
        fun fromUnixEpochMillisecond(millisecond: Long, offset: UtcOffset): OffsetDateTime {
            return fromMillisecondOfUnixEpoch(millisecond, offset)
        }

        @Deprecated(
            "Use fromSecondOfUnixEpoch() instead.",
            ReplaceWith("OffsetDateTime.fromSecondOfUnixEpoch(second, nanoOfSecond, offset)"),
            DeprecationLevel.ERROR
        )
        fun fromUnixEpochSecond(second: Long, nanoOfSecond: Int, offset: UtcOffset): OffsetDateTime {
            return fromSecondOfUnixEpoch(second, nanoOfSecond, offset)
        }
    }
}

/**
 * Converts a string to an [OffsetDateTime].
 *
 * The string is assumed to be an ISO-8601 date-time with the UTC offset in extended format. For example,
 * `2019-05-30T02:30+01:00`. The output of [OffsetDateTime.toString] can be safely parsed using this method.
 *
 * @throws DateTimeParseException if parsing fails
 * @throws DateTimeException if the parsed date-time or offset is invalid
 */
fun String.toOffsetDateTime(): OffsetDateTime = toOffsetDateTime(DateTimeParsers.Iso.Extended.OFFSET_DATE_TIME)

/**
 * Converts a string to an [OffsetDateTime] using a specific parser.
 *
 * A set of predefined parsers can be found in [DateTimeParsers].
 *
 * Any custom parser must be capable of supplying the fields necessary to resolve a [Date], [Time] and [UtcOffset].
 *
 * @throws DateTimeParseException if parsing fails
 * @throws DateTimeException if the parsed date-time or offset is invalid
 */
fun String.toOffsetDateTime(
    parser: DateTimeParser,
    settings: DateTimeParserSettings = DateTimeParserSettings.DEFAULT
): OffsetDateTime {
    val result = parser.parse(this, settings)
    return result.toOffsetDateTime() ?: throwParserFieldResolutionException(this)
}

internal fun DateTimeParseResult.toOffsetDateTime(): OffsetDateTime? {
    val dateTime = this.toDateTime()
    val utcOffset = this.toUtcOffset()

    return if (dateTime != null && utcOffset != null) {
        OffsetDateTime(dateTime, utcOffset)
    } else {
        null
    }
}

internal const val MAX_OFFSET_DATE_TIME_STRING_LENGTH = MAX_DATE_TIME_STRING_LENGTH + MAX_UTC_OFFSET_STRING_LENGTH

internal fun StringBuilder.appendOffsetDateTime(offsetDateTime: OffsetDateTime): StringBuilder {
    with(offsetDateTime) {
        appendDateTime(dateTime)
        appendUtcOffset(offset)
    }
    return this
}

@Deprecated(
    "Use 'toOffsetDateTime()' instead.",
    ReplaceWith("this.toOffsetDateTime()"),
    DeprecationLevel.ERROR
)
fun ZonedDateTime.asOffsetDateTime() = toOffsetDateTime()




© 2015 - 2025 Weber Informatics LLC | Privacy Policy