Как сделать неявный заказ на java.time.LocalDate

Я хочу использовать java.time.LocalDate и java.time.LocalDateTime с неявным Ordered:

val date1 = java.time.LocalDate.of(2000, 1, 1)
val date2 = java.time.LocalDate.of(2010, 10, 10)
if (date1 < date2) ...

import scala.math.Ordering.Implicits._ не работает, потому что LocalDate наследует от Comparable<ChronoLocalDate> вместо Comparable<LocalDate>. Как я могу написать свой собственный неявный Orderd для использования <, <=,>,> = операторов/методов для сравнения LocalDate?

Редактировать:

Я нашел способ использования неявного класса:

import java.time.{LocalDate}

object MyDateTimeUtils {
  implicit class MyLocalDateImprovements(val ld: LocalDate)
           extends Ordered[LocalDate] {
    def compare(that: LocalDate): Int = ld.compareTo(that)
  }
}

// Test

import MyDateTimeUtils._

val d1 = LocalDate.of(2016, 1, 1)
val d2 = LocalDate.of(2017, 2, 3)

if (d1 < d2) println("d1 is less than d2")

Но я бы предпочел такой способ, как Scala для всех классов Java, который реализует Comparable<T>. Вам просто нужно import scala.math.Ordering.Implicits._ в свой код. Scala реализует его как:

implicit def ordered[A <% Comparable[A]]: Ordering[A] = new Ordering[A] {
  def compare(x: A, y: A): Int = x compareTo y
}

Но, к сожалению, LocalDate реализует LocalDate Comparable<ChronoLocalDate> вместо Comparable<LocalDate>. Я не мог найти способ изменить вышеупомянутый неявный упорядоченный метод, чтобы он соответствовал LocalDate/Comparable<ChronoLocalDate>. Любая идея?

Ответ 1

Вы можете использовать Ordering.by, чтобы создать упорядочение для любого типа, задав функцию из этого типа на то, что уже имеет заказ - в этом случае - Long:

implicit val localDateOrdering: Ordering[LocalDate] = Ordering.by(_.toEpochDay)

Ответ 2

Сравнение по LocalDate.toEpochDay понятно, хотя, может быть, и относительно медленно...

Ответ @tzach-zohar великолепен тем, что наиболее очевидно, что происходит; вы заказываете к дню эпохи:

implicit val localDateOrdering: Ordering[LocalDate] = Ordering.by(_.toEpochDay)

Однако, если вы посмотрите на реализацию toEpochDay то увидите, что он относительно задействован - 18 строк кода, 4 деления, 3 условия и вызов isLeapYear() - и результирующее значение не кэшируется, поэтому он получает пересчитывается при каждом сравнении, что может быть дорогостоящим, если будет отсортировано большое количество LocalDate.

... использование LocalDate.compareTo, вероятно, более производительным...

Реализация LocalDate.compareTo более проста - всего 2 условия, без деления - и это то, что вы получите при неявном преобразовании java.lang.Comparable scala.math.Ordering с scala.math.Ordering что scala.math.Ordering.Implicits._ предлагает, лишь бы это сработало! Но, как вы сказали, это не так, потому что LocalDate наследуется от Comparable<ChronoLocalDate> вместо Comparable<LocalDate>. Одним из способов воспользоваться этим может быть...

import scala.math.Ordering.Implicits._

implicit val localDateOrdering: Ordering[LocalDate] =
  Ordering.by(identity[ChronoLocalDate])

... который позволяет вам упорядочить LocalDate их к ChronoLocalDate s и используя Ordering[ChronoLocalDate] который scala.math.Ordering.Implicits._ дает scala.math.Ordering.Implicits._ !

... и, в конце концов, лучше всего выглядит с лямбда-синтаксисом для типов SAM

Лямбда-синтаксис для типов SAM, представленный в Scala 2.12, может сделать очень короткую работу по созданию new Ordering:

implicit val localDateOrdering: Ordering[LocalDate] = _ compareTo _

... и я думаю, что это становится моим личным фаворитом! Краткий, все еще довольно ясный и использующий (я думаю) самый эффективный метод сравнения.

Ответ 3

Вот решение, которое я использую:

определить два имплицита. Первый для создания Ordering[LocalDate]. И второй способ дать метод LocalDate a compare, который очень пригодится. Я обычно помещаю их в объекты пакета в библиотеке, я могу просто включить туда, где они мне нужны.

package object net.fosdal.oslo.odatetime {

  implicit val orderingLocalDate: Ordering[LocalDate] = Ordering.by(d => (d.getYear, d.getDayOfYear))

  implicit class LocalDateOps(private val localDate: LocalDate) extends AnyVal with Ordered[LocalDate] {
    override def compare(that: LocalDate): Int = Ordering[LocalDate].compare(localDate, that)
  }
}

с обоими этими определениями вы можете теперь делать такие вещи, как:

import net.fosdal.oslo.odatetime._

val bool: Boolean = localDate1 < localDate1

val localDates: Seq[LocalDate] = ...
val sortedSeq = localDates.sorted

В качестве альтернативы... вы можете просто использовать мою библиотеку (v0.4.3) напрямую. см. https://github.com/sfosdal/oslo

Ответ 4

Вот мое решение для java.time.LocalDateTime

implicit val localDateTimeOrdering: Ordering[LocalDateTime] =
  Ordering.by(x => x.atZone(ZoneId.of("UTC")).toEpochSecond)

Ответ 5

Небольшое изменение неявного ordered должно сделать трюк.

type AsComparable[A] = A => Comparable[_ >: A]

implicit def ordered[A: AsComparable]: Ordering[A] = new Ordering[A] {
  def compare(x: A, y: A): Int = x compareTo y
}

Теперь каждый тип, который сопоставим с самим собой или с супертипом, должен иметь Ordering.