Java中日期加一个月有两种方法,结果不同是为什么?(闰年问题?)

3
假设2000年1月31日是一个闰年,以下两种添加一个月的方法会得到不同的结果。我是否做错了什么,还是这两种处理闰年月份的方式不同?如果这两种方法只是哲学上的差异,你如何知道该选择哪种方法? 方法1: 使用LocalDate plusMonths():
LocalDate atestDate = LocalDate.parse("2000-01-31");
System.out.println("One month in future using LocalDate.addMonths() " +   atestDate.plusMonths(1));

输出:

One month in future using LocalDate.addMonths() 2000-02-29

方法二:使用 Calendar
Calendar zcal = Calendar.getInstance();
zcal.set(Calendar.DAY_OF_MONTH, 31);
zcal.set(Calendar.MONTH, 1);
zcal.set(Calendar.YEAR, 2000);
zcal.add(Calendar.MONTH, 0);
System.out.println("ONE MONTH IN FUTURE using Calendar: " 
 + zcal.getTime());

输出:

ONE MONTH IN FUTURE using Calendar: Thu Mar 02 2000 

为什么这两个日期的输出不同呢?
谢谢。

请指定Java版本。 - this_is_om_vm
2
zcal.add(Calendar.MONTH, 0) 不会改变日历的日期,因为你加的是零。此外,zcal.set(Calendar.MONTH, 1) 将月份设置为二月,因为 Calendar.FEBRUARY 等于 1,而不是 2。Calendar.JANUARY 是 0。许多人认为这是一个设计错误,但无论如何,如果你使用常量而不是字面整数值,就不会有这个问题。 - VGR
实际上,对我来说,zcal.add(Calendar.Month, 0)确实会将月份加一。你可以试试看。 - Morkus
更正我上面的评论,我应该这样做:zcal.set(Calendar.MONTH, Calendar.JANUARY)。然后zcal.add(Calendar.MONTH, 1)就会按预期正确执行。是我的错误。 - Morkus
值得注意的是,像 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 等老旧的日期-时间类现已成为遗留的类,已被内置于 Java 8 及更高版本的 java.time 类所取代。请参阅 Oracle 的 教程 - Basil Bourque
3个回答

3
zcal.set(Calendar.DAY_OF_MONTH, 31);
zcal.set(Calendar.MONTH, 1);
zcal.set(Calendar.YEAR, 2000);

这定义了2000年2月31日的日期,由于宽松的计算方式,该日期被等同于2000年3月2日。月份值从零开始索引。

设置为严格模式:

zcal.setLenient(false);

我们遇到了一个异常:

Exception in thread "main" java.lang.IllegalArgumentException: MONTH: 1 -> 2
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2829)
at java.util.Calendar.updateTime(Calendar.java:3393)
at java.util.Calendar.getTimeInMillis(Calendar.java:1782)
at java.util.Calendar.getTime(Calendar.java:1755)
at Employee.Tester.main(Tester.java:19)

因此,您可以看到插值已经移到了三月份。如果您希望硬编码日期以进行测试,请使用严格的验证来避免这些情况。已经有其他记录问题与设置值相关,这会使事情变得混乱。


2

简述

只需使用LocalDate,忘记所有关于Calendar(麻烦的旧类)的问题。

LocalDate.parse( "2000-01-31" ).plusMonths( 1 ).toString()

2000年2月29日

方法1

方法1:使用LocalDate plusMonths():

LocalDate类的文档解释了它首先添加月份,保持日期不变。如果该日期在该月份中无效(例如29、30或31),则向后调整到上一个有效日期。

此方法将按以下三个步骤将指定数量添加到月份字段中:

  1. 将输入月份添加到年份字段中
  2. 检查结果日期是否无效
  3. 必要时将日期调整为上一个有效日期

例如,2007-03-31加一月会导致无效日期2007-04-31。而不是返回无效结果,选择该月的最后一个有效日期2007-04-30。

我认为这是一个聪明的方法。

在您的示例中,LocalDate首先将2000-01-31更改为2000-02-31。二月没有31号,因此它向后走到30号。但是二月也没有30日。所以它继续向后走到29日。恭喜!在那一年的那个月中确实有29天,因为2000年是闰年。因此答案是2000-02-29

LocalDate.parse( "2000-01-31" )
         .plusMonths( 1 )
         .toString()

2000-02-29

方法2

方法2:使用Calendar:

不要再烦恼了。

这个极其麻烦的旧的Calendar类现已被完全取代,由JSR 310中定义的java.time类替代。具体而言,被ZonedDateTime替换。

永远不要再碰Calendar类了。节省一些痛苦和头疼,假装这个类从未存在过。

此外,Calendar是一个带有时间值的日期。不适合像问题中那样只有日期的值。

转换旧版<->现代版

如果您必须与尚未更新到java.time的代码进行交互,请通过调用添加到旧类中的新转换方法在旧类和java.time之间进行转换。有关更多信息,请参见问题:将java.util.Date转换为“java.time”类型?


关于java.time

java.time框架内置于Java 8及更高版本。这些类取代了麻烦的旧旧版日期时间类,例如java.util.DateCalendarSimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

要了解更多,请参阅Oracle Tutorial。并在Stack Overflow上搜索许多示例和解释。规范为JSR 310
您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,也不需要java.sql.*类。
如何获取java.time类? ThreeTen-Extra 项目是在 java.time 的基础上扩展出的额外类库。该项目是可能未来添加到 java.time 中的一个试验场。您可以在这里找到一些有用的类,例如 IntervalYearWeekYearQuarter 等等。更多信息

1
在我之前发布的内容中出现的问题实际上是一个打字错误。
Calendar zcal = Calendar.getInstance();
zcal.set(Calendar.DAY_OF_MONTH, 31);
zcal.set(Calendar.MONTH, 1);
zcal.set(Calendar.YEAR, 2000);
zcal.add(Calendar.MONTH, 0);
System.out.println("ONE MONTH IN FUTURE using Calendar: " 
 + zcal.getTime());

应该是:
Calendar zcal = Calendar.getInstance();
zcal.set(Calendar.DAY_OF_MONTH, 31);
zcal.set(Calendar.MONTH, Calendar.JANUARY);
zcal.set(Calendar.YEAR, 2000);
zcal.add(Calendar.MONTH, 1);
System.out.println("ONE MONTH IN FUTURE using Calendar: " 
 + zcal.getTime());

然后我得到了期望的匹配日期。
感谢所有回答的人! :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接