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

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

There is a newer version: 0.3.1
Show 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 Create 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()
    }

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

    /**
     * Create 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)

    /**
     * Create 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)

    /**
     * Create 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.
     */
    inline 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 -- also known as the ordinal date in ISO-8601.
     */
    inline val dayOfYear: Int get() = dateTime.dayOfYear

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

    /**
     * Check if this date falls within a leap year.
     */
    inline val isInLeapYear: Boolean get() = dateTime.isInLeapYear

    /**
     * Check if this is a leap day.
     */
    inline val isLeapDay: Boolean get() = dateTime.isLeapDay

    /**
     * The length of this date's month in days.
     */
    inline val lengthOfMonth: IntDays get() = dateTime.lengthOfMonth

    /**
     * The length of this date's year in days.
     */
    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.fromUnixEpochSecond(unixEpochSecond, nanosecond)

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

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

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

    /**
     * Change the offset of an [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)
        }
    }

    /**
     * Return an [OffsetDateTime] 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. This behavior is consistent with the order of operations for period addition as
     * defined in ISO-8601-2.
     */
    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)

    /**
     * Return an [OffsetDateTime] with [period] subtracted from it.
     *
     * Years are subtracted first, then months, then days. If the day exceeds the maximum month length at any step, it
     * will be coerced into the valid range. This behavior is consistent with the order of operations for period
     * addition as defined in ISO-8601-2.
     */
    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.withInclusiveEnd(this, other)

    override fun toString() = 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()
    }

    /**
     * Return a new [OffsetDateTime], replacing any of the components with new values.
     */
    fun copy(
        dateTime: DateTime = this.dateTime,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(dateTime, offset)

    /**
     * Return a new [OffsetDateTime], replacing any of the components with new values.
     */
    fun copy(
        date: Date = this.date,
        time: Time = this.time,
        offset: UtcOffset = this.offset
    ) = OffsetDateTime(date, time, offset)

    /**
     * Return a new [OffsetDateTime], replacing any of the components with new values.
     */
    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)

    /**
     * Return a new [OffsetDateTime], replacing any of the components with new values.
     */
    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 {
        val MIN = DateTime.MIN at UtcOffset.MAX
        val MAX = DateTime.MAX at UtcOffset.MIN

        /**
         * Compare by instant, then date-time. Using this `Comparator` guarantees a deterministic order when sorting.
         */
        val DEFAULT_SORT_ORDER = compareBy { it.unixEpochSecond }
            .thenBy { it.unixEpochNanoOfSecond }
            .thenBy { it.dateTime }

        /**
         * Compare by timeline order only, ignoring any offset differences.
         */
        val TIMELINE_ORDER get() = TimePoint.TIMELINE_ORDER

        fun fromMillisecondsSinceUnixEpoch(milliseconds: LongMilliseconds, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(
                DateTime.fromMillisecondsSinceUnixEpoch(milliseconds, offset),
                offset
            )
        }

        fun fromSecondsSinceUnixEpoch(
            seconds: LongSeconds,
            nanosecondAdjustment: IntNanoseconds,
            offset: UtcOffset
        ): OffsetDateTime {
            return OffsetDateTime(
                DateTime.fromSecondsSinceUnixEpoch(seconds, nanosecondAdjustment, offset),
                offset
            )
        }

        fun fromUnixEpochMillisecond(millisecond: Long, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(
                DateTime.fromUnixEpochMillisecond(millisecond, offset),
                offset
            )
        }

        fun fromUnixEpochSecond(second: Long, nanoOfSecond: Int, offset: UtcOffset): OffsetDateTime {
            return OffsetDateTime(
                DateTime.fromUnixEpochSecond(second, nanoOfSecond, offset),
                offset
            )
        }
    }
}

/**
 * Combine a local date and time with a UTC offset to create an [OffsetDateTime].
 */
infix fun DateTime.at(offset: UtcOffset) = OffsetDateTime(this, offset)

/**
 * Combine a local date with a time and UTC offset to create an [OffsetDateTime].
 */
infix fun Date.at(offsetTime: OffsetTime) = OffsetDateTime(this, offsetTime.time, offsetTime.offset)

/**
 * Combine an instant with a UTC offset to create an [OffsetDateTime].
 */
infix fun Instant.at(offset: UtcOffset) = OffsetDateTime(this.toDateTimeAt(offset), offset)

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

/**
 * Convert 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() = toOffsetDateTime(DateTimeParsers.Iso.Extended.OFFSET_DATE_TIME)

/**
 * Convert 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
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy