diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt index 3f535bc0f..ed0900f1a 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt @@ -148,7 +148,7 @@ fun ZonedDateTimeInterval.toInstantInterval(): InstantInterval { return (this as TimePointInterval<*>).toInstantInterval() } -private inline fun TimeInterval.toDateRange(toDateTime: T.() -> DateTime): DateRange { +private inline fun Interval.toDateRange(toDateTime: T.() -> DateTime): DateRange { return when { isEmpty() -> DateRange.EMPTY isUnbounded() -> DateRange.UNBOUNDED diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateIterators.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateIterators.kt index fafe40c42..fba014d5b 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateIterators.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateIterators.kt @@ -1,19 +1,14 @@ package io.islandtime.ranges import io.islandtime.Date -import io.islandtime.measures.* - -abstract class DateIterator : Iterator { - override fun next() = nextDate() - - abstract fun nextDate(): Date -} +import io.islandtime.measures.IntDays +import io.islandtime.measures.IntMonths internal class DateDayProgressionIterator( - first: LongDays, - last: LongDays, + first: Date, + last: Date, private val step: IntDays -) : DateIterator() { +) : Iterator { private val finalElement = last private var hasNext = if (step.value > 0) first <= last else first >= last @@ -21,7 +16,7 @@ internal class DateDayProgressionIterator( override fun hasNext() = hasNext - override fun nextDate(): Date { + override fun next(): Date { val value = next if (value == finalElement) { @@ -34,7 +29,7 @@ internal class DateDayProgressionIterator( next += step } - return Date.fromDaysSinceUnixEpoch(value) + return value } } @@ -42,7 +37,7 @@ internal class DateMonthProgressionIterator( first: Date, last: Date, private val step: IntMonths -) : DateIterator() { +) : Iterator { private val finalElement = last private var hasNext = if (step.value > 0) first <= last else first >= last @@ -50,7 +45,7 @@ internal class DateMonthProgressionIterator( override fun hasNext() = hasNext - override fun nextDate(): Date { + override fun next(): Date { val value = next if (value == finalElement) { diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateProgressions.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateProgressions.kt index 24cafadbc..f4eea56e5 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateProgressions.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateProgressions.kt @@ -5,11 +5,30 @@ import io.islandtime.internal.MONTHS_PER_YEAR import io.islandtime.measures.* import kotlin.math.abs -open class DateDayProgression protected constructor( - first: Date, +abstract class DateDayProgression internal constructor(): Iterable { + abstract val first: Date + abstract val last: Date + abstract val step: IntDays + + /** + * Checks if this progression is empty. + */ + abstract fun isEmpty(): Boolean + + override fun iterator(): Iterator = DateDayProgressionIterator(first, last, step) + + companion object { + fun fromClosedRange(rangeStart: Date, rangeEnd: Date, step: IntDays): DateDayProgression { + return DefaultDateDayProgression(rangeStart, rangeEnd, step) + } + } +} + +private class DefaultDateDayProgression( + start: Date, endInclusive: Date, - val step: IntDays -) : Iterable { + override val step: IntDays +) : DateDayProgression() { init { require(step.value != 0) { "Step must be non-zero" } @@ -18,45 +37,29 @@ open class DateDayProgression protected constructor( } } - protected val firstUnixEpochDay: LongDays = first.daysSinceUnixEpoch - protected val lastUnixEpochDay: LongDays = getLastDayInProgression(firstUnixEpochDay, endInclusive, step) - val first: Date get() = Date.fromDaysSinceUnixEpoch(firstUnixEpochDay) - val last: Date get() = Date.fromDaysSinceUnixEpoch(lastUnixEpochDay) - - /** - * Checks if this progression is empty. - */ - open fun isEmpty(): Boolean = if (step.value > 0) first > last else first < last + override val first: Date = start + override val last: Date = getLastDateInProgression(start, endInclusive, step) - override fun iterator(): DateIterator = DateDayProgressionIterator(firstUnixEpochDay, lastUnixEpochDay, step) + override fun isEmpty(): Boolean = if (step.value > 0) first > last else first < last override fun toString() = if (step.value > 0) "$first..$last step $step" else "$first downTo $last step ${-step}" override fun equals(other: Any?): Boolean { return other is DateDayProgression && - (isEmpty() && other.isEmpty() || - firstUnixEpochDay == other.firstUnixEpochDay && - lastUnixEpochDay == other.lastUnixEpochDay && - step == other.step) + ((isEmpty() && other.isEmpty()) || (first == other.first && last == other.last && step == other.step)) } override fun hashCode(): Int { return if (isEmpty()) { -1 } else { - 31 * (31 * firstUnixEpochDay.hashCode() + lastUnixEpochDay.hashCode()) + step.value - } - } - - companion object { - fun fromClosedRange(rangeStart: Date, rangeEnd: Date, step: IntDays): DateDayProgression { - return DateDayProgression(rangeStart, rangeEnd, step) + 31 * (31 * first.hashCode() + last.hashCode()) + step.value } } } class DateMonthProgression private constructor( - val first: Date, + start: Date, endInclusive: Date, val step: IntMonths ) : Iterable { @@ -65,32 +68,29 @@ class DateMonthProgression private constructor( require(step.value != 0) { "Step must be non-zero" } } - val last = getLastDateInProgression(first, endInclusive, step) + val first = start + val last = getLastDateInProgression(start, endInclusive, step) /** * Checks if this progression is empty. */ fun isEmpty(): Boolean = if (step.value > 0) first > last else first < last - override fun iterator(): DateIterator = DateMonthProgressionIterator(first, last, step) + override fun iterator(): Iterator = DateMonthProgressionIterator(first, last, step) override fun toString() = if (step.value > 0) "$first..$last step $step" else "$first downTo $last step ${-step}" override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as DateMonthProgression - - if (first != other.first) return false - if (step != other.step) return false - if (last != other.last) return false - - return true + return other is DateMonthProgression && + ((isEmpty() && other.isEmpty()) || (first == other.first && last == other.last && step == other.step)) } override fun hashCode(): Int { - return if (isEmpty()) -1 else 31 * (31 * first.hashCode() + last.hashCode()) + step.value + return if (isEmpty()) { + -1 + } else { + 31 * (31 * first.hashCode() + last.hashCode()) + step.value + } } companion object { @@ -154,27 +154,25 @@ fun DateMonthProgression.reversed() = DateMonthProgression.fromClosedRange(last, /** * Assumes step is non-zero */ -private fun getLastDayInProgression(startInDays: LongDays, endDate: Date, step: IntDays): LongDays { - val endInDays = endDate.daysSinceUnixEpoch - +private fun getLastDateInProgression(start: Date, end: Date, step: IntDays): Date { return when { - step.value > 0L -> if (startInDays >= endInDays) { - endInDays + step.value > 0L -> if (start >= end) { + end } else { - endInDays - (abs(endInDays.value - startInDays.value) % step.value).days + val endEpochDay = end.dayOfUnixEpoch + Date.fromDayOfUnixEpoch(endEpochDay - (abs(endEpochDay - start.dayOfUnixEpoch) % step.value)) } - else -> if (startInDays <= endInDays) { - endInDays + else -> if (start <= end) { + end } else { - endInDays - (abs(startInDays.value - endInDays.value) % step.value).days + val endEpochDay = end.dayOfUnixEpoch + Date.fromDayOfUnixEpoch(endEpochDay - (abs(start.dayOfUnixEpoch - end.dayOfUnixEpoch) % step.value)) } } } private fun getLastDateInProgression(start: Date, end: Date, step: IntMonths): Date { - return if ((step.value > 0 && start >= end) || - (step.value < 0 && start <= end) - ) { + return if ((step.value > 0 && start >= end) || (step.value < 0 && start <= end)) { end } else { val monthsBetween = progressionMonthsBetween(start, end) @@ -184,11 +182,11 @@ private fun getLastDateInProgression(start: Date, end: Date, step: IntMonths): D } /** - * Gets the number of months between two dates for the purposes of a progression. This works a little differently than + * Gets the number of months between two dates for the purposes of a progression. This works a little differently than * the usual [monthsBetween] since it tries to use the same day as the start date while stepping months, coercing that * day as needed to fit the number of days in the current month. */ -internal fun progressionMonthsBetween(start: Date, endInclusive: Date): IntMonths { +private fun progressionMonthsBetween(start: Date, endInclusive: Date): IntMonths { val yearsBetween = endInclusive.year - start.year val monthsBetween = yearsBetween * MONTHS_PER_YEAR + (endInclusive.month.ordinal - start.month.ordinal) diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt index 88f858972..45743c776 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt @@ -5,6 +5,7 @@ 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 import kotlin.random.Random import kotlin.random.nextLong @@ -13,62 +14,45 @@ import kotlin.random.nextLong * An inclusive range of dates. * * [Date.MIN] and [Date.MAX] are used as sentinels to indicate an unbounded (ie. infinite) start or end. - * - * @property start The start of this interval, inclusive. - * @property endInclusive The end of this interval, inclusive. */ class DateRange( - start: Date = Date.MIN, - endInclusive: Date = Date.MAX -) : DateDayProgression(start, endInclusive, 1.days), + override val start: Date = Date.MIN, + override val endInclusive: Date = Date.MAX +) : DateDayProgression(), + Interval, ClosedRange { - fun hasUnboundedStart(): Boolean = start == Date.MIN - fun hasUnboundedEnd(): Boolean = endInclusive == Date.MAX + override val endExclusive: Date get() = endInclusive + 1.days - fun hasBoundedStart(): Boolean = !hasUnboundedStart() - fun hasBoundedEnd(): Boolean = !hasUnboundedEnd() - fun isBounded(): Boolean = hasBoundedStart() && hasBoundedEnd() - fun isUnbounded(): Boolean = hasUnboundedStart() && hasUnboundedEnd() + override fun hasUnboundedStart(): Boolean = start == Date.MIN + override fun hasUnboundedEnd(): Boolean = endInclusive == Date.MAX + override fun contains(value: Date): Boolean = super.contains(value) - override val start: Date get() = first - override val endInclusive: Date get() = last + override val first: Date get() = start + override val last: Date get() = endInclusive + override val step: IntDays get() = 1.days override fun isEmpty(): Boolean { - return firstUnixEpochDay > lastUnixEpochDay || endInclusive == Date.MIN || start == Date.MAX + return start > endInclusive || endInclusive == Date.MIN || start == Date.MAX } /** * Converts this range to a string in ISO-8601 extended format. */ - override fun toString(): String { - return if (isEmpty()) { - "" - } else { - buildString(2 * MAX_DATE_STRING_LENGTH + 1) { - if (hasBoundedStart()) { - appendDate(start) - } else { - append("..") - } - - append('/') - - if (hasBoundedEnd()) { - appendDate(endInclusive) - } else { - append("..") - } - } - } - } + override fun toString(): String = buildIsoString( + maxElementSize = MAX_DATE_STRING_LENGTH, + inclusive = true, + appendFunction = StringBuilder::appendDate + ) override fun equals(other: Any?): Boolean { - return other is DateRange && (isEmpty() && other.isEmpty() || first == other.first && last == other.last) + return other is DateRange && + ((isEmpty() && other.isEmpty()) || + (start == other.start && endInclusive == other.endInclusive)) } override fun hashCode(): Int { - return if (isEmpty()) -1 else (31 * first.hashCode() + last.hashCode()) + return if (isEmpty()) -1 else (31 * start.hashCode() + endInclusive.hashCode()) } /** diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt index 553e5bb2c..ccf3bf16c 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/DateTimeInterval.kt @@ -16,7 +16,10 @@ import kotlin.random.Random class DateTimeInterval( override val start: DateTime = UNBOUNDED.start, override val endExclusive: DateTime = UNBOUNDED.endExclusive -) : TimeInterval { +) : Interval { + + override val endInclusive: DateTime + get() = if (hasUnboundedEnd()) endExclusive else endExclusive - 1.nanoseconds override fun hasUnboundedStart(): Boolean = start == DateTime.MIN override fun hasUnboundedEnd(): Boolean = endExclusive == DateTime.MAX @@ -45,7 +48,11 @@ class DateTimeInterval( /** * Converts this interval to a string in ISO-8601 extended format. */ - override fun toString() = buildIsoString(MAX_DATE_TIME_STRING_LENGTH, StringBuilder::appendDateTime) + override fun toString() = buildIsoString( + maxElementSize = MAX_DATE_TIME_STRING_LENGTH, + inclusive = false, + appendFunction = StringBuilder::appendDateTime + ) /** * Converts this interval to the [Duration] between the start and end date-time, assuming they're in the same time diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/InstantInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/InstantInterval.kt index 5898050a9..fa9740beb 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/InstantInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/InstantInterval.kt @@ -29,7 +29,11 @@ class InstantInterval( /** * Converts this interval to a string in ISO-8601 extended format. */ - override fun toString() = buildIsoString(MAX_INSTANT_STRING_LENGTH, StringBuilder::appendInstant) + override fun toString() = buildIsoString( + maxElementSize = MAX_INSTANT_STRING_LENGTH, + inclusive = false, + appendFunction = StringBuilder::appendInstant + ) companion object { /** diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/Interval.kt similarity index 71% rename from core/src/commonMain/kotlin/io/islandtime/ranges/TimeInterval.kt rename to core/src/commonMain/kotlin/io/islandtime/ranges/Interval.kt index db15acb99..795939980 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/Interval.kt @@ -1,14 +1,19 @@ package io.islandtime.ranges /** - * A half-open time interval. + * A half-open or closed interval. */ -interface TimeInterval { +interface Interval { /** * The start of this interval, inclusive. */ val start: T + /** + * The end of this interval, inclusive. + */ + val endInclusive: T + /** * The end of this interval, exclusive. */ @@ -40,13 +45,13 @@ interface TimeInterval { fun isBounded(): Boolean = hasBoundedStart() && hasBoundedEnd() /** - * Checks if both the start and end of the interval are unbounded, meaning this is an infinite time period in both + * Checks if both the start and end of this interval are unbounded, meaning this is an infinite time period in both * directions. */ fun isUnbounded(): Boolean = hasUnboundedStart() && hasUnboundedEnd() /** - * Checks if [value] is within this interval based on timeline order. + * Checks if this interval contains [value]. */ operator fun contains(value: T): Boolean @@ -55,3 +60,10 @@ interface TimeInterval { */ fun isEmpty(): Boolean } + +/** + * Checks if this interval contains [value]. + * + * This will always return `false` if [value] is `null`. + */ +fun Interval.contains(value: T?): Boolean = value != null && contains(value) diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt index e44c2c14f..6c1875f53 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/OffsetDateTimeInterval.kt @@ -24,8 +24,11 @@ class OffsetDateTimeInterval( /** * Converts this interval to a string in ISO-8601 extended format. */ - override fun toString() = - buildIsoString(MAX_OFFSET_DATE_TIME_STRING_LENGTH, StringBuilder::appendOffsetDateTime) + override fun toString() = buildIsoString( + maxElementSize = MAX_OFFSET_DATE_TIME_STRING_LENGTH, + inclusive = false, + appendFunction = StringBuilder::appendOffsetDateTime + ) /** * Converts this interval into a [Period] of the same length. diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt index 750e1a7c6..4b252f9f6 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointInterval.kt @@ -4,45 +4,33 @@ import io.islandtime.base.TimePoint import io.islandtime.measures.* import io.islandtime.measures.internal.minusWithOverflow import io.islandtime.ranges.internal.* -import kotlin.jvm.JvmName /** * A half-open interval of time points. */ abstract class TimePointInterval> internal constructor( - private val _start: T, - private val _endExclusive: T -) : TimeInterval { + override val start: T, + override val endExclusive: T +) : Interval { - override val start: T get() = _start - - /** - * The last representable time point within the interval. - */ - val endInclusive: T get() = if (hasUnboundedEnd()) _endExclusive else _endExclusive - 1.nanoseconds - - override val endExclusive: T get() = _endExclusive + override val endInclusive: T + get() = if (hasUnboundedEnd()) endExclusive else endExclusive - 1.nanoseconds override fun equals(other: Any?): Boolean { return other is TimePointInterval<*> && (isEmpty() && other.isEmpty() || - ((hasUnboundedStart() && other.hasUnboundedStart()) || _start == other._start) && - ((hasUnboundedEnd() && other.hasUnboundedEnd()) || _endExclusive == other._endExclusive)) + ((hasUnboundedStart() && other.hasUnboundedStart()) || start == other.start) && + ((hasUnboundedEnd() && other.hasUnboundedEnd()) || endExclusive == other.endExclusive)) } override fun hashCode(): Int { - return if (isEmpty()) -1 else (31 * _start.hashCode() + _endExclusive.hashCode()) + return if (isEmpty()) -1 else (31 * start.hashCode() + endExclusive.hashCode()) } override fun contains(value: T): Boolean { - return (value >= _start || hasUnboundedStart()) && (value < _endExclusive || hasUnboundedEnd()) - } - - @JvmName("containsOther") - operator fun contains(value: TimePoint<*>): Boolean { - return (value >= _start || hasUnboundedStart()) && (value < _endExclusive || hasUnboundedEnd()) + return (value >= start || hasUnboundedStart()) && (value < endExclusive || hasUnboundedEnd()) } - override fun isEmpty(): Boolean = _start >= _endExclusive + override fun isEmpty(): Boolean = start >= endExclusive /** * Converts this interval into a [Duration] of the same length. @@ -51,7 +39,7 @@ abstract class TimePointInterval> internal constructor( fun asDuration(): Duration { return when { isEmpty() -> Duration.ZERO - isBounded() -> durationBetween(_start, _endExclusive) + isBounded() -> durationBetween(start, endExclusive) else -> throwUnboundedIntervalException() } } @@ -63,7 +51,7 @@ abstract class TimePointInterval> internal constructor( open val lengthInDays: LongDays get() = when { isEmpty() -> 0L.days - isBounded() -> daysBetween(_start, _endExclusive) + isBounded() -> daysBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -74,7 +62,7 @@ abstract class TimePointInterval> internal constructor( val lengthInHours: LongHours get() = when { isEmpty() -> 0L.hours - isBounded() -> hoursBetween(_start, _endExclusive) + isBounded() -> hoursBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -85,7 +73,7 @@ abstract class TimePointInterval> internal constructor( val lengthInMinutes: LongMinutes get() = when { isEmpty() -> 0L.minutes - isBounded() -> minutesBetween(_start, _endExclusive) + isBounded() -> minutesBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -96,7 +84,7 @@ abstract class TimePointInterval> internal constructor( val lengthInSeconds: LongSeconds get() = when { isEmpty() -> 0L.seconds - isBounded() -> secondsBetween(_start, _endExclusive) + isBounded() -> secondsBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -107,7 +95,7 @@ abstract class TimePointInterval> internal constructor( val lengthInMilliseconds: LongMilliseconds get() = when { isEmpty() -> 0L.milliseconds - isBounded() -> millisecondsBetween(_start, _endExclusive) + isBounded() -> millisecondsBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -118,7 +106,7 @@ abstract class TimePointInterval> internal constructor( val lengthInMicroseconds: LongMicroseconds get() = when { isEmpty() -> 0L.microseconds - isBounded() -> microsecondsBetween(_start, _endExclusive) + isBounded() -> microsecondsBetween(start, endExclusive) else -> throwUnboundedIntervalException() } @@ -129,11 +117,22 @@ abstract class TimePointInterval> internal constructor( val lengthInNanoseconds: LongNanoseconds get() = when { isEmpty() -> 0L.nanoseconds - isBounded() -> nanosecondsBetween(_start, _endExclusive) + isBounded() -> nanosecondsBetween(start, endExclusive) else -> throwUnboundedIntervalException() } } +/** + * Check if this interval contains [value]. + * + * This will always return `false` if [value] is `null`. + */ +operator fun > TimePointInterval.contains(value: TimePoint<*>?): Boolean { + return value != null && + (value >= start || hasUnboundedStart()) && + (value < endExclusive || hasUnboundedEnd()) +} + /** * Gets the [Duration] between two time points. */ diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointIterators.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointIterators.kt index 7e00d8ed3..9c50fcc59 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointIterators.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointIterators.kt @@ -3,25 +3,19 @@ package io.islandtime.ranges import io.islandtime.base.TimePoint import io.islandtime.measures.* -abstract class TimePointIterator> : Iterator { - override fun next() = nextTimePoint() - - abstract fun nextTimePoint(): T -} - internal class TimePointSecondProgressionIterator>( first: T, last: T, private val step: LongSeconds -) : TimePointIterator() { +) : Iterator { private val finalElement = last - private var hasNext = if (step.isPositive()) last > first else last < first + private var hasNext = if (step.value > 0) last > first else last < first private var next = if (hasNext) first else finalElement override fun hasNext() = hasNext - override fun nextTimePoint(): T { + override fun next(): T { val value = next if (value.isSameInstantAs(finalElement)) { @@ -42,15 +36,15 @@ internal class TimePointNanosecondProgressionIterator>( first: T, last: T, private val step: LongNanoseconds -) : TimePointIterator() { +) : Iterator { private val finalElement = last - private var hasNext = if (step.isPositive()) last > first else last < first + private var hasNext = if (step.value > 0) last > first else last < first private var next = if (hasNext) first else finalElement override fun hasNext() = hasNext - override fun nextTimePoint(): T { + override fun next(): T { val value = next if (value.isSameInstantAs(finalElement)) { diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt index 68322d6af..a3aeb8077 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/TimePointProgressions.kt @@ -23,7 +23,7 @@ private class DefaultTimePointProgressionBuilder>( ) : TimePointProgressionBuilder class TimePointSecondProgression> private constructor( - override val first: T, + start: T, endInclusive: T, val step: LongSeconds ) : TimePointProgressionBuilder, @@ -33,14 +33,15 @@ class TimePointSecondProgression> private constructor( require(!step.isZero()) { "Step must be non-zero" } } - override val last: T = getLastTimePointInProgression(first, endInclusive, step) + override val first: T = start + override val last: T = getLastTimePointInProgression(start, endInclusive, step) - fun isEmpty(): Boolean = if (step.isPositive()) first > last else first < last + fun isEmpty(): Boolean = if (step.value > 0) first > last else first < last - override fun iterator(): TimePointIterator = TimePointSecondProgressionIterator(first, last, step) + override fun iterator(): Iterator = TimePointSecondProgressionIterator(first, last, step) override fun toString(): String { - return if (step.isPositive()) { + return if (step.value > 0) { "$first..$last step $step" } else { "$first downTo $last step ${-step}" @@ -80,7 +81,7 @@ class TimePointSecondProgression> private constructor( } class TimePointNanosecondProgression> private constructor( - override val first: T, + start: T, endInclusive: T, val step: LongNanoseconds ) : TimePointProgressionBuilder, @@ -90,14 +91,15 @@ class TimePointNanosecondProgression> private constructor( require(!step.isZero()) { "Step must be non-zero" } } - override val last: T = getLastTimePointInProgression(first, endInclusive, step) + override val first: T = start + override val last: T = getLastTimePointInProgression(start, endInclusive, step) - fun isEmpty(): Boolean = if (step.isPositive()) first > last else first < last + fun isEmpty(): Boolean = if (step.value > 0) first > last else first < last - override fun iterator(): TimePointIterator = TimePointNanosecondProgressionIterator(first, last, step) + override fun iterator(): Iterator = TimePointNanosecondProgressionIterator(first, last, step) override fun toString(): String { - return if (step.isPositive()) { + return if (step.value > 0) { "$first..$last step $step" } else { "$first downTo $last step ${-step}" @@ -226,7 +228,7 @@ infix fun > TimePointProgressionBuilder.step( * Assumes step is non-zero */ private fun > getLastTimePointInProgression(start: T, end: T, step: LongSeconds): T { - return if ((step.isPositive() && start >= end) || (step.isNegative() && start <= end)) { + return if ((step.value > 0 && start >= end) || (step.value < 0 && start <= end)) { end } else { val secondsBetween = secondsBetween(start, end) @@ -239,7 +241,7 @@ private fun > getLastTimePointInProgression(start: T, end: T, s * Assumes step is non-zero */ private fun > getLastTimePointInProgression(start: T, end: T, step: LongNanoseconds): T { - return if ((step.isPositive() && start >= end) || (step.isNegative() && start <= end)) { + return if ((step.value > 0 && start >= end) || (step.value < 0 && start <= end)) { end } else { val nanosecondsBetween = nanosecondsBetween(start, end) diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt index 74e315a22..56f032d4b 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/ZonedDateTimeInterval.kt @@ -25,7 +25,11 @@ class ZonedDateTimeInterval( /** * Converts this interval to a string in ISO-8601 extended format. */ - override fun toString() = buildIsoString(MAX_ZONED_DATE_TIME_STRING_LENGTH, StringBuilder::appendZonedDateTime) + override fun toString() = buildIsoString( + maxElementSize = MAX_ZONED_DATE_TIME_STRING_LENGTH, + inclusive = false, + appendFunction = StringBuilder::appendZonedDateTime + ) /** * Converts this interval into a [Period] of the same length. diff --git a/core/src/commonMain/kotlin/io/islandtime/ranges/internal/Common.kt b/core/src/commonMain/kotlin/io/islandtime/ranges/internal/Common.kt index 14bbb9151..5324494ea 100644 --- a/core/src/commonMain/kotlin/io/islandtime/ranges/internal/Common.kt +++ b/core/src/commonMain/kotlin/io/islandtime/ranges/internal/Common.kt @@ -5,7 +5,7 @@ import io.islandtime.base.TimePoint import io.islandtime.internal.NANOSECONDS_PER_SECOND import io.islandtime.measures.* import io.islandtime.measures.internal.minusWithOverflow -import io.islandtime.ranges.TimeInterval +import io.islandtime.ranges.Interval import io.islandtime.ranges.TimePointInterval import kotlin.random.Random @@ -57,14 +57,15 @@ internal fun nanosecondsBetween( (endExclusiveNanoseconds - startNanoseconds) } -internal inline fun TimeInterval.buildIsoString( - baseCapacity: Int, +internal inline fun Interval.buildIsoString( + maxElementSize: Int, + inclusive: Boolean, appendFunction: StringBuilder.(T) -> StringBuilder ): String { return if (isEmpty()) { "" } else { - buildString(2 * baseCapacity + 1) { + buildString(2 * maxElementSize + 1) { if (hasBoundedStart()) { appendFunction(start) } else { @@ -74,7 +75,7 @@ internal inline fun TimeInterval.buildIsoString( append('/') if (hasBoundedEnd()) { - appendFunction(endExclusive) + appendFunction(if (inclusive) endInclusive else endExclusive) } else { append("..") } @@ -88,7 +89,7 @@ internal fun throwUnboundedIntervalException(): Nothing { ) } -internal inline fun TimeInterval.random( +internal inline fun Interval.random( random: Random, secondGetter: (T) -> Long, nanosecondGetter: (T) -> Int, @@ -101,7 +102,7 @@ internal inline fun TimeInterval.random( } } -internal inline fun TimeInterval.randomOrNull( +internal inline fun Interval.randomOrNull( random: Random, secondGetter: (T) -> Long, nanosecondGetter: (T) -> Int, @@ -128,7 +129,7 @@ internal inline fun > TimePointInterval.randomOrNull( return randomOrNull(random, { it.secondOfUnixEpoch }, { it.nanosecond }, creator) } -private inline fun TimeInterval.generateRandom( +private inline fun Interval.generateRandom( random: Random, secondGetter: (T) -> Long, nanosecondGetter: (T) -> Int, diff --git a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt index 256a0b2f6..0c0af8aba 100644 --- a/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt +++ b/core/src/commonTest/kotlin/io/islandtime/ranges/DateTimeIntervalTest.kt @@ -34,7 +34,7 @@ class DateTimeIntervalTest : AbstractIslandTimeTest() { @Test fun `equality works correctly`() { - assertNotEquals>(DateTimeInterval.UNBOUNDED, InstantInterval.UNBOUNDED) + assertNotEquals>(DateTimeInterval.UNBOUNDED, InstantInterval.UNBOUNDED) assertEquals(DateTimeInterval.EMPTY, DateTimeInterval.EMPTY) assertNotEquals(DateTimeInterval.UNBOUNDED, DateTimeInterval.EMPTY) assertNotEquals(DateTimeInterval.EMPTY, DateTimeInterval.UNBOUNDED)