Joda Time и Java8 Разница во времени

Я ищу решение для расчета месяцев между двумя датами. Я думаю, что joda или java8 time могут это сделать. Но когда я сравнивал их, я обнаружил кое-что действительно странное.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import org.joda.time.Months;

public class DateMain {
    public static void main(String[] args) throws ParseException {

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    Date d1 = simpleDateFormat.parse("2017-01-28 00:00:00.000");
    Date d2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 1");
    System.out.println("joda time api result: " + monthsBetweenJoda(d1, d2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(d1, d2) + " month");

    Date dd1 = simpleDateFormat.parse("2017-01-29 00:00:00.000");
    Date dd2 = simpleDateFormat.parse("2017-02-28 00:00:00.000");

    System.out.println("Test Cast 2");
    System.out.println("joda time api result: " + monthsBetweenJoda(dd1, dd2) + " month");
    System.out.println("java8 time api result: " + monthsBetweenJava8(dd1, dd2) + " month");
}

public static int monthsBetweenJoda(Date fromDate, Date toDate) {
    if (fromDate == null || toDate == null) {
        throw new IllegalArgumentException();
    }
    org.joda.time.LocalDateTime fromLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(fromDate);
    org.joda.time.LocalDateTime toLocalDateTime = org.joda.time.LocalDateTime
        .fromDateFields(toDate);
    Months months = Months.monthsBetween(fromLocalDateTime, toLocalDateTime);
    return months.getMonths();
}

public static long monthsBetweenJava8(Date fromDate, Date toDate) {
    if (fromDate == null || toDate == null) {
        throw new IllegalArgumentException();
    }
    LocalDateTime ldt1 = fromDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    LocalDateTime ldt2 = toDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    return ChronoUnit.MONTHS.between(ldt1, ldt2);
}

}

вывод ниже:

Test Cast 1
joda time api result: 1 month
java8 time api result: 1 month
Test Cast 2
joda time api result: 1 month
java8 time api result: 0 month

Я очень смущен тестовым примером 2, и какой из них разумен?

Извините, это мой первый вопрос. Заполненный код прилагается.


person Jiaqi    schedule 15.09.2017    source источник
comment
Мне Джода кажется неправильным. В документации указано количество месяцев, но ваш тест не соответствует этому.   -  person Joe C    schedule 15.09.2017
comment
но результат joda выглядит более разумным, когда плюс 1 месяц к 2017-01-29 или 2017-01-30, он всегда возвращает 2017-02-28, поэтому месяцы между 2017-01-29 и 2017-02-28 должны быть 1 ригтом?   -  person Jiaqi    schedule 15.09.2017
comment
@JoeC: Я подозреваю, что он берет самое большое число, такое, что fromDate.plusMonths(x) <= toDate, что разумно, если добавить 1 месяц к 29 января, все равно будет 28 февраля. В основном календарная арифметика странная.   -  person Jon Skeet    schedule 15.09.2017
comment
Это происходит в другие месяцы. Возможно, это время joda, когда он смотрит на длину текущего месяца (feb) с 28d и говорит, что это все еще 1 месяц.   -  person user3206236    schedule 15.09.2017
comment
Вы используете java.sql.Date или java.util.Date? Пожалуйста, задокументируйте этот факт и покажите, как вы генерируете свои Date объекты, передаваемые в виде пары аргументов. И покажите, как вы сгенерировали выходные строки. Ваш вывод не совпадает с выводом метода toString на java.sql.Date или java.util.Date. Предоставьте нам MCVE . Ваш код, как показано, не является полным, поэтому мы не можем воспроизвести ваш сценарий.   -  person Basil Bourque    schedule 15.09.2017
comment
Разве результат йоды не должен быть более правильным? В феврале 2017 года нет 29-го числа - значит, между 29 января 2017 года и 28 февраля 2017 года должен быть ровно 1 месяц? Есть ли у нас ошибка в JDK? LocalDate.of(2017, 1, 29).plusMonths(1L) приводит к java.time.LocalDate = 2017-02-28, но получение месячного интервала между ними приводит к 0. Может быть ошибкой.   -  person ailveen    schedule 15.09.2017
comment
Хм. JavaDocs ofr ChonoUnit.between явно не упоминает, как метод работает с единицами переменной длины (например, DAYS, MONTHS или YEARS или почти все из них на самом деле).   -  person Hulk    schedule 15.09.2017
comment
Моя реализация заканчивается вызовом LocalDateTime.until, который (для не привязанных ко времени единиц), в свою очередь, вызывает LocalDate.until. Однако это может уменьшить дату окончания на один день, если end.time.isBefore(time) - может быть, это так в вашем тесте (хотя это не похоже на ваш результат)?   -  person Hulk    schedule 15.09.2017


Ответы (1)


Joda-Time и Java 8 java.time.* имеют разные алгоритмы определения количества месяцев между ними.

java.time.* объявляет, что месяц прошел, только когда день месяца больше (или в следующем месяце). С 2017-01-29 по 2017-02-28 ясно, что второй день месяца (28) меньше первого (29), поэтому месяц еще не завершен.

Joda-Time заявляет, что месяц прошел, потому что добавление 1 месяца к 2017-01-29 даст 2017-02-28.

Оба являются правдоподобными алгоритмами, но я считаю, что алгоритм java.time.* больше соответствует ожиданиям людей (поэтому я выбрал другой алгоритм при написании java.time.*).

person JodaStephen    schedule 15.09.2017
comment
Меня беспокоит, что это решение было принято во время Java 8. Итак, каждый раз, когда у нас будет 28 февраля, 1 месяц пройдет только к 1 марта с 29 января - мне это кажется неправильным, особенно с учетом добавления месяца к результатам 29 января в 28 февраля. Как мне с этим справиться, чтобы я мог просто говорю себе - отпустить? - person ailveen; 15.09.2017