【问题标题】:Calculate days between two dates ignoring year计算两个日期之间的天数,忽略年份
【发布时间】:2019-04-18 02:10:01
【问题描述】:

我有“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 年 4 月 29 日,那么它应该告诉我这个日期是否在接下来的 30 天内。意思是,如果上述日期的周年纪念日在接下来的 30 天内。

【问题讨论】:

  • 那么,您要取“原始”日期,将年份改为今年,然后检查日期是否在 30 天内?
  • @MadProgrammer 不,没那么简单。计算也应该在 12 月起作用。
  • @RolandIllig 确实如此,但我正在尝试确定基本要求

标签: java java-8


【解决方案1】:

此计算所需的测试用例是:

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) {
    ...
}

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

如您所见,测试代码比实际应用程序代码长得多。在处理日期计算时,这是必要的。当不同的时区发挥作用时更是如此。和闰秒。以及其他日历异常。

【讨论】:

  • 是的,代码非常相似。我专注于不在代码中重复自己。由于神奇的数字 30,我担心我可能会在代码中犯一些错误,因为只有 30 作为测试用例会使选择好的测试用例变得更加困难。我还想避免将日期拆分为 (year, month) 元组,因为它们可能与其他日期混淆。更少的变量通常意味着代码更容易理解。哦,我想通过拥有良好的测试用例来更多地关注开发可靠代码的过程。
【解决方案2】:

因此,“基本”概念是,您希望获取日期列表并更改年份以匹配今年。 “catch”是,如果结果日期在今天之前,你应该将年份加一,这样如果现在是 12 月,你将捕捉到 1 月发生的所有周年纪念日。

也许是……

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:我随机生成我的测试日期,使其在锚定日期的 +/- 一个月内,这样我可以获得更好的测试数据。

【讨论】:

    【解决方案3】:

    因为你根本不关心年份:

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

    【讨论】:

    • 我认为您需要将其设置为当前年份才能正确计算结束日期。而且我认为您根本不需要一年来比较开始日期。
    • @shmosel 有什么区别?它只是两边的常数
    • 闰日可能会影响 30 天的计算。
    【解决方案4】:

    尝试计算今年的周年纪念日并找出现在和那个日期之间的差异:

        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
    
    

    【讨论】:

    • 举个不同的例子,婚礼是 2009 年 1 月 14 日,今天是 2019 年 12 月 30 日。预期产出为 15 天(因此在 30 天内)。在你的 sn-p 周年纪念日将是 2019 年 1 月 14 日和 -350 天。
    • @allanshivji 你应该在 12 月再次问同样的问题,看看它是否仍然适合你。
    猜你喜欢
    • 2014-08-30
    • 2021-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-02
    • 1970-01-01
    • 2022-01-19
    相关资源
    最近更新 更多