Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace unit between functions with companion object methods #186

Merged
merged 2 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
469 changes: 469 additions & 0 deletions core/src/commonMain/generated/io/islandtime/ranges/_Properties.kt

Large diffs are not rendered by default.

529 changes: 529 additions & 0 deletions core/src/commonMain/kotlin/io/islandtime/operators/Between.kt

Large diffs are not rendered by default.

90 changes: 78 additions & 12 deletions core/src/commonMain/kotlin/io/islandtime/ranges/Conversions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -148,21 +151,84 @@ fun ZonedDateTimeInterval.toInstantInterval(): InstantInterval {
return (this as TimePointInterval<*>).toInstantInterval()
}

private inline fun <T> Interval<T>.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 <T> Interval<T>.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
}
}

Expand Down
178 changes: 62 additions & 116 deletions core/src/commonMain/kotlin/io/islandtime/ranges/DateRange.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -53,75 +51,23 @@ 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()
}
}

/**
* 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()
}

@Deprecated(
message = "Replace with toPeriod()",
replaceWith = ReplaceWith("this.toPeriod()", "io.islandtime.ranges.toPeriod"),
level = DeprecationLevel.WARNING
)
fun asPeriod(): Period = toPeriod()

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)
}
}

Expand Down Expand Up @@ -181,57 +127,57 @@ 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
daysBetween(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)
}

/**
* 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 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()",
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)
Loading