MicroUtils/klock/src/commonMain/kotlin/korlibs/time/DateTimeRange.kt

155 lines
5.0 KiB
Kotlin

package korlibs.time
import korlibs.time.internal.Serializable
/**
* Represents a right-opened range between two dates.
*/
data class DateTimeRange(val from: DateTime, val to: DateTime) : Comparable<DateTime>, Serializable {
val valid get() = from <= to
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
operator fun invoke(base: Date, from: Time, to: Time): DateTimeRange = DateTimeRange(base + from, base + to)
}
val size: TimeSpan get() = to - from
val min get() = from
val max get() = to
/**
* Duration [TimeSpan] without having into account actual months/years.
*/
val duration: TimeSpan get() = to - from
/**
* [DateTimeSpan] distance between two dates, month and year aware.
*/
val span: DateTimeSpan by lazy {
val reverse = to < from
val rfrom = if (!reverse) from else to
val rto = if (!reverse) to else from
var years = 0
var months = 0
var pivot = rfrom
// Compute years
val diffYears = (rto.year - pivot.year)
pivot += diffYears.years
years += diffYears
if (pivot > rto) {
pivot -= 1.years
years--
}
// Compute months (at most an iteration of 12)
while (true) {
val t = pivot + 1.months
if (t <= rto) {
months++
pivot = t
} else {
break
}
}
val out = DateTimeSpan(years.years + months.months, rto - pivot)
if (reverse) -out else out
}
/**
* Checks if a date is contained in this range.
*/
operator fun contains(date: DateTime): Boolean {
val unix = date.unixMillisDouble
val from = from.unixMillisDouble
val to = to.unixMillisDouble
return if (unix < from) false else unix < to
}
operator fun contains(other: DateTimeRange): Boolean {
return other.min >= this.min && other.max <= this.max
}
private inline fun <T> _intersectionWith(that: DateTimeRange, rightOpen: Boolean, handler: (from: DateTime, to: DateTime, matches: Boolean) -> T): T {
val from = max(this.from, that.from)
val to = min(this.to, that.to)
return handler(from, to, if (rightOpen) from < to else from <= to)
}
/**
* Returns new [DateTimeRange] or null - the result of intersection of this and [that] DateTimeRanges.
*/
fun intersectionWith(that: DateTimeRange, rightOpen: Boolean = true): DateTimeRange? {
return _intersectionWith(that, rightOpen) { from, to, matches ->
when {
matches -> DateTimeRange(from, to)
else -> null
}
}
}
/**
* Returns true if this and [that] DateTimeRanges have intersection otherwise false.
*/
fun intersectsWith(that: DateTimeRange, rightOpen: Boolean = true): Boolean = _intersectionWith(that, rightOpen) { _, _, matches -> matches }
/**
* Returns true if this and [that] DateTimeRanges have intersection or at least a common end otherwise false.
*/
fun intersectsOrInContactWith(that: DateTimeRange): Boolean = intersectsWith(that, rightOpen = false)
/**
* Returns new [DateTimeRange] or null - the result of merging this and [that] DateTimeRanges if they have intersection.
*/
fun mergeOnContactOrNull(that: DateTimeRange): DateTimeRange? {
if (!intersectsOrInContactWith(that)) return null
val min = min(this.min, that.min)
val max = max(this.max, that.max)
return DateTimeRange(min, max)
}
/**
* Returns a [List] of 0, 1 or 2 [DateTimeRange]s - the result of removing [that] DateTimeRange from this one
*/
fun without(that: DateTimeRange): List<DateTimeRange> = when {
// Full remove
(that.min <= this.min) && (that.max >= this.max) -> listOf()
// To the right or left, nothing to remove
(that.min >= this.max) || (that.max <= this.min) -> listOf(this)
// In the middle
else -> {
val p0 = this.min
val p1 = that.min
val p2 = that.max
val p3 = this.max
val c1 = if (p0 < p1) DateTimeRange(p0, p1) else null
val c2 = if (p2 < p3) DateTimeRange(p2, p3) else null
listOfNotNull(c1, c2)
}
}
fun toString(format: DateFormat): String = "${min.toString(format)}..${max.toString(format)}"
fun toStringLongs(): String = "${min.unixMillisLong}..${max.unixMillisLong}"
fun toStringDefault(): String = toString(DateFormat.FORMAT1)
//override fun toString(): String = toString(DateFormat.FORMAT1)
override fun toString(): String = "$min..$max"
override fun compareTo(other: DateTime): Int {
if (this.max <= other) return -1
if (this.min > other) return +1
return 0
}
}
fun List<DateTimeRange>.toStringLongs() = this.map { it.toStringLongs() }.toString()
/**
* Generates a right-opened range between two [DateTime]s
*/
infix fun DateTime.until(other: DateTime) = DateTimeRange(this, other)