简体   繁体   English

计算两个日期之间的天数,忽略年份

[英]Calculate days between two dates ignoring year

I have lists of marriage dates in "LocalDate" type and I want to find the list of people whose anniversary is in next 30 days. 我有“ LocalDate”类型的结婚日期列表,我想查找在接下来的30天内周年纪念日的人员列表。 All of the marriage dates are from past years like 1980, 1990, 2000... 所有的结婚日期都来自过去的年份,例如1980、1990、2000 ...

I have tried to use ChronoUnit.DAYS.between() function but it only shows the number of days if the date is of todays and future day. 我尝试使用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);

The answer that I'm expecting is that if the marriage date is like 1990-04-29, then it should show me if this date is within the next 30 days or not. 我期望的答案是,如果结婚日期是1990-04-29,那么应该告诉我该结婚日期是否在接下来的30天内。 Meaning, if the anniversary of the above date is within next 30 days or not. 也就是说,上述日期的周年纪念日是否在接下来的30天内。

The required test cases for this calculation are: 此计算所需的测试用例为:

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

These test cases give a hint at what to do. 这些测试用例提示了如何做。

  1. take the marriage date 结婚日期
  2. try that date in the current year 尝试当年的那个日期
  3. if it's in the past, take that date in the next year. 如果是过去的日期,则以第二年的日期为准。
  4. calculate the number of days between today and that date 计算今天到该日期之间的天数

Step 4 will take care of leap years. 步骤4将处理of年。

I suggest to write this method: 我建议写这种方法:

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

The word anniversary already carries the information that the year of the anniversary is irrelevant, which matches the test cases above. 周年纪念字已经带有与周年纪念日无关的信息,与上面的测试用例相匹配。

Then you can easily use it like this: 然后,您可以像这样轻松地使用它:

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

Here's the test code for the above test cases: 这是上述测试用例的测试代码:

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);
    }
}

And here's the actual code for the calculation: 这是计算的实际代码:

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));
    }
}

As you can see, the test code is much longer than the actual application code. 如您所见,测试代码比实际的应用程序代码长得多。 When dealing with date calculations, this is necessary. 在处理日期计算时,这是必需的。 Even more when different time zones come into play. 当不同时区发挥作用时,甚至更多。 And leap seconds. leap秒。 And other calendar anomalies. 和其他日历异常。

So, the "basic" concept is, you want to take your list of dates and change the year to match this year. 因此,“基本”概念是,您希望获取日期列表并更改年份以匹配今年。 The "catch" is, if the resulting date is before today, you should increment the year by one, so that if it's December now, you will catch all the anniversaries which occur in January. “捕获”是,如果结果日期在今天之前,则应将年份加一,这样,如果现在是十二月,则将捕获在一月份发生的所有周年纪念日。

Maybe something like... 也许像...

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);
}

Then you would simply check to see if the dates fall within your required range... 然后,您只需检查日期是否在所需范围内...

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);
    }
}

So, with same pseudo, randomly generated date, I can get a result which looks something like... 因此,使用相同的伪随机生成的日期,我可以获得类似以下内容的结果:

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            |
+---------------+---------------+--------------+

And if we change the anchor date to something like 2019-12-20 , it could generate something like... 如果将锚定日期更改为2019-12-20类的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    |              |
+---------------+---------------+--------------+

So it's capturing the dates which land in next year. 因此,它正在捕获明年的日期。

nb: I randomly generate my test date to be within +/- one month of the anchor date so I would get better test data. nb:我随机生成的测试日期在锚定日期的+/-一个月内,因此我可以获得更好的测试数据。

Since you don't care about year at all: 由于您根本不关心年份:

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);

}

Try computing this year's anniversary and finding the difference between now and that date: 尝试计算今年的周年纪念日,找出现在和该日期之间的差额:

    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");

Outputs 输出

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM