用Java两种方式在一个日期添加一个月会产生两种不同的结果(闰年哲学?)

时间:2018-04-27 17:22:13

标签: java date localdate java.util.calendar

假设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

方法2:使用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 

为什么这两个日期'输出不一样?

感谢。

3 个答案:

答案 0 :(得分: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)

所以你可以看到插值转移到了三月。如果您希望硬编码日期以用于测试目的,我建议使用严格验证来避免这些情况。还有其他与设置值相关的文档问题会使事情变得混乱。

答案 1 :(得分: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());

然后我得到预期的匹配日期。

感谢所有回答的人! :)

答案 2 :(得分:1)

TL;博士

只需使用LocalDate ,忘记所有Calendar(麻烦遗留类)。

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

2000-02-29

方法1

  

方法1:使用LocalDate plusMonths():

LocalDate课程文档解释说,它首先添加月份编号,然后单独留下日期。如果该月中的某一天在该月(29,30或31)无效,则会向后调整到最后一个有效的日期。

  

此方法分三个步骤将指定的金额添加到月份字段:

     
      
  1. 将输入月份添加到年度字段
  2.   
  3. 检查结果日期是否无效
  4.   
  5. 如有必要,将日期调整为最后一个有效日
  6.         

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

对我来说似乎是一种聪明的态度。

在您的示例中,LocalDate首先将2000-01-31更改为2000-02-31。 2月份没有第31届,所以它又回到了30年。但也没有2月30日。所以它进一步回到了29.宾果!在那个月的那一年,确实有第29天,因为2000年是Leap Year。所以答案是2000-02-29

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

2000-02-29

方法2

  

方法2:使用日历:

不要打扰。

这个非常麻烦的旧Calendar类现在已经遗留下来,完全由JSR 310中定义的 java.time 类取代。具体而言,由ZonedDateTime取代。

永远不要再触摸Calendar课程。为自己保留一些痛苦和头痛,并假装这门课程从未存在过。

此外,Calendar是与时间同步的值。不适合像问题一样的仅限日期的值。

转换旧版< - >现代

如果您必须与尚未更新到 java.time 的代码进行互操作,请通过调用添加到 java.time 的新转换方法在旧版类和 java.time 之间进行转换老班。有关详情,请参阅问题Convert java.util.Date to what “java.time” type?

关于 java.time

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

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore