Как получить правильное количество месяцев и дней между несколькими датами? - PullRequest
0 голосов
/ 27 марта 2019

Мне нужно вычислить правильное количество месяцев и дней между списками интервалов:

31/03/2017  30/09/2017
01/10/2017  31/03/2018
01/04/2018  30/09/2018
01/10/2018  31/12/2019

Если я вычислю все дни между этими интервалами, а затем разделю на 30, я получу

MONTHS: 33 DAYS: 12

но я хочу:

MONTHS: 33 DAYS: 1 (the only day to compute is the first day of first interval)

Ответы [ 2 ]

2 голосов
/ 28 марта 2019

Ответ Ole VV является правильным и разумно использует современные java.time классы.

LocalDateRange

КакВ качестве бонуса я упомяну добавление отличной библиотеки ThreeTen-Extra в ваш проект для доступа к классу LocalDateRange.Этот класс представляет промежуток времени в виде пары LocalDate объектов.Кажется, это соответствует вашей бизнес-проблеме.

LocalDate start = … ;
LocalDate stop = … ;
LocalDateRange range = LocalDateRange.of( start , stop ) ;

Из этого объекта LocalDateRange вы можете получить Period объекты, используемые в этом Answer by Ole VV

Period period = range.toPeriod() ;

Обратите внимание, что LocalDateRange имеет удобные методы для сравнения, такие как abuts, contains, overlaps и т. Д.

Half-Open

Вы можетебыть запутанным о том, как обращаться с концом месяца / первым месяцем.

Обычно при обработке даты и времени лучше всего представлять промежуток времени, используя подход Half-Open.В этом подходе начало включительно , а окончание исключительно .

Таким образом, месяц начинается с первого числа месяца и продолжается до, но не включает первое число следующего месяца.

LocalDateRange rangeOfMonthOfNovember2019 = 
    LocalDateRange.of(
        LocalDate.of( 2019 , Month.NOVEMBER , 1 ) ,
        LocalDate.of( 2019 , Month.DECEMBER , 1
    )
;

Вы можете попросить каждого LocalDateRange за прошедшие дни: LocalDateRange::lengthInDays.

DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd/MM/uuuu" );
List < LocalDateRange > ranges = new ArrayList <>( 4 );
ranges.add(
        LocalDateRange.of(
                LocalDate.parse( "31/03/2017" , f ) ,
                LocalDate.parse( "30/09/2017" , f )
        )
);
ranges.add(
        LocalDateRange.of(
                LocalDate.parse( "01/10/2017" , f ) ,
                LocalDate.parse( "31/03/2018" , f )
        )
);
ranges.add(
        LocalDateRange.of(
                LocalDate.parse( "01/04/2018" , f ) ,
                LocalDate.parse( "30/09/2018" , f )
        )
);
ranges.add(
        LocalDateRange.of(
                LocalDate.parse( "01/10/2018" , f ) ,
                LocalDate.parse( "31/12/2019" , f )
        )
);

// Sum the periods, one from each range.
Period period = Period.ZERO;
int days = 0;
for ( LocalDateRange range : ranges )
{
    days = ( days + range.lengthInDays() );
    period = period.plus( range.toPeriod() ).normalized();
}

Дамп на консоль.

System.out.println( "ranges: " + ranges );
System.out.println( "days: " + days + "  |  pseudo-months: " + ( days / 30 ) + " and days: " + ( days % 30 ) );
System.out.println( "period: " + period );

диапазоны: [2017-03-31 / 2017-09-30, 2017-10-01 / 2018-03-31, 2018-04-01 / 2018-09-30, 2018-10-01 / 2019-12-31]

дней: 1002 |псевдо-месяцы: 33 и дни: 12

период: P2Y5M119D

Если вы настаиваете на том, чтобы быть полностью закрытым, а не полуоткрытым, LocalDateRange все равно может помочь вам с его ofClosed метод.Но я настоятельно рекомендую вместо этого принять полуоткрытое, поскольку, как я полагаю, вы обнаружите, что вам будет проще использовать один согласованный подход ко всему коду.И обучайте своих пользователей.Я видел много путаницы среди офисного персонала, делающего неправильные предположения о инклюзивных / эксклюзивных датах.Ваша практика отслеживания конца месяца и конца месяца, вероятно, вызовет еще большую путаницу.

YearMonth

Еще один класс, который может оказаться полезным, встроен в Java: YearMonth.Этот класс представляет отдельный месяц как единое целое.

YearMonth yearMonth = YearMonth.of( 2019 , Month.NOVEMBER ) ;

Обратите внимание на такие методы, как atDay для получения LocalDate из YearMonth.

ISO 8601

Совет. При сериализации значений даты и времени в виде текста используйте привычку использовать только стандартные форматы ISO 8601, а не локализованные форматы.

Поэтому для даты используйте YYYY-MM-DD.

Классы java.time по умолчанию используют эти форматы при разборе / генерации текста.Поэтому не нужно указывать шаблон форматирования.

LocalDate.parse( "2019-01-23" ) 

Стандартный формат ISO 8601 для всего месяца - ГГГГ-ММ, например 2019-11.

YearMonth yearMonth = YearMonth.parse( "2019-11" ) ;  // Entire month of November 2019.

О java.time

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

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

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

Вы можете обмениваться 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 .

1 голос
/ 28 марта 2019

Вы можете игнорировать промежуточные даты и просто вычислить разницу между первой и последней датой:

    LocalDate first = LocalDate.of(2017, 3, 31);
    LocalDate lastInclusive = LocalDate.of(2019, 12, 31);
    LocalDate lastExclusive = lastInclusive.plusDays(1);
    Period between = Period.between(first, lastExclusive);

    System.out.format("Months: %s days: %d%n",
            between.toTotalMonths(), between.getDays());

В этом случае выводится:

Месяцев: 33дни: 1

Поскольку Period.between() вычисляет разницу до конечной даты исключая и вы хотите, чтобы конечная дата была включена, я добавляю один день к вашей конечной дате.

Месяцы могут длиться 28, 29, 30 и 31 день и в среднем превышают 30 дней, поэтому деление на 30 дало вам слишком много дней.Вы можете прочитать точную работу Period.between() и проверить, всегда ли она дает вам то, что вы хотите.

Ссылки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...