【问题标题】:Java Date - Leap year - strange behaviorJava Date - 闰年 - 奇怪的行为
【发布时间】:2021-01-31 21:46:21
【问题描述】:

所以我在 Java 的 Date 中遇到了这种奇怪的问题。

此问题的入口点是 2 个字符串日期 startDate("30 Jan 2016") 和 endate ""29 Jan 2017"。

这个问题有点复杂,但我已经总结了这个案例。

我需要做的是从这些数据开始,得到一个与下面的完全一样的 1 年时间表。

该表包含每个月的:第一天、最后一天以及这两天之间的天数。

预期结果:

/**

  • 2016 年 1 月 30 日星期六 00:00:00 GMT - 2016 年 2 月 28 日星期日 23:59:59 GMT - 30

  • 2016 年 2 月 29 日星期一 00:00:00 GMT - 2016 年 3 月 29 日星期二 23:59:59 BST - 30

  • 2016 年 3 月 30 日星期三 00:00:00 BST - 2016 年 4 月 29 日星期五 23:59:59 BST 2016 年 - 31

  • 2016 年 4 月 30 日星期六 00:00:00 BST - 2016 年 5 月 29 日星期日 23:59:59 BST - 30

  • 2016 年 5 月 30 日星期一 00:00:00 BST - 2016 年 6 月 29 日星期三 23:59:59 BST - 31

  • 2016 年 6 月 30 日星期四 00:00:00 BST - 2016 年 7 月 29 日星期五 23:59:59 BST 2016 年 - 30

  • 2016 年 7 月 30 日星期六 00:00:00 BST - 2016 年 8 月 29 日星期一 23:59:59 BST - 31

  • 2016 年 8 月 30 日星期二 00:00:00 BST - 2016 年 9 月 29 日星期四 23:59:59 BST - 31

  • 2016 年 9 月 30 日星期五 00:00:00 BST 2016 年 10 月 29 日星期六 23:59:59 BST 2016 年 - 30

  • 2016 年 10 月 30 日星期日 00:00:00 BST - 2016 年 11 月 29 日星期二 23:59:59 GMT - 31

  • 2016 年 11 月 30 日星期三 00:00:00 GMT - 2016 年 12 月 29 日星期四 23:59:59 GMT - 30

  • 2016 年 12 月 30 日星期五 00:00:00 GMT - 2017 年 1 月 29 日星期日 23:59:59 GMT - 31

*/

我的结果是

2016 年 1 月 30 日星期六 00:00:00 EET ::::: 2016 年 2 月 28 日星期日 23:59:59 EET ::::: 29

2016 年 2 月 29 日星期一 00:00:00 EET ::::: 2016 年 3 月 28 日星期一 23:59:59 EEST ::::: 28

2016 年 3 月 29 日星期二 00:00:00 EEST ::::: Thu Apr 28 23:59:59 EEST 2016 ::::: 30

2016 年 4 月 29 日星期五 00:00:00 EEST ::::: 2016 年 5 月 28 日星期六 23:59:59 EEST ::::: 29

2016 年 5 月 29 日星期日 00:00:00 EEST ::::: 2016 年 6 月 28 日星期二 23:59:59 EEST ::::: 30

2016 年 6 月 29 日星期三 00:00:00 EEST ::::: Thu Jul 28 23:59:59 EEST 2016 ::::: 29

2016 年 7 月 29 日星期五 00:00:00 EEST ::::: 2016 年 8 月 28 日星期日 23:59:59 EEST ::::: 30

2016 年 8 月 29 日星期一 00:00:00 EEST ::::: 2016 年 9 月 28 日星期三 23:59:59 EEST ::::: 30

2016 年 9 月 29 日星期四 00:00:00 EEST ::::: Fri Oct 28 23:59:59 EEST 2016 ::::: 29

2016 年 10 月 29 日星期六 00:00:00 EEST ::::: Mon Nov 28 23:59:59 EET 2016 ::::: 31

2016 年 11 月 29 日星期二 00:00:00 EET ::::: 2016 年 12 月 28 日星期三 23:59:59 EET ::::: 29

2016 年 12 月 29 日星期四 00:00:00 EET ::::: 2017 年 1 月 28 日星期六 23:59:59 EET ::::: 30

所以我不确定这是因为 2 月,还是因为 2016 年是闰年,其中包括 2 月 29 日。

我错过了什么?我有同样的问题,运行多个测试用例,其他都可以,但是这个。

我也尝试使用 Javas 8 LocalDate 和 LocalDateTime 执行此操作,得到完全相同的结果。

这是我的代码

import org.apache.commons.lang.time.DateUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


public class Main {

    public static void main(String[] args) throws ParseException {
        String startDate = "30 Jan 2016";
        String endDate = "29 Jan 2017"; // current not using this ?!
        SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");

        Date start = formatter.parse(startDate);

        List<Item> items = new ArrayList<>();

        for (int i = 0; i < 12; i++) {
            Date end = DateUtils.addMonths(start, 1);
            end = DateUtils.addSeconds(end, -1);
            items.add(new Item(start, end));
            start = DateUtils.addMonths(start, 1);
        }

        items.forEach(item -> {
            System.out.println(item.getStart() + " ::::: " + item.getEnd() + " ::::: " + getDifferenceDays(item.getStart(), item.getEnd()));
        });

    }

    public static long getDifferenceDays(Date d1, Date d2) {
        return ChronoUnit.DAYS.between(d1.toInstant(), d2.toInstant());
    }

}
  • 物品类别
import java.util.Date;

public class Item {
    Date start;
    Date end;

    public Item(Date start, Date end) {
        this.start = start;
        this.end = end;
    }

    public Date getStart() {
        return start;
    }

    public void setStart(Date start) {
        this.start = start;
    }

    public Date getEnd() {
        return end;
    }

    public void setEnd(Date end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "Item{" +
                "start=" + start +
                ", end=" + end +
                '}';
    }
}

【问题讨论】:

  • 好吧,对于初学者,您不应该使用DateCalendarSimpleDateFormat 类。他们很麻烦。请改用java.time 中的类。
  • 正如我所说...我使用了 Javas 8 LocalDate 和 LocalDateTime 并且得到了相同的行为。
  • 你试过docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html;设置一个区域。哦,我看到 DateUtils 是一个外部库,回想一下我对它的评论。
  • DateUtils 来自导入 org.apache.commons.lang.time.DateUtils;我不明白 ZonedDateTime 类的确切含义。仍然 O 将我的计算机日期更改为 GTM,而不是我得到相同的时区,但日期行为是相同的。
  • PeterMmm 是正确的:您没有正确使用 DateUtils。但更重要的是,如果您使用的是 Java 8 或更高版本,那么使用新的 Java“时间”会更好。建议:请尝试 Ole V.V 的建议。如果可行,请“支持”并“接受”它。

标签: java date datetime


【解决方案1】:

这里有一个稍微不同的方法:

LocalDate startDate = LocalDate.of(2016, 1, 30);
LocalDate endDate = LocalDate.of(2017, 1, 30);

Function<YearMonth, LocalDate> addMonthFunction = ym -> ym
    .atDay(Math.min(startDate.getDayOfMonth(), ym.lengthOfMonth()));

long months = ChronoUnit.MONTHS.between(startDate, endDate);
YearMonth yearMonth = YearMonth.from(startDate);
Stream.iterate(yearMonth, ym -> ym.plusMonths(1))
    .limit(months)
    .map(addMonthFunction)
    .map(LocalDate::atStartOfDay)
    .map(date -> {
        LocalDateTime end = addMonthFunction.apply(YearMonth.from(date.plusMonths(1)))
            .atStartOfDay()
            .minusSeconds(1);
        long between = ChronoUnit.DAYS.between(date, end) + 1;
        return String.format("%s to %s (%s days)", date, end, between);
    })
    .forEach(System.out::println);

这个想法是,每个下一个日期总是在该月的第 30 天(或更准确地说,与开始日期相同的日期),除非该日期无效。

我在这里使用了LocalDates,但您可以使用ZonedDateTime 使其对时区敏感。

【讨论】:

  • 非常有趣!谢谢。
【解决方案2】:

java.time

由于您可以使用现代 Java 日期和时间 API java.time,我建议您坚持使用旧类 SimpleDateFormatDate。那么你也不需要 Apache DateUtilsChronoUnit 枚举来自 java.time。

    DateTimeFormatter dateFormatter
            = DateTimeFormatter.ofPattern("d MMM u", Locale.ENGLISH);
    
    String startDateString = "30 Jan 2016";
    LocalDate originalStartDate
            = LocalDate.parse(startDateString, dateFormatter);
    for (int i = 0; i < 12; i++) {
        LocalDate startDate = originalStartDate.plusMonths(i);
        LocalDate nextStartDate = originalStartDate.plusMonths(i + 1);
        LocalDate endDate = nextStartDate.minusDays(1);
        long differenceDays = ChronoUnit.DAYS.between(startDate, nextStartDate);
        
        System.out.format("%s - %s : %d%n", startDate, endDate, differenceDays);
    }

输出是:

2016-01-30 - 2016-02-28 : 30
2016-02-29 - 2016-03-29 : 30
2016-03-30 - 2016-04-29 : 31
2016-04-30 - 2016-05-29 : 30
2016-05-30 - 2016-06-29 : 31
2016-06-30 - 2016-07-29 : 30
2016-07-30 - 2016-08-29 : 31
2016-08-30 - 2016-09-29 : 31
2016-09-30 - 2016-10-29 : 30
2016-10-30 - 2016-11-29 : 31
2016-11-30 - 2016-12-29 : 30
2016-12-30 - 2017-01-29 : 31

你的代码出了什么问题?

您观察到的意外结果背后有几个原因。

  1. 当您将一个月添加到 1 月 30 日时,您会得到预期的 2 月 29 日。在非闰年,你会得到 2 月 28 日。当你在 2 月 29 日再增加一个月时,你会得到 3 月 29 日。你想想是不是很惊讶?在非闰年,您会得到 3 月 28 日,因此 2016 年是闰年实际上帮助您更接近 获得想要的结果。在我的代码中,我通过在原始开始日期添加正确的月数而不是在之前的开始日期添加一个月来解决此问题。
  2. 正如 PeterMmm 已经说过的,ChronoUnit.DAYS.Between() 可以计算满 24 小时。丢弃任何部分日期。甚至是 23 小时 59 分 59 秒。从 Sat Jan 30 00:00:00 EET 2016 到 Sun Feb 28 23:59:59 EET 2016 是 30 天 23 小时 59 分 59 秒,所以你得到的结果是 30 天。在我的代码中,我通过计算下一项开始之前的天数来解决问题。

顺便说一句:从 2016 年 2 月 29 日星期一 00:00:00 EET 到 2016 年 3 月 28 日星期一 23:59:59 EEST,因为过渡到夏令时 (DST) 只有 28 天 22 小时强>59分59秒。因此,在这种情况下,避免减去一秒并不能解决您的问题。

【讨论】:

  • 谢谢!!!我没这么看。日期/时间/闰年在编程中有些棘手。很高兴带有 java.time 包的 Java 8 解决了很多这些问题。
【解决方案3】:

这个

end = DateUtils.addSeconds(end, -1);

没有必要。或者做

end = DateUtils.addSeconds(end, 0);

查看between() 文档。 第二个参数是专有。这意味着,您的第二个参数没有达到一天的结束,因此会少一天计算(仅计算完整的 24 小时天数)。

【讨论】:

    猜你喜欢
    • 2017-01-30
    • 2012-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多