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

144 lines
6.1 KiB
Kotlin

package korlibs.time
import korlibs.time.internal.MILLIS_PER_DAY
import korlibs.time.internal.MILLIS_PER_HOUR
import korlibs.time.internal.MILLIS_PER_MINUTE
import korlibs.time.internal.MILLIS_PER_SECOND
import korlibs.time.internal.MILLIS_PER_WEEK
import korlibs.time.internal.Moduler
import korlibs.time.internal.Serializable
/**
* Immutable structure representing a set of a [monthSpan] and a [timeSpan].
* This structure loses information about which months are included, that makes it impossible to generate a real [TimeSpan] including months.
* You can use [DateTimeRange.duration] to get this information from two real [DateTime].
*/
data class DateTimeSpan(
/** The [MonthSpan] part */
val monthSpan: MonthSpan,
/** The [TimeSpan] part */
val timeSpan: TimeSpan
) : Comparable<DateTimeSpan>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
}
constructor(
years: Int = 0,
months: Int = 0,
weeks: Int = 0,
days: Int = 0,
hours: Int = 0,
minutes: Int = 0,
seconds: Int = 0,
milliseconds: Double = 0.0
) : this(
years.years + months.months,
weeks.weeks + days.days + hours.hours + minutes.minutes + seconds.seconds + milliseconds.milliseconds
)
operator fun unaryMinus() = DateTimeSpan(-monthSpan, -timeSpan)
operator fun unaryPlus() = DateTimeSpan(+monthSpan, +timeSpan)
operator fun plus(other: TimeSpan) = DateTimeSpan(monthSpan, timeSpan + other)
operator fun plus(other: MonthSpan) = DateTimeSpan(monthSpan + other, timeSpan)
operator fun plus(other: DateTimeSpan) = DateTimeSpan(monthSpan + other.monthSpan, timeSpan + other.timeSpan)
operator fun minus(other: TimeSpan) = this + -other
operator fun minus(other: MonthSpan) = this + -other
operator fun minus(other: DateTimeSpan) = this + -other
operator fun times(times: Double) = DateTimeSpan((monthSpan * times), (timeSpan * times))
operator fun times(times: Int) = this * times.toDouble()
operator fun times(times: Float) = this * times.toDouble()
operator fun div(times: Double) = times(1.0 / times)
operator fun div(times: Int) = this / times.toDouble()
operator fun div(times: Float) = this / times.toDouble()
/** From the date part, all months represented as a [totalYears] [Double] */
val totalYears: Double get() = monthSpan.totalYears
/** From the date part, all months including months and years */
val totalMonths: Int get() = monthSpan.totalMonths
/** From the time part, all the milliseconds including milliseconds, seconds, minutes, hours, days and weeks */
val totalMilliseconds: Double get() = timeSpan.milliseconds
/** The [years] part as an integer. */
val years: Int get() = monthSpan.years
/** The [months] part as an integer. */
val months: Int get() = monthSpan.months
/** The [weeks] part as an integer. */
val weeks: Int get() = computed.weeks
val daysNotIncludingWeeks: Int get() = days
/** The [daysIncludingWeeks] part as an integer including days and weeks. */
val daysIncludingWeeks: Int get() = computed.days + (computed.weeks * DayOfWeek.Count)
/** The [days] part as an integer. */
val days: Int get() = computed.days
/** The [hours] part as an integer. */
val hours: Int get() = computed.hours
/** The [minutes] part as an integer. */
val minutes: Int get() = computed.minutes
/** The [seconds] part as an integer. */
val seconds: Int get() = computed.seconds
/** The [milliseconds] part as a double. */
val milliseconds: Double get() = computed.milliseconds
/** The [secondsIncludingMilliseconds] part as a doble including seconds and milliseconds. */
val secondsIncludingMilliseconds: Double get() = computed.seconds + computed.milliseconds / MILLIS_PER_SECOND
/**
* Note that if milliseconds overflow months this could not be exactly true. But probably will work in most cases.
* This structure doesn't have information about which months are counted. So some months could have 28-31 days and thus can't be done.
* You can use [DateTimeRange.duration] to compare this with real precision using a range between two [DateTime].
*/
override fun compareTo(other: DateTimeSpan): Int {
if (this.totalMonths != other.totalMonths) return this.monthSpan.compareTo(other.monthSpan)
return this.timeSpan.compareTo(other.timeSpan)
}
/**
* Represents this [DateTimeSpan] as a string like `50Y 10M 3W 6DH 30m 15s`.
* Parts that are zero, won't be included. You can omit weeks and represent them
* as days by adjusting the [includeWeeks] parameter.
*/
fun toString(includeWeeks: Boolean): String = arrayListOf<String>().apply {
if (years != 0) add("${years}Y")
if (months != 0) add("${months}M")
if (includeWeeks && weeks != 0) add("${weeks}W")
if (days != 0 || (!includeWeeks && weeks != 0)) add("${if (includeWeeks) days else daysIncludingWeeks}D")
if (hours != 0) add("${hours}H")
if (minutes != 0) add("${minutes}m")
if (seconds != 0 || milliseconds != 0.0) add("${secondsIncludingMilliseconds}s")
if (monthSpan == 0.years && ((timeSpan == 0.seconds) || (timeSpan == (-0).seconds))) add("0s")
}.joinToString(" ")
override fun toString(): String = toString(includeWeeks = true)
private class ComputedTime(val weeks: Int, val days: Int, val hours: Int, val minutes: Int, val seconds: Int, val milliseconds: Double) {
companion object {
operator fun invoke(time: TimeSpan): ComputedTime = Moduler(time.milliseconds).run {
val weeks = int(MILLIS_PER_WEEK)
val days = int(MILLIS_PER_DAY)
val hours = int(MILLIS_PER_HOUR)
val minutes = int(MILLIS_PER_MINUTE)
val seconds = int(MILLIS_PER_SECOND)
val milliseconds = double(1)
return ComputedTime(weeks, days, hours, minutes, seconds, milliseconds)
}
}
}
private val computed by lazy { ComputedTime(timeSpan) }
}