How to make a Kotlin Comparable Type? - kotlin

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?

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

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)

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)

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
}

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

Related

How to compare LocalDateTime instances in kotlin

so I'm having a little problem with kotlin LocalDateTime
val now = CurrentDateTime
val someDate = someService.someLocalDateTime
I have two dates as you can see and I want to know if now is bigger than someDate or not.
and also I need to know if it's bigger, how much is bigger.
i can do it by checking year, month, day, minute and second like this:
if (now.year().toString() == someDate.year.toString())
but it's not a good way
any suggesstions would be welcome.
You can simply convert both dates in seconds and:
compare them to know which one is bigger
subtract them to know how much one is bigger than the other
an example would be
val now = CurrentDateTime.toEpochSeconds()
val someDate = someService.someLocalDateTime.toEpochSeconds();
if(now > someDate)
//someDate is in the past
else
//someDate is in the future or both dates are equal
val distance = now-someDate
hope this helps
The standard solution to compare two Date objects is by using the compareTo() function. It returns a value
= 0, if both dates are equal.
< 0, if date is before the specified date.
> 0, if date is after the specified date.
The following program demonstrates it:
import java.text.SimpleDateFormat
import java.util.*
fun main() {
val now = CurrentDateTime // "01/21/2023"
val someDate = someService.someLocalDateTime // "01/21/2020"
val sdf = SimpleDateFormat("MM/dd/yyyy")
val firstDate: Date = sdf.parse(now)
val secondDate: Date = sdf.parse(someDate)
val cmp = firstDate.compareTo(secondDate)
when {
cmp > 0 -> {
System.out.printf("%s is after %s", d1, d2)
}
cmp < 0 -> {
System.out.printf("%s is before %s", d1, d2)
}
else -> {
print("Both dates are equal")
}
}
}
Convert Kotlin LocalDateTime to Java LocalDateTime
To convert Kotlin LocalDateTime to Java LocalDateTime, you can make use of this function:
fun LocalDateTime.toJavaLocalDateTime(): LocalDateTime
Converts this kotlinx.datetime.LocalDateTime value to a java.time.LocalDateTime value.
And then you can choose to use the following method or other suggested method to compare the converted java.time.LocalDateTime.
Compare Java LocalDateTime
To compare LocalDateTime, you can use LocalDateTime's isAfter(), isBefore(), isEqual().
import java.time.LocalDateTime
fun main() {
val currentTime = LocalDateTime.now()
val ytdTime = LocalDateTime.now().minusDays(1)
println(currentTime.isAfter(ytdTime))
println(currentTime.isBefore(ytdTime))
println(currentTime.isEqual(ytdTime))
}
Output
true
false
false
To find the difference between LocalDateTime, you can use ChronoUnit:
import java.time.temporal.ChronoUnit
fun main() {
val currentTime = LocalDateTime.now()
val ytdTime = LocalDateTime.now().minusDays(1)
val secondDifference = ChronoUnit.SECONDS.between(ytdTime, currentTime)
val minutesDifference = ChronoUnit.MINUTES.between(ytdTime, currentTime)
val hourDifference = ChronoUnit.HOURS.between(ytdTime, currentTime)
println(secondDifference)
println(minutesDifference)
println(hourDifference)
}
Output
86399
1439
23

Kotlin: How to use higher order functions with OpenEndRange<LocalDate>

I want to use higher order functions like map for open end ranges.
val from = LocalDate.now().minusDays(10)
val to = LocalDate.now()
(from ..< to).forEach(::println)
I tried to copy an example for ClosedRange<LocalDate> but it does not work.
package de.otto.di.extensions
import java.time.LocalDate
class OpenEndRangeLocalDateIterator(
startDate: LocalDate,
private val endExclusive: LocalDate,
private val stepDays: Long
) : Iterator<LocalDate> {
private var currentDate = startDate
override fun hasNext() = currentDate.plusDays(stepDays) <= endExclusive
override fun next(): LocalDate {
val next = currentDate
currentDate = currentDate.plusDays(stepDays)
return next
}
}
#OptIn(ExperimentalStdlibApi::class)
class OpenEndLocalDateRange(
override val start: LocalDate,
override val endExclusive: LocalDate,
private val stepDays: Long = 1
) : Iterable<LocalDate>, OpenEndRange<LocalDate> {
override fun iterator(): Iterator<LocalDate> =
OpenEndRangeLocalDateIterator(start, endExclusive, stepDays)
infix fun step(days: Long) = OpenEndLocalDateRange(start, endExclusive, days)
}
infix operator fun LocalDate.rangeUntil(to: LocalDate): OpenEndLocalDateRange =
OpenEndLocalDateRange(this, to)
It is implemented for Int so I assume it must be possible somehow. How can I achieve this?
The issue here is that you've defined the operator function to return OpenEndRange<LocalDate> rather than OpenEndedLocalDateRange. If you change the return type of your operator function that should fix the issue.
The reason why it isn't working as is is because OpenEndRange doesn't have the higher order functions defined for it (ClosedRange doesn't have them defined as well). Int has it because the operators return an IntRange which indirectly extends Iterable<Int> via IntProgression and Iterable has these higher order functions defined, so, the only missing piece is failing to return the correct type from your operator function.

2 operator overload functions of the same type

I'm doing Kotlin Koans's Operator Overloading exercise and am wondering how the compiler chooses which MyDate.plus() function to use:
import TimeInterval.*
import java.util.Calendar
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
enum class TimeInterval { DAY, WEEK, YEAR }
operator fun MyDate.plus(timeInterval: TimeInterval) = addTimeIntervals(timeInterval, 1)
class FullTimeInterval(val timeInterval: TimeInterval, val number: Int)
operator fun TimeInterval.times(number: Int) = FullTimeInterval(this, number)
operator fun MyDate.plus(timeIntervals: FullTimeInterval)
= addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)
fun task1(today: MyDate): MyDate {
return today + YEAR + WEEK
}
fun task2(today: MyDate): MyDate {
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
You have two classes: TimeInterval and FullTimeInterval and two overloaded functions:
MyDate.plus(timeIntervals: TimeInterval) and MyDate.plus(timeIntervals: FullTimeInterval)
The compiler knows the type of the argument and it selects the closest function by the signature. The decision is made at the compilation time and depends on the computed types of the arguments.
You may find more information on that at
https://jetbrains.github.io/kotlin-spec/#overload-resolution

Operator overloading in Kotlin and how does this code work?

I am reading the following code from a tutorial but I don't really get it.
Basically it tries to use operator overloading so that the following code works:
return today + YEAR * 2 + WEEK * 3 + DAY * 5
What I understand:
This part:
operator fun MyDate.plus(timeInterval: TimeInterval): MyDate {
return addTimeIntervals(timeInterval, 1)
}
Enhances the class MyDate to support the + with a timeInterval so this would work myDate + YEAR for example.
This part:
operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval)
= addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)
Enhances the class MyDate to support * with a RepeatedInterval
This part just declares an empty class with 2 member variables timeInterval and number
class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)
What I don't understand is how the multiplication is actually happening
since RepeatedInterval is just an empty class.
Could someone please help my understand this?
import TimeInterval.*
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
enum class TimeInterval { DAY, WEEK, YEAR }
operator fun MyDate.plus(timeInterval: TimeInterval): MyDate {
return addTimeIntervals(timeInterval, 1)
}
class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)
operator fun TimeInterval.times(number: Int) = RepeatedTimeInterval(this, number)
operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval)
= addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)
fun task1(today: MyDate): MyDate {
return today + YEAR + WEEK
}
fun task2(today: MyDate): MyDate {
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
Also this is part of the tutorial:
import java.util.Calendar
fun MyDate.addTimeIntervals(timeInterval: TimeInterval, number: Int): MyDate {
val c = Calendar.getInstance()
c.set(year, month, dayOfMonth)
when (timeInterval) {
TimeInterval.DAY -> c.add(Calendar.DAY_OF_MONTH, number)
TimeInterval.WEEK -> c.add(Calendar.WEEK_OF_MONTH, number)
TimeInterval.YEAR -> c.add(Calendar.YEAR, number)
}
return MyDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DATE))
}
YEAR * 2 is TimeInterval * Int. The compiler sees it isn't a built-in combination, so it looks for a method times marked as operator on TimeInterval which accepts Int (so e.g. TimeInterval.times(Int) or TimeInterval.times(Any)). This method can be a member of TimeInterval or an extension; there's absolutely no reason for it to be a member of RepeatedTimeInterval.
In fact, RepeatedTimeInterval doesn't have any part in resolving YEAR * 2 at all, it just happens to be the return type. Then today + YEAR * 2 is MyDate + RepeatedTimeInterval and the same rule is applied to pick operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) (and not operator fun MyDate.plus(timeInterval: TimeInterval) which is used for today + YEAR).
Note that with this code it isn't legal to have e.g. YEAR * 2 * 2; that would require RepeatedTimeInterval.times(Int) which again could be a member or an extension.
What I don't understand is how the multiplication is actually happening since RepeatedInterval is just an empty class.Could someone please help my understand this?
Well, the RepeatedTimeInterval class is indeed "an empty class", but notice that it has an extension function TimeInterval.times(number: Int) which makes it support the operation TimeInterval * Int, for example: YEAR * 2
I came accross this program while doing the Kotlin Koans: Operators overloading task, here's my solution:
import TimeInterval.*
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
// Supported intervals that might be added to dates:
enum class TimeInterval { DAY, WEEK, YEAR }
class MultipleTimeInterval(val timeInterval: TimeInterval, val amont : Int)
operator fun TimeInterval.times(amont: Int) = MultipleTimeInterval (this, amont)
operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = addTimeIntervals(timeInterval, 1)
operator fun MyDate.plus(multi: MultipleTimeInterval): MyDate = addTimeIntervals(multi.timeInterval, multi.amont)
fun task1(today: MyDate): MyDate {
return today + YEAR + WEEK
}
fun task2(today: MyDate): MyDate {
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
The utility function MyDate.addTimeIntervals() is:
import java.util.Calendar
fun MyDate.addTimeIntervals(timeInterval: TimeInterval, amount: Int): MyDate {
val c = Calendar.getInstance()
c.set(year + if (timeInterval == TimeInterval.YEAR) amount else 0, month, dayOfMonth)
var timeInMillis = c.timeInMillis
val millisecondsInADay = 24 * 60 * 60 * 1000L
timeInMillis += amount * when (timeInterval) {
TimeInterval.DAY -> millisecondsInADay
TimeInterval.WEEK -> 7 * millisecondsInADay
TimeInterval.YEAR -> 0L
}
val result = Calendar.getInstance()
result.timeInMillis = timeInMillis
return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE))
}

How to understand a fun with = in Kotlin?

I know a standard fun just like Code 0.
The Code 1 is a sample code from webpage, I can't understand completely the
fun convertFromDomain(forecast: ForecastList) = with(forecast) {...}
Why is the symbol = added to the fun? and is the return value of the fun convertFromDomain unit ?
Code 0
fun My(forecast: ForecastList):Boolean {
...
return true;
}
Code 1
data class ForecastList(val id: Long, val city: String, val country: String, val dailyForecast: List<Forecast>) {
val size: Int
get() = dailyForecast.size
operator fun get(position: Int) = dailyForecast[position]
}
data class Forecast(val id: Long, val date: Long, val description: String, val high: Int, val low: Int,
val iconUrl: String)
fun convertFromDomain(forecast: ForecastList) = with(forecast) {
val daily = dailyForecast.map { convertDayFromDomain(id, it) }
CityForecast(id, city, country, daily)
}
Block body
Consider this function:
fun sum(a: Int, b: Int): Int {
return a + b
}
The behaviour is defined in a block body. It has an explicit return type (Int) and an explicit return statement. Both are mandatory here. If you don't specify a return type explicitely it will be Unit by default and since the inferred type of a + b is Int it won't compile.
Expression body
If you write it like this
fun sum(a: Int, b: Int) = a + b
you don't need to specify the return type because it can be inferred from the expression.You don't need a return statement either because the last expression is returned. What follows the = sign is called an expression body.
So, both functions do the same thing. The second one is only written more concise.
Note
A common mistake is to use them both at once like this:
fun sum(a: Int, b: Int) = { a + b }
What this would do becomes clear, if you specify the returned type explicitely:
fun sum(a: Int, b: Int): () -> Int = { a + b }
You would actually return a lambda of type () -> Int which is surely not what you want.
A function has usually the following form in kotlin:
fun name([parameters])[: return_type] {
// function-body
}
e.g.
fun doubleTheValue(number: Int): Int {
return number * 2
}
If your function-body is just a single-expression, you can use a shorter version of the function declaration
fun name([parameters])[: return_type] = [expression]
e.g.
fun doubleTheValue(number: Int): Int = nummber * 2
fun doubleTheValue(number: Int) = nummber * 2 // type can also be inferred
So it's nothing special, just a shorter version of a function declaration.
Traditional way to define a function is just like what you write in Code 0, which consists of function name, parameters, return type and the block body. In Kotlin, function can be defined with an expression body and the return type can be inferred from the expression body.
Suppose there is a function which convert Boolean to Int, in traditional way:
fun Boolean.toInt(): Int {
return if (this) 1 else 0
}
It can be simplified to:
fun Boolean.toInt() = if (this) 1 else 0
where the return type is inferred as Int because 1 and 0 are both Int which will be returned from the expression if (this) 1 else 0.