java. Время сравнения не является последовательным - PullRequest
1 голос
/ 28 марта 2020

Я использую LocalData объекты в своем коде, и я заметил, что использование compareTo метода не согласовано.
Я использую compareTo метод, чтобы получить разницу (в днях) между двумя датами.
Однако, похоже, что это работает только для дат в тех же месяцах.

Я прилагаю фрагмент кода для демонстрации этой проблемы:

import java.text.MessageFormat;
import java.time.LocalDate;

public class timeComparison {
    public static void main(String[] args) {
        LocalDate valentinesDay = LocalDate.of(2020, 2, 14);
        LocalDate darwinDay = LocalDate.of(2020, 2, 12);
        LocalDate leapDay = LocalDate.of(2020, 2, 29);
        LocalDate superTuesday = LocalDate.of(2020, 3, 3);

        String valentinesVsDarwin = MessageFormat.format(
                "Valentine day is {0} days after Darwin day",
                valentinesDay.compareTo(darwinDay)
        );
        System.out.println(valentinesVsDarwin);

        String darwinVsLeap = MessageFormat.format(
                "Leap day is {0} days after Darwin day",
                leapDay.compareTo(darwinDay)
        );
        System.out.println(darwinVsLeap);

        String superTuesdayVsLeap = MessageFormat.format(
                "Super Tuesday is {0} days after leap day",
                superTuesday.compareTo(leapDay)
        );
        System.out.println(superTuesdayVsLeap);
    }
}

Вывод, который я получаю:

Valentine day is 2 days after Darwin day
Leap day is 17 days after Darwin day
Super Tuesday is 1 days after leap day

Я ожидал получить Super Tuesday is 3 days after leap day ,

Я хотел бы знать, что является причиной проблемы и как я могу получить разницу между двумя отдельными датами.

Ответы [ 2 ]

7 голосов
/ 28 марта 2020

TL; DR

Метод compareTo не предназначен для этого использования.
Он должен быть индикатором порядка, а не показать разницу во времени.

Решение загадки

Чтобы лучше это понять, можно взглянуть на исходный код LocalDate.
В любой IDE вы можете выбрать "Go To => Реализация "для просмотра исходного кода.

public int compareTo(ChronoLocalDate other) {
    return other instanceof LocalDate ? this.compareTo0((LocalDate)other) : super.compareTo(other);
}

int compareTo0(LocalDate otherDate) {
    int cmp = this.year - otherDate.year;
    if (cmp == 0) {
        cmp = this.month - otherDate.month;
        if (cmp == 0) {
            cmp = this.day - otherDate.day;
        }
    }

    return cmp;
}

Из приведенного выше исходного кода вы можете узнать, что метод compareTo не возвращает разницу между датой в днях, а скорее разница между датами в первом параметре, который имеет разницу (это могут быть годы, месяцы или дни).

Какова цель compareTo Методы

Чтобы понять вышеприведенный код и его назначение,
нужно немного понять, как работает сравнение в Java .
Java Класс LocalDate реализует ChronoLocalDate, который является сопоставимым объектом.
Это означает, что этот объект способен сравнивать себя с другим объектом.
Для этого сам класс должен реализовывать интерфейс java.lang.Comparable для сравнения своих экземпляров.
Как описано в документации по API Java :

Этот интерфейс требует полного упорядочения на объектах каждого класса, который его реализует. Это упорядочение называется естественным упорядочением класса, а метод CompareTo класса называется его естественным методом сравнения.

Более того, подробности метода compareTo следующие: 1045 *

Сравнивает этот объект с указанным объектом для заказа.
Возвращает отрицательное целое число, ноль или положительное целое число, так как этот объект меньше чем , равно или больше указанного объекта.

Это означает, что для того, чтобы сравнение сработало, нам нужно вернуть целое число, соответствующее приведенному выше правилу. Следовательно, само число не имеет никакого значения, а не является указанием «порядка» объектов. Вот почему метод, как показано выше, не рассчитывает разницу в днях, а скорее ищет непосредственное указание на естественный порядок объектов: сначала в годах, затем в месяцах, а в конце в днях.
Когда вы сравнивали даты с другим монтированием - метод показывал только это.
То же самое применимо, если вы сравниваете дату в 2020 году и дату в 2018 году - в результате вы получите 2 / -2.

Как измерять разницу данных

Мы обнаружили, что compareTo не предназначен для использования в качестве инструмента измерения разности дат.
Вы можете использовать либо Period или ChronoUnit, в зависимости от ваших потребностей.
Из Java Учебник по периоду и продолжительности :

ChronoUnit

ChronoUnit enum, обсуждаемый во временном пакете, определяет единицы измерения, используемые для измерения времени.
Метод ChronoUnit.between полезен, когда вы хотите измерить количество времени только в одной единице времени, например днях или секундах.
Метод Между работает со всеми временными объектами, но возвращает сумму только в одной единице.

Период

Чтобы определить количество времени со значениями на основе даты ( годы, месяцы, дни), используйте класс Period.
Класс Period предоставляет различные методы get, такие как getMonths, getDays и getYears, чтобы можно было извлечь количество время от периода.
Общий период времени представлен всеми ree единицы вместе: месяцы, дни и годы.
Чтобы представить количество времени, измеренное в одной единице времени, например днях, вы можете использовать метод ChronoUnit.between.

Вот фрагмент кода, демонстрирующий использование упомянутых выше методов:

jshell> import java.time.LocalDate;

jshell> LocalDate darwinDay = LocalDate.of(2020, 2, 12);
darwinDay ==> 2020-02-12

jshell> LocalDate leapDay = LocalDate.of(2020, 2, 29);
leapDay ==> 2020-02-29

jshell> leapDay.compareTo(darwinDay);
 ==> 17

jshell> import java.time.Period;

jshell> Period.between(darwinDay, leapDay).getDays();
 ==> 17

jshell> import java.time.temporal.ChronoUnit;

jshell> ChronoUnit.DAYS.between(darwinDay, leapDay);
 ==> 17

jshell> LocalDate now = LocalDate.of(2020, 3, 28);
now ==> 2020-03-28

jshell> LocalDate yearAgo = LocalDate.of(2019, 3, 28);
yearAgo ==> 2019-03-28

jshell> yearAgo.compareTo(now);
 ==> -1

jshell> Period.between(yearAgo, now).getDays();
 ==> 0

jshell> ChronoUnit.DAYS.between(yearAgo, now);
 ==> 366
2 голосов
/ 28 марта 2020

Я ожидал получить Супер вторник через 3 дня после високосного дня.

Это было неверное ожидание. Посмотрите, как Java сравнил два случая LocalDate:

int compareTo0(LocalDate otherDate) {
    int cmp = (year - otherDate.year);
    if (cmp == 0) {
        cmp = (month - otherDate.month);
        if (cmp == 0) {
            cmp = (day - otherDate.day);
        }
    }
    return cmp;
}

Поскольку год был таким же (2020), он дал вам разницу между месяцами, которая составляет 3 - 2 = 1.

Вы должны использовать java.time.temporal.ChronoUnit::between, чтобы найти количество дней между двумя экземплярами LocalDate.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        System.out.println(ChronoUnit.DAYS.between(LocalDate.of(2020, 2, 29), LocalDate.of(2020, 4, 3)));
    }
}

Вывод:

34
...