From 3ad69b4c3084fb7505dca70c1811e636cb2d6c70 Mon Sep 17 00:00:00 2001 From: Erik Christensen Date: Mon, 24 May 2021 17:39:35 -0400 Subject: [PATCH 1/2] Replace unit between functions with companion object methods --- .../io/islandtime/ranges/_Properties.kt | 469 ++++++++++++++++++ .../kotlin/io/islandtime/operators/Between.kt | 460 +++++++++++++++++ .../kotlin/io/islandtime/ranges/DateRange.kt | 124 ++--- .../io/islandtime/ranges/DateTimeInterval.kt | 337 +++++-------- .../ranges/OffsetDateTimeInterval.kt | 107 ++-- .../io/islandtime/ranges/TimePointInterval.kt | 230 +++------ .../ranges/TimePointProgressions.kt | 5 +- .../ranges/ZonedDateTimeInterval.kt | 116 ++--- .../io/islandtime/operators/BetweenTest.kt | 410 +++++++++++++++ .../io/islandtime/ranges/DateRangeTest.kt | 224 ++------- .../islandtime/ranges/DateTimeIntervalTest.kt | 107 ++-- .../islandtime/ranges/InstantIntervalTest.kt | 190 ------- .../ranges/OffsetDateTimeIntervalTest.kt | 121 ++--- .../ranges/ZonedDateTimeIntervalTest.kt | 64 ++- docs/basics/durations.md | 6 +- docs/basics/intervals.md | 2 +- .../main/kotlin/io/islandtime/codegen/Main.kt | 7 +- .../descriptions/DateTimeDescription.kt | 35 +- .../descriptions/IntervalDescription.kt | 4 + .../descriptions/TemporalUnitDescription.kt | 4 +- .../generators/DatePropertiesGenerator.kt | 6 +- .../generators/IntervalOperatorsGenerator.kt | 11 +- .../generators/IntervalPropertiesGenerator.kt | 108 ++++ .../generators/TemporalUnitGenerator.kt | 8 +- .../islandtime/codegen/generators/Utility.kt | 13 + 25 files changed, 2030 insertions(+), 1138 deletions(-) create mode 100644 core/src/commonMain/generated/io/islandtime/ranges/_Properties.kt create mode 100644 core/src/commonMain/kotlin/io/islandtime/operators/Between.kt create mode 100644 core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt create mode 100644 tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt create mode 100644 tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/Utility.kt diff --git a/core/src/commonMain/generated/io/islandtime/ranges/_Properties.kt b/core/src/commonMain/generated/io/islandtime/ranges/_Properties.kt new file mode 100644 index 000000000..357d8dda5 --- /dev/null +++ b/core/src/commonMain/generated/io/islandtime/ranges/_Properties.kt @@ -0,0 +1,469 @@ +// +// This file is auto-generated by 'tools:code-generator' +// +@file:JvmMultifileClass +@file:JvmName("RangesKt") + +package io.islandtime.ranges + +import io.islandtime.between +import io.islandtime.measures.Centuries +import io.islandtime.measures.Days +import io.islandtime.measures.Decades +import io.islandtime.measures.Hours +import io.islandtime.measures.Microseconds +import io.islandtime.measures.Milliseconds +import io.islandtime.measures.Minutes +import io.islandtime.measures.Months +import io.islandtime.measures.Nanoseconds +import io.islandtime.measures.Seconds +import io.islandtime.measures.Weeks +import io.islandtime.measures.Years +import io.islandtime.measures.centuries +import io.islandtime.measures.days +import io.islandtime.measures.decades +import io.islandtime.measures.hours +import io.islandtime.measures.microseconds +import io.islandtime.measures.milliseconds +import io.islandtime.measures.minutes +import io.islandtime.measures.months +import io.islandtime.measures.nanoseconds +import io.islandtime.measures.seconds +import io.islandtime.measures.weeks +import io.islandtime.measures.years +import io.islandtime.ranges.`internal`.throwUnboundedIntervalException +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +/** + * The number of days between the start and end of this range. A range is inclusive, so if the start + * and end date are the same, the length will be one day. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInDays: Days + get() = when { + isEmpty() -> 0.days + isBounded() -> Days.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole weeks between the start and end of this range. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInWeeks: Weeks + get() = when { + isEmpty() -> 0.weeks + isBounded() -> Weeks.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole months between the start and end of this range. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInMonths: Months + get() = when { + isEmpty() -> 0.months + isBounded() -> Months.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole years between the start and end of this range. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInYears: Years + get() = when { + isEmpty() -> 0.years + isBounded() -> Years.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole decades between the start and end of this range. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInDecades: Decades + get() = when { + isEmpty() -> 0.decades + isBounded() -> Decades.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole centuries between the start and end of this range. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +public val DateRange.lengthInCenturies: Centuries + get() = when { + isEmpty() -> 0.centuries + isBounded() -> Centuries.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of nanoseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInNanoseconds: Nanoseconds + get() = when { + isEmpty() -> 0.nanoseconds + isBounded() -> Nanoseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole microseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInMicroseconds: Microseconds + get() = when { + isEmpty() -> 0.microseconds + isBounded() -> Microseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole milliseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInMilliseconds: Milliseconds + get() = when { + isEmpty() -> 0.milliseconds + isBounded() -> Milliseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole seconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInSeconds: Seconds + get() = when { + isEmpty() -> 0.seconds + isBounded() -> Seconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole minutes between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInMinutes: Minutes + get() = when { + isEmpty() -> 0.minutes + isBounded() -> Minutes.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole hours between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInHours: Hours + get() = when { + isEmpty() -> 0.hours + isBounded() -> Hours.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole days between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInDays: Days + get() = when { + isEmpty() -> 0.days + isBounded() -> Days.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole weeks between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInWeeks: Weeks + get() = when { + isEmpty() -> 0.weeks + isBounded() -> Weeks.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole months between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInMonths: Months + get() = when { + isEmpty() -> 0.months + isBounded() -> Months.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole years between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInYears: Years + get() = when { + isEmpty() -> 0.years + isBounded() -> Years.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole decades between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInDecades: Decades + get() = when { + isEmpty() -> 0.decades + isBounded() -> Decades.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole centuries between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val DateTimeInterval.lengthInCenturies: Centuries + get() = when { + isEmpty() -> 0.centuries + isBounded() -> Centuries.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole days between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInDays: Days + get() = when { + isEmpty() -> 0.days + isBounded() -> Days.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole weeks between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInWeeks: Weeks + get() = when { + isEmpty() -> 0.weeks + isBounded() -> Weeks.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole months between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInMonths: Months + get() = when { + isEmpty() -> 0.months + isBounded() -> Months.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole years between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInYears: Years + get() = when { + isEmpty() -> 0.years + isBounded() -> Years.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole decades between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInDecades: Decades + get() = when { + isEmpty() -> 0.decades + isBounded() -> Decades.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole centuries between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val OffsetDateTimeInterval.lengthInCenturies: Centuries + get() = when { + isEmpty() -> 0.centuries + isBounded() -> Centuries.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole days between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInDays: Days + get() = when { + isEmpty() -> 0.days + isBounded() -> Days.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole weeks between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInWeeks: Weeks + get() = when { + isEmpty() -> 0.weeks + isBounded() -> Weeks.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole months between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInMonths: Months + get() = when { + isEmpty() -> 0.months + isBounded() -> Months.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole years between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInYears: Years + get() = when { + isEmpty() -> 0.years + isBounded() -> Years.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole decades between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInDecades: Decades + get() = when { + isEmpty() -> 0.decades + isBounded() -> Decades.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole centuries between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val ZonedDateTimeInterval.lengthInCenturies: Centuries + get() = when { + isEmpty() -> 0.centuries + isBounded() -> Centuries.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of nanoseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInNanoseconds: Nanoseconds + get() = when { + isEmpty() -> 0.nanoseconds + isBounded() -> Nanoseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole microseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInMicroseconds: Microseconds + get() = when { + isEmpty() -> 0.microseconds + isBounded() -> Microseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole milliseconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInMilliseconds: Milliseconds + get() = when { + isEmpty() -> 0.milliseconds + isBounded() -> Milliseconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole seconds between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInSeconds: Seconds + get() = when { + isEmpty() -> 0.seconds + isBounded() -> Seconds.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole minutes between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInMinutes: Minutes + get() = when { + isEmpty() -> 0.minutes + isBounded() -> Minutes.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } + +/** + * The number of whole hours between the start and end of this interval. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +public val TimePointInterval<*>.lengthInHours: Hours + get() = when { + isEmpty() -> 0.hours + isBounded() -> Hours.between(start, endExclusive) + else -> throwUnboundedIntervalException() + } diff --git a/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt b/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt new file mode 100644 index 000000000..aa54f09ba --- /dev/null +++ b/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt @@ -0,0 +1,460 @@ +@file:JvmName("DateTimesKt") +@file:JvmMultifileClass +@file:Suppress("PackageDirectoryMismatch") + +package io.islandtime + +import io.islandtime.base.TimePoint +import io.islandtime.measures.* +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +/** + * Returns the number of whole centuries between two years. + */ +fun Centuries.Companion.between(start: Year, endExclusive: Year): Centuries { + return Years.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole centuries between two year-months. + */ +fun Centuries.Companion.between(start: YearMonth, endExclusive: YearMonth): Centuries { + return Years.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole centuries between two dates. + */ +fun Centuries.Companion.between(start: Date, endExclusive: Date): Centuries { + return Months.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole centuries between two date-times, which are assumed to be in the same time zone. + */ +fun Centuries.Companion.between(start: DateTime, endExclusive: DateTime): Centuries { + return Months.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole centuries between two date-times, adjusting the offset of [endExclusive] if necessary to + * match the starting date-time. + */ +fun Centuries.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Centuries { + return Months.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole centuries between two date-times, adjusting the time zone of [endExclusive] if necessary + * to match the starting date-time. + */ +fun Centuries.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Centuries { + return Months.between(start, endExclusive).inWholeCenturies +} + +/** + * Returns the number of whole decades between two years. + */ +fun Decades.Companion.between(start: Year, endExclusive: Year): Decades { + return Years.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of whole decades between two year-months. + */ +fun Decades.Companion.between(start: YearMonth, endExclusive: YearMonth): Decades { + return Years.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of whole decades between two dates. + */ +fun Decades.Companion.between(start: Date, endExclusive: Date): Decades { + return Months.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of whole decades between two date-times, which are assumed to be in the same time zone. + */ +fun Decades.Companion.between(start: DateTime, endExclusive: DateTime): Decades { + return Months.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of whole decades between two date-times, adjusting the offset of [endExclusive] if necessary to + * match the starting date-time. + */ +fun Decades.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Decades { + return Months.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of whole decades between two date-times, adjusting the time zone of [endExclusive] if necessary to + * match the starting date-time. + */ +fun Decades.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Decades { + return Months.between(start, endExclusive).inWholeDecades +} + +/** + * Returns the number of years between two years. + */ +fun Years.Companion.between(start: Year, endExclusive: Year): Years { + return Years(endExclusive.value - start.value) +} + +/** + * Returns the number of whole years between two year-months. + */ +fun Years.Companion.between(start: YearMonth, endExclusive: YearMonth): Years { + return Months.between(start, endExclusive).inWholeYears +} + +/** + * Returns the number of whole years between two dates. + */ +fun Years.Companion.between(start: Date, endExclusive: Date): Years { + return Months.between(start, endExclusive).inWholeYears +} + +/** + * Returns the number of whole years between two date-times, which are assumed to be in the same time zone. + */ +fun Years.Companion.between(start: DateTime, endExclusive: DateTime): Years { + return Years.between(start.date, adjustedEndDate(start, endExclusive)) +} + +/** + * Returns the number of whole years between two date-times, adjusting the offset of [endExclusive] if necessary to + * match the starting date-time. + */ +fun Years.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Years { + return Years.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole years between date-times, adjusting the time zone of [endExclusive] if necessary to match + * the starting date-time. + */ +fun Years.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Years { + return Years.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of months between two year-months. + */ +fun Months.Companion.between(start: YearMonth, endExclusive: YearMonth): Months { + return (endExclusive.monthsSinceYear0 - start.monthsSinceYear0).months +} + +/** + * Returns the number of whole months between two dates. + */ +fun Months.Companion.between(start: Date, endExclusive: Date): Months { + val startDays = start.monthsSinceYear0 * 32L + start.dayOfMonth + val endDays = endExclusive.monthsSinceYear0 * 32L + endExclusive.dayOfMonth + return ((endDays - startDays) / 32).months +} + +/** + * Returns the number of whole months between two date-times, which are assumed to be in the same time zone. + */ +fun Months.Companion.between(start: DateTime, endExclusive: DateTime): Months { + return Months.between(start.date, adjustedEndDate(start, endExclusive)) +} + +/** + * Returns the number of whole months between two date-times, adjusting the offset of [endExclusive] if necessary to match + * the starting date-time. + */ +fun Months.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Months { + return Months.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole months between two zoned date-times, adjusting the time zone of [endExclusive] if necessary + * to match the starting date-time. + */ +fun Months.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Months { + return Months.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole weeks between two dates. + */ +fun Weeks.Companion.between(start: Date, endExclusive: Date): Weeks { + return Days.between(start, endExclusive).inWholeWeeks +} + +/** + * Returns the number whole weeks between two date-times, which are assumed to be in the same time zone. + */ +fun Weeks.Companion.between(start: DateTime, endExclusive: DateTime): Weeks { + return Days.between(start, endExclusive).inWholeWeeks +} + +/** + * Returns the number whole weeks between two date-times, adjusting the offset of [endExclusive] if necessary to match + * the starting date-time. + */ +fun Weeks.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Weeks { + return Weeks.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole weeks between two zoned date-times, adjusting the time zone of [endExclusive] if + * necessary to match the starting date-time. + */ +fun Weeks.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Weeks { + return Weeks.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of days between two dates. + */ +fun Days.Companion.between(start: Date, endExclusive: Date): Days { + return endExclusive.daysSinceUnixEpoch - start.daysSinceUnixEpoch +} + +/** + * Returns the number whole days between two date-times, which are assumed to be in the same time zone. + */ +fun Days.Companion.between(start: DateTime, endExclusive: DateTime): Days { + return Seconds.between(start, endExclusive).inWholeDays +} + +/** + * Returns the number of 24-hour days between two time points. + */ +//fun Days.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Days { +// return Seconds.between(start, endExclusive).inWholeDays +//} + +/** + * Returns the number whole days between two date-times, adjusting the offset of [endExclusive] if necessary to match + * the starting date-time. + */ +fun Days.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Days { + return Days.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole days between two zoned date-times, adjusting the time zone of [endExclusive] if necessary + * to match the starting date-time. + */ +fun Days.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Days { + return Days.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the number of whole hours between two date-times, which are assumed to be at the same UTC offset. In general, + * it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be + * taken into account when working with [DateTime] directly. + */ +fun Hours.Companion.between(start: DateTime, endExclusive: DateTime): Hours { + return Seconds.between(start, endExclusive).inWholeHours +} + +/** + * Returns the number of whole hours between two time points. + */ +fun Hours.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Hours { + return Seconds.between(start, endExclusive).inWholeHours +} + +/** + * Returns the number of whole minutes between two date-times, which are assumed to be at the same UTC offset. In + * general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules + * won't be taken into account when working with [DateTime] directly. + */ +fun Minutes.Companion.between(start: DateTime, endExclusive: DateTime): Minutes { + return Seconds.between(start, endExclusive).inWholeMinutes +} + +/** + * Returns the number of whole minutes between two time points. + */ +fun Minutes.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Minutes { + return Seconds.between(start, endExclusive).inWholeMinutes +} + +/** + * Returns the number of whole seconds between two date-times, which are assumed to be at the same UTC offset. In + * general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules + * won't be taken into account when working with [DateTime] directly. + * + * @throws ArithmeticException if the result overflows + */ +fun Seconds.Companion.between(start: DateTime, endExclusive: DateTime): Seconds { + return secondsBetween( + start.secondOfUnixEpochAt(UtcOffset.ZERO), + start.nanosecond, + endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), + endExclusive.nanosecond + ) +} + +/** + * Returns the number of whole seconds between two time points. + * @throws ArithmeticException if the result overflows + */ +fun Seconds.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Seconds { + return secondsBetween( + start.secondOfUnixEpoch, + start.nanosecond, + endExclusive.secondOfUnixEpoch, + endExclusive.nanosecond + ) +} + +/** + * Returns the number of whole milliseconds between two date-times, which are assumed to be at the same UTC offset. In + * general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules + * won't be taken into account when working with [DateTime] directly. + * + * @throws ArithmeticException if the result overflows + */ +fun Milliseconds.Companion.between(start: DateTime, endExclusive: DateTime): Milliseconds { + return millisecondsBetween( + start.secondOfUnixEpochAt(UtcOffset.ZERO), + start.nanosecond, + endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), + endExclusive.nanosecond + ) +} + +/** + * Returns the number of whole milliseconds between two time points. + * @throws ArithmeticException if the result overflows + */ +fun Milliseconds.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Milliseconds { + return millisecondsBetween( + start.secondOfUnixEpoch, + start.nanosecond, + endExclusive.secondOfUnixEpoch, + endExclusive.nanosecond + ) +} + +/** + * Returns the number of whole microseconds between two date-times, which are assumed to be at the same UTC offset. In + * general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules + * won't be taken into account when working with [DateTime] directly. + * + * @throws ArithmeticException if the result overflows + */ +fun Microseconds.Companion.between(start: DateTime, endExclusive: DateTime): Microseconds { + return microsecondsBetween( + start.secondOfUnixEpochAt(UtcOffset.ZERO), + start.nanosecond, + endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), + endExclusive.nanosecond + ) +} + +/** + * Returns the number of whole microseconds between two time points. + * @throws ArithmeticException if the result overflows + */ +fun Microseconds.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Microseconds { + return microsecondsBetween( + start.secondOfUnixEpoch, + start.nanosecond, + endExclusive.secondOfUnixEpoch, + endExclusive.nanosecond + ) +} + +/** + * Returns the number of nanoseconds between two date-times, which are assumed to be at the same UTC offset. In general, + * it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be + * taken into account when working with [DateTime] directly. + * + * @throws ArithmeticException if the result overflows + */ +fun Nanoseconds.Companion.between(start: DateTime, endExclusive: DateTime): Nanoseconds { + return nanosecondsBetween( + start.secondOfUnixEpochAt(UtcOffset.ZERO), + start.nanosecond, + endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), + endExclusive.nanosecond + ) +} + +/** + * Returns the number of nanoseconds between two time points. + * @throws ArithmeticException if the result overflows + */ +fun Nanoseconds.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Nanoseconds { + return nanosecondsBetween( + start.secondOfUnixEpoch, + start.nanosecond, + endExclusive.secondOfUnixEpoch, + endExclusive.nanosecond + ) +} + +private inline val YearMonth.monthsSinceYear0: Long get() = year * 12L + month.ordinal + +private fun secondsBetween( + startSecond: Long, + startNanosecond: Int, + endExclusiveSecond: Long, + endExclusiveNanosecond: Int +): Seconds { + val secondDiff = endExclusiveSecond - startSecond + val nanoDiff = endExclusiveNanosecond - startNanosecond + + return when { + secondDiff > 0 && nanoDiff < 0 -> secondDiff - 1 + secondDiff < 0 && nanoDiff > 0 -> secondDiff + 1 + else -> secondDiff + }.seconds +} + +private fun millisecondsBetween( + startSecond: Long, + startNanosecond: Int, + endExclusiveSecond: Long, + endExclusiveNanosecond: Int +): Milliseconds { + return (endExclusiveSecond - startSecond).seconds + + (endExclusiveNanosecond - startNanosecond).nanoseconds.inWholeMilliseconds +} + +private fun microsecondsBetween( + startSecond: Long, + startNanosecond: Int, + endExclusiveSecond: Long, + endExclusiveNanosecond: Int +): Microseconds { + return (endExclusiveSecond - startSecond).seconds + + (endExclusiveNanosecond - startNanosecond).nanoseconds.inWholeMicroseconds +} + +private fun nanosecondsBetween( + startSecond: Long, + startNanosecond: Int, + endExclusiveSecond: Long, + endExclusiveNanosecond: Int +): Nanoseconds { + return (endExclusiveSecond - startSecond).seconds + (endExclusiveNanosecond - startNanosecond).nanoseconds +} + +private fun adjustedEndDate(start: DateTime, endExclusive: DateTime): Date { + return when { + endExclusive.date > start.date && endExclusive.time < start.time -> endExclusive.date - 1.days + endExclusive.date < start.date && endExclusive.time > start.time -> endExclusive.date + 1.days + else -> endExclusive.date + } +} + +private fun adjustedEndDateTime(start: OffsetDateTime, endExclusive: OffsetDateTime): DateTime { + val offsetDelta = start.offset.totalSeconds - endExclusive.offset.totalSeconds + return endExclusive.dateTime + offsetDelta +} + +private fun adjustedEndDateTime(start: ZonedDateTime, endExclusive: ZonedDateTime): DateTime { + return endExclusive.adjustedTo(start.zone).dateTime +} diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt index 333f2635c..8f078f3b8 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt @@ -66,62 +66,16 @@ class DateRange( } } - /** - * The number of whole years in this range. - * @throws UnsupportedOperationException if the range isn't bounded - */ - val lengthInYears: Years - get() = when { - isEmpty() -> 0.years - isBounded() -> yearsBetween(start, endInclusive + 1.days) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole months in this range. - * @throws UnsupportedOperationException if the range isn't bounded - */ - val lengthInMonths: Months - get() = when { - isEmpty() -> 0.months - isBounded() -> monthsBetween(start, endInclusive + 1.days) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole weeks in this range. - * @throws UnsupportedOperationException if the range isn't bounded - */ - val lengthInWeeks: Weeks - get() = when { - isEmpty() -> 0.weeks - isBounded() -> weeksBetween(start, endInclusive + 1.days) - else -> throwUnboundedIntervalException() - } - - /** - * The number of days in this range. As a range is inclusive, if the start and end date are the same, the result - * will be one day. - * @throws UnsupportedOperationException if the range isn't bounded - */ - val lengthInDays: Days - get() = when { - isEmpty() -> 0.days - isBounded() -> daysBetween(start, endInclusive + 1.days) - else -> throwUnboundedIntervalException() - } - - companion object { /** * An empty range. */ - val EMPTY = DateRange(Date.fromDayOfUnixEpoch(1L), Date.fromDayOfUnixEpoch(0L)) + val EMPTY: DateRange = DateRange(Date.fromDayOfUnixEpoch(1L), Date.fromDayOfUnixEpoch(0L)) /** * An unbounded (ie. infinite) range of dates. */ - val UNBOUNDED = DateRange(Date.MIN, Date.MAX) + val UNBOUNDED: DateRange = DateRange(Date.MIN, Date.MAX) } } @@ -192,7 +146,7 @@ fun periodBetween(start: Date, endExclusive: Date): Period { totalMonths > 0 && dayDiff < 0 -> { totalMonths-- val testDate = start + totalMonths.months - daysBetween(testDate, endExclusive) + Days.between(testDate, endExclusive) } totalMonths < 0 && dayDiff > 0 -> { totalMonths++ @@ -206,32 +160,46 @@ fun periodBetween(start: Date, endExclusive: Date): Period { return periodOf(years, months, days) } -/** - * Gets the number of whole years between two dates. - */ -fun yearsBetween(start: Date, endExclusive: Date): Years { - return monthsBetween(start, endExclusive).inWholeYears -} - -/** - * Gets the number of whole months between two dates. - */ -fun monthsBetween(start: Date, endExclusive: Date): Months { - val startDays = start.monthsSinceYear0 * 32L + start.dayOfMonth - val endDays = endExclusive.monthsSinceYear0 * 32L + endExclusive.dayOfMonth - return ((endDays - startDays) / 32).months -} - -/** - * Gets the number of whole weeks between two dates. - */ -fun weeksBetween(start: Date, endExclusive: Date): Weeks { - return daysBetween(start, endExclusive).inWholeWeeks -} - -/** - * Gets the number of days between two dates. - */ -fun daysBetween(start: Date, endExclusive: Date): Days { - return endExclusive.daysSinceUnixEpoch - start.daysSinceUnixEpoch -} +@Deprecated( + message = "Replace with Years.between()", + replaceWith = ReplaceWith( + "Years.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Years" + ), + level = DeprecationLevel.WARNING +) +fun yearsBetween(start: Date, endExclusive: Date): Years = Years.between(start, endExclusive) + +@Deprecated( + message = "Replace with Months.between()", + replaceWith = ReplaceWith( + "Months.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Months" + ), + level = DeprecationLevel.WARNING +) +fun monthsBetween(start: Date, endExclusive: Date): Months = Months.between(start, endExclusive) + +@Deprecated( + message = "Replace with Weeks.between()", + replaceWith = ReplaceWith( + "Weeks.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Weeks" + ), + level = DeprecationLevel.WARNING +) +fun weeksBetween(start: Date, endExclusive: Date): Weeks = Weeks.between(start, endExclusive) + +@Deprecated( + message = "Replace with Days.between()", + replaceWith = ReplaceWith( + "Days.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Days" + ), + level = DeprecationLevel.WARNING +) +fun daysBetween(start: Date, endExclusive: Date): Days = Days.between(start, endExclusive) diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt index 707f35e80..2f69422b6 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt @@ -4,7 +4,9 @@ import io.islandtime.* import io.islandtime.base.DateTimeField import io.islandtime.measures.* import io.islandtime.parser.* -import io.islandtime.ranges.internal.* +import io.islandtime.ranges.internal.MAX_INCLUSIVE_END_DATE_TIME +import io.islandtime.ranges.internal.buildIsoString +import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * An interval between two date-times, assumed to be at the same offset from UTC. @@ -79,121 +81,11 @@ class DateTimeInterval( } } - /** - * The number of whole years in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInYears: Years - get() = when { - isEmpty() -> 0.years - isBounded() -> yearsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole months in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMonths: Months - get() = when { - isEmpty() -> 0.months - isBounded() -> monthsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole weeks in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInWeeks: Weeks - get() = when { - isEmpty() -> 0L.weeks - isBounded() -> weeksBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole days in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInDays: Days - get() = when { - isEmpty() -> 0L.days - isBounded() -> daysBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole hours in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInHours: Hours - get() = when { - isEmpty() -> 0L.hours - isBounded() -> hoursBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole minutes in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMinutes: Minutes - get() = when { - isEmpty() -> 0L.minutes - isBounded() -> minutesBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole seconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInSeconds: Seconds - get() = when { - isEmpty() -> 0L.seconds - isBounded() -> secondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole milliseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMilliseconds: Milliseconds - get() = when { - isEmpty() -> 0L.milliseconds - isBounded() -> millisecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole microseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMicroseconds: Microseconds - get() = when { - isEmpty() -> 0L.microseconds - isBounded() -> microsecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of nanoseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInNanoseconds: Nanoseconds - get() = when { - isEmpty() -> 0L.nanoseconds - isBounded() -> nanosecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - companion object { /** * An empty interval. */ - val EMPTY = DateTimeInterval( + val EMPTY: DateTimeInterval = DateTimeInterval( DateTime.fromSecondOfUnixEpoch(0L, 0, UtcOffset.ZERO), DateTime.fromSecondOfUnixEpoch(0L, 0, UtcOffset.ZERO) ) @@ -201,7 +93,7 @@ class DateTimeInterval( /** * An unbounded (ie. infinite) interval. */ - val UNBOUNDED = DateTimeInterval(DateTime.MIN, DateTime.MAX) + val UNBOUNDED: DateTimeInterval = DateTimeInterval(DateTime.MIN, DateTime.MAX) internal fun withInclusiveEnd(start: DateTime, endInclusive: DateTime): DateTimeInterval { val endExclusive = when { @@ -279,33 +171,50 @@ fun periodBetween(start: DateTime, endExclusive: DateTime): Period { return periodBetween(start.date, adjustedEndDate(start, endExclusive)) } -/** - * Gets the number of whole years between two date-times, assuming they're in the same time zone. - */ -fun yearsBetween(start: DateTime, endExclusive: DateTime): Years { - return yearsBetween(start.date, adjustedEndDate(start, endExclusive)) -} - -/** - * Gets the number of whole months between two date-times, assuming they're in the same time zone. - */ -fun monthsBetween(start: DateTime, endExclusive: DateTime): Months { - return monthsBetween(start.date, adjustedEndDate(start, endExclusive)) -} - -/** - * Gets the number whole weeks between two date-times, assuming they're in the same time zone. - */ -fun weeksBetween(start: DateTime, endExclusive: DateTime): Weeks { - return daysBetween(start, endExclusive).inWholeWeeks -} - -/** - * Gets the number whole days between two date-times, assuming they're in the same time zone. - */ -fun daysBetween(start: DateTime, endExclusive: DateTime): Days { - return secondsBetween(start, endExclusive).inWholeDays -} +@Deprecated( + message = "Replace with Years.between()", + replaceWith = ReplaceWith( + "Years.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Years" + ), + level = DeprecationLevel.WARNING +) +fun yearsBetween(start: DateTime, endExclusive: DateTime): Years = Years.between(start, endExclusive) + +@Deprecated( + message = "Replace with Months.between()", + replaceWith = ReplaceWith( + "Months.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Months" + ), + level = DeprecationLevel.WARNING +) +fun monthsBetween(start: DateTime, endExclusive: DateTime): Months = Months.between(start, endExclusive) + +@Deprecated( + message = "Replace with Weeks.between()", + replaceWith = ReplaceWith( + "Weeks.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Weeks" + ), + level = DeprecationLevel.WARNING +) +fun weeksBetween(start: DateTime, endExclusive: DateTime): Weeks = Weeks.between(start, endExclusive) + + +@Deprecated( + message = "Replace with Days.between()", + replaceWith = ReplaceWith( + "Days.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Days" + ), + level = DeprecationLevel.WARNING +) +fun daysBetween(start: DateTime, endExclusive: DateTime): Days = Days.between(start, endExclusive) /** * Gets the [Duration] between two date-times, assuming they have the same UTC offset. In general, it's more appropriate @@ -318,87 +227,73 @@ fun durationBetween(start: DateTime, endExclusive: DateTime): Duration { return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) } -/** - * Gets the number of whole hours between two date-times, assuming they have the same UTC offset. In general, it's more - * appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be taken - * into account when working with [DateTime] directly. - */ -fun hoursBetween(start: DateTime, endExclusive: DateTime): Hours { - return secondsBetween(start, endExclusive).inWholeHours -} - -/** - * Gets the number of whole minutes between two date-times, assuming they have the same UTC offset. In general, it's - * more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be - * taken into account when working with [DateTime] directly. - */ -fun minutesBetween(start: DateTime, endExclusive: DateTime): Minutes { - return secondsBetween(start, endExclusive).inWholeMinutes -} - -/** - * Gets the number of whole seconds between two date-times, assuming they have the same UTC offset. In general, it's - * more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be - * taken into account when working with [DateTime] directly. - * - * @throws ArithmeticException if the result overflows - */ -fun secondsBetween(start: DateTime, endExclusive: DateTime): Seconds { - return secondsBetween( - start.secondOfUnixEpochAt(UtcOffset.ZERO), - start.nanosecond, - endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), - endExclusive.nanosecond - ) -} - -/** - * Gets the number of whole milliseconds between two date-times, assuming they have the same UTC offset. In general, - * it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be - * taken into account when working with [DateTime] directly. - * - * @throws ArithmeticException if the result overflows - */ -fun millisecondsBetween(start: DateTime, endExclusive: DateTime): Milliseconds { - return millisecondsBetween( - start.secondOfUnixEpochAt(UtcOffset.ZERO), - start.nanosecond, - endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), - endExclusive.nanosecond - ) -} - -/** - * Gets the number of whole microseconds between two date-times, assuming they have the same UTC offset. In general, - * it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be - * taken into account when working with [DateTime] directly. - * - * @throws ArithmeticException if the result overflows - */ -fun microsecondsBetween(start: DateTime, endExclusive: DateTime): Microseconds { - return microsecondsBetween( - start.secondOfUnixEpochAt(UtcOffset.ZERO), - start.nanosecond, - endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), - endExclusive.nanosecond - ) -} - -/** - * Gets the number of nanoseconds between two date-times, assuming they have the same UTC offset. In general, it's more - * appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be taken - * into account when working with [DateTime] directly. - * - * @throws ArithmeticException if the result overflows - */ -fun nanosecondsBetween(start: DateTime, endExclusive: DateTime): Nanoseconds { - return nanosecondsBetween( - start.secondOfUnixEpochAt(UtcOffset.ZERO), - start.nanosecond, - endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO), - endExclusive.nanosecond - ) -} +@Deprecated( + message = "Replace with Hours.between()", + replaceWith = ReplaceWith( + "Hours.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Hours" + ), + level = DeprecationLevel.WARNING +) +fun hoursBetween(start: DateTime, endExclusive: DateTime): Hours = Hours.between(start, endExclusive) + +@Deprecated( + message = "Replace with Minutes.between()", + replaceWith = ReplaceWith( + "Minutes.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Minutes" + ), + level = DeprecationLevel.WARNING +) +fun minutesBetween(start: DateTime, endExclusive: DateTime): Minutes = Minutes.between(start, endExclusive) + +@Deprecated( + message = "Replace with Seconds.between()", + replaceWith = ReplaceWith( + "Seconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Seconds" + ), + level = DeprecationLevel.WARNING +) +fun secondsBetween(start: DateTime, endExclusive: DateTime): Seconds = Seconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Milliseconds.between()", + replaceWith = ReplaceWith( + "Milliseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Milliseconds" + ), + level = DeprecationLevel.WARNING +) +fun millisecondsBetween(start: DateTime, endExclusive: DateTime): Milliseconds = + Milliseconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Microseconds.between()", + replaceWith = ReplaceWith( + "Microseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Microseconds" + ), + level = DeprecationLevel.WARNING +) +fun microsecondsBetween(start: DateTime, endExclusive: DateTime): Microseconds = + Microseconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Nanoseconds.between()", + replaceWith = ReplaceWith( + "Nanoseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Nanoseconds" + ), + level = DeprecationLevel.WARNING +) +fun nanosecondsBetween(start: DateTime, endExclusive: DateTime): Nanoseconds = Nanoseconds.between(start, endExclusive) internal fun adjustedEndDate(start: DateTime, endExclusive: DateTime): Date { return when { diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt index 401c228e2..d5a9b645e 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt @@ -43,39 +43,6 @@ class OffsetDateTimeInterval( } } - /** - * The number of whole years in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInYears: Years - get() = when { - isEmpty() -> 0.years - isBounded() -> yearsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole months in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMonths: Months - get() = when { - isEmpty() -> 0.months - isBounded() -> monthsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole weeks in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInWeeks: Weeks - get() = when { - isEmpty() -> 0L.weeks - isBounded() -> weeksBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - companion object { /** * An empty interval. @@ -176,37 +143,49 @@ fun periodBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Period { return periodBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) } -/** - * Gets the number of whole years between two date-times, adjusting the offset of [endExclusive] if necessary to match - * the starting date-time. - */ -fun yearsBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Years { - return yearsBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) -} - -/** - * Gets the number of whole months between two date-times, adjusting the offset of [endExclusive] if necessary to match - * the starting date-time. - */ -fun monthsBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Months { - return monthsBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) -} - -/** - * Gets the number whole weeks between two date-times, adjusting the offset of [endExclusive] if necessary to match the - * starting date-time. - */ -fun weeksBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Weeks { - return weeksBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) -} - -/** - * Gets the number whole days between two date-times, adjusting the offset of [endExclusive] if necessary to match the - * starting date-time. - */ -fun daysBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Days { - return daysBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) -} +@Deprecated( + message = "Replace with Years.between()", + replaceWith = ReplaceWith( + "Years.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Years" + ), + level = DeprecationLevel.WARNING +) +fun yearsBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Years = Years.between(start, endExclusive) + +@Deprecated( + message = "Replace with Months.between()", + replaceWith = ReplaceWith( + "Months.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Months" + ), + level = DeprecationLevel.WARNING +) +fun monthsBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Months = Months.between(start, endExclusive) + +@Deprecated( + message = "Replace with Weeks.between()", + replaceWith = ReplaceWith( + "Weeks.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Weeks" + ), + level = DeprecationLevel.WARNING +) +fun weeksBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Weeks = Weeks.between(start, endExclusive) + +@Deprecated( + message = "Replace with Days.between()", + replaceWith = ReplaceWith( + "Days.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Days" + ), + level = DeprecationLevel.WARNING +) +fun daysBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Days = Days.between(start, endExclusive) private fun adjustedEndDateTime(start: OffsetDateTime, endExclusive: OffsetDateTime): DateTime { val offsetDelta = start.offset.totalSeconds - endExclusive.offset.totalSeconds diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt index e866c42a9..8df19a171 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt @@ -1,8 +1,10 @@ package io.islandtime.ranges import io.islandtime.base.TimePoint +import io.islandtime.between +import io.islandtime.internal.deprecatedToError import io.islandtime.measures.* -import io.islandtime.ranges.internal.* +import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * A half-open interval of time points. @@ -42,83 +44,6 @@ abstract class TimePointInterval> internal constructor( else -> throwUnboundedIntervalException() } } - - /** - * The number of 24-hour days in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - open val lengthInDays: Days - get() = when { - isEmpty() -> 0L.days - isBounded() -> daysBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole hours in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInHours: Hours - get() = when { - isEmpty() -> 0L.hours - isBounded() -> hoursBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole minutes in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMinutes: Minutes - get() = when { - isEmpty() -> 0L.minutes - isBounded() -> minutesBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole seconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInSeconds: Seconds - get() = when { - isEmpty() -> 0L.seconds - isBounded() -> secondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole milliseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMilliseconds: Milliseconds - get() = when { - isEmpty() -> 0L.milliseconds - isBounded() -> millisecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole microseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMicroseconds: Microseconds - get() = when { - isEmpty() -> 0L.microseconds - isBounded() -> microsecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole nanoseconds in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInNanoseconds: Nanoseconds - get() = when { - isEmpty() -> 0L.nanoseconds - isBounded() -> nanosecondsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } } /** @@ -139,75 +64,80 @@ fun durationBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Duration { return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) } -/** - * Gets the number of 24-hour days between two time points. - */ -fun daysBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Days { - return secondsBetween(start, endExclusive).inWholeDays -} - -/** - * Gets the number of whole hours between two time points. - */ -fun hoursBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Hours { - return secondsBetween(start, endExclusive).inWholeHours -} - -/** - * Gets the number of whole minutes between two time points. - */ -fun minutesBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Minutes { - return secondsBetween(start, endExclusive).inWholeMinutes -} - -/** - * Gets the number of whole seconds between two time points. - * @throws ArithmeticException if the result overflows - */ -fun secondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Seconds { - return secondsBetween( - start.secondOfUnixEpoch, - start.nanosecond, - endExclusive.secondOfUnixEpoch, - endExclusive.nanosecond - ) -} - -/** - * Gets the number of whole milliseconds between two time points. - * @throws ArithmeticException if the result overflows - */ -fun millisecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Milliseconds { - return millisecondsBetween( - start.secondOfUnixEpoch, - start.nanosecond, - endExclusive.secondOfUnixEpoch, - endExclusive.nanosecond - ) -} - -/** - * Gets the number of whole microseconds between two time points. - * @throws ArithmeticException if the result overflows - */ -fun microsecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Microseconds { - return microsecondsBetween( - start.secondOfUnixEpoch, - start.nanosecond, - endExclusive.secondOfUnixEpoch, - endExclusive.nanosecond - ) -} - -/** - * Gets the number of nanoseconds between two time points. - * @throws ArithmeticException if the result overflows - */ -fun nanosecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Nanoseconds { - return nanosecondsBetween( - start.secondOfUnixEpoch, - start.nanosecond, - endExclusive.secondOfUnixEpoch, - endExclusive.nanosecond - ) -} +@Deprecated( + message = "Replace with Days.between() or Hours.between().inWholeDays as appropriate", + replaceWith = ReplaceWith(""), + level = DeprecationLevel.ERROR +) +@Suppress("UNUSED_PARAMETER", "unused") +fun daysBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Days = deprecatedToError() + +@Deprecated( + message = "Replace with Hours.between()", + replaceWith = ReplaceWith( + "Hours.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Hours" + ), + level = DeprecationLevel.WARNING +) +fun hoursBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Hours = Hours.between(start, endExclusive) + +@Deprecated( + message = "Replace with Minutes.between()", + replaceWith = ReplaceWith( + "Minutes.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Minutes" + ), + level = DeprecationLevel.WARNING +) +fun minutesBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Minutes = + Minutes.between(start, endExclusive) + +@Deprecated( + message = "Replace with Seconds.between()", + replaceWith = ReplaceWith( + "Seconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Seconds" + ), + level = DeprecationLevel.WARNING +) +fun secondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Seconds = Seconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Milliseconds.between()", + replaceWith = ReplaceWith( + "Milliseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Milliseconds" + ), + level = DeprecationLevel.WARNING +) +fun millisecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Milliseconds = + Milliseconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Microseconds.between()", + replaceWith = ReplaceWith( + "Microseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Microseconds" + ), + level = DeprecationLevel.WARNING +) +fun microsecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Microseconds = + Microseconds.between(start, endExclusive) + +@Deprecated( + message = "Replace with Nanoseconds.between()", + replaceWith = ReplaceWith( + "Nanoseconds.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Nanoseconds" + ), + level = DeprecationLevel.WARNING +) +fun nanosecondsBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Nanoseconds = + Nanoseconds.between(start, endExclusive) diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt index a42c29940..153c0d8f8 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt @@ -1,6 +1,7 @@ package io.islandtime.ranges import io.islandtime.base.TimePoint +import io.islandtime.between import io.islandtime.measures.* /** @@ -200,7 +201,7 @@ private fun > getLastTimePointInProgression(start: T, end: T, s return if ((step.value > 0 && start >= end) || (step.value < 0 && start <= end)) { end } else { - val secondsBetween = secondsBetween(start, end) + val secondsBetween = Seconds.between(start, end) val steppedSeconds = secondsBetween - (secondsBetween % step.value) start + steppedSeconds } @@ -213,7 +214,7 @@ private fun > getLastTimePointInProgression(start: T, end: T, s return if ((step.value > 0 && start >= end) || (step.value < 0 && start <= end)) { end } else { - val nanosecondsBetween = nanosecondsBetween(start, end) + val nanosecondsBetween = Nanoseconds.between(start, end) val steppedNanoseconds = nanosecondsBetween - (nanosecondsBetween % step.value) start + steppedNanoseconds } diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt index d22bee1df..487fba015 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt @@ -45,55 +45,11 @@ class ZonedDateTimeInterval( } } - /** - * The number of whole years in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInYears: Years - get() = when { - isEmpty() -> 0.years - isBounded() -> yearsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole months is this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInMonths: Months - get() = when { - isEmpty() -> 0.months - isBounded() -> monthsBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole weeks in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - val lengthInWeeks: Weeks - get() = when { - isEmpty() -> 0.weeks - isBounded() -> weeksBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - - /** - * The number of whole days in this interval. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - override val lengthInDays: Days - get() = when { - isEmpty() -> 0.days - isBounded() -> daysBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - companion object { /** * An empty interval. */ - val EMPTY = ZonedDateTimeInterval( + val EMPTY: ZonedDateTimeInterval = ZonedDateTimeInterval( Instant.UNIX_EPOCH at TimeZone.UTC, Instant.UNIX_EPOCH at TimeZone.UTC ) @@ -101,7 +57,7 @@ class ZonedDateTimeInterval( /** * An unbounded (ie. infinite) interval. */ - val UNBOUNDED = ZonedDateTimeInterval( + val UNBOUNDED: ZonedDateTimeInterval = ZonedDateTimeInterval( DateTime.MIN at TimeZone.UTC, DateTime.MAX at TimeZone.UTC ) @@ -198,34 +154,46 @@ fun periodBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Period { return periodBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) } -/** - * Gets the number of whole years between two zoned date-times, adjusting the time zone of [endExclusive] if necessary - * to match the starting date-time. - */ -fun yearsBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Years { - return yearsBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) -} +@Deprecated( + message = "Replace with Years.between()", + replaceWith = ReplaceWith( + "Years.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Years" + ), + level = DeprecationLevel.WARNING +) +fun yearsBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Years = Years.between(start, endExclusive) -/** - * Gets the number of whole months between two zoned date-times, adjusting the time zone of [endExclusive] if necessary - * to match the starting date-time. - */ -fun monthsBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Months { - return monthsBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) -} +@Deprecated( + message = "Replace with Months.between()", + replaceWith = ReplaceWith( + "Months.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Months" + ), + level = DeprecationLevel.WARNING +) +fun monthsBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Months = Months.between(start, endExclusive) -/** - * Gets the number of whole weeks between two zoned date-times, adjusting the time zone of [endExclusive] if necessary - * to match the starting date-time. - */ -fun weeksBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Weeks { - return weeksBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) -} +@Deprecated( + message = "Replace with Weeks.between()", + replaceWith = ReplaceWith( + "Weeks.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Weeks" + ), + level = DeprecationLevel.WARNING +) +fun weeksBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Weeks = Weeks.between(start, endExclusive) -/** - * Gets the number of whole days between two zoned date-times, adjusting the time zone of [endExclusive] if necessary to - * match the starting date-time. - */ -fun daysBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Days { - return daysBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) -} +@Deprecated( + message = "Replace with Days.between()", + replaceWith = ReplaceWith( + "Days.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Days" + ), + level = DeprecationLevel.WARNING +) +fun daysBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Days = Days.between(start, endExclusive) diff --git a/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt b/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt new file mode 100644 index 000000000..b93e105f1 --- /dev/null +++ b/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt @@ -0,0 +1,410 @@ +@file:Suppress("PackageDirectoryMismatch") + +package io.islandtime + +import io.islandtime.measures.* +import io.islandtime.test.AbstractIslandTimeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class BetweenTest : AbstractIslandTimeTest() { + @Test + fun `returns zero when the start and end date are the same`() { + listOf( + Date(2200, Month.JULY, 15), + Date(2019, Month.MAY, 1), + Date(1970, Month.JANUARY, 1), + Date(1969, Month.MAY, 1) + ).forEach { date -> + assertEquals(0.centuries, Centuries.between(date, date), date.toString()) + assertEquals(0.decades, Decades.between(date, date), date.toString()) + assertEquals(0.years, Years.between(date, date), date.toString()) + assertEquals(0.months, Months.between(date, date), date.toString()) + assertEquals(0.weeks, Weeks.between(date, date), date.toString()) + assertEquals(0.days, Days.between(date, date), date.toString()) + } + } + + @Test + fun `years between dates in positive progression`() { + assertEquals( + 0.years, + Years.between(Date(2019, Month.JULY, 15), Date(2020, Month.JULY, 14)) + ) + + assertEquals( + 1.years, + Years.between(Date(2019, Month.JULY, 15), Date(2020, Month.JULY, 15)) + ) + } + + @Test + fun `years between dates in negative progression`() { + assertEquals( + 0.years, + Years.between(Date(2020, Month.JULY, 15), Date(2019, Month.JULY, 16)) + ) + + assertEquals( + (-1).years, + Years.between(Date(2020, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) + ) + } + + @Test + fun `months between dates in positive progression`() { + assertEquals( + 0.months, + Months.between(Date(2019, Month.JULY, 15), Date(2019, Month.AUGUST, 14)) + ) + + assertEquals( + 1.months, + Months.between(Date(2019, Month.JULY, 15), Date(2019, Month.AUGUST, 15)) + ) + + assertEquals( + 13.months, + Months.between(Date(2019, Month.JULY, 15), Date(2020, Month.AUGUST, 15)) + ) + } + + @Test + fun `months between dates in negative progression`() { + assertEquals( + 0.months, + Months.between(Date(2019, Month.AUGUST, 14), Date(2019, Month.JULY, 15)) + ) + + assertEquals( + (-1).months, + Months.between(Date(2019, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) + ) + + assertEquals( + (-13).months, + Months.between(Date(2020, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) + ) + } + + @Test + fun `weeks between dates in positive progression`() { + assertEquals( + 4.weeks, + Weeks.between(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) + ) + + assertEquals( + 5.weeks, + Weeks.between(Date(1969, Month.MAY, 1), Date(1969, Month.JUNE, 5)) + ) + } + + @Test + fun `weeks between dates in negative progression`() { + assertEquals( + (-4L).weeks, + Weeks.between(Date(2019, Month.JUNE, 3), Date(2019, Month.MAY, 1)) + ) + + assertEquals( + (-5L).weeks, + Weeks.between(Date(1969, Month.JUNE, 5), Date(1969, Month.MAY, 1)) + ) + } + + @Test + fun `days between dates in positive progression`() { + assertEquals( + 33.days, + Days.between(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) + ) + + assertEquals( + 33.days, + Days.between(Date(1969, Month.MAY, 1), Date(1969, Month.JUNE, 3)) + ) + } + + @Test + fun `days between dates in negative progression`() { + assertEquals( + (-16L).days, + Days.between(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 15)) + ) + + assertEquals( + (-16L).days, + Days.between(Date(1969, Month.MAY, 1), Date(1969, Month.APRIL, 15)) + ) + + assertEquals( + (-20L).days, + Days.between(Date(1970, Month.JANUARY, 4), Date(1969, Month.DECEMBER, 15)) + ) + } + + @Test + fun `hours between instants`() { + assertEquals( + 0.hours, + Hours.between(Instant(0.seconds, 1.nanoseconds), Instant(3600.seconds)) + ) + + assertEquals( + 0.hours, + Hours.between(Instant(3600.seconds), Instant(0.seconds, 1.nanoseconds)) + ) + + assertEquals( + 1.hours, + Hours.between(Instant.UNIX_EPOCH, Instant(3600.seconds)) + ) + } + + @Test + fun `minutes between instants`() { + assertEquals( + 0.minutes, + Minutes.between(Instant(0.seconds, 1.nanoseconds), Instant(60.seconds)) + ) + + assertEquals( + 0.minutes, + Minutes.between(Instant(60.seconds), Instant(0.seconds, 1.nanoseconds)) + ) + + assertEquals( + 1.minutes, + Minutes.between(Instant.UNIX_EPOCH, Instant(60.seconds)) + ) + } + + @Test + fun `seconds between instants`() { + assertEquals( + 0.seconds, + Seconds.between(Instant(0.seconds, 1.nanoseconds), Instant(1.seconds)) + ) + + assertEquals( + 0.seconds, + Seconds.between(Instant(1.seconds), Instant(0.seconds, 1.nanoseconds)) + ) + + assertEquals( + 0.seconds, + Seconds.between(Instant(0.seconds, 999_999_999.nanoseconds), Instant.UNIX_EPOCH) + ) + + assertEquals( + 0.seconds, + Seconds.between(Instant.UNIX_EPOCH, Instant(0.seconds, 999_999_999.nanoseconds)) + ) + + assertEquals( + 1.seconds, + Seconds.between(Instant.UNIX_EPOCH, Instant(1.seconds)) + ) + + assertEquals( + (-1L).seconds, + Seconds.between(Instant(1.seconds), Instant.UNIX_EPOCH) + ) + + assertEquals( + 1.seconds, + Seconds.between( + Instant(0.seconds, 500_000_000.nanoseconds), + Instant(1.seconds, 500_000_000.nanoseconds) + ) + ) + assertEquals( + 1.seconds, + Seconds.between( + Instant(0.seconds, 500_000_000.nanoseconds), + Instant(2.seconds, 499_999_999.nanoseconds) + ) + ) + } + + @Test + fun `milliseconds between instants`() { + assertEquals( + 0.milliseconds, + Milliseconds.between( + Instant(0.seconds, 1.nanoseconds), + Instant(0.seconds, 1_000_000.nanoseconds) + ) + ) + + assertEquals( + 0.milliseconds, + Milliseconds.between( + Instant(0.seconds, 1_000_000.nanoseconds), + Instant(0.seconds, 1.nanoseconds) + ) + ) + + assertEquals( + 1.milliseconds, + Milliseconds.between( + Instant.UNIX_EPOCH, + Instant(0.seconds, 1_000_000.nanoseconds) + ) + ) + } + + @Test + fun `microseconds between instants`() { + assertEquals( + 0.microseconds, + Microseconds.between( + Instant(0.seconds, 1.nanoseconds), + Instant(0.seconds, 1_000.nanoseconds) + ) + ) + + assertEquals( + 0.microseconds, + Microseconds.between( + Instant(0.seconds, 1_000.nanoseconds), + Instant(0.seconds, 1.nanoseconds) + ) + ) + + assertEquals( + 1.microseconds, + Microseconds.between(Instant.UNIX_EPOCH, Instant(0.seconds, 1_000.nanoseconds)) + ) + } + + @Test + fun `nanoseconds returns zero when both instants are the same`() { + assertEquals( + 0.nanoseconds, + Nanoseconds.between( + Instant(1.seconds, 1.nanoseconds), + Instant(1.seconds, 1.nanoseconds) + ) + ) + } + + @Test + fun `nanoseconds between instants`() { + assertEquals( + 2.nanoseconds, + Nanoseconds.between( + Instant(0.seconds, (-1).nanoseconds), + Instant(0.seconds, 1.nanoseconds) + ) + ) + + assertEquals( + (-1_000_000_000L).nanoseconds, + Nanoseconds.between(Instant.UNIX_EPOCH, Instant((-1L).seconds)) + ) + } + + @Test + fun `throws an exception when the number of nanoseconds between time points results in overflow`() { + assertFailsWith { Nanoseconds.between(Instant.MIN, Instant.UNIX_EPOCH) } + assertFailsWith { Nanoseconds.between(Instant.UNIX_EPOCH, OffsetDateTime.MAX) } + } + + @Test + fun `centuries betwen years`() { + assertEquals(0.centuries, Centuries.between(Year(2000), Year(2099))) + assertEquals(1.centuries, Centuries.between(Year(2000), Year(2100))) + } + + @Test + fun `decades betwen years`() { + assertEquals(0.decades, Decades.between(Year(2000), Year(2009))) + assertEquals(1.decades, Decades.between(Year(2000), Year(2010))) + } + + @Test + fun `years betwen years`() { + assertEquals(0.years, Years.between(Year(2000), Year(2000))) + assertEquals(1.years, Years.between(Year(2000), Year(2001))) + assertEquals((-1).years, Years.between(Year(2000), Year(1999))) + } + + @Test + fun `centuries between year-months`() { + assertEquals( + 0.centuries, + Centuries.between(YearMonth(2019, Month.JULY), YearMonth(2119, Month.JUNE)) + ) + + assertEquals( + 1.centuries, + Centuries.between(YearMonth(2019, Month.JULY), YearMonth(2119, Month.JULY)) + ) + } + + @Test + fun `decades between year-months`() { + assertEquals( + 0.decades, + Decades.between(YearMonth(2019, Month.JULY), YearMonth(2029, Month.JUNE)) + ) + + assertEquals( + 1.decades, + Decades.between(YearMonth(2019, Month.JULY), YearMonth(2029, Month.JULY)) + ) + } + + @Test + fun `years between year-months`() { + assertEquals( + 0.years, + Years.between(YearMonth(2019, Month.JULY), YearMonth(2020, Month.JUNE)) + ) + + assertEquals( + 1.years, + Years.between(YearMonth(2019, Month.JULY), YearMonth(2020, Month.JULY)) + ) + } + + @Test + fun `months between the same year-month returns 0`() { + listOf( + YearMonth(2019, Month.JULY), + YearMonth(0, Month.JANUARY), + YearMonth(-1, Month.DECEMBER) + ).forEach { yearMonth -> + assertEquals(0.months, Months.between(yearMonth, yearMonth)) + } + } + + @Test + fun `months between year-months in positive progression`() { + assertEquals( + 1.months, + Months.between(YearMonth(2019, Month.JULY), YearMonth(2019, Month.AUGUST)) + ) + + assertEquals( + 13.months, + Months.between(YearMonth(2019, Month.JULY), YearMonth(2020, Month.AUGUST)) + ) + } + + @Test + fun `months between year-months in negative progression`() { + assertEquals( + (-1).months, + Months.between(YearMonth(2019, Month.AUGUST), YearMonth(2019, Month.JULY)) + ) + + assertEquals( + (-13).months, + Months.between(YearMonth(2020, Month.AUGUST), YearMonth(2019, Month.JULY)) + ) + } +} diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt index 95dda031e..784466443 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt @@ -2,6 +2,7 @@ package io.islandtime.ranges import io.islandtime.Date import io.islandtime.Month +import io.islandtime.between import io.islandtime.measures.* import io.islandtime.parser.DateTimeParseException import io.islandtime.parser.DateTimeParsers @@ -233,51 +234,50 @@ class DateRangeTest : AbstractIslandTimeTest() { Date.MIN..Date(2019, Month.JUNE, 1) ).forEach { assertFailsWith { it.asPeriod() } - assertFailsWith { it.lengthInDays } - assertFailsWith { it.lengthInMonths } + assertFailsWith { it.lengthInCenturies } + assertFailsWith { it.lengthInDecades } assertFailsWith { it.lengthInYears } + assertFailsWith { it.lengthInMonths } + assertFailsWith { it.lengthInWeeks } + assertFailsWith { it.lengthInDays } } } @Test - fun `lengthInDays property returns 0 when range is empty`() { - assertEquals(0L.days, DateRange.EMPTY.lengthInDays) + fun `length properties return 0 when the range is empty`() { + assertEquals(Period.ZERO, DateRange.EMPTY.asPeriod()) + assertEquals(0.centuries, DateRange.EMPTY.lengthInCenturies) + assertEquals(0.decades, DateRange.EMPTY.lengthInDecades) + assertEquals(0.years, DateRange.EMPTY.lengthInYears) + assertEquals(0.months, DateRange.EMPTY.lengthInMonths) + assertEquals(0.weeks, DateRange.EMPTY.lengthInWeeks) + assertEquals(0.days, DateRange.EMPTY.lengthInDays) } @Test fun `lengthInDays property returns 1 when the start and end date are the same`() { val date = Date(2019, Month.JUNE, 1) - assertEquals(1L.days, (date..date).lengthInDays) + assertEquals(1.days, (date..date).lengthInDays) } @Test - fun `lengthInDays property returns the expected number of days in a non-empty range`() { + fun `lengthInDays property returns expected length when bounded`() { val start = Date(2018, Month.FEBRUARY, 1) val end = Date(2018, Month.FEBRUARY, 28) - assertEquals(28L.days, (start..end).lengthInDays) + assertEquals(28.days, (start..end).lengthInDays) } @Test - fun `lengthInWeeks property returns 0 when range is empty`() { - assertEquals(0L.weeks, DateRange.EMPTY.lengthInWeeks) - } - - @Test - fun `lengthInWeeks property returns the expected number of weeks in a non-empty range`() { + fun `lengthInWeeks property returns expected length when bounded`() { val range1 = Date(2018, Month.FEBRUARY, 1)..Date(2018, Month.FEBRUARY, 28) - assertEquals(4L.weeks, range1.lengthInWeeks) + assertEquals(4.weeks, range1.lengthInWeeks) val range2 = Date(2018, Month.FEBRUARY, 1)..Date(2018, Month.FEBRUARY, 27) - assertEquals(3L.weeks, range2.lengthInWeeks) + assertEquals(3.weeks, range2.lengthInWeeks) } @Test - fun `lengthInMonths property returns 0 when range is empty`() { - assertEquals(0.months, DateRange.EMPTY.lengthInMonths) - } - - @Test - fun `lengthInMonths property returns the expected number of months in a non-empty range`() { + fun `lengthInMonths property returns expected length when bounded`() { val range1 = Date(2018, Month.FEBRUARY, 20)..Date(2018, Month.MARCH, 18) assertEquals(0.months, range1.lengthInMonths) @@ -286,12 +286,7 @@ class DateRangeTest : AbstractIslandTimeTest() { } @Test - fun `lengthInYears property returns 0 when range is empty`() { - assertEquals(0.years, DateRange.EMPTY.lengthInYears) - } - - @Test - fun `lengthInYears property returns the expected number of years in a non-empty range`() { + fun `lengthInYears property returns expected length when bounded`() { val range1 = Date(2018, Month.FEBRUARY, 20)..Date(2019, Month.FEBRUARY, 18) assertEquals(0.years, range1.lengthInYears) @@ -300,8 +295,23 @@ class DateRangeTest : AbstractIslandTimeTest() { } @Test - fun `asPeriod() returns a period of zero when range is empty`() { - assertEquals(Period.ZERO, DateRange.EMPTY.asPeriod()) + fun `lengthInDecades property returns expected length when bounded`() { + val start = Date(2019, Month.JULY, 15) + val end1 = Date(2029, Month.JULY, 14) + val end2 = Date(2029, Month.JULY, 15) + + assertEquals(0.decades, (start until end1).lengthInDecades) + assertEquals(1.decades, (start until end2).lengthInDecades) + } + + @Test + fun `lengthInCenturies property returns expected length when bounded`() { + val start = Date(2019, Month.JULY, 15) + val end1 = Date(2119, Month.JULY, 14) + val end2 = Date(2119, Month.JULY, 15) + + assertEquals(0.centuries, (start until end1).lengthInCenturies) + assertEquals(1.centuries, (start until end2).lengthInCenturies) } @Test @@ -360,160 +370,4 @@ class DateRangeTest : AbstractIslandTimeTest() { periodBetween(Date(2019, Month.MAY, 25), Date(2017, Month.JULY, 4)) ) } - - @Test - fun `daysBetween() returns zero days when the start and end date are the same`() { - assertEquals( - 0L.days, - daysBetween(Date(2019, Month.MAY, 1), Date(2019, Month.MAY, 1)) - ) - - assertEquals( - 0L.days, - daysBetween(Date(1969, Month.MAY, 1), Date(1969, Month.MAY, 1)) - ) - } - - @Test - fun `daysBetween() returns the number of days between two dates in positive progression`() { - assertEquals( - 33L.days, - daysBetween(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) - ) - - assertEquals( - 33L.days, - daysBetween(Date(1969, Month.MAY, 1), Date(1969, Month.JUNE, 3)) - ) - } - - @Test - fun `daysBetween() returns the number of days between two dates in negative progression`() { - assertEquals( - (-16L).days, - daysBetween(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 15)) - ) - - assertEquals( - (-16L).days, - daysBetween(Date(1969, Month.MAY, 1), Date(1969, Month.APRIL, 15)) - ) - - assertEquals( - (-20L).days, - daysBetween(Date(1970, Month.JANUARY, 4), Date(1969, Month.DECEMBER, 15)) - ) - } - - @Test - fun `weeksBetween() returns zero when the start and end date are the same`() { - assertEquals( - 0L.weeks, - weeksBetween(Date(2019, Month.AUGUST, 23), Date(2019, Month.AUGUST, 23)) - ) - } - - @Test - fun `weeksBetween() returns the number of weeks between two dates in positive progression`() { - assertEquals( - 4L.weeks, - weeksBetween(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) - ) - - assertEquals( - 5L.weeks, - weeksBetween(Date(1969, Month.MAY, 1), Date(1969, Month.JUNE, 5)) - ) - } - - @Test - fun `weeksBetween() returns the number of weeks between two dates in negative progression`() { - assertEquals( - (-4L).weeks, - weeksBetween(Date(2019, Month.JUNE, 3), Date(2019, Month.MAY, 1)) - ) - - assertEquals( - (-5L).weeks, - weeksBetween(Date(1969, Month.JUNE, 5), Date(1969, Month.MAY, 1)) - ) - } - - @Test - fun `monthsBetween() returns zero when the start and end date are the same`() { - assertEquals( - 0.months, - monthsBetween(Date(2019, Month.JULY, 15), Date(2019, Month.JULY, 15)) - ) - } - - @Test - fun `monthsBetween() returns the months between two dates in positive progression`() { - assertEquals( - 0.months, - monthsBetween(Date(2019, Month.JULY, 15), Date(2019, Month.AUGUST, 14)) - ) - - assertEquals( - 1.months, - monthsBetween(Date(2019, Month.JULY, 15), Date(2019, Month.AUGUST, 15)) - ) - - assertEquals( - 13.months, - monthsBetween(Date(2019, Month.JULY, 15), Date(2020, Month.AUGUST, 15)) - ) - } - - @Test - fun `monthsBetween() returns the months between two dates in negative progression`() { - assertEquals( - 0.months, - monthsBetween(Date(2019, Month.AUGUST, 14), Date(2019, Month.JULY, 15)) - ) - - assertEquals( - (-1).months, - monthsBetween(Date(2019, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) - ) - - assertEquals( - (-13).months, - monthsBetween(Date(2020, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) - ) - } - - @Test - fun `yearsBetween() returns zero when the start and end date are the same`() { - assertEquals( - 0.years, - yearsBetween(Date(2019, Month.JULY, 15), Date(2019, Month.JULY, 15)) - ) - } - - @Test - fun `yearsBetween() returns the years between two dates in positive progression`() { - assertEquals( - 0.years, - yearsBetween(Date(2019, Month.JULY, 15), Date(2020, Month.JULY, 14)) - ) - - assertEquals( - 1.years, - yearsBetween(Date(2019, Month.JULY, 15), Date(2020, Month.JULY, 15)) - ) - } - - @Test - fun `yearsBetween() returns the years between two dates in negative progression`() { - assertEquals( - 0.years, - yearsBetween(Date(2020, Month.JULY, 15), Date(2019, Month.JULY, 16)) - ) - - assertEquals( - (-1).years, - yearsBetween(Date(2020, Month.AUGUST, 15), Date(2019, Month.JULY, 15)) - ) - } } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt index ca0c22596..42fcbf61a 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt @@ -224,20 +224,24 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { @Test fun `lengthIn properties return zero when the range is empty`() { + assertEquals(0.centuries, DateTimeInterval.EMPTY.lengthInCenturies) + assertEquals(0.decades, DateTimeInterval.EMPTY.lengthInDecades) assertEquals(0.years, DateTimeInterval.EMPTY.lengthInYears) assertEquals(0.months, DateTimeInterval.EMPTY.lengthInMonths) - assertEquals(0L.weeks, DateTimeInterval.EMPTY.lengthInWeeks) - assertEquals(0L.days, DateTimeInterval.EMPTY.lengthInDays) - assertEquals(0L.hours, DateTimeInterval.EMPTY.lengthInHours) - assertEquals(0L.minutes, DateTimeInterval.EMPTY.lengthInMinutes) - assertEquals(0L.seconds, DateTimeInterval.EMPTY.lengthInSeconds) - assertEquals(0L.milliseconds, DateTimeInterval.EMPTY.lengthInMilliseconds) - assertEquals(0L.microseconds, DateTimeInterval.EMPTY.lengthInMicroseconds) - assertEquals(0L.nanoseconds, DateTimeInterval.EMPTY.lengthInNanoseconds) + assertEquals(0.weeks, DateTimeInterval.EMPTY.lengthInWeeks) + assertEquals(0.days, DateTimeInterval.EMPTY.lengthInDays) + assertEquals(0.hours, DateTimeInterval.EMPTY.lengthInHours) + assertEquals(0.minutes, DateTimeInterval.EMPTY.lengthInMinutes) + assertEquals(0.seconds, DateTimeInterval.EMPTY.lengthInSeconds) + assertEquals(0.milliseconds, DateTimeInterval.EMPTY.lengthInMilliseconds) + assertEquals(0.microseconds, DateTimeInterval.EMPTY.lengthInMicroseconds) + assertEquals(0.nanoseconds, DateTimeInterval.EMPTY.lengthInNanoseconds) } @Test fun `lengthIn properties throw an exception when the interval is unbounded`() { + assertFailsWith { DateTimeInterval.UNBOUNDED.lengthInCenturies } + assertFailsWith { DateTimeInterval.UNBOUNDED.lengthInDecades } assertFailsWith { DateTimeInterval.UNBOUNDED.lengthInYears } assertFailsWith { DateTimeInterval.UNBOUNDED.lengthInMonths } assertFailsWith { DateTimeInterval.UNBOUNDED.lengthInWeeks } @@ -251,125 +255,118 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { } @Test - fun `years between two date-times`() { + fun `lengthInCenturies returns expected length when bounded`() { + val start = DateTime(2019, Month.MARCH, 1, 13, 0) + val end1 = DateTime(2119, Month.MARCH, 1, 13, 0) + val end2 = DateTime(2119, Month.MARCH, 1, 12, 59, 59, 999_999_999) + + assertEquals(1.centuries, (start until end1).lengthInCenturies) + assertEquals(0.centuries, (start until end2).lengthInCenturies) + } + + @Test + fun `lengthInDecades returns expected length when bounded`() { + val start = DateTime(2019, Month.MARCH, 1, 13, 0) + val end1 = DateTime(2029, Month.MARCH, 1, 13, 0) + val end2 = DateTime(2029, Month.MARCH, 1, 12, 59, 59, 999_999_999) + + assertEquals(1.decades, (start until end1).lengthInDecades) + assertEquals(0.decades, (start until end2).lengthInDecades) + } + + @Test + fun `lengthInYears returns expected length when bounded`() { val start = DateTime(2019, Month.MARCH, 1, 13, 0) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) assertEquals(1.years, (start until end1).lengthInYears) - assertEquals(1.years, yearsBetween(start, end1)) - assertEquals(0.years, (start until end2).lengthInYears) - assertEquals(0.years, yearsBetween(start, end2)) } @Test - fun `months between two date-times`() { + fun `lengthInMonths returns expected length when bounded`() { val start = DateTime(2020, Month.FEBRUARY, 1, 13, 0) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) assertEquals(1.months, (start until end1).lengthInMonths) - assertEquals(1.months, monthsBetween(start, end1)) - assertEquals(0.months, (start until end2).lengthInMonths) - assertEquals(0.months, monthsBetween(start, end2)) } @Test - fun `weeks between two date-times`() { + fun `lengthInWeeks returns expected length when bounded`() { val start = DateTime(2020, Month.FEBRUARY, 29, 13, 0) val end1 = DateTime(2020, Month.MARCH, 7, 13, 0) val end2 = DateTime(2020, Month.MARCH, 7, 12, 59, 59, 999_999_999) - assertEquals(1L.weeks, (start until end1).lengthInWeeks) - assertEquals(1L.weeks, weeksBetween(start, end1)) - - assertEquals(0L.weeks, (start until end2).lengthInWeeks) - assertEquals(0L.weeks, weeksBetween(start, end2)) + assertEquals(1.weeks, (start until end1).lengthInWeeks) + assertEquals(0.weeks, (start until end2).lengthInWeeks) } @Test - fun `days between two date-times`() { + fun `lengthInDays returns expected length when bounded`() { val start = DateTime(2020, Month.FEBRUARY, 29, 13, 0) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) - assertEquals(1L.days, (start until end1).lengthInDays) - assertEquals(1L.days, daysBetween(start, end1)) - - assertEquals(0L.days, (start until end2).lengthInDays) - assertEquals(0L.days, daysBetween(start, end2)) + assertEquals(1.days, (start until end1).lengthInDays) + assertEquals(0.days, (start until end2).lengthInDays) } @Test - fun `hours between two date-times`() { + fun `lengthInHours returns expected length when bounded`() { val start = DateTime(2020, Month.MARCH, 1, 12, 0) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) assertEquals(1L.hours, (start until end1).lengthInHours) - assertEquals(1L.hours, hoursBetween(start, end1)) - assertEquals(0L.hours, (start until end2).lengthInHours) - assertEquals(0L.hours, hoursBetween(start, end2)) } @Test - fun `minutes between two date-times`() { + fun `lengthInMinutes returns expected length when bounded`() { val start = DateTime(2020, Month.MARCH, 1, 12, 59) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) assertEquals(1L.minutes, (start until end1).lengthInMinutes) - assertEquals(1L.minutes, minutesBetween(start, end1)) - assertEquals(0L.minutes, (start until end2).lengthInMinutes) - assertEquals(0L.minutes, minutesBetween(start, end2)) } @Test - fun `seconds between two date-times`() { + fun `lengthInSeconds returns expected length when bounded`() { val start = DateTime(2020, Month.MARCH, 1, 12, 59, 59) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) - assertEquals(1L.seconds, (start until end1).lengthInSeconds) - assertEquals(1L.seconds, secondsBetween(start, end1)) - - assertEquals(0L.seconds, (start until end2).lengthInSeconds) - assertEquals(0L.seconds, secondsBetween(start, end2)) + assertEquals(1.seconds, (start until end1).lengthInSeconds) + assertEquals(0.seconds, (start until end2).lengthInSeconds) } @Test - fun `milliseconds between two date-times`() { + fun `lengthInMilliseconds returns expected length when bounded`() { val start = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_000_000) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) - assertEquals(1L.milliseconds, (start until end1).lengthInMilliseconds) - assertEquals(1L.milliseconds, millisecondsBetween(start, end1)) - - assertEquals(0L.milliseconds, (start until end2).lengthInMilliseconds) - assertEquals(0L.milliseconds, millisecondsBetween(start, end2)) + assertEquals(1.milliseconds, (start until end1).lengthInMilliseconds) + assertEquals(0.milliseconds, (start until end2).lengthInMilliseconds) } @Test - fun `microseconds between two date-times`() { + fun `lengthInMicroseconds returns expected length when bounded`() { val start = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_000) val end1 = DateTime(2020, Month.MARCH, 1, 13, 0) val end2 = DateTime(2020, Month.MARCH, 1, 12, 59, 59, 999_999_999) - assertEquals(1L.microseconds, (start until end1).lengthInMicroseconds) - assertEquals(1L.microseconds, microsecondsBetween(start, end1)) - - assertEquals(0L.microseconds, (start until end2).lengthInMicroseconds) - assertEquals(0L.microseconds, microsecondsBetween(start, end2)) + assertEquals(1.microseconds, (start until end1).lengthInMicroseconds) + assertEquals(0.microseconds, (start until end2).lengthInMicroseconds) } @Test fun `lengthInNanoseconds returns 1 in an inclusive interval where the start and end date-times are the same`() { - val dateTime = Date(2019, Month.MARCH, 10) at MIDNIGHT + val dateTime = Date(2019, Month.MARCH, 10) at Time.MIDNIGHT assertEquals(1L.nanoseconds, (dateTime..dateTime).lengthInNanoseconds) } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt index a4b51ece1..995b47fb2 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt @@ -204,7 +204,6 @@ class InstantIntervalTest : AbstractIslandTimeTest() { @Test fun `lengthIn properties return zero when the range is empty`() { - assertEquals(0L.days, InstantInterval.EMPTY.lengthInDays) assertEquals(0L.hours, InstantInterval.EMPTY.lengthInHours) assertEquals(0L.minutes, InstantInterval.EMPTY.lengthInMinutes) assertEquals(0L.seconds, InstantInterval.EMPTY.lengthInSeconds) @@ -215,7 +214,6 @@ class InstantIntervalTest : AbstractIslandTimeTest() { @Test fun `lengthIn properties throw an exception when the interval is unbounded`() { - assertFailsWith { InstantInterval.UNBOUNDED.lengthInDays } assertFailsWith { InstantInterval.UNBOUNDED.lengthInHours } assertFailsWith { InstantInterval.UNBOUNDED.lengthInMinutes } assertFailsWith { InstantInterval.UNBOUNDED.lengthInSeconds } @@ -252,192 +250,4 @@ class InstantIntervalTest : AbstractIslandTimeTest() { durationBetween(Instant(1L.milliseconds), Instant((-2L).milliseconds)) ) } - - @Test - fun `daysBetween returns the number of 24-hour days between two instants`() { - assertEquals( - 0L.days, - daysBetween(Instant(0L.seconds, 1.nanoseconds), Instant(86400L.seconds)) - ) - - assertEquals( - 0L.days, - daysBetween(Instant(86400L.seconds), Instant(0L.seconds, 1.nanoseconds)) - ) - - assertEquals( - 1L.days, - daysBetween(Instant.UNIX_EPOCH, Instant(86400L.seconds)) - ) - } - - @Test - fun `hoursBetween() returns the number of whole hours between two instants`() { - assertEquals( - 0L.hours, - hoursBetween( - Instant(0L.seconds, 1.nanoseconds), - Instant(3600L.seconds) - ) - ) - - assertEquals( - 0L.hours, - hoursBetween(Instant(3600L.seconds), Instant(0L.seconds, 1.nanoseconds)) - ) - - assertEquals( - 1L.hours, - hoursBetween(Instant.UNIX_EPOCH, Instant(3600L.seconds)) - ) - } - - @Test - fun `minutesBetween() returns the number of whole minutes between two instants`() { - assertEquals( - 0L.minutes, - minutesBetween(Instant(0L.seconds, 1.nanoseconds), Instant(60L.seconds)) - ) - - assertEquals( - 0L.minutes, - minutesBetween(Instant(60L.seconds), Instant(0L.seconds, 1.nanoseconds)) - ) - - assertEquals( - 1L.minutes, - minutesBetween(Instant.UNIX_EPOCH, Instant(60L.seconds)) - ) - } - - @Test - fun `secondsBetween() returns the number of whole seconds between two instants`() { - assertEquals( - 0L.seconds, - secondsBetween(Instant(0L.seconds, 1.nanoseconds), Instant(1L.seconds)) - ) - - assertEquals( - 0L.seconds, - secondsBetween(Instant(1L.seconds), Instant(0L.seconds, 1.nanoseconds)) - ) - - assertEquals( - 0L.seconds, - secondsBetween(Instant(0L.seconds, 999_999_999.nanoseconds), Instant.UNIX_EPOCH) - ) - - assertEquals( - 0L.seconds, - secondsBetween(Instant.UNIX_EPOCH, Instant(0L.seconds, 999_999_999.nanoseconds)) - ) - - assertEquals( - 1L.seconds, - secondsBetween(Instant.UNIX_EPOCH, Instant(1L.seconds)) - ) - - assertEquals( - (-1L).seconds, - secondsBetween(Instant(1L.seconds), Instant.UNIX_EPOCH) - ) - - assertEquals( - 1L.seconds, - secondsBetween( - Instant(0L.seconds, 500_000_000.nanoseconds), - Instant(1L.seconds, 500_000_000.nanoseconds) - ) - ) - assertEquals( - 1L.seconds, - secondsBetween( - Instant(0L.seconds, 500_000_000.nanoseconds), - Instant(2L.seconds, 499_999_999.nanoseconds) - ) - ) - } - - @Test - fun `millisecondsBetween() returns the number of whole milliseconds between two instants`() { - assertEquals( - 0L.milliseconds, - millisecondsBetween( - Instant(0L.seconds, 1.nanoseconds), - Instant(0L.seconds, 1_000_000.nanoseconds) - ) - ) - - assertEquals( - 0L.milliseconds, - millisecondsBetween( - Instant(0L.seconds, 1_000_000.nanoseconds), - Instant(0L.seconds, 1.nanoseconds) - ) - ) - - assertEquals( - 1L.milliseconds, - millisecondsBetween( - Instant.UNIX_EPOCH, - Instant(0L.seconds, 1_000_000.nanoseconds) - ) - ) - } - - @Test - fun `microsecondsBetween() returns the number of whole microseconds between two instants`() { - assertEquals( - 0L.microseconds, - microsecondsBetween( - Instant(0L.seconds, 1.nanoseconds), - Instant(0L.seconds, 1_000.nanoseconds) - ) - ) - - assertEquals( - 0L.microseconds, - microsecondsBetween( - Instant(0L.seconds, 1_000.nanoseconds), - Instant(0L.seconds, 1.nanoseconds) - ) - ) - - assertEquals( - 1L.microseconds, - microsecondsBetween(Instant.UNIX_EPOCH, Instant(0L.seconds, 1_000.nanoseconds)) - ) - } - - @Test - fun `nanosecondsBetween() returns zero when both instants are the same`() { - assertEquals( - 0L.nanoseconds, - nanosecondsBetween( - Instant(1L.seconds, 1.nanoseconds), - Instant(1L.seconds, 1.nanoseconds) - ) - ) - } - - @Test - fun `nanosecondsBetween() returns the number of nanoseconds between two instants`() { - assertEquals( - 2L.nanoseconds, - nanosecondsBetween( - Instant(0L.seconds, (-1).nanoseconds), - Instant(0L.seconds, 1.nanoseconds) - ) - ) - - assertEquals( - (-1_000_000_000L).nanoseconds, - nanosecondsBetween(Instant.UNIX_EPOCH, Instant((-1L).seconds)) - ) - } - - @Test - fun `nanosecondsBetween() throws an exception when the result overflows`() { - assertFailsWith { nanosecondsBetween(Instant.MIN, Instant.UNIX_EPOCH) } - } } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt index 9d6c6b41a..1a3cca317 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt @@ -180,20 +180,24 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { @Test fun `lengthIn properties return zero when the range is empty`() { + assertEquals(0.centuries, OffsetDateTimeInterval.EMPTY.lengthInCenturies) + assertEquals(0.decades, OffsetDateTimeInterval.EMPTY.lengthInDecades) assertEquals(0.years, OffsetDateTimeInterval.EMPTY.lengthInYears) assertEquals(0.months, OffsetDateTimeInterval.EMPTY.lengthInMonths) - assertEquals(0L.weeks, OffsetDateTimeInterval.EMPTY.lengthInWeeks) - assertEquals(0L.days, OffsetDateTimeInterval.EMPTY.lengthInDays) - assertEquals(0L.hours, OffsetDateTimeInterval.EMPTY.lengthInHours) - assertEquals(0L.minutes, OffsetDateTimeInterval.EMPTY.lengthInMinutes) - assertEquals(0L.seconds, OffsetDateTimeInterval.EMPTY.lengthInSeconds) - assertEquals(0L.milliseconds, OffsetDateTimeInterval.EMPTY.lengthInMilliseconds) - assertEquals(0L.microseconds, OffsetDateTimeInterval.EMPTY.lengthInMicroseconds) - assertEquals(0L.nanoseconds, OffsetDateTimeInterval.EMPTY.lengthInNanoseconds) + assertEquals(0.weeks, OffsetDateTimeInterval.EMPTY.lengthInWeeks) + assertEquals(0.days, OffsetDateTimeInterval.EMPTY.lengthInDays) + assertEquals(0.hours, OffsetDateTimeInterval.EMPTY.lengthInHours) + assertEquals(0.minutes, OffsetDateTimeInterval.EMPTY.lengthInMinutes) + assertEquals(0.seconds, OffsetDateTimeInterval.EMPTY.lengthInSeconds) + assertEquals(0.milliseconds, OffsetDateTimeInterval.EMPTY.lengthInMilliseconds) + assertEquals(0.microseconds, OffsetDateTimeInterval.EMPTY.lengthInMicroseconds) + assertEquals(0.nanoseconds, OffsetDateTimeInterval.EMPTY.lengthInNanoseconds) } @Test fun `lengthIn properties throw an exception when the interval is unbounded`() { + assertFailsWith { OffsetDateTimeInterval.UNBOUNDED.lengthInCenturies } + assertFailsWith { OffsetDateTimeInterval.UNBOUNDED.lengthInDecades } assertFailsWith { OffsetDateTimeInterval.UNBOUNDED.lengthInYears } assertFailsWith { OffsetDateTimeInterval.UNBOUNDED.lengthInMonths } assertFailsWith { OffsetDateTimeInterval.UNBOUNDED.lengthInWeeks } @@ -207,7 +211,35 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { } @Test - fun `years between two date-times`() { + fun `lengthInCenturies property returns expected length when bounded`() { + val offset1 = UtcOffset(1.hours) + val offset2 = UtcOffset(2.hours) + val start = DateTime(2019, Month.MARCH, 1, 13, 0) at offset1 + val end1 = DateTime(2119, Month.MARCH, 1, 14, 0) at offset2 + val end2 = Date(2119, Month.MARCH, 1) at + Time(13, 59, 59, 999_999_999) at + offset2 + + assertEquals(1.centuries, (start until end1).lengthInCenturies) + assertEquals(0.centuries, (start until end2).lengthInCenturies) + } + + @Test + fun `lengthInDecades property returns expected length when bounded`() { + val offset1 = UtcOffset(1.hours) + val offset2 = UtcOffset(2.hours) + val start = DateTime(2019, Month.MARCH, 1, 13, 0) at offset1 + val end1 = DateTime(2029, Month.MARCH, 1, 14, 0) at offset2 + val end2 = Date(2029, Month.MARCH, 1) at + Time(13, 59, 59, 999_999_999) at + offset2 + + assertEquals(1.decades, (start until end1).lengthInDecades) + assertEquals(0.decades, (start until end2).lengthInDecades) + } + + @Test + fun `lengthInYears property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2019, Month.MARCH, 1, 13, 0) at offset1 @@ -217,14 +249,11 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { offset2 assertEquals(1.years, (start until end1).lengthInYears) - assertEquals(1.years, yearsBetween(start, end1)) - assertEquals(0.years, (start until end2).lengthInYears) - assertEquals(0.years, yearsBetween(start, end2)) } @Test - fun `months between two date-times`() { + fun `lengthInMonths property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.FEBRUARY, 1, 13, 0) at offset1 @@ -234,14 +263,11 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { offset2 assertEquals(1.months, (start until end1).lengthInMonths) - assertEquals(1.months, monthsBetween(start, end1)) - assertEquals(0.months, (start until end2).lengthInMonths) - assertEquals(0.months, monthsBetween(start, end2)) } @Test - fun `weeks between two date-times`() { + fun `lengthInWeeks property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.FEBRUARY, 29, 13, 0) at offset1 @@ -250,15 +276,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(13, 59, 59, 999_999_999) at offset2 - assertEquals(1L.weeks, (start until end1).lengthInWeeks) - assertEquals(1L.weeks, weeksBetween(start, end1)) - - assertEquals(0L.weeks, (start until end2).lengthInWeeks) - assertEquals(0L.weeks, weeksBetween(start, end2)) + assertEquals(1.weeks, (start until end1).lengthInWeeks) + assertEquals(0.weeks, (start until end2).lengthInWeeks) } @Test - fun `days between two date-times`() { + fun `lengthInDays property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.FEBRUARY, 29, 13, 0) at offset1 @@ -267,15 +290,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(13, 59, 59, 999_999_999) at offset2 - assertEquals(1L.days, (start until end1).lengthInDays) - assertEquals(1L.days, daysBetween(start, end1)) - - assertEquals(0L.days, (start until end2).lengthInDays) - assertEquals(0L.days, daysBetween(start, end2)) + assertEquals(1.days, (start until end1).lengthInDays) + assertEquals(0.days, (start until end2).lengthInDays) } @Test - fun `hours between two date-times`() { + fun `lengthInHours property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.MARCH, 1, 13, 0) at offset1 @@ -284,15 +304,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(14, 59, 59, 999_999_999) at offset2 - assertEquals(1L.hours, (start until end1).lengthInHours) - assertEquals(1L.hours, hoursBetween(start, end1)) - - assertEquals(0L.hours, (start until end2).lengthInHours) - assertEquals(0L.hours, hoursBetween(start, end2)) + assertEquals(1.hours, (start until end1).lengthInHours) + assertEquals(0.hours, (start until end2).lengthInHours) } @Test - fun `minutes between two date-times`() { + fun `lengthInMinutes property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.MARCH, 1, 13, 59) at offset1 @@ -301,15 +318,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(14, 59, 59, 999_999_999) at offset2 - assertEquals(1L.minutes, (start until end1).lengthInMinutes) - assertEquals(1L.minutes, minutesBetween(start, end1)) - - assertEquals(0L.minutes, (start until end2).lengthInMinutes) - assertEquals(0L.minutes, minutesBetween(start, end2)) + assertEquals(1.minutes, (start until end1).lengthInMinutes) + assertEquals(0.minutes, (start until end2).lengthInMinutes) } @Test - fun `seconds between two date-times`() { + fun `lengthInSeconds property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = DateTime(2020, Month.MARCH, 1, 13, 59, 59) at offset1 @@ -318,15 +332,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(14, 59, 59, 999_999_999) at offset2 - assertEquals(1L.seconds, (start until end1).lengthInSeconds) - assertEquals(1L.seconds, secondsBetween(start, end1)) - - assertEquals(0L.seconds, (start until end2).lengthInSeconds) - assertEquals(0L.seconds, secondsBetween(start, end2)) + assertEquals(1.seconds, (start until end1).lengthInSeconds) + assertEquals(0.seconds, (start until end2).lengthInSeconds) } @Test - fun `milliseconds between two date-times`() { + fun `lengthInMilliseconds property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = Date(2020, Month.MARCH, 1) at @@ -337,15 +348,12 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(14, 59, 59, 999_999_999) at offset2 - assertEquals(1L.milliseconds, (start until end1).lengthInMilliseconds) - assertEquals(1L.milliseconds, millisecondsBetween(start, end1)) - - assertEquals(0L.milliseconds, (start until end2).lengthInMilliseconds) - assertEquals(0L.milliseconds, millisecondsBetween(start, end2)) + assertEquals(1.milliseconds, (start until end1).lengthInMilliseconds) + assertEquals(0.milliseconds, (start until end2).lengthInMilliseconds) } @Test - fun `microseconds between two date-times`() { + fun `lengthInMicroseconds property returns expected length when bounded`() { val offset1 = UtcOffset(1.hours) val offset2 = UtcOffset(2.hours) val start = Date(2020, Month.MARCH, 1) at @@ -356,11 +364,8 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { Time(14, 59, 59, 999_999_999) at offset2 - assertEquals(1L.microseconds, (start until end1).lengthInMicroseconds) - assertEquals(1L.microseconds, microsecondsBetween(start, end1)) - - assertEquals(0L.microseconds, (start until end2).lengthInMicroseconds) - assertEquals(0L.microseconds, microsecondsBetween(start, end2)) + assertEquals(1.microseconds, (start until end1).lengthInMicroseconds) + assertEquals(0.microseconds, (start until end2).lengthInMicroseconds) } @Test diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt index 99a008086..46733ba0f 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt @@ -150,20 +150,26 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { @Test fun `lengthIn properties return zero when the range is empty`() { + assertEquals(0.centuries, ZonedDateTimeInterval.EMPTY.lengthInCenturies) + assertEquals(0.decades, ZonedDateTimeInterval.EMPTY.lengthInDecades) assertEquals(0.years, ZonedDateTimeInterval.EMPTY.lengthInYears) assertEquals(0.months, ZonedDateTimeInterval.EMPTY.lengthInMonths) - assertEquals(0L.weeks, ZonedDateTimeInterval.EMPTY.lengthInWeeks) - assertEquals(0L.days, ZonedDateTimeInterval.EMPTY.lengthInDays) - assertEquals(0L.hours, ZonedDateTimeInterval.EMPTY.lengthInHours) - assertEquals(0L.minutes, ZonedDateTimeInterval.EMPTY.lengthInMinutes) - assertEquals(0L.seconds, ZonedDateTimeInterval.EMPTY.lengthInSeconds) - assertEquals(0L.milliseconds, ZonedDateTimeInterval.EMPTY.lengthInMilliseconds) - assertEquals(0L.microseconds, ZonedDateTimeInterval.EMPTY.lengthInMicroseconds) - assertEquals(0L.nanoseconds, ZonedDateTimeInterval.EMPTY.lengthInNanoseconds) + assertEquals(0.weeks, ZonedDateTimeInterval.EMPTY.lengthInWeeks) + assertEquals(0.days, ZonedDateTimeInterval.EMPTY.lengthInDays) + assertEquals(0.hours, ZonedDateTimeInterval.EMPTY.lengthInHours) + assertEquals(0.minutes, ZonedDateTimeInterval.EMPTY.lengthInMinutes) + assertEquals(0.seconds, ZonedDateTimeInterval.EMPTY.lengthInSeconds) + assertEquals(0.milliseconds, ZonedDateTimeInterval.EMPTY.lengthInMilliseconds) + assertEquals(0.microseconds, ZonedDateTimeInterval.EMPTY.lengthInMicroseconds) + assertEquals(0.nanoseconds, ZonedDateTimeInterval.EMPTY.lengthInNanoseconds) } @Test fun `lengthIn properties throw an exception when the interval is unbounded`() { + assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInCenturies } + assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInDecades } + assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInYears } + assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInMonths } assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInWeeks } assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInDays } assertFailsWith { ZonedDateTimeInterval.UNBOUNDED.lengthInHours } @@ -243,14 +249,32 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { assertEquals(durationOf(25.hours.inSeconds, 1.nanoseconds), (then..now).asDuration()) } + @Test + fun `lengthInCenturies property`() { + val zone = nyZone + val then = Date(2019, 3, 10).startOfDayAt(zone) + val now = Date(2119, 3, 10).startOfDayAt(zone) + + assertEquals(1.centuries, (then until now).lengthInCenturies) + assertEquals(0.centuries, (then until now - 1.nanoseconds).lengthInCenturies) + } + + @Test + fun `lengthInDecades property`() { + val zone = nyZone + val then = Date(2019, 3, 10).startOfDayAt(zone) + val now = Date(2029, 3, 10).startOfDayAt(zone) + + assertEquals(1.decades, (then until now).lengthInDecades) + assertEquals(0.decades, (then until now - 1.nanoseconds).lengthInDecades) + } + @Test fun `lengthInYears property`() { val zone = nyZone val then = Date(2019, 3, 10).startOfDayAt(zone) val now = Date(2020, 3, 10).startOfDayAt(zone) - assertEquals(1.years, yearsBetween(then, now)) - assertEquals(0.years, yearsBetween(then, now - 1.nanoseconds)) assertEquals(1.years, (then until now).lengthInYears) assertEquals(0.years, (then until now - 1.nanoseconds).lengthInYears) } @@ -261,8 +285,6 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10).startOfDayAt(zone) val now = Date(2019, 4, 10).startOfDayAt(zone) - assertEquals(1.months, monthsBetween(then, now)) - assertEquals(0.months, monthsBetween(then, now - 1.nanoseconds)) assertEquals(1.months, (then until now).lengthInMonths) assertEquals(0.months, (then until now - 1.nanoseconds).lengthInMonths) } @@ -273,10 +295,8 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10).startOfDayAt(zone) val now = Date(2019, 3, 17).startOfDayAt(zone) - assertEquals(1L.weeks, weeksBetween(then, now)) - assertEquals(0L.weeks, weeksBetween(then, now - 1.nanoseconds)) - assertEquals(1L.weeks, (then until now).lengthInWeeks) - assertEquals(0L.weeks, (then until now - 1.nanoseconds).lengthInWeeks) + assertEquals(1.weeks, (then until now).lengthInWeeks) + assertEquals(0.weeks, (then until now - 1.nanoseconds).lengthInWeeks) } @Test @@ -285,10 +305,8 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10).startOfDayAt(zone) val now = Date(2019, 3, 11).startOfDayAt(zone) - assertEquals(1L.days, daysBetween(then, now)) - assertEquals(0L.days, daysBetween(then, now - 1.nanoseconds)) - assertEquals(1L.days, (then until now).lengthInDays) - assertEquals(0L.days, (then until now - 1.nanoseconds).lengthInDays) + assertEquals(1.days, (then until now).lengthInDays) + assertEquals(0.days, (then until now - 1.nanoseconds).lengthInDays) } @Test @@ -297,9 +315,7 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10).startOfDayAt(zone) val now = Date(2019, 3, 10) at Time(5, 0) at zone - assertEquals(4L.hours, hoursBetween(then, now)) - assertEquals(3L.hours, hoursBetween(then, now - 1.nanoseconds)) - assertEquals(4L.hours, (then until now).lengthInHours) - assertEquals(3L.hours, (then until now - 1.nanoseconds).lengthInHours) + assertEquals(4.hours, (then until now).lengthInHours) + assertEquals(3.hours, (then until now - 1.nanoseconds).lengthInHours) } } diff --git a/docs/basics/durations.md b/docs/basics/durations.md index b88d6247e..fc6ab1b56 100644 --- a/docs/basics/durations.md +++ b/docs/basics/durations.md @@ -28,10 +28,10 @@ Or converted to another unit. val hours: Hours = 60.minutes.inWholeHours ``` -You can also get the duration between two date-times in terms of any given unit, using functions like `hoursBetween()` or `daysBetween()`. +You can also get the duration between two date-times in terms of any given unit, using companion methods such as `Hours.between()` or `Days.between()`. ```kotlin -val hours = hoursBetween(firstDateTime, secondDateTime) +val hours = Hours.between(firstDateTime, secondDateTime) val absHours = hours.absoluteValue // hours may be negative ``` @@ -55,7 +55,7 @@ The methods and operators available to `Duration` are mostly the same as those o ```kotlin val duration: Duration = durationBetween(firstDateTime, secondDateTime) -val minutes: LongMinutes = duration.inMinutes +val minutes: Minutes = duration.inMinutes ``` ## `Period` diff --git a/docs/basics/intervals.md b/docs/basics/intervals.md index 351713f0b..efe5aadd8 100644 --- a/docs/basics/intervals.md +++ b/docs/basics/intervals.md @@ -37,7 +37,7 @@ Additional operations are also supported, such as `random()` and the ability to val randomDate = (today..today + 1.months).random() // Get the total number of days -val totalDays: LongDays = (today until today + 6.months).lengthInDays +val totalDays: Days = (today until today + 6.months).lengthInDays // Get the Period represented by the range val period: Period = (today..today + 1.months).asPeriod() diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/Main.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/Main.kt index f61a634c9..43defe1e4 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/Main.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/Main.kt @@ -6,7 +6,8 @@ import java.io.File private const val OUTPUT_PATH = "../../core/src/commonMain/generated" -private const val BASE_PACKAGE_NAME = "io.islandtime" +private const val ROOT_PACKAGE_NAME = "io.islandtime" +private const val BASE_PACKAGE_NAME = "io.islandtime.base" private const val CALENDAR_PACKAGE_NAME = "io.islandtime.calendar" private const val INTERNAL_PACKAGE_NAME = "io.islandtime.internal" private const val LOCALE_PACKAGE_NAME = "io.islandtime.locale" @@ -14,6 +15,7 @@ private const val MEASURES_PACKAGE_NAME = "io.islandtime.measures" private const val RANGES_PACKAGE_NAME = "io.islandtime.ranges" private const val RANGES_INTERNAL_PACKAGE_NAME = "io.islandtime.ranges.internal" +fun root(name: String) = ClassName(ROOT_PACKAGE_NAME, name) fun base(name: String) = ClassName(BASE_PACKAGE_NAME, name) fun calendar(name: String) = ClassName(CALENDAR_PACKAGE_NAME, name) fun internal(name: String) = ClassName(INTERNAL_PACKAGE_NAME, name) @@ -29,7 +31,8 @@ private val generators = arrayOf( ConstantsGenerator, DatePropertiesGenerator, DateConversionsGenerator, - IntervalOperatorsGenerator + IntervalOperatorsGenerator, + IntervalPropertiesGenerator ) fun main() { diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/DateTimeDescription.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/DateTimeDescription.kt index f4d436b14..c001e2e2f 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/DateTimeDescription.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/DateTimeDescription.kt @@ -1,65 +1,74 @@ package io.islandtime.codegen.descriptions import com.squareup.kotlinpoet.TypeName -import io.islandtime.codegen.base +import io.islandtime.codegen.root enum class DateTimeDescription( val typeName: TypeName, val simpleName: String, val smallestUnit: TemporalUnitDescription, val interval: IntervalDescription? = null, - val isDateBased: Boolean = false + val isDateBased: Boolean = false, + val isTimeBased: Boolean = false, + val isTimePoint: Boolean = false ) { Year( - typeName = base("Year"), + typeName = root("Year"), simpleName = "year", smallestUnit = TemporalUnitDescription.YEARS, isDateBased = true ), YearMonth( - typeName = base("YearMonth"), + typeName = root("YearMonth"), simpleName = "year-month", smallestUnit = TemporalUnitDescription.MONTHS, isDateBased = true ), Date( - typeName = base("Date"), + typeName = root("Date"), simpleName = "date", smallestUnit = TemporalUnitDescription.DAYS, interval = IntervalDescription.DateRange, isDateBased = true ), DateTime( - typeName = base("DateTime"), + typeName = root("DateTime"), simpleName = "date-time", smallestUnit = TemporalUnitDescription.NANOSECONDS, interval = IntervalDescription.DateTimeInterval, - isDateBased = true + isDateBased = true, + isTimeBased = true ) { override val datePropertyName: String get() = "date" }, OffsetDateTime( - typeName = base("OffsetDateTime"), + typeName = root("OffsetDateTime"), simpleName = "date-time", smallestUnit = TemporalUnitDescription.NANOSECONDS, interval = IntervalDescription.OffsetDateTimeInterval, - isDateBased = true + isDateBased = true, + isTimeBased = true, + isTimePoint = true ) { override val datePropertyName: String get() = "dateTime" }, ZonedDateTime( - typeName = base("ZonedDateTime"), + typeName = root("ZonedDateTime"), simpleName = "date-time", smallestUnit = TemporalUnitDescription.NANOSECONDS, interval = IntervalDescription.ZonedDateTimeInterval, - isDateBased = true + isDateBased = true, + isTimeBased = true, + isTimePoint = true ) { override val datePropertyName: String get() = "dateTime" }, Instant( - typeName = base("Instant"), + typeName = root("Instant"), simpleName = "instant", - smallestUnit = TemporalUnitDescription.NANOSECONDS + smallestUnit = TemporalUnitDescription.NANOSECONDS, + isTimeBased = true, + isTimePoint = true ); open val datePropertyName: String get() = throw NotImplementedError() diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/IntervalDescription.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/IntervalDescription.kt index 3c8005072..6806db1c7 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/IntervalDescription.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/IntervalDescription.kt @@ -45,4 +45,8 @@ enum class IntervalDescription( }; abstract val elementDescription: DateTimeDescription + + val isDateBased: Boolean get() = elementDescription.isDateBased + val isTimeBased: Boolean get() = elementDescription.isTimeBased + val isTimePointInterval: Boolean get() = elementDescription.isTimePoint } diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/TemporalUnitDescription.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/TemporalUnitDescription.kt index b7599405e..4e95d0341 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/TemporalUnitDescription.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/descriptions/TemporalUnitDescription.kt @@ -58,7 +58,7 @@ enum class TemporalUnitDescription( open val isoPeriodUnitConversion: TemporalUnitConversion get() = TemporalUnitConversion(this, this) val isoPeriodZeroString: String get() = "${isoPeriodPrefix}0$isoPeriodUnit" open val forceLongInOperators: Boolean = false - val isDayBased get() = this >= DAYS + val isDateBased get() = this >= DAYS val isTimeBased get() = this <= DAYS } @@ -79,7 +79,7 @@ data class TemporalUnitConversion( fun isSupportedAndNecessary() = isSupported() && isNecessary() fun isSupported(): Boolean { - return ((fromUnit.isDayBased && toUnit.isDayBased) || (fromUnit.isTimeBased && toUnit.isTimeBased)) && + return ((fromUnit.isDateBased && toUnit.isDateBased) || (fromUnit.isTimeBased && toUnit.isTimeBased)) && constantValue > 0L } diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/DatePropertiesGenerator.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/DatePropertiesGenerator.kt index 8d58bd504..0f07c27aa 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/DatePropertiesGenerator.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/DatePropertiesGenerator.kt @@ -27,7 +27,7 @@ private fun buildDatePropertiesFile() = file( private fun FileBuilder.buildDatePropertiesForClass(receiverClass: DateTimeDescription) { receiverClass.interval?.let { intervalClass -> fun CodeBlockBuilder.intervalOfWeekCode(vararg startOfWeekArgs: String): String { - using("startOfWeek", base("startOfWeek")) + using("startOfWeek", root("startOfWeek")) using("days", measures("days")) return buildString { @@ -493,7 +493,7 @@ private fun FileBuilder.buildPreviousNextFunctions( } } receiver(receiverClass.typeName) - argument("dayOfWeek", base("DayOfWeek")) + argument("dayOfWeek", root("DayOfWeek")) returns(receiverClass.typeName) if (receiverClass == Date) { @@ -534,7 +534,7 @@ private fun FileBuilder.buildPreviousNextFunctions( } receiver(receiverClass.typeName) - argument("dayOfWeek", base("DayOfWeek")) + argument("dayOfWeek", root("DayOfWeek")) returns(receiverClass.typeName) code { "return if (dayOfWeek == this.dayOfWeek) this else $name(dayOfWeek)" } } diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalOperatorsGenerator.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalOperatorsGenerator.kt index 99b87f193..1f5dbab15 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalOperatorsGenerator.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalOperatorsGenerator.kt @@ -2,16 +2,13 @@ package io.islandtime.codegen.generators import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec -import com.squareup.kotlinpoet.KModifier -import io.islandtime.codegen.* -import io.islandtime.codegen.descriptions.DateTimeDescription -import io.islandtime.codegen.descriptions.DateTimeDescription.Date +import io.islandtime.codegen.SingleFileGenerator import io.islandtime.codegen.descriptions.IntervalDescription -import io.islandtime.codegen.descriptions.IntervalDescription.* -import io.islandtime.codegen.descriptions.TemporalUnitDescription.DAYS -import io.islandtime.codegen.dsl.CodeBlockBuilder +import io.islandtime.codegen.descriptions.IntervalDescription.OffsetDateTimeInterval +import io.islandtime.codegen.descriptions.IntervalDescription.ZonedDateTimeInterval import io.islandtime.codegen.dsl.FileBuilder import io.islandtime.codegen.dsl.file +import io.islandtime.codegen.rangesInternal object IntervalOperatorsGenerator : SingleFileGenerator() { override fun generateSingle(): FileSpec = buildOperatorsFile() diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt new file mode 100644 index 000000000..e441ecad5 --- /dev/null +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt @@ -0,0 +1,108 @@ +package io.islandtime.codegen.generators + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.STAR +import com.squareup.kotlinpoet.TypeName +import io.islandtime.codegen.* +import io.islandtime.codegen.descriptions.IntervalDescription +import io.islandtime.codegen.descriptions.IntervalDescription.* +import io.islandtime.codegen.descriptions.TemporalUnitDescription +import io.islandtime.codegen.dsl.FileBuilder +import io.islandtime.codegen.dsl.file + +object IntervalPropertiesGenerator : SingleFileGenerator() { + override fun generateSingle(): FileSpec = buildPropertiesFile() +} + +private fun buildPropertiesFile() = file( + packageName = "io.islandtime.ranges", + fileName = "_Properties", + jvmName = "RangesKt" +) { + IntervalDescription.values().forEach { buildPropertiesForClass(it) } + buildPropertiesForTimePoint() +} + +private fun FileBuilder.buildPropertiesForClass(receiverClass: IntervalDescription) { + TemporalUnitDescription.values() + .filter { unit -> + unit >= receiverClass.elementDescription.smallestUnit && + ((unit.isDateBased && receiverClass.isDateBased) || !receiverClass.isTimePointInterval) + } + .forEach { unit -> + val additionalText = if (receiverClass == DateRange && unit == TemporalUnitDescription.DAYS) { + "A range is inclusive, so if the start and end date are the same, the length will be one day." + } else { + "" + } + + buildLengthProperty( + receiverTypeName = receiverClass.typeName, + receiverSimpleName = receiverClass.simpleName, + unit = unit, + isSmallestUnit = unit == receiverClass.elementDescription.smallestUnit, + additionalText = additionalText + ) + } +} + +private fun FileBuilder.buildPropertiesForTimePoint() { + TemporalUnitDescription.values() + .filter { it.isTimeBased && !it.isDateBased } + .forEach { unit -> + buildLengthProperty( + receiverTypeName = ranges("TimePointInterval").parameterizedBy(STAR), + receiverSimpleName = "interval", + unit = unit, + isSmallestUnit = unit == TemporalUnitDescription.NANOSECONDS + ) + } +} + +private fun FileBuilder.buildLengthProperty( + receiverTypeName: TypeName, + receiverSimpleName: String, + unit: TemporalUnitDescription, + isSmallestUnit: Boolean, + additionalText: String = "" +) { + property("lengthIn${unit.pluralName}", unit.className) { + receiver(receiverTypeName) + + kdoc { + val unitText = if (isSmallestUnit) unit.lowerPluralName else "whole ${unit.lowerPluralName}" + + """ + The number of $unitText between the start and end of this $receiverSimpleName. $additionalText + + @throws UnsupportedOperationException if the $receiverSimpleName isn't bounded + """.trimIndent() + } + + getter { + code { + using( + "unitClassName" to unit.className, + "unitConstructorProperty" to measures(unit.lowerPluralName), + "between" to root("between"), + "exception" to rangesInternal("throwUnboundedIntervalException") + ) + """ + |return when { + | isEmpty() -> 0.%unitConstructorProperty:T + | isBounded() -> %unitClassName:T.%between:T(start, endExclusive) + | else -> %exception:T() + |} + """.trimMargin() + } + } + } +} + +private val IntervalDescription.additionalOperatorKdocs: String + get() = when (this) { + OffsetDateTimeInterval -> "The offset of the start date-time will be used." + ZonedDateTimeInterval -> "The zone of the start date-time will be used." + else -> "" + } diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/TemporalUnitGenerator.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/TemporalUnitGenerator.kt index eb8d9d405..8f68d8f05 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/TemporalUnitGenerator.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/TemporalUnitGenerator.kt @@ -10,7 +10,6 @@ import io.islandtime.codegen.descriptions.per import io.islandtime.codegen.dsl.* import io.islandtime.codegen.internal import io.islandtime.codegen.javamath2kmp -import io.islandtime.codegen.measures import kotlin.reflect.KClass object TemporalUnitGenerator : Generator { @@ -50,9 +49,6 @@ private fun KClass<*>.literalValueString(value: Long) = when (this) { else -> throw IllegalStateException("Unsupported primitive type") } -private val TemporalUnitDescription.className get() = measures(pluralName) -private val TemporalUnitDescription.nextBiggest get() = TemporalUnitDescription.values()[this.ordinal + 1] - private val TemporalUnitConversion.propertyClassName get() = internal(constantName) private fun buildTemporalUnitFile(description: TemporalUnitDescription) = file( @@ -674,7 +670,7 @@ private fun CodeBlockBuilder.exactlyOnceContractCode(parameter: String) { +"%contract:T { callsInPlace($parameter, %invocationKind:T.EXACTLY_ONCE) }" } -fun FileBuilder.buildPrimitiveConstructorProperty(primitive: KClass<*>, description: TemporalUnitDescription) { +private fun FileBuilder.buildPrimitiveConstructorProperty(primitive: KClass<*>, description: TemporalUnitDescription) { property(description.lowerPluralName, description.className) { kdoc { "Converts this value to a duration of ${description.lowerPluralName}." } receiver(primitive) @@ -684,7 +680,7 @@ fun FileBuilder.buildPrimitiveConstructorProperty(primitive: KClass<*>, descript } } -fun FileBuilder.buildTimesExtensionFunction(scalarPrimitive: KClass<*>, description: TemporalUnitDescription) { +private fun FileBuilder.buildTimesExtensionFunction(scalarPrimitive: KClass<*>, description: TemporalUnitDescription) { function("times") { kdoc { """ diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/Utility.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/Utility.kt new file mode 100644 index 000000000..84aaa8e7a --- /dev/null +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/Utility.kt @@ -0,0 +1,13 @@ +package io.islandtime.codegen.generators + +import io.islandtime.codegen.descriptions.TemporalUnitDescription +import io.islandtime.codegen.measures + +internal val TemporalUnitDescription.className get() = measures(pluralName) + +internal val TemporalUnitDescription.nextBiggest get() = TemporalUnitDescription.values()[this.ordinal + 1] + +internal val TemporalUnitDescription.nextSmallest get() = TemporalUnitDescription.values()[this.ordinal - 1] + +internal val TemporalUnitDescription.nextSmallestOrNull + get() = if (this == TemporalUnitDescription.NANOSECONDS) null else nextSmallest From 37a812247147bbc3c0d89c22fa4aeec667b9acd8 Mon Sep 17 00:00:00 2001 From: Erik Christensen Date: Mon, 24 May 2021 22:15:26 -0400 Subject: [PATCH 2/2] Make Duration and Period consistent --- .../kotlin/io/islandtime/operators/Between.kt | 69 ++++++++++++++ .../io/islandtime/ranges/Conversions.kt | 90 ++++++++++++++++--- .../kotlin/io/islandtime/ranges/DateRange.kt | 54 ++++------- .../io/islandtime/ranges/DateTimeInterval.kt | 82 +++++++---------- .../ranges/OffsetDateTimeInterval.kt | 40 ++++----- .../io/islandtime/ranges/TimePointInterval.kt | 36 ++++---- .../ranges/ZonedDateTimeInterval.kt | 35 ++++---- .../io/islandtime/operators/BetweenTest.kt | 67 ++++++++++++++ .../io/islandtime/ranges/DateRangeTest.kt | 57 ++---------- .../islandtime/ranges/DateTimeIntervalTest.kt | 30 +++---- .../islandtime/ranges/InstantIntervalTest.kt | 23 ----- .../ranges/OffsetDateTimeIntervalTest.kt | 18 ++-- .../ranges/ZonedDateTimeIntervalTest.kt | 46 +++++----- .../io/islandtime/jvm/DateComparisonTest.kt | 12 +-- docs/basics/durations.md | 4 +- docs/basics/intervals.md | 2 +- .../generators/IntervalPropertiesGenerator.kt | 7 -- 17 files changed, 361 insertions(+), 311 deletions(-) diff --git a/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt b/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt index aa54f09ba..acdff23f4 100644 --- a/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt +++ b/core/src/commonMain/kotlin/io/islandtime/operators/Between.kt @@ -5,6 +5,7 @@ package io.islandtime import io.islandtime.base.TimePoint +import io.islandtime.internal.MONTHS_PER_YEAR import io.islandtime.measures.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -395,6 +396,74 @@ fun Nanoseconds.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<* ) } +/** + * Returns the [Duration] between two date-times, which are assumed to be at the same UTC offset. In general, it's more + * appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be taken + * into account when working with [DateTime] directly. + */ +fun Duration.Companion.between(start: DateTime, endExclusive: DateTime): Duration { + val secondDiff = endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO) - start.secondOfUnixEpochAt(UtcOffset.ZERO) + val nanoDiff = endExclusive.nanosecond - start.nanosecond + return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) +} + +/** + * Returns the [Duration] between two time points. + */ +fun Duration.Companion.between(start: TimePoint<*>, endExclusive: TimePoint<*>): Duration { + val secondDiff = endExclusive.secondOfUnixEpoch - start.secondOfUnixEpoch + val nanoDiff = endExclusive.nanosecond - start.nanosecond + return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) +} + +/** + * Returns the [Period] between two dates. + */ +fun Period.Companion.between(start: Date, endExclusive: Date): Period { + var totalMonths = endExclusive.monthsSinceYear0 - start.monthsSinceYear0 + val dayDiff = endExclusive.dayOfMonth - start.dayOfMonth + + val days = when { + totalMonths > 0 && dayDiff < 0 -> { + totalMonths-- + val testDate = start + totalMonths.months + Days.between(testDate, endExclusive) + } + totalMonths < 0 && dayDiff > 0 -> { + totalMonths++ + (dayDiff - endExclusive.lengthOfMonth.value).days + } + else -> dayDiff.days + } + val years = (totalMonths / MONTHS_PER_YEAR).years + val months = (totalMonths % MONTHS_PER_YEAR).months + + return periodOf(years, months, days) +} + +/** + * Returns the [Period] between two date-times, which are assumed to be in the same time zone. + */ +fun Period.Companion.between(start: DateTime, endExclusive: DateTime): Period { + return Period.between(start.date, adjustedEndDate(start, endExclusive)) +} + +/** + * Returns the [Period] between two date-times, adjusting the offset of [endExclusive] if necessary to match the + * starting date-time. + */ +fun Period.Companion.between(start: OffsetDateTime, endExclusive: OffsetDateTime): Period { + return Period.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + +/** + * Returns the [Period] between two zoned date-times, adjusting the time zone of [endExclusive] if necessary to match + * the starting date-time. + */ +fun Period.Companion.between(start: ZonedDateTime, endExclusive: ZonedDateTime): Period { + return Period.between(start.dateTime, adjustedEndDateTime(start, endExclusive)) +} + private inline val YearMonth.monthsSinceYear0: Long get() = year * 12L + month.ordinal private fun secondsBetween( diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt index ed0900f1a..038fce44c 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt @@ -5,7 +5,10 @@ package io.islandtime.ranges import io.islandtime.* import io.islandtime.base.TimePoint +import io.islandtime.measures.Duration +import io.islandtime.measures.Period import io.islandtime.measures.days +import io.islandtime.ranges.internal.throwUnboundedIntervalException import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -148,21 +151,84 @@ fun ZonedDateTimeInterval.toInstantInterval(): InstantInterval { return (this as TimePointInterval<*>).toInstantInterval() } -private inline fun Interval.toDateRange(toDateTime: T.() -> DateTime): DateRange { - return when { - isEmpty() -> DateRange.EMPTY - isUnbounded() -> DateRange.UNBOUNDED - else -> { - val endDateTime = toDateTime(endExclusive) +/** + * Converts this interval to the [Duration] between the start and end date-time, which are assumed to be in the same + * time zone. In general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight + * savings rules won't be taken into account when working with [DateTime] directly. + * + * @throws UnsupportedOperationException if the interval isn't bounded + */ +fun DateTimeInterval.toDuration(): Duration = when { + isEmpty() -> Duration.ZERO + isBounded() -> Duration.between(start, endExclusive) + else -> throwUnboundedIntervalException() +} + +/** + * Converts this range into a [Period] of the same length. As a range is inclusive, if the start and end date are the + * same, the resulting period will contain one day. + * + * @throws UnsupportedOperationException if the range isn't bounded + */ +fun DateRange.toPeriod(): Period = when { + isEmpty() -> Period.ZERO + isBounded() -> Period.between(start, endInclusive + 1.days) + else -> throwUnboundedIntervalException() +} + +/** + * Converts this interval into a [Period] of the same length. + * @throws UnsupportedOperationException if the interval isn't bounded + */ +fun DateTimeInterval.toPeriod(): Period = when { + isEmpty() -> Period.ZERO + isBounded() -> Period.between(start, endExclusive) + else -> throwUnboundedIntervalException() +} + +/** + * Converts this interval into a [Period] of the same length. + * @throws UnsupportedOperationException if the interval isn't bounded + */ +fun OffsetDateTimeInterval.toPeriod(): Period = when { + isEmpty() -> Period.ZERO + isBounded() -> Period.between(start, endExclusive) + else -> throwUnboundedIntervalException() +} + +/** + * Converts this interval into a [Period] of the same length. + * @throws UnsupportedOperationException if the interval isn't bounded + */ +fun ZonedDateTimeInterval.toPeriod(): Period = when { + isEmpty() -> Period.ZERO + isBounded() -> Period.between(start, endExclusive) + else -> throwUnboundedIntervalException() +} - val endDate = if (endDateTime.time == Time.MIDNIGHT) { - endDateTime.date - 1.days - } else { - endDateTime.date - } +/** + * Converts this interval into a [Duration] of the same length. + * @throws UnsupportedOperationException if the interval isn't bounded + */ +fun TimePointInterval<*>.toDuration(): Duration = when { + isEmpty() -> Duration.ZERO + isBounded() -> Duration.between(start, endExclusive) + else -> throwUnboundedIntervalException() +} + +private inline fun Interval.toDateRange(toDateTime: T.() -> DateTime): DateRange = when { + isEmpty() -> DateRange.EMPTY + isUnbounded() -> DateRange.UNBOUNDED + else -> { + val endDateTime = toDateTime(endExclusive) - toDateTime(start).date..endDate + val endDate = if (endDateTime.time == Time.MIDNIGHT) { + endDateTime.date - 1.days + } else { + endDateTime.date } + + toDateTime(start).date..endDate } } diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt index 8f078f3b8..5c07b0b36 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt @@ -2,11 +2,9 @@ package io.islandtime.ranges import io.islandtime.* import io.islandtime.base.DateTimeField -import io.islandtime.internal.MONTHS_PER_YEAR import io.islandtime.measures.* import io.islandtime.parser.* import io.islandtime.ranges.internal.buildIsoString -import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * An inclusive range of dates. @@ -53,18 +51,12 @@ class DateRange( return if (isEmpty()) -1 else (31 * start.hashCode() + endInclusive.hashCode()) } - /** - * Converts this range into a [Period] of the same length. As a range is inclusive, if the start and end date are - * the same, the resulting period will contain one day. - * @throws UnsupportedOperationException if the range isn't bounded - */ - fun asPeriod(): Period { - return when { - isEmpty() -> Period.ZERO - isBounded() -> periodBetween(start, endInclusive + 1.days) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toPeriod()", + replaceWith = ReplaceWith("this.toPeriod()", "io.islandtime.ranges.toPeriod"), + level = DeprecationLevel.WARNING + ) + fun asPeriod(): Period = toPeriod() companion object { /** @@ -135,30 +127,16 @@ fun String.toDateRange( */ infix fun Date.until(to: Date): DateRange = DateRange(this, to - 1.days) -/** - * Gets the [Period] between two dates. - */ -fun periodBetween(start: Date, endExclusive: Date): Period { - var totalMonths = endExclusive.monthsSinceYear0 - start.monthsSinceYear0 - val dayDiff = endExclusive.dayOfMonth - start.dayOfMonth - - val days = when { - totalMonths > 0 && dayDiff < 0 -> { - totalMonths-- - val testDate = start + totalMonths.months - Days.between(testDate, endExclusive) - } - totalMonths < 0 && dayDiff > 0 -> { - totalMonths++ - (dayDiff - endExclusive.lengthOfMonth.value).days - } - else -> dayDiff.days - } - val years = (totalMonths / MONTHS_PER_YEAR).years - val months = (totalMonths % MONTHS_PER_YEAR).months - - return periodOf(years, months, days) -} +@Deprecated( + message = "Replace with Period.between()", + replaceWith = ReplaceWith( + "Period.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Period" + ), + level = DeprecationLevel.WARNING +) +fun periodBetween(start: Date, endExclusive: Date): Period = Period.between(start, endExclusive) @Deprecated( message = "Replace with Years.between()", diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt index 2f69422b6..f5043f63f 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt @@ -6,7 +6,6 @@ import io.islandtime.measures.* import io.islandtime.parser.* import io.islandtime.ranges.internal.MAX_INCLUSIVE_END_DATE_TIME import io.islandtime.ranges.internal.buildIsoString -import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * An interval between two date-times, assumed to be at the same offset from UTC. @@ -54,32 +53,19 @@ class DateTimeInterval( appendFunction = StringBuilder::appendDateTime ) - /** - * Converts this interval to the [Duration] between the start and end date-time, assuming they're in the same time - * zone. In general, it's more appropriate to calculate duration using [Instant] or [ZonedDateTime] as any daylight - * savings rules won't be taken into account when working with [DateTime] directly. - * - * @throws UnsupportedOperationException if the interval isn't bounded - */ - fun asDuration(): Duration { - return when { - isEmpty() -> Duration.ZERO - isBounded() -> durationBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toDuration()", + replaceWith = ReplaceWith("this.toDuration()", "io.islandtime.ranges.toDuration"), + level = DeprecationLevel.WARNING + ) + fun asDuration(): Duration = toDuration() - /** - * Converts this interval into a [Period] of the same length. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - fun asPeriod(): Period { - return when { - isEmpty() -> Period.ZERO - isBounded() -> periodBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toPeriod()", + replaceWith = ReplaceWith("this.toPeriod()", "io.islandtime.ranges.toPeriod"), + level = DeprecationLevel.WARNING + ) + fun asPeriod(): Period = toPeriod() companion object { /** @@ -164,12 +150,16 @@ fun String.toDateTimeInterval( */ infix fun DateTime.until(to: DateTime): DateTimeInterval = DateTimeInterval(this, to) -/** - * Gets the [Period] between two date-times, assuming they're in the same time zone. - */ -fun periodBetween(start: DateTime, endExclusive: DateTime): Period { - return periodBetween(start.date, adjustedEndDate(start, endExclusive)) -} +@Deprecated( + message = "Replace with Period.between()", + replaceWith = ReplaceWith( + "Period.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Period" + ), + level = DeprecationLevel.WARNING +) +fun periodBetween(start: DateTime, endExclusive: DateTime): Period = Period.between(start, endExclusive) @Deprecated( message = "Replace with Years.between()", @@ -216,16 +206,16 @@ fun weeksBetween(start: DateTime, endExclusive: DateTime): Weeks = Weeks.between ) fun daysBetween(start: DateTime, endExclusive: DateTime): Days = Days.between(start, endExclusive) -/** - * Gets the [Duration] between two date-times, assuming they have the same UTC offset. In general, it's more appropriate - * to calculate duration using [Instant] or [ZonedDateTime] as any daylight savings rules won't be taken into account - * when working with [DateTime] directly. - */ -fun durationBetween(start: DateTime, endExclusive: DateTime): Duration { - val secondDiff = endExclusive.secondOfUnixEpochAt(UtcOffset.ZERO) - start.secondOfUnixEpochAt(UtcOffset.ZERO) - val nanoDiff = endExclusive.nanosecond - start.nanosecond - return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) -} +@Deprecated( + message = "Replace with Duration.between()", + replaceWith = ReplaceWith( + "Duration.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Duration" + ), + level = DeprecationLevel.WARNING +) +fun durationBetween(start: DateTime, endExclusive: DateTime): Duration = Duration.between(start, endExclusive) @Deprecated( message = "Replace with Hours.between()", @@ -294,11 +284,3 @@ fun microsecondsBetween(start: DateTime, endExclusive: DateTime): Microseconds = level = DeprecationLevel.WARNING ) fun nanosecondsBetween(start: DateTime, endExclusive: DateTime): Nanoseconds = Nanoseconds.between(start, endExclusive) - -internal fun adjustedEndDate(start: DateTime, endExclusive: DateTime): Date { - return when { - endExclusive.date > start.date && endExclusive.time < start.time -> endExclusive.date - 1.days - endExclusive.date < start.date && endExclusive.time > start.time -> endExclusive.date + 1.days - else -> endExclusive.date - } -} diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt index d5a9b645e..4fb195694 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt @@ -6,7 +6,6 @@ import io.islandtime.measures.* import io.islandtime.parser.* import io.islandtime.ranges.internal.MAX_INCLUSIVE_END_DATE_TIME import io.islandtime.ranges.internal.buildIsoString -import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * A half-open interval between two offset date-times based on timeline order. @@ -31,17 +30,12 @@ class OffsetDateTimeInterval( appendFunction = StringBuilder::appendOffsetDateTime ) - /** - * Converts this interval into a [Period] of the same length. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - fun asPeriod(): Period { - return when { - isEmpty() -> Period.ZERO - isBounded() -> periodBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toPeriod()", + replaceWith = ReplaceWith("this.toPeriod()", "io.islandtime.ranges.toPeriod"), + level = DeprecationLevel.WARNING + ) + fun asPeriod(): Period = toPeriod() companion object { /** @@ -135,13 +129,16 @@ fun String.toOffsetDateTimeInterval( */ infix fun OffsetDateTime.until(to: OffsetDateTime): OffsetDateTimeInterval = OffsetDateTimeInterval(this, to) -/** - * Gets the [Period] between two date-times, adjusting the offset of [endExclusive] if necessary to match the starting - * date-time. - */ -fun periodBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Period { - return periodBetween(start.dateTime, adjustedEndDateTime(start, endExclusive)) -} +@Deprecated( + message = "Replace with Period.between()", + replaceWith = ReplaceWith( + "Period.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Period" + ), + level = DeprecationLevel.WARNING +) +fun periodBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Period = Period.between(start, endExclusive) @Deprecated( message = "Replace with Years.between()", @@ -186,8 +183,3 @@ fun weeksBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Weeks = W level = DeprecationLevel.WARNING ) fun daysBetween(start: OffsetDateTime, endExclusive: OffsetDateTime): Days = Days.between(start, endExclusive) - -private fun adjustedEndDateTime(start: OffsetDateTime, endExclusive: OffsetDateTime): DateTime { - val offsetDelta = start.offset.totalSeconds - endExclusive.offset.totalSeconds - return endExclusive.dateTime + offsetDelta -} diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt index 8df19a171..9f67e3421 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt @@ -4,7 +4,6 @@ import io.islandtime.base.TimePoint import io.islandtime.between import io.islandtime.internal.deprecatedToError import io.islandtime.measures.* -import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * A half-open interval of time points. @@ -33,17 +32,12 @@ abstract class TimePointInterval> internal constructor( override fun isEmpty(): Boolean = start >= endExclusive - /** - * Converts this interval into a [Duration] of the same length. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - fun asDuration(): Duration { - return when { - isEmpty() -> Duration.ZERO - isBounded() -> durationBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toDuration()", + replaceWith = ReplaceWith("this.toDuration()", "io.islandtime.ranges.toDuration"), + level = DeprecationLevel.WARNING + ) + fun asDuration(): Duration = toDuration() } /** @@ -55,14 +49,16 @@ operator fun > TimePointInterval.contains(value: TimePoint<* (value < endExclusive || hasUnboundedEnd()) } -/** - * Gets the [Duration] between two time points. - */ -fun durationBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Duration { - val secondDiff = endExclusive.secondOfUnixEpoch - start.secondOfUnixEpoch - val nanoDiff = endExclusive.nanosecond - start.nanosecond - return durationOf(secondDiff.seconds, nanoDiff.nanoseconds) -} +@Deprecated( + message = "Replace with Duration.between()", + replaceWith = ReplaceWith( + "Duration.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Duration" + ), + level = DeprecationLevel.WARNING +) +fun durationBetween(start: TimePoint<*>, endExclusive: TimePoint<*>): Duration = Duration.between(start, endExclusive) @Deprecated( message = "Replace with Days.between() or Hours.between().inWholeDays as appropriate", diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt index 487fba015..57aa98b17 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt @@ -7,7 +7,6 @@ import io.islandtime.measures.* import io.islandtime.parser.* import io.islandtime.ranges.internal.MAX_INCLUSIVE_END_DATE_TIME import io.islandtime.ranges.internal.buildIsoString -import io.islandtime.ranges.internal.throwUnboundedIntervalException /** * A half-open interval of zoned date-times based on timeline order. @@ -33,17 +32,12 @@ class ZonedDateTimeInterval( appendFunction = StringBuilder::appendZonedDateTime ) - /** - * Converts this interval into a [Period] of the same length. - * @throws UnsupportedOperationException if the interval isn't bounded - */ - fun asPeriod(): Period { - return when { - isEmpty() -> Period.ZERO - isBounded() -> periodBetween(start, endExclusive) - else -> throwUnboundedIntervalException() - } - } + @Deprecated( + message = "Replace with toPeriod()", + replaceWith = ReplaceWith("this.toPeriod()", "io.islandtime.ranges.toPeriod"), + level = DeprecationLevel.WARNING + ) + fun asPeriod(): Period = toPeriod() companion object { /** @@ -146,13 +140,16 @@ infix fun ZonedDateTime.until(to: ZonedDateTime): ZonedDateTimeInterval = ZonedD @Suppress("UNUSED_PARAMETER", "unused") fun DateRange.toZonedDateTimeInterval(zone: TimeZone): ZonedDateTimeInterval = deprecatedToError() -/** - * Gets the [Period] between two zoned date-times, adjusting the time zone of [endExclusive] if necessary to match the - * starting date-time. - */ -fun periodBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Period { - return periodBetween(start.dateTime, endExclusive.adjustedTo(start.zone).dateTime) -} +@Deprecated( + message = "Replace with Period.between()", + replaceWith = ReplaceWith( + "Period.between(start, endExclusive)", + "io.islandtime.between", + "io.islandtime.measures.Period" + ), + level = DeprecationLevel.WARNING +) +fun periodBetween(start: ZonedDateTime, endExclusive: ZonedDateTime): Period = Period.between(start, endExclusive) @Deprecated( message = "Replace with Years.between()", diff --git a/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt b/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt index b93e105f1..927603184 100644 --- a/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/operators/BetweenTest.kt @@ -313,6 +313,73 @@ class BetweenTest : AbstractIslandTimeTest() { assertFailsWith { Nanoseconds.between(Instant.UNIX_EPOCH, OffsetDateTime.MAX) } } + @Test + fun `duration between instants`() { + assertEquals( + 0.milliseconds.asDuration(), + Duration.between(Instant(1L.milliseconds), Instant(1L.milliseconds)) + ) + + assertEquals( + 1.milliseconds.asDuration(), + Duration.between(Instant.UNIX_EPOCH, Instant(1L.milliseconds)) + ) + + assertEquals( + (-1).milliseconds.asDuration(), + Duration.between(Instant(1L.milliseconds), Instant.UNIX_EPOCH) + ) + + assertEquals( + (-3).milliseconds.asDuration(), + Duration.between(Instant(1L.milliseconds), Instant((-2L).milliseconds)) + ) + } + + @Test + fun `returns a zeroed period when the start and end dates are the same`() { + assertEquals( + Period.ZERO, + Period.between(Date(2019, Month.MAY, 1), Date(2019, Month.MAY, 1)) + ) + } + + @Test + fun `period between dates in positive progression`() { + assertEquals( + periodOf(1.months, 2.days), + Period.between(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) + ) + + assertEquals( + periodOf(1.months, 8.days), + Period.between(Date(2019, Month.MAY, 25), Date(2019, Month.JULY, 3)) + ) + + assertEquals( + periodOf(2.years, 29.days), + Period.between(Date(2018, Month.JANUARY, 31), Date(2020, Month.FEBRUARY, 29)) + ) + } + + @Test + fun `period between dates in negative progression`() { + assertEquals( + periodOf((-28).days), + Period.between(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 3)) + ) + + assertEquals( + periodOf((-1).months), + Period.between(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 1)) + ) + + assertEquals( + periodOf((-1).years, (-10).months, (-21).days), + Period.between(Date(2019, Month.MAY, 25), Date(2017, Month.JULY, 4)) + ) + } + @Test fun `centuries betwen years`() { assertEquals(0.centuries, Centuries.between(Year(2000), Year(2099))) diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt index 784466443..6d66c7b62 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/DateRangeTest.kt @@ -2,7 +2,6 @@ package io.islandtime.ranges import io.islandtime.Date import io.islandtime.Month -import io.islandtime.between import io.islandtime.measures.* import io.islandtime.parser.DateTimeParseException import io.islandtime.parser.DateTimeParsers @@ -233,7 +232,7 @@ class DateRangeTest : AbstractIslandTimeTest() { Date(2019, Month.JUNE, 1)..Date.MAX, Date.MIN..Date(2019, Month.JUNE, 1) ).forEach { - assertFailsWith { it.asPeriod() } + assertFailsWith { it.toPeriod() } assertFailsWith { it.lengthInCenturies } assertFailsWith { it.lengthInDecades } assertFailsWith { it.lengthInYears } @@ -245,7 +244,7 @@ class DateRangeTest : AbstractIslandTimeTest() { @Test fun `length properties return 0 when the range is empty`() { - assertEquals(Period.ZERO, DateRange.EMPTY.asPeriod()) + assertEquals(Period.ZERO, DateRange.EMPTY.toPeriod()) assertEquals(0.centuries, DateRange.EMPTY.lengthInCenturies) assertEquals(0.decades, DateRange.EMPTY.lengthInDecades) assertEquals(0.years, DateRange.EMPTY.lengthInYears) @@ -315,59 +314,15 @@ class DateRangeTest : AbstractIslandTimeTest() { } @Test - fun `asPeriod() returns a period of 1 day when the start and end date are equal`() { + fun `toPeriod() returns a period of 1 day when the start and end date are equal`() { val date = Date(2019, Month.JUNE, 1) - assertEquals(periodOf(1.days), (date..date).asPeriod()) + assertEquals(periodOf(1.days), (date..date).toPeriod()) } @Test - fun `asPeriod() returns the expected period for non-empty ranges`() { + fun `toPeriod() returns the expected period for non-empty ranges`() { val start = Date(2018, Month.FEBRUARY, 20) val end = Date(2019, Month.MARCH, 20) - assertEquals(periodOf(1.years, 1.months, 1.days), (start..end).asPeriod()) - } - - @Test - fun `periodBetween() returns a zeroed period when the start and end dates are the same`() { - assertEquals( - Period.ZERO, - periodBetween(Date(2019, Month.MAY, 1), Date(2019, Month.MAY, 1)) - ) - } - - @Test - fun `periodBetween() returns the period between two dates in positive progression`() { - assertEquals( - periodOf(1.months, 2.days), - periodBetween(Date(2019, Month.MAY, 1), Date(2019, Month.JUNE, 3)) - ) - - assertEquals( - periodOf(1.months, 8.days), - periodBetween(Date(2019, Month.MAY, 25), Date(2019, Month.JULY, 3)) - ) - - assertEquals( - periodOf(2.years, 29.days), - periodBetween(Date(2018, Month.JANUARY, 31), Date(2020, Month.FEBRUARY, 29)) - ) - } - - @Test - fun `periodBetween() returns the period between two dates in negative progression`() { - assertEquals( - periodOf((-28).days), - periodBetween(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 3)) - ) - - assertEquals( - periodOf((-1).months), - periodBetween(Date(2019, Month.MAY, 1), Date(2019, Month.APRIL, 1)) - ) - - assertEquals( - periodOf((-1).years, (-10).months, (-21).days), - periodBetween(Date(2019, Month.MAY, 25), Date(2017, Month.JULY, 4)) - ) + assertEquals(periodOf(1.years, 1.months, 1.days), (start..end).toPeriod()) } } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt index 42fcbf61a..e599ca213 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt @@ -173,14 +173,14 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { } @Test - fun `asDuration() returns a zeroed out duration when the range is empty`() { - assertEquals(Duration.ZERO, DateTimeInterval.EMPTY.asDuration()) + fun `toDuration() returns a zeroed out duration when the range is empty`() { + assertEquals(Duration.ZERO, DateTimeInterval.EMPTY.toDuration()) } @Test - fun `asDuration() throws an exception when the range is unbounded`() { + fun `toDuration() throws an exception when the range is unbounded`() { assertFailsWith { - DateTimeInterval(endExclusive = "2018-09-10T09:15".toDateTime()).asDuration() + DateTimeInterval(endExclusive = "2018-09-10T09:15".toDateTime()).toDuration() } } @@ -188,24 +188,19 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { fun `duration is correct when bounded`() { assertEquals( 10.minutes.asDuration(), - ("2019-10-11T09:15".toDateTime() until "2019-10-11T09:25".toDateTime()).asDuration() - ) - - assertEquals( - 10.minutes.asDuration(), - durationBetween("2019-10-11T09:15".toDateTime(), "2019-10-11T09:25".toDateTime()) + ("2019-10-11T09:15".toDateTime() until "2019-10-11T09:25".toDateTime()).toDuration() ) } @Test - fun `asPeriod() returns a zeroed out period when the range is empty`() { - assertEquals(Period.ZERO, DateTimeInterval.EMPTY.asPeriod()) + fun `toPeriod() returns a zeroed out period when the range is empty`() { + assertEquals(Period.ZERO, DateTimeInterval.EMPTY.toPeriod()) } @Test - fun `asPeriod() throws an exception when the range is unbounded`() { + fun `toPeriod() throws an exception when the range is unbounded`() { assertFailsWith { - DateTimeInterval(endExclusive = "2018-09-10T09:15".toDateTime()).asPeriod() + DateTimeInterval(endExclusive = "2018-09-10T09:15".toDateTime()).toPeriod() } } @@ -213,12 +208,7 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { fun `period is correct when bounded`() { assertEquals( periodOf(1.years, 1.months, 1.days), - ("2018-09-10T09:15".toDateTime() until "2019-10-11T09:15".toDateTime()).asPeriod() - ) - - assertEquals( - periodOf(1.years, 1.months, 1.days), - periodBetween("2018-09-10T09:15".toDateTime(), "2019-10-11T09:15".toDateTime()) + ("2018-09-10T09:15".toDateTime() until "2019-10-11T09:15".toDateTime()).toPeriod() ) } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt index 995b47fb2..6c1abe2e2 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/InstantIntervalTest.kt @@ -227,27 +227,4 @@ class InstantIntervalTest : AbstractIslandTimeTest() { val instant = Instant(2L.days.inSeconds) assertEquals(1L.nanoseconds, (instant..instant).lengthInNanoseconds) } - - @Test - fun `durationBetween() returns the duration between two instants`() { - assertEquals( - 0.milliseconds.asDuration(), - durationBetween(Instant(1L.milliseconds), Instant(1L.milliseconds)) - ) - - assertEquals( - 1.milliseconds.asDuration(), - durationBetween(Instant.UNIX_EPOCH, Instant(1L.milliseconds)) - ) - - assertEquals( - (-1).milliseconds.asDuration(), - durationBetween(Instant(1L.milliseconds), Instant.UNIX_EPOCH) - ) - - assertEquals( - (-3).milliseconds.asDuration(), - durationBetween(Instant(1L.milliseconds), Instant((-2L).milliseconds)) - ) - } } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt index 1a3cca317..7f43c7d4d 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/OffsetDateTimeIntervalTest.kt @@ -151,14 +151,14 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { } @Test - fun `asPeriod() returns a zeroed out period when the range is empty`() { - assertEquals(Period.ZERO, OffsetDateTimeInterval.EMPTY.asPeriod()) + fun `toPeriod() returns a zeroed out period when the range is empty`() { + assertEquals(Period.ZERO, OffsetDateTimeInterval.EMPTY.toPeriod()) } @Test - fun `asPeriod() throws an exception when the range is unbounded`() { + fun `toPeriod() throws an exception when the range is unbounded`() { assertFailsWith { - OffsetDateTimeInterval(endExclusive = "2018-09-10T09:15-06:00".toOffsetDateTime()).asPeriod() + OffsetDateTimeInterval(endExclusive = "2018-09-10T09:15-06:00".toOffsetDateTime()).toPeriod() } } @@ -166,15 +166,7 @@ class OffsetDateTimeIntervalTest : AbstractIslandTimeTest() { fun `period is correct when bounded`() { assertEquals( periodOf(1.years, 1.months, 1.days), - ("2018-09-10T09:15-06:00".toOffsetDateTime() until "2019-10-11T09:15-07:00".toOffsetDateTime()).asPeriod() - ) - - assertEquals( - periodOf(1.years, 1.months, 1.days), - periodBetween( - "2018-09-10T09:15-06:00".toOffsetDateTime(), - "2019-10-11T09:15-07:00".toOffsetDateTime() - ) + ("2018-09-10T09:15-06:00".toOffsetDateTime() until "2019-10-11T09:15-07:00".toOffsetDateTime()).toPeriod() ) } diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt index 46733ba0f..ba8ccc214 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/ZonedDateTimeIntervalTest.kt @@ -192,12 +192,12 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10) at Time(1, 0) at zone val now = Date(2019, 4, 10) at Time(1, 0) at zone - assertEquals(periodOf(1.months), periodBetween(then, now)) - assertEquals(periodOf((-1).months), periodBetween(now, then)) - assertEquals(periodOf(30.days), periodBetween(then, now - 1.nanoseconds)) - assertEquals(periodOf((-30).days), periodBetween(now, then + 1.nanoseconds)) - assertEquals(periodOf(1.months), (then until now).asPeriod()) - assertEquals(periodOf(30.days), (then until now - 1.nanoseconds).asPeriod()) + assertEquals(periodOf(1.months), Period.between(then, now)) + assertEquals(periodOf((-1).months), Period.between(now, then)) + assertEquals(periodOf(30.days), Period.between(then, now - 1.nanoseconds)) + assertEquals(periodOf((-30).days), Period.between(now, then + 1.nanoseconds)) + assertEquals(periodOf(1.months), (then until now).toPeriod()) + assertEquals(periodOf(30.days), (then until now - 1.nanoseconds).toPeriod()) } @Test @@ -206,12 +206,12 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10) at Time(1, 0) at zone val now = Date(2019, 3, 11) at Time(1, 0) at zone - assertEquals(periodOf(1.days), periodBetween(then, now)) - assertEquals(periodOf((-1).days), periodBetween(now, then)) - assertEquals(Period.ZERO, periodBetween(then, now - 1.nanoseconds)) - assertEquals(Period.ZERO, periodBetween(now, then + 1.nanoseconds)) - assertEquals(periodOf(1.days), (then until now).asPeriod()) - assertEquals(Period.ZERO, (then until now - 1.nanoseconds).asPeriod()) + assertEquals(periodOf(1.days), Period.between(then, now)) + assertEquals(periodOf((-1).days), Period.between(now, then)) + assertEquals(Period.ZERO, Period.between(then, now - 1.nanoseconds)) + assertEquals(Period.ZERO, Period.between(now, then + 1.nanoseconds)) + assertEquals(periodOf(1.days), (then until now).toPeriod()) + assertEquals(Period.ZERO, (then until now - 1.nanoseconds).toPeriod()) } @Test @@ -220,10 +220,10 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 3, 10) at Time(1, 0) at zone val now = Date(2019, 3, 11) at Time(1, 0) at zone - assertEquals(durationOf(23.hours), durationBetween(then, now)) - assertEquals(durationOf((-23).hours), durationBetween(now, then)) - assertEquals(durationOf(23.hours), (then until now).asDuration()) - assertEquals(durationOf(23.hours.inSeconds, 1.nanoseconds), (then..now).asDuration()) + assertEquals(durationOf(23.hours), Duration.between(then, now)) + assertEquals(durationOf((-23).hours), Duration.between(now, then)) + assertEquals(durationOf(23.hours), (then until now).toDuration()) + assertEquals(durationOf(23.hours.inSeconds, 1.nanoseconds), (then..now).toDuration()) } @Test @@ -232,10 +232,10 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 11, 3) at Time(1, 0) at zone val now = Date(2019, 11, 4) at Time(1, 0) at zone - assertEquals(periodOf(1.days), periodBetween(then, now)) - assertEquals(Period.ZERO, periodBetween(then, now - 1.nanoseconds)) - assertEquals(periodOf(1.days), (then until now).asPeriod()) - assertEquals(Period.ZERO, (then until now - 1.nanoseconds).asPeriod()) + assertEquals(periodOf(1.days), Period.between(then, now)) + assertEquals(Period.ZERO, Period.between(then, now - 1.nanoseconds)) + assertEquals(periodOf(1.days), (then until now).toPeriod()) + assertEquals(Period.ZERO, (then until now - 1.nanoseconds).toPeriod()) } @Test @@ -244,9 +244,9 @@ class ZonedDateTimeIntervalTest : AbstractIslandTimeTest() { val then = Date(2019, 11, 3) at Time(1, 0) at zone val now = Date(2019, 11, 4) at Time(1, 0) at zone - assertEquals(durationOf(25.hours), durationBetween(then, now)) - assertEquals(durationOf(25.hours), (then until now).asDuration()) - assertEquals(durationOf(25.hours.inSeconds, 1.nanoseconds), (then..now).asDuration()) + assertEquals(durationOf(25.hours), Duration.between(then, now)) + assertEquals(durationOf(25.hours), (then until now).toDuration()) + assertEquals(durationOf(25.hours.inSeconds, 1.nanoseconds), (then..now).toDuration()) } @Test diff --git a/core/src/jvmTest/kotlin/io/islandtime/jvm/DateComparisonTest.kt b/core/src/jvmTest/kotlin/io/islandtime/jvm/DateComparisonTest.kt index 6c331dc4b..4e8a4d8ea 100644 --- a/core/src/jvmTest/kotlin/io/islandtime/jvm/DateComparisonTest.kt +++ b/core/src/jvmTest/kotlin/io/islandtime/jvm/DateComparisonTest.kt @@ -1,14 +1,10 @@ package io.islandtime.jvm import com.google.common.truth.Truth.assertThat -import io.islandtime.Date -import io.islandtime.isInLeapYear -import io.islandtime.lengthOfMonth -import io.islandtime.lengthOfYear -import io.islandtime.ranges.periodBetween +import io.islandtime.* +import io.islandtime.measures.Period import org.junit.Test import java.time.LocalDate -import java.time.Period class DateComparisonTest { private val javaDates = listOf( @@ -50,8 +46,8 @@ class DateComparisonTest { val islandEndDate = javaEndDate.toIslandDate() javaDates.compareWithIsland { javaDate, islandDate -> - val javaPeriod = Period.between(javaDate, javaEndDate) - val islandPeriod = periodBetween(islandDate, islandEndDate) + val javaPeriod = java.time.Period.between(javaDate, javaEndDate) + val islandPeriod = Period.between(islandDate, islandEndDate) assertThat(javaPeriod.days).isEqualTo(islandPeriod.days.value) assertThat(javaPeriod.months).isEqualTo(islandPeriod.months.value) diff --git a/docs/basics/durations.md b/docs/basics/durations.md index fc6ab1b56..99bf8ac29 100644 --- a/docs/basics/durations.md +++ b/docs/basics/durations.md @@ -54,7 +54,7 @@ val duration: Duration = durationOf(5.seconds, 100.nanoseconds) The methods and operators available to `Duration` are mostly the same as those on the single unit durations. ```kotlin -val duration: Duration = durationBetween(firstDateTime, secondDateTime) +val duration: Duration = Duration.between(firstDateTime, secondDateTime) val minutes: Minutes = duration.inMinutes ``` @@ -78,7 +78,7 @@ val invertedPeriod = -period // (-5).years, (-13).months, (-10).days val (years, months, days) = period // We can also get the period between two dates -val periodBetweenDates = periodBetween(date1, date2) +val periodBetweenDates = Period.between(date1, date2) ``` ## ISO Representation diff --git a/docs/basics/intervals.md b/docs/basics/intervals.md index efe5aadd8..120979b38 100644 --- a/docs/basics/intervals.md +++ b/docs/basics/intervals.md @@ -40,7 +40,7 @@ val randomDate = (today..today + 1.months).random() val totalDays: Days = (today until today + 6.months).lengthInDays // Get the Period represented by the range -val period: Period = (today..today + 1.months).asPeriod() +val period: Period = (today..today + 1.months).toPeriod() ``` ## Time Intervals diff --git a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt index e441ecad5..86225c01c 100644 --- a/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt +++ b/tools/code-generator/src/main/kotlin/io/islandtime/codegen/generators/IntervalPropertiesGenerator.kt @@ -99,10 +99,3 @@ private fun FileBuilder.buildLengthProperty( } } } - -private val IntervalDescription.additionalOperatorKdocs: String - get() = when (this) { - OffsetDateTimeInterval -> "The offset of the start date-time will be used." - ZonedDateTimeInterval -> "The zone of the start date-time will be used." - else -> "" - }