commonMain.io.islandtime.measures.Period.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-metadata Show documentation
Show all versions of core-metadata Show documentation
A multiplatform library for working with dates and times
The newest version!
package io.islandtime.measures
import dev.erikchristensen.javamath2kmp.timesExact
import dev.erikchristensen.javamath2kmp.toIntExact
import io.islandtime.base.DateTimeField
import io.islandtime.internal.MONTHS_PER_YEAR
import io.islandtime.parser.*
/**
* A date-based period of time, such as "2 years, 5 months, 16 days". Unlike [Duration], which uses exact increments,
* a [Period] works with conceptual days, months, and years, ignoring daylight savings and length differences.
*
* @property years The number of years in this period.
* @property months The number of months in this period.
* @property days The number of days in this period.
*/
class Period private constructor(
val years: IntYears = 0.years,
val months: IntMonths = 0.months,
val days: IntDays = 0.days
) {
/**
* The total number of months in this period, including years.
*/
val totalMonths: LongMonths
get() = (years.toLongYears().inMonthsUnchecked.value + months.value).months
/**
* Checks if this period is zero.
*/
fun isZero(): Boolean = this == ZERO
/**
* Checks if any component of this period is negative.
*/
fun isNegative(): Boolean = years.value < 0 || months.value < 0 || days.value < 0
/**
* Reverses the sign of each component in the period.
* @throws ArithmeticException if overflow occurs
*/
operator fun unaryMinus() = create(-years, -months, -days)
/**
* Adds each component of another period to each component of this period.
* @throws ArithmeticException if overflow occurs
*/
operator fun plus(other: Period) = create(
years + other.years,
months + other.months,
days + other.days
)
/**
* Subtracts each component of another period from this period.
* @throws ArithmeticException if overflow occurs
*/
operator fun minus(other: Period) = create(
years - other.years,
months - other.months,
days - other.days
)
operator fun plus(years: IntYears) = copy(years = this.years + years)
operator fun plus(months: IntMonths) = copy(months = this.months + months)
operator fun plus(weeks: IntWeeks) = plus(weeks.toLongWeeks().inDaysUnchecked)
operator fun plus(days: IntDays) = copy(days = this.days + days)
operator fun plus(years: LongYears) = copy(years = (this.years.toLong() + years.value).toIntExact().years)
operator fun plus(months: LongMonths) = copy(months = (this.months.toLong() + months.value).toIntExact().months)
operator fun plus(weeks: LongWeeks) = plus(weeks.inDays)
operator fun plus(days: LongDays) = copy(days = (this.days.toLong() + days.value).toIntExact().days)
operator fun minus(years: IntYears) = copy(years = this.years - years)
operator fun minus(months: IntMonths) = copy(months = this.months - months)
operator fun minus(weeks: IntWeeks) = minus(weeks.toLongWeeks().inDaysUnchecked)
operator fun minus(days: IntDays) = copy(days = this.days - days)
operator fun minus(years: LongYears) = copy(years = (this.years.toLong() - years.value).toIntExact().years)
operator fun minus(months: LongMonths) = copy(months = (this.months.toLong() - months.value).toIntExact().months)
operator fun minus(weeks: LongWeeks) = minus(weeks.inDays)
operator fun minus(days: LongDays) = copy(days = (this.days.toLong() - days.value).toIntExact().days)
/**
* Multiplies each component of this period by a scalar value.
* @throws ArithmeticException if overflow occurs
*/
operator fun times(scalar: Int): Period {
return if (this.isZero() || scalar == 1) {
this
} else {
create(years * scalar, months * scalar, days * scalar)
}
}
operator fun component1() = years
operator fun component2() = months
operator fun component3() = days
/**
* Normalizes the number of years and months such that "1 year, 15 months" becomes "2 years, 3 months". Only the
* months and years components are combined. Days are never adjusted.
*/
fun normalized(): Period {
val monthTotal = totalMonths
val newYears = monthTotal.inYears.toIntYears()
val newMonths = (monthTotal % MONTHS_PER_YEAR).toIntMonthsUnchecked()
return if (newYears == years && newMonths == months) {
this
} else {
create(newYears, newMonths, days)
}
}
override fun equals(other: Any?): Boolean {
return this === other ||
(other is Period &&
other.years == years &&
other.months == months &&
other.days == days)
}
override fun hashCode(): Int {
var result = years.hashCode()
result = 31 * result + months.hashCode()
result = 31 * result + days.hashCode()
return result
}
/**
* Returns an ISO-8601 duration representation, such as "P1Y10M3D".
*/
override fun toString(): String {
return if (isZero()) {
"P0D"
} else {
buildString {
append('P')
if (years.value != 0) {
append(years.value)
append('Y')
}
if (months.value != 0) {
append(months.value)
append('M')
}
if (days.value != 0) {
append(days.value)
append('D')
}
}
}
}
/**
* Returns a new Period, replacing the years, months, and days components with new values, as desired
* @param years new years value
* @param months new months value
* @param days new days value
* @return a new Period with the supplied values
*/
fun copy(
years: IntYears = this.years,
months: IntMonths = this.months,
days: IntDays = this.days
) = create(years, months, days)
companion object {
/**
* A [Period] of zero length.
*/
val ZERO = Period()
internal fun create(
years: IntYears = 0.years,
months: IntMonths = 0.months,
days: IntDays = 0.days
): Period {
return if (years.value or months.value or days.value == 0) {
ZERO
} else {
Period(years, months, days)
}
}
}
}
/**
* Creates a [Period].
*/
fun periodOf(years: IntYears, months: IntMonths = 0.months, days: IntDays = 0.days): Period {
return Period.create(years, months, days)
}
/**
* Creates a [Period].
*/
fun periodOf(years: IntYears, days: IntDays) = Period.create(years = years, days = days)
/**
* Creates a [Period].
*/
fun periodOf(months: IntMonths, days: IntDays = 0.days) = Period.create(months = months, days = days)
/**
* Creates a [Period].
* @throws ArithmeticException if overflow occurs
*/
fun periodOf(weeks: IntWeeks) = Period.create(days = weeks.inDays)
/**
* Creates a [Period].
*/
fun periodOf(days: IntDays) = Period.create(days = days)
/**
* Converts this duration into a [Period] with the same number of years.
*/
fun IntYears.asPeriod() = Period.create(years = this)
/**
* Converts this duration into a [Period] with the same number of months.
*/
fun IntMonths.asPeriod() = Period.create(months = this)
/**
* Converts this duration into a [Period] with the same number of weeks.
* @throws ArithmeticException if the resulting [Period] would overflow
*/
fun IntWeeks.asPeriod() = Period.create(days = this.inDays)
/**
* Converts this duration into a [Period] with the same number of days.
*/
fun IntDays.asPeriod() = Period.create(days = this)
/**
* Converts this duration into a [Period] with the same number of years.
* @throws ArithmeticException if the resulting [Period] would overflow
*/
fun LongYears.asPeriod() = this.toIntYears().asPeriod()
/**
* Converts this duration into a [Period] with the same number of months.
* @throws ArithmeticException if the resulting [Period] would overflow
*/
fun LongMonths.asPeriod() = this.toIntMonths().asPeriod()
/**
* Converts this duration into a [Period] with the same number of weeks.
* @throws ArithmeticException if the resulting [Period] would overflow
*/
fun LongWeeks.asPeriod() = this.inDays.asPeriod()
/**
* Converts this duration into a [Period] with the same number of days.
* @throws ArithmeticException if the resulting [Period] would overflow
*/
fun LongDays.asPeriod() = this.toIntDays().asPeriod()
operator fun IntYears.plus(period: Period) = period.copy(years = this + period.years)
operator fun IntMonths.plus(period: Period) = period.copy(months = this + period.months)
operator fun IntWeeks.plus(period: Period) = this.toLongWeeks().inDaysUnchecked + period
operator fun IntDays.plus(period: Period) = period.copy(days = this + period.days)
operator fun LongYears.plus(period: Period) = period.copy(years = (this + period.years).toIntYears())
operator fun LongMonths.plus(period: Period) = period.copy(months = (this + period.months).toIntMonths())
operator fun LongWeeks.plus(period: Period) = this.inDays + period
operator fun LongDays.plus(period: Period) = period.copy(days = (this + period.days).toIntDays())
operator fun IntYears.minus(period: Period) = Period.create(
this - period.years,
-period.months,
-period.days
)
operator fun IntMonths.minus(period: Period) = Period.create(
-period.years,
this - period.months,
-period.days
)
operator fun IntWeeks.minus(period: Period) = this.toLongWeeks().inDaysUnchecked - period
operator fun IntDays.minus(period: Period) = Period.create(
-period.years,
-period.months,
this - period.days
)
operator fun LongYears.minus(period: Period) = Period.create(
(this - period.years).toIntYears(),
-period.months,
-period.days
)
operator fun LongMonths.minus(period: Period) = Period.create(
-period.years,
(this - period.months).toIntMonths(),
-period.days
)
operator fun LongWeeks.minus(period: Period) = this.inDays - period
operator fun LongDays.minus(period: Period) = Period.create(
-period.years,
-period.months,
(this - period.days).toIntDays()
)
operator fun Int.times(period: Period) = period * this
fun String.toPeriod() = toPeriod(DateTimeParsers.Iso.PERIOD)
fun String.toPeriod(
parser: DateTimeParser,
settings: DateTimeParserSettings = DateTimeParserSettings.DEFAULT
): Period {
val result = parser.parse(this, settings)
return result.toPeriod() ?: throwParserFieldResolutionException(this)
}
internal fun DateTimeParseResult.toPeriod(): Period? {
val sign = fields[DateTimeField.PERIOD_SIGN]?.toInt() ?: 1
val yearsValue = fields[DateTimeField.PERIOD_OF_YEARS]
val monthsValue = fields[DateTimeField.PERIOD_OF_MONTHS]
val weeksValue = fields[DateTimeField.PERIOD_OF_WEEKS]
val daysValue = fields[DateTimeField.PERIOD_OF_DAYS]
// Make sure we got at least one supported field out of the parser
return if (yearsValue == null && monthsValue == null && weeksValue == null && daysValue == null) {
null
} else {
val years = yearsValue?.toIntExact()?.timesExact(sign)?.years ?: 0.years
val months = monthsValue?.toIntExact()?.timesExact(sign)?.months ?: 0.months
var days = daysValue?.toIntExact()?.timesExact(sign)?.days ?: 0.days
if (weeksValue != null) {
days += weeksValue.toIntExact().timesExact(sign).weeks.inDays
}
periodOf(years, months, days)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy