Микоропост о том, как можно себя обмануть при использовании фичи Котлин: возможность работы с операторами сравнения типа Comparable.
Кто юзает Котлин не могли не оценить перегрузку операторов (ну точнее как она сделана), правда я допустим жил в Java и без нее прекрасно, но да тут не об этой фичи языка, а об основанной на ней: Comparison Operations. Это когда можно применять знаки сравнения для классов, реализующих Comparable, что является сахаром, но очень приятным.
И так, поупражняемся на синтетике: у нас есть какой-либо временной отрезок, т.е. начальная и конечная дата время и нам нужно проверить факт пересечения временных отрезков.
На Java (пишу максимально кратко и без принятых норм, просто передать идею):
class TimeIterval {
LocalDateTime from;
LocalDateTime to;
}
class TimeIntervalUtil {
public boolean areOverlapped(TimeInterval first, TimeInterval second) {
return (first.from.isEqual(second.to) || first.from.isBefore(second.to)) &&
(first.to.isEqual(second.from) || first.to.isAfter(second.from));
}
}
т.е. ничего сложного, но на всякий случай поясню код (мне приходиться самому пояснять себе такой код, когда его встречаю): пересечение двух интервалов возможно только если дата начала одного наступила ранее или в тот же момент времени по отношению к дате окончания второго и в это же время дата окончания первого интервала наступает позднее или в момент начала второго.
Теперь то же самое но на Котлине с его сахаром, но без рейнджей:
data class TimeInterval(val from: LocalDateTime, val to: LocalDateTime)
fun areOverlapped(first: TimeInterval, second: TimeInterval): Boolean =
first.from <= second.to && first.to >= second.from
Ну я думаю без комментариев где видно лучше, что приятней использовать и быстрее понимать. Довольные переходим на Котлин и начинаем работать по аналогии уже с OffsetDateTime.
Тут нам нужно сделать то же самое, но уже с OffsetDateTime. Он тоже Comparable, как и почти все в java.time. Следовательно мы будем использовать такие же подходы как и LocalDateTime. В частности на Java код точно не измениться и будет работать так же как и ранее, а вот с Котлином будет засада.
Если посмотреть compareTo, в вызовы которого интерпретируется код на Kotlin при использовании знаков сравнения, то окажется что для LocalDateTime в принципе получается корректный код (сравниваются дни, часы, месяца и прочее по отдельности), что вроде как нормально.
В случае с OffsetDateTime будет сравнение, не которое мы ожидаем получить, так как compareTo учитывает зону времени, т.е. при сравнении 2021-04-25 10:00+0 и 2021-04-25 11:00+1 они не будут эквиваленты. Простой пример:
val inUtc = OffsetDateTime.of(LocalDateTime.of(2021, 4, 25, 10, 0), ZoneOffset.UTC)
val inUtc1 = OffsetDateTime.of(LocalDateTime.of(2021, 4, 25, 11, 0), ZoneOffset.ofTotalSeconds(60*60))
println(inUtc1>=inUtc && inUtc1 <= inUtc)
println(inUtc.isEqual(inUtc1))
Вот так можно легко и непринужденно нарваться на неочевидную багу.