Рассчитать дни между двумя датами без учета года - PullRequest
2 голосов
/ 18 апреля 2019

У меня есть списки дат вступления в брак в типе "LocalDate", и я хочу найти список людей, чья годовщина наступает в следующие 30 дней. Все даты брака относятся к прошлым годам, таким как 1980, 1990, 2000 ...

Я пытался использовать функцию ChronoUnit.DAYS.between (), но она показывает только количество дней, если дата имеет сегодняшний день и будущий день.

String str = "2019-04-24";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(str, formatter);
LocalDate today = LocalDate.now();          
long dd = ChronoUnit.DAYS.between(today, date);

Ответ, который я ожидаю, состоит в том, что, если дата брака будет такой же, как 1990-04-29, тогда она должна показать мне, будет ли эта дата в ближайшие 30 дней или нет. Значение, если годовщина вышеуказанной даты наступает в течение следующих 30 дней или нет.

Ответы [ 4 ]

3 голосов
/ 18 апреля 2019

Необходимые тестовые примеры для этого расчета:

Today        Marriage     Days between
2000-01-01   ????-01-01     0
2000-01-01   ????-01-02     1
2000-01-01   ????-01-31    30
2000-01-01   ????-12-31   365   since 2000 is a leap year
2001-01-01   ????-12-31   364   since 2001 is not a leap year
2000-02-28   ????-03-01     2   since 2000 is a leap year
2000-12-31   ????-01-01     1
2000-12-01   ????-11-30   364   since 2001 is not a leap year
1999-12-01   ????-11-30   365   since 2000 is not a leap year

Эти тесты дают подсказку, что делать.

  1. взять дату свадьбы
  2. попробуйте эту дату в текущем году
  3. Если это в прошлом, возьмите эту дату в следующем году.
  4. рассчитать количество дней между сегодняшним днем ​​и этой датой

Шаг 4 будет заботиться о високосных годах.

Предлагаю написать этот метод:

int daysUntilNextAnniversary(LocalDate today, LocalDate anniversary) {
    ...
}

Слово годовщина уже содержит информацию о том, что год годовщины не имеет значения, что соответствует тестовым примерам выше.

Тогда вы можете легко использовать это так:

int days = daysUntilNextAnniversary(LocalDate.now(), marriage);
if (1 <= days && days <= 30) {
    ...
}

Вот код теста для приведенных выше тестов:

package de.roland_illig.so;

import static org.junit.Assert.assertEquals;

import java.time.LocalDate;
import org.junit.Test;

public class AnnivTest {

    // Interesting test cases are:
    //
    // Marriage:
    // - Jan 01 in leap year
    // - Feb 29 in leap year
    // - Dec 31 in leap year
    // - Jan 01 in year after leap year
    //
    // Today:
    // - Jan 01 in leap year
    // - Feb 28 in leap year
    // - Feb 28 in year before leap year
    // - Feb 29 in leap year
    // - Dec 31 in leap year
    // - Dec 31 in year before leap year
    //
    // Ideally the test would test every combination of marriage and today.
    @Test
    public void daysUntilNextAnniversary() {
        test("2000-01-01", "01-01", 0);
        test("2000-01-01", "01-02", 1);
        test("2000-01-01", "01-31", 30);
        test("2000-01-01", "12-31", 365); // since 2000 is a leap year
        test("2001-01-01", "12-31", 364); // since 2001 is not a leap year
        test("2000-02-28", "03-01", 2); //   since 2000 is a leap year
        test("2000-12-31", "01-01", 1);
        test("2000-12-01", "11-30", 364); // since 2001 is not a leap year
        test("1999-12-01", "11-30", 365); // since 2000 is not a leap year

        // Ensures that the marriage is not moved to Feb 28 just
        // because the current year doesn't have Feb 29. This happens
        // when an intermediate result is 2019-02-29, which is corrected
        // to 2019-02-28.
        test("2019-12-31", "02-29", 60);

        // In a non-leap-year, Feb 28 and Feb 29 are merged into one day.
        test("2019-02-28", "02-29", 0);
    }

    private void test(String today, String marriage, int expectedDays) {
        int actual = Anniv.daysUntilNextAnniversary(
                LocalDate.parse(today),
                LocalDate.parse("1996-" + marriage));
        assertEquals(expectedDays, actual);
    }
}

А вот фактический код для расчета:

package de.roland_illig.so;

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

public class Anniv {

    public static int daysUntilNextAnniversary(LocalDate today, LocalDate anniversary) {
        LocalDate d = anniversary.withYear(today.getYear());
        if (d.isBefore(today)) {
            d = anniversary.withYear(today.getYear() + 1);
        }
        return Math.toIntExact(ChronoUnit.DAYS.between(today, d));
    }
}

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

1 голос
/ 19 апреля 2019

Итак, «основная» концепция заключается в том, что вы хотите взять свой список дат и изменить год, чтобы соответствовать этому году. «Подвох» заключается в том, что если полученная дата до сегодняшнего дня, вам следует увеличить год на единицу, чтобы, если наступил декабрь, вы заметили все годовщины, которые происходят в январе.

Может быть, что-то вроде ...

LocalDate now = LocalDate.now();
int year = now.getYear();
List<LocalDate> dates = ...;
List<LocalDate> adjusted = new ArrayList<>(10);
for (LocalDate date : dates) {
    LocalDate warped = date.withYear(year);
    if (warped.isBefore(now)) {
        warped = warped.withYear(year + 1);
    }
    adjusted.add(warped);
}

Тогда вы просто проверите, попадают ли даты в требуемый диапазон ...

LocalDate limit = now.plusDays(30);
for (LocalDate date : adjusted) {
    if ((date.isAfter(now) || date.isEqual(now)) && (date.isBefore(limit) || date.isEqual(limit))) {
        System.out.println("~~ " + date);
    }
}

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

Input date 2019-04-19
+---------------+---------------+--------------+
| Original Date | Adjusted Date | Within range |
+---------------+---------------+--------------+
| 1996-04-13    | 2020-04-13    |              |
| 1986-04-24    | 2019-04-24    | X            |
| 1989-04-23    | 2019-04-23    | X            |
| 1960-05-11    | 2019-05-11    | X            |
| 1986-05-18    | 2019-05-18    | X            |
| 1984-04-06    | 2020-04-06    |              |
| 1997-05-29    | 2019-05-29    |              |
| 2008-03-31    | 2020-03-31    |              |
| 2014-04-18    | 2020-04-18    |              |
| 1982-04-23    | 2019-04-23    | X            |
+---------------+---------------+--------------+

И если мы изменим дату привязки на что-то вроде 2019-12-20, это может сгенерировать что-то вроде ...

+---------------+---------------+--------------+
| Original Date | Adjusted Date | Within range |
+---------------+---------------+--------------+
| 2001-12-16    | 2020-12-16    |              |
| 2005-12-28    | 2019-12-28    | X            |
| 1988-12-31    | 2019-12-31    | X            |
| 1989-11-13    | 2020-11-13    |              |
| 1976-11-13    | 2020-11-13    |              |
| 1991-01-09    | 2020-01-09    | X            |
| 1963-11-04    | 2020-11-04    |              |
| 2001-11-02    | 2020-11-02    |              |
| 1980-01-11    | 2020-01-11    | X            |
| 1979-11-17    | 2020-11-17    |              |
+---------------+---------------+--------------+

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

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

0 голосов
/ 18 апреля 2019

Так как вам вообще безразличен год:

private static boolean isWithinNext30Days(LocalDate date, LocalDate today) {

    int todayYear = today.getYear();
    int todayMonth = today.getMonthValue();

    int dateMonth = date.getMonthValue();

    if (todayMonth > dateMonth) {
        date = date.withYear(todayYear + 1);
        return today.plusDays(30).isAfter(date) && date.isAfter(today);
    }

    LocalDate alteredDate = date.withYear(todayYear);
    return today.plusDays(30).isAfter(alteredDate) && alteredDate.isAfter(today);

}
0 голосов
/ 18 апреля 2019

Попробуйте вычислить годовщину этого года и найдите разницу между текущей и этой датой:

    String str = "2008-05-20";
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDate date = LocalDate.parse(str, formatter);
    LocalDate today = LocalDate.now();          
    long dd = ChronoUnit.DAYS.between(today, date);
    System.err.println("YOUR ANSWER WAS " + dd);

    LocalDate thisYearsAnniversary = LocalDate.of(today.getYear(), date.getMonth(), date.getDayOfMonth());
    System.err.println("THIS YEARS ANNIVERSARY IS " + thisYearsAnniversary);

    dd = ChronoUnit.DAYS.between(today, thisYearsAnniversary);
    System.err.println("WHICH IS IN " + dd + " days");

Выходы

YOUR ANSWER WAS -3981
THIS YEARS ANNIVERSARY IS 2019-05-24
WHICH IS IN 36 days

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