22

Just learning to define a DateRange type

val wholeYear2017 = Date(2017,1,1)..Date(2017,12,31)

So I created the type as below

class DateRange<Date: Comparable<Date>>(override val start: Date, override val endInclusive: Date)
    : ClosedRange<Date>

class Date (val year: Int, val month: Int, val day: Int) {

    operator fun compareTo(other: Date): Int {
        if (this.year > other.year) return 1
        if (this.year < other.year) return -1
        if (this.month > other.month) return 1
        if (this.month < other.month) return -1
        if (this.day > other.day) return 1
        if (this.day < other.day) return -1
        return 0
    }

    operator fun rangeTo(that: Date): DateRange = DateRange(this, that)
}

But I got a compile error

One type of argument expected for class DateRange<Date: Comparable<Date>> : ClosedRange<Date>

What did I missed? Did I do it correctly?

Elye
  • 53,639
  • 54
  • 212
  • 474

5 Answers5

40

You need to implement Comparable interface. You can use compareValuesBy helper function:

data class Data(
        val a: Int,
        val b: Int
) : Comparable<Data> {
    override fun compareTo(other: Data) = compareValuesBy(this, other,
            { it.a },
            { it.b }
    )
}
cubuspl42
  • 7,833
  • 4
  • 41
  • 65
32

Is your question really about how to create a Comparable type? Then just have your type implement the Comparable interface (override compareTo).

class Date(val year: Int, val month: Int, val day: Int) : Comparable<Date> {
    override operator fun compareTo(other: Date): Int {
        if (this.year > other.year) return 1
        if (this.year < other.year) return -1
        if (this.month > other.month) return 1
        if (this.month < other.month) return -1
        if (this.day > other.day) return 1
        if (this.day < other.day) return -1
        return 0
    }
 }

You don't need a rangeTo method because all Comparable<T> types have a rangeTo extension already defined. See Ranges and rangeTo. But, if you still want your own DateRange type (for other purposes), the simpler form of the DateRange class would be...

class DateRange(override val start: Date, override val endInclusive: Date)
    : ClosedRange<Date>

In other words, there is no need for the generic parameter on DateRange.

Then you would write your own rangeTo operator. Either, add operator fun rangeTo to your Date class, or provide a root level extension function (my preference which is consistent with the Kotlin library approach). Both will shadow/hide the Comparable<T>.rangeTo extension function for your Date type.

// class level rangeTo operator
operator fun rangeTo(that: Date) = DateRange(this,that)

// root level extension
operator fun Date.rangeTo(that: Date) = DateRange(this,that)
Les
  • 10,335
  • 4
  • 40
  • 60
  • I can't do `class DateRange(override val start: Date, override val endInclusive: Date): ClosedRange `. It will complain `Type argument is not within its bounds: should be subtype of 'Comparable'` – Elye Sep 30 '17 at 15:12
  • So, that's the reason I use `class DateRange>(override val start: Date, override val endInclusive: Date) : ClosedRange` to solve the compilation error. – Elye Sep 30 '17 at 15:20
  • 1
    If you add `: Comparable` to your `Date` class, then your `DateRange` class does not need generic parameters. – Les Oct 01 '17 at 02:44
  • Indeed. Thanks!! – Elye Oct 01 '17 at 04:45
4

You are almost there:

Firstly, you must make your class Comparable:

class Date (val year: Int, val month: Int, val day: Int): Comparable<Date> {

Secondly, you must specify the generic of the return type or just omit it (let the compiler infer it)

operator fun rangeTo(that: Date): DateRange<Date> = DateRange(this, that)
operator fun rangeTo(that: Date) = DateRange(this, that)
guenhter
  • 11,255
  • 3
  • 35
  • 66
1

Straight from Koans. I really encourage you to get to know the documentation first.

override fun compareTo(other: Date) = when {
        year != other.year -> year - other.year
        month != other.month -> month - other.month
        else -> day - other.day
    }
  • 2
    Comparing values by subtraction is susceptible to integer overflow: [Avoiding the Subtraction Trick (Baeldung)](https://www.baeldung.com/java-comparator-comparable#avoid-subtraction) – Gabriel Pizarro Sep 05 '22 at 08:22
0

You can translate date to the Integer representation using such formula, for example: year * 10000 + month * 100 + day.

class Date (val year: Int, val month: Int, val day: Int) {

    operator fun compareTo(other: Date): Int {
        return (this.year * 10000 + this.month * 100 + this.day) - (other.year * 10000 + other.month * 100 + other.day)
    }
}

zortech
  • 21
  • 3