Skip to content

Remove extra hierarchy level in DateTimeUnit sealed class inheritors #131

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

Merged
merged 1 commit into from
Sep 21, 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
77 changes: 42 additions & 35 deletions core/common/src/DateTimeUnit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,44 +66,51 @@ public sealed class DateTimeUnit {

@Serializable(with = DateBasedDateTimeUnitSerializer::class)
public sealed class DateBased : DateTimeUnit() {
// TODO: investigate how to move subclasses up to DateTimeUnit scope
@Serializable(with = DayBasedDateTimeUnitSerializer::class)
public class DayBased(public val days: Int) : DateBased() {
init {
require(days > 0) { "Unit duration must be positive, but was $days days." }
}
@Suppress("TOPLEVEL_TYPEALIASES_ONLY")
@Deprecated("Use DateTimeUnit.DayBased", ReplaceWith("DateTimeUnit.DayBased", "kotlinx.datetime.DateTimeUnit"))
public typealias DayBased = DateTimeUnit.DayBased
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to remember that these type aliases didn't work in some regard, right? I tried to use them and everything worked fine. Does this mean that you found a solution to that issue? If not, Deprecated should probably be with the DeprecationLevel.ERROR.

Copy link
Member Author

@ilya-g ilya-g Sep 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I recall, they caused problems when used from inside of the DateTimeUnit class the inheritors of DateBased class. That's why I had to prefix the affected types with DateTimeUnit. — to avoid resolving to type aliases there.

@Suppress("TOPLEVEL_TYPEALIASES_ONLY")
@Deprecated("Use DateTimeUnit.MonthBased", ReplaceWith("DateTimeUnit.MonthBased", "kotlinx.datetime.DateTimeUnit"))
public typealias MonthBased = DateTimeUnit.MonthBased
}

@Serializable(with = DayBasedDateTimeUnitSerializer::class)
public class DayBased(public val days: Int) : DateBased() {
init {
require(days > 0) { "Unit duration must be positive, but was $days days." }
}

override fun times(scalar: Int): DateTimeUnit.DayBased = DateTimeUnit.DayBased(safeMultiply(days, scalar))

override fun times(scalar: Int): DayBased = DayBased(safeMultiply(days, scalar))
override fun equals(other: Any?): Boolean =
this === other || (other is DateTimeUnit.DayBased && this.days == other.days)

override fun equals(other: Any?): Boolean =
this === other || (other is DayBased && this.days == other.days)
override fun hashCode(): Int = days xor 0x10000

override fun hashCode(): Int = days xor 0x10000
override fun toString(): String = if (days % 7 == 0)
formatToString(days / 7, "WEEK")
else
formatToString(days, "DAY")
}

override fun toString(): String = if (days % 7 == 0)
formatToString(days / 7, "WEEK")
else
formatToString(days, "DAY")
@Serializable(with = MonthBasedDateTimeUnitSerializer::class)
public class MonthBased(public val months: Int) : DateBased() {
init {
require(months > 0) { "Unit duration must be positive, but was $months months." }
}
@Serializable(with = MonthBasedDateTimeUnitSerializer::class)
public class MonthBased(public val months: Int) : DateBased() {
init {
require(months > 0) { "Unit duration must be positive, but was $months months." }
}

override fun times(scalar: Int): MonthBased = MonthBased(safeMultiply(months, scalar))
override fun times(scalar: Int): DateTimeUnit.MonthBased = DateTimeUnit.MonthBased(safeMultiply(months, scalar))

override fun equals(other: Any?): Boolean =
this === other || (other is MonthBased && this.months == other.months)
override fun equals(other: Any?): Boolean =
this === other || (other is DateTimeUnit.MonthBased && this.months == other.months)

override fun hashCode(): Int = months xor 0x20000
override fun hashCode(): Int = months xor 0x20000

override fun toString(): String = when {
months % 12_00 == 0 -> formatToString(months / 12_00, "CENTURY")
months % 12 == 0 -> formatToString(months / 12, "YEAR")
months % 3 == 0 -> formatToString(months / 3, "QUARTER")
else -> formatToString(months, "MONTH")
}
override fun toString(): String = when {
months % 12_00 == 0 -> formatToString(months / 12_00, "CENTURY")
months % 12 == 0 -> formatToString(months / 12, "YEAR")
months % 3 == 0 -> formatToString(months / 3, "QUARTER")
else -> formatToString(months, "MONTH")
}
}

Expand All @@ -117,11 +124,11 @@ public sealed class DateTimeUnit {
public val SECOND: TimeBased = MILLISECOND * 1000
public val MINUTE: TimeBased = SECOND * 60
public val HOUR: TimeBased = MINUTE * 60
public val DAY: DateBased.DayBased = DateBased.DayBased(days = 1)
public val WEEK: DateBased.DayBased = DAY * 7
public val MONTH: DateBased.MonthBased = DateBased.MonthBased(months = 1)
public val QUARTER: DateBased.MonthBased = MONTH * 3
public val YEAR: DateBased.MonthBased = MONTH * 12
public val CENTURY: DateBased.MonthBased = YEAR * 100
public val DAY: DayBased = DayBased(days = 1)
public val WEEK: DayBased = DAY * 7
public val MONTH: MonthBased = MonthBased(months = 1)
public val QUARTER: MonthBased = MONTH * 3
public val YEAR: MonthBased = MONTH * 12
public val CENTURY: MonthBased = YEAR * 100
}
}
20 changes: 10 additions & 10 deletions core/common/src/serializers/DateTimeUnitSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,21 @@ public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBase
}
}

public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DateBased.DayBased> {
public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") {
element<Int>("days")
}

override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.DayBased) {
override fun serialize(encoder: Encoder, value: DateTimeUnit.DayBased) {
encoder.encodeStructure(descriptor) {
encodeIntElement(descriptor, 0, value.days)
}
}

@ExperimentalSerializationApi
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.DayBased {
override fun deserialize(decoder: Decoder): DateTimeUnit.DayBased {
var seen = false
var days = 0
decoder.decodeStructure(descriptor) {
Expand All @@ -88,25 +88,25 @@ public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DateBased
}
}
if (!seen) throw MissingFieldException("days")
return DateTimeUnit.DateBased.DayBased(days)
return DateTimeUnit.DayBased(days)
}
}

public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DateBased.MonthBased> {
public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBased> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") {
element<Int>("months")
}

override fun serialize(encoder: Encoder, value: DateTimeUnit.DateBased.MonthBased) {
override fun serialize(encoder: Encoder, value: DateTimeUnit.MonthBased) {
encoder.encodeStructure(descriptor) {
encodeIntElement(descriptor, 0, value.months)
}
}

@ExperimentalSerializationApi
@Suppress("INVISIBLE_MEMBER") // to be able to throw `MissingFieldException`
override fun deserialize(decoder: Decoder): DateTimeUnit.DateBased.MonthBased {
override fun deserialize(decoder: Decoder): DateTimeUnit.MonthBased {
var seen = false
var months = 0
decoder.decodeStructure(descriptor) {
Expand All @@ -127,7 +127,7 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DateBas
}
}
if (!seen) throw MissingFieldException("months")
return DateTimeUnit.DateBased.MonthBased(months)
return DateTimeUnit.MonthBased(months)
}
}

Expand All @@ -136,7 +136,7 @@ public object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer<Dat

private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased",
DateTimeUnit.DateBased::class,
arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class),
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer))

@InternalSerializationApi
Expand Down Expand Up @@ -164,7 +164,7 @@ public object DateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit

private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit",
DateTimeUnit::class,
arrayOf(DateTimeUnit.DateBased.DayBased::class, DateTimeUnit.DateBased.MonthBased::class, DateTimeUnit.TimeBased::class),
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class, DateTimeUnit.TimeBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer, TimeBasedDateTimeUnitSerializer))

@InternalSerializationApi
Expand Down
12 changes: 6 additions & 6 deletions core/js/src/Instant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
is DateTimeUnit.TimeBased -> {
plus(value, unit).value.checkZone(timeZone)
}
is DateTimeUnit.DateBased.DayBased ->
is DateTimeUnit.DayBased ->
thisZdt.plusDays(value.toDouble() * unit.days).toInstant()
is DateTimeUnit.DateBased.MonthBased ->
is DateTimeUnit.MonthBased ->
thisZdt.plusMonths(value.toDouble() * unit.months).toInstant()
}.let(::Instant)
} catch (e: Throwable) {
Expand All @@ -161,9 +161,9 @@ public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZon
when (unit) {
is DateTimeUnit.TimeBased ->
plus(value.toLong(), unit).value.checkZone(timeZone)
is DateTimeUnit.DateBased.DayBased ->
is DateTimeUnit.DayBased ->
thisZdt.plusDays(value.toDouble() * unit.days).toInstant()
is DateTimeUnit.DateBased.MonthBased ->
is DateTimeUnit.MonthBased ->
thisZdt.plusMonths(value.toDouble() * unit.months).toInstant()
}.let(::Instant)
} catch (e: Throwable) {
Expand Down Expand Up @@ -208,8 +208,8 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti
val otherZdt = other.atZone(timeZone)
when(unit) {
is DateTimeUnit.TimeBased -> until(other, unit)
is DateTimeUnit.DateBased.DayBased -> (thisZdt.until(otherZdt, ChronoUnit.DAYS).toDouble() / unit.days).toLong()
is DateTimeUnit.DateBased.MonthBased -> (thisZdt.until(otherZdt, ChronoUnit.MONTHS).toDouble() / unit.months).toLong()
is DateTimeUnit.DayBased -> (thisZdt.until(otherZdt, ChronoUnit.DAYS).toDouble() / unit.days).toLong()
is DateTimeUnit.MonthBased -> (thisZdt.until(otherZdt, ChronoUnit.MONTHS).toDouble() / unit.months).toLong()
}
} catch (e: ArithmeticException) {
if (this < other) Long.MAX_VALUE else Long.MIN_VALUE
Expand Down
8 changes: 4 additions & 4 deletions core/js/src/LocalDate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public actual fun LocalDate.plus(value: Long, unit: DateTimeUnit.DateBased): Loc
private fun LocalDate.plusNumber(value: Number, unit: DateTimeUnit.DateBased): LocalDate =
try {
when (unit) {
is DateTimeUnit.DateBased.DayBased -> this.value.plusDays(value.toDouble() * unit.days)
is DateTimeUnit.DateBased.MonthBased -> this.value.plusMonths(value.toDouble() * unit.months)
is DateTimeUnit.DayBased -> this.value.plusDays(value.toDouble() * unit.days)
is DateTimeUnit.MonthBased -> this.value.plusMonths(value.toDouble() * unit.months)
}.let(::LocalDate)
} catch (e: Throwable) {
if (!e.isJodaDateTimeException() && !e.isJodaArithmeticException()) throw e
Expand Down Expand Up @@ -92,8 +92,8 @@ public actual fun LocalDate.periodUntil(other: LocalDate): DatePeriod {
}

public actual fun LocalDate.until(other: LocalDate, unit: DateTimeUnit.DateBased): Int = when(unit) {
is DateTimeUnit.DateBased.MonthBased -> monthsUntil(other) / unit.months
is DateTimeUnit.DateBased.DayBased -> daysUntil(other) / unit.days
is DateTimeUnit.MonthBased -> monthsUntil(other) / unit.months
is DateTimeUnit.DayBased -> daysUntil(other) / unit.days
}

public actual fun LocalDate.daysUntil(other: LocalDate): Int =
Expand Down
8 changes: 4 additions & 4 deletions core/jvm/src/Instant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
when (unit) {
is DateTimeUnit.TimeBased ->
plus(value, unit).value.also { it.atZone(timeZone.zoneId) }
is DateTimeUnit.DateBased.DayBased ->
is DateTimeUnit.DayBased ->
thisZdt.plusDays(safeMultiply(value, unit.days.toLong())).toInstant()
is DateTimeUnit.DateBased.MonthBased ->
is DateTimeUnit.MonthBased ->
thisZdt.plusMonths(safeMultiply(value, unit.months.toLong())).toInstant()
}.let(::Instant)
} catch (e: Exception) {
Expand Down Expand Up @@ -173,8 +173,8 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti
val otherZdt = other.atZone(timeZone)
when(unit) {
is DateTimeUnit.TimeBased -> until(other, unit)
is DateTimeUnit.DateBased.DayBased -> thisZdt.until(otherZdt, ChronoUnit.DAYS) / unit.days
is DateTimeUnit.DateBased.MonthBased -> thisZdt.until(otherZdt, ChronoUnit.MONTHS) / unit.months
is DateTimeUnit.DayBased -> thisZdt.until(otherZdt, ChronoUnit.DAYS) / unit.days
is DateTimeUnit.MonthBased -> thisZdt.until(otherZdt, ChronoUnit.MONTHS) / unit.months
}
} catch (e: DateTimeException) {
throw DateTimeArithmeticException(e)
Expand Down
8 changes: 4 additions & 4 deletions core/jvm/src/LocalDate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public actual fun LocalDate.minus(value: Int, unit: DateTimeUnit.DateBased): Loc
public actual fun LocalDate.plus(value: Long, unit: DateTimeUnit.DateBased): LocalDate =
try {
when (unit) {
is DateTimeUnit.DateBased.DayBased -> {
is DateTimeUnit.DayBased -> {
val addDays: Long = safeMultiply(value, unit.days.toLong())
ofEpochDayChecked(safeAdd(this.value.toEpochDay(), addDays))
}
is DateTimeUnit.DateBased.MonthBased ->
is DateTimeUnit.MonthBased ->
this.value.plusMonths(safeMultiply(value, unit.months.toLong()))
}.let(::LocalDate)
} catch (e: Exception) {
Expand Down Expand Up @@ -109,8 +109,8 @@ public actual fun LocalDate.periodUntil(other: LocalDate): DatePeriod {
}

public actual fun LocalDate.until(other: LocalDate, unit: DateTimeUnit.DateBased): Int = when(unit) {
is DateTimeUnit.DateBased.MonthBased -> (this.value.until(other.value, ChronoUnit.MONTHS) / unit.months).clampToInt()
is DateTimeUnit.DateBased.DayBased -> (this.value.until(other.value, ChronoUnit.DAYS) / unit.days).clampToInt()
is DateTimeUnit.MonthBased -> (this.value.until(other.value, ChronoUnit.MONTHS) / unit.months).clampToInt()
is DateTimeUnit.DayBased -> (this.value.until(other.value, ChronoUnit.DAYS) / unit.days).clampToInt()
}

public actual fun LocalDate.daysUntil(other: LocalDate): Int =
Expand Down
8 changes: 4 additions & 4 deletions core/native/src/LocalDate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ public actual fun LocalDate.plus(unit: DateTimeUnit.DateBased): LocalDate =
public actual fun LocalDate.plus(value: Int, unit: DateTimeUnit.DateBased): LocalDate =
try {
when (unit) {
is DateTimeUnit.DateBased.DayBased -> plusDays(safeMultiply(value, unit.days))
is DateTimeUnit.DateBased.MonthBased -> plusMonths(safeMultiply(value, unit.months))
is DateTimeUnit.DayBased -> plusDays(safeMultiply(value, unit.days))
is DateTimeUnit.MonthBased -> plusMonths(safeMultiply(value, unit.months))
}
} catch (e: ArithmeticException) {
throw DateTimeArithmeticException("Arithmetic overflow when adding a value to a date", e)
Expand Down Expand Up @@ -286,8 +286,8 @@ public actual operator fun LocalDate.plus(period: DatePeriod): LocalDate =
}

public actual fun LocalDate.until(other: LocalDate, unit: DateTimeUnit.DateBased): Int = when(unit) {
is DateTimeUnit.DateBased.MonthBased -> monthsUntil(other) / unit.months
is DateTimeUnit.DateBased.DayBased -> daysUntil(other) / unit.days
is DateTimeUnit.MonthBased -> monthsUntil(other) / unit.months
is DateTimeUnit.DayBased -> daysUntil(other) / unit.days
}

// org.threeten.bp.LocalDate#daysUntil
Expand Down
4 changes: 2 additions & 2 deletions core/native/src/LocalDateTime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ internal fun LocalDateTime.until(other: LocalDateTime, unit: DateTimeUnit.DateBa
endDate = endDate.plusDays(1) // won't throw: date - endDate >= 1
}
return when (unit) {
is DateTimeUnit.DateBased.MonthBased -> date.monthsUntil(endDate) / unit.months
is DateTimeUnit.DateBased.DayBased -> date.daysUntil(endDate) / unit.days
is DateTimeUnit.MonthBased -> date.monthsUntil(endDate) / unit.months
is DateTimeUnit.DayBased -> date.daysUntil(endDate) / unit.days
}
}

Expand Down
16 changes: 8 additions & 8 deletions serialization/common/test/DateTimeUnitSerializationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ class DateTimeUnitSerializationTest {
}
}

private fun dayBasedSerialization(serializer: KSerializer<DateTimeUnit.DateBased.DayBased>) {
private fun dayBasedSerialization(serializer: KSerializer<DateTimeUnit.DayBased>) {
repeat(100) {
val days = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.DayBased(days)
val unit = DateTimeUnit.DayBased(days)
val json = "{\"days\":$days}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
}
}

private fun monthBasedSerialization(serializer: KSerializer<DateTimeUnit.DateBased.MonthBased>) {
private fun monthBasedSerialization(serializer: KSerializer<DateTimeUnit.MonthBased>) {
repeat(100) {
val months = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.MonthBased(months)
val unit = DateTimeUnit.MonthBased(months)
val json = "{\"months\":$months}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
Expand All @@ -47,14 +47,14 @@ class DateTimeUnitSerializationTest {
private fun dateBasedSerialization(serializer: KSerializer<DateTimeUnit.DateBased>) {
repeat(100) {
val days = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.DayBased(days)
val unit = DateTimeUnit.DayBased(days)
val json = "{\"type\":\"DayBased\",\"days\":$days}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
}
repeat(100) {
val months = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.MonthBased(months)
val unit = DateTimeUnit.MonthBased(months)
val json = "{\"type\":\"MonthBased\",\"months\":$months}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
Expand All @@ -71,14 +71,14 @@ class DateTimeUnitSerializationTest {
}
repeat(100) {
val days = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.DayBased(days)
val unit = DateTimeUnit.DayBased(days)
val json = "{\"type\":\"DayBased\",\"days\":$days}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
}
repeat(100) {
val months = Random.nextInt(1, Int.MAX_VALUE)
val unit = DateTimeUnit.DateBased.MonthBased(months)
val unit = DateTimeUnit.MonthBased(months)
val json = "{\"type\":\"MonthBased\",\"months\":$months}"
assertEquals(json, Json.encodeToString(serializer, unit))
assertEquals(unit, Json.decodeFromString(serializer, json))
Expand Down