Разница между двумя календарными экземплярами, дающими противоречивые ответы - PullRequest
0 голосов
/ 28 сентября 2018

Я использую jdk 1.7 и использую класс Calendar для вычисления разницы между двумя датами.Я использую приведенный ниже код, но он дает противоречивые результаты.Имеется в виду, что иногда это правильно, но иногда он выключен на день или еще что-то, и нет никакого шаблона для него.

public class Test {

    public static void main(String ar[]) {

        System.out.println(calculateDays());
    }

    private static long calculateDays() {
        long days_past_due;
           Calendar cal1 = Calendar.getInstance();
           Calendar cal2 = Calendar.getInstance();
           cal1.set(2013, 12, 1);
           cal2.set(2013, 12, 30);
           days_past_due = getDifference(cal1, cal2, TimeUnit.DAYS);
        return days_past_due;
    }

    public static long getDifference(Calendar b, Calendar a, TimeUnit units) 
    {

        return units.convert(b.getTimeInMillis() - a.getTimeInMillis(), TimeUnit.MILLISECONDS);
    }

Примеры:

Example 1: cal1.set(2013, 12, 1);
           cal2.set(2013, 12, 1);
           Answer returned: 0 (Correct)
Example 2: cal1.set(2013, 12, 1);
           al2.set(2013, 12, 2);
           Answer returned: -1 (Correct)
Example 3: cal1.set(2013, 11, 30);
           cal2.set(2013, 12, 1);
           Answer returned: -2 (Incorrect)
Example 4: cal1.set(2013, 8, 31);
           cal2.set(2013, 8, 31);
           Answer returned: 0 (Correct)
Example 5: cal1.set(2013, 8, 31);
           cal2.set(2013, 9, 1);
           Answer returned: 0 (Incorrect)
Example 6: cal1.set(2013, 6, 30);
           cal2.set(2013, 6, 30);
           Answer returned: 0 (Correct)
Example 7: cal1.set(2013, 6, 30);
           cal2.set(2013, 7, 1);
           Answer returned: -2 (Incorrect)

Что я делаю здесь неправильно

Ответы [ 3 ]

0 голосов
/ 28 сентября 2018

Javadoc для Calendar#set(int,int,int) говорит (мой акцент)

Параметры:

year - значение, используемое для установки поля календаря YEAR.

month- значение, используемое для установки поля календаря MONTH. Значение месяца основано на 0.например, 0 для января.

дата - значение, используемое для установки календарного поля DAY_OF_MONTH.

Если вы пересмотрите свои примеры с учетом этогои помните, что даты становятся «нормализованными» (т. е. если вы укажете 13-й месяц, он станет первым месяцем следующего года), вы обнаружите, что все рассчитанные различия действительно корректны.

0 голосов
/ 29 сентября 2018

что было бы проще сделать тогда?Использование того же класса Календаря

Нет более простого способа использования Calendar.Существует более громоздкий способ объявления ошибок.

Сначала необходимо правильно инициализировать два Calendar объекта:

    Calendar cal1 = Calendar.getInstance();
    Calendar cal2 = Calendar.getInstance();
    cal1.clear();
    cal2.clear();
    cal1.set(2013, Calendar.NOVEMBER, 30);
    cal2.set(2013, Calendar.DECEMBER, 1);

A Calendar объект, несмотря на то, что его имя представляет дату, время суток, часовой пояс и система календаря.Когда мы хотим использовать его для даты в григорианском календаре только в часовом поясе, нам нужно сначала очистить поля, чтобы избавиться от времени суток, иначе это помешает последующим вычислениям.Затем используйте именованные константы класса Calendar для месяцев дат, которые вы хотите установить, чтобы уменьшить путаницу.

Во-вторых, чтобы Calendar выполнял переходы в летнее время (DST) и другие и другиеаномалии во внимание, нет другого способа, кроме как добавить дни и посмотреть, когда мы там - это обратный способ сделать это:

    if (cal1.before(cal2)) {
        int daysPastDue = 0;
        while (! cal1.after(cal2)) {
            daysPastDue++;
            cal1.add(Calendar.DATE, 1);
        }
        // Now cal1 is after cal2, so we’ve counted 1 day too many. Compensate:
        daysPastDue--;
        System.out.println("Answer returned: " + daysPastDue);
    }

Это печатает:

Ответ возвращен: 1

Я, конечно, не поощряю делать это так, как я показал.Это сравнительно много строк кода, и довольно легко забыть одну деталь и получить неправильный результат или сделать ошибку по одному.Обновите до Java 8 или 9 или 10 или 11, если можете.Если вы не можете, ответ Бэзила Бурка прекрасно работает и с библиотекой ThreeTen Backport, посмотрите его статью о Java SE 6 и Java SE 7.

PS: Перечисление TimeUnit отличнодля преобразования между чем-то от микросекунд до часов, не используйте это в течение нескольких дней, по крайней мере, в этом случае.TimeUnit.DAYS считается 24 часа, в то время как дни в календаре могут быть 23, 24 или 25 часов и иногда имеют другую продолжительность.

0 голосов
/ 28 сентября 2018

tl; dr

Вы должны использовать LocalDate с нормальным подсчетом вместо проблемного Calendar класса.

ChronoUnit.DAYS.between(
    LocalDate.of( 2013 , 11 , 30 ) ,
    LocalDate.of( 2013 , 12 , 1 )
)

См. Этот код, запущенный в прямом эфире на IdeOne.com .

1

Сумасшедший счет

Вы, кажется, не знаете о сумасшедшем подсчете, используемом в ужасном классе Calendar: 0-11 в течение месяцев январь-декабрь.

То есть cal1.set(2013, 11, 30) до cal2.set(2013, 12, 1) означает 30 октября - 1 ноября, что действительно составляет два дня, что соответствует 31 октября. Вы, очевидно, ошибочно считали это с 30 ноября по 1 декабря, но нет.

Это подсчет индекса, начиная с нуля .К сожалению, подсчет индексов проявляется слишком часто, когда некоторые программисты неуместно используют то, что имело смысл с примитивными массивами или скачками памяти в системном программировании или программировании в стиле C старой школы.В обычных бизнес-приложениях с современными языками подсчет порядковых номеров обычно имеет больше смысла.Например, думать о январе как о первом месяце, месяце 1 и декабре как о двенадцатом месяце, месяце 12.

java.time

К счастью, у нас есть java.time классы сейчас.У вас нет причин использовать ужасный беспорядок, который представляет собой устаревшие классы даты и времени, такие как Date, Calendar и SimpleDateFormat.

Для использования с Java 7 см. ThreeTen-Backport проект, связанный в пулях внизу.

LocalDate

Класс LocalDate представляет значение только для датыбез времени суток и без часового пояса.

Часовой пояс имеет решающее значение при определении даты.В любой момент времени дата меняется по всему земному шару в зависимости от зоны.Например, через несколько минут после полуночи в Париж Франция - это новый день, в то время как "вчера" в Монреаль Квебек .

Если часовой пояс не указан,JVM неявно применяет свой текущий часовой пояс по умолчанию.Это значение по умолчанию может измениться в любой момент во время выполнения (!), Поэтому ваши результаты могут отличаться.Лучше указать в качестве аргумента желаемый / ожидаемый часовой пояс .

Укажите правильное имя часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland.Никогда не используйте 3-4-буквенное сокращение, например EST или IST, так как они не истинных часовых поясов, не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

Если вы хотите использовать текущий часовой пояс JVM по умолчанию, запросите его и передайте в качестве аргумента.Если опущено, текущее значение по умолчанию JVM применяется неявно.Лучше быть явным, поскольку значение по умолчанию может быть изменено в любой момент во время выполнения любым кодом в любом потоке любого приложения в JVM.

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

Или укажите дату.Вы можете установить месяц по номеру, с нормальным номером 1-12 для января-декабря.

LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

Или, лучше, использовать предварительно определенные объекты перечисления Month, по одному на каждый месяц года.Совет: используйте эти Month объекты по всей вашей кодовой базе, а не просто целое число, чтобы сделать ваш код более самодокументируемым, обеспечить допустимые значения и обеспечить type-safety .

LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;

ChronoUnit.DAYS

Для подсчета прошедших дней используйте перечисление ChronoUnit.

long days = ChronoUnit.DAYS.between( start , stop ) ;

О java.time

Среда java.time встроена в Java 8 и более поздние версии.Эти классы вытесняют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует выполнить переход на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial .И поиск переполнения стека для многих примеров и объяснений.Спецификация JSR 310 .

Youможет обмениваться java.time объектами напрямую с вашей базой данных.Используйте драйвер JDBC , совместимый с JDBC 4.2 или более поздней версией.Нет необходимости в строках, нет необходимости в java.sql.* классах.

Где получить классы java.time?

  • Java SE 8 , Java SE 9 , Java SE 10, Java SE 11 и более поздние версии - часть стандартного Java API с связанной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и JavaSE 7
    • Большая часть функций java.time перенесена на Java 6 & 7 в ThreeTen-Backport .
  • Android
    • Более поздние версии пакетов Android для реализации java.time классы.
    • Для более ранних версий Android (<26) проект <a href="https://github.com/JakeWharton/ThreeTenABP" rel="nofollow noreferrer"> ThreeTenABP адаптируется ThreeTen-Backport (упомянуто выше).См. Как использовать ThreeTenABP… .

ThreeTen-Extra Проект расширяет java.time дополнительными классами.Этот проект является полигоном для возможных будущих дополнений к java.time.Здесь вы можете найти некоторые полезные классы, такие как Interval, YearWeek, YearQuarter и more .

...