[英]Strange behaviour with GregorianCalendar
我刚刚遇到GregorianCalendar类的一个奇怪的行为,我想知道我是不是真的做了坏事。
这仅在初始化日期的月份的actualMaximum大于我要将日历设置为的月份时附加。
这是示例代码:
// today is 2010/05/31
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, 1); // FEBRUARY
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));
return cal.getTime(); // => 2010/03/03, wtf
我知道问题是由于日历初始化日期是31天月(可能),这与设置为2月(28天)的月份相混淆。 修复很容易(在设置年份和月份之前将day_of_month设置为1),但我想知道这真的是想要的行为。 有什么想法吗 ?
它获得当前日期/时间的实际最大值。 5月有31天,比2月28日多3天,因此将转移到3月3日。
获取/创建后,您需要调用Calendar#clear()
:
GregorianCalendar cal = new GregorianCalendar();
cal.clear();
// ...
这导致:
Sun Feb 28 23:59:59 GMT-04:00 2010
(根据我的时区,这是正确的)
如其中一个答案所述, java.util.Calendar
和Date
是史诗般的失败。 在进行密集的日期/时间操作时考虑JodaTime 。
是的,这就是它的工作方式。 如果您从具有精确日期的GregorianCalendar
开始,并通过使其不一致来修改它,那么您不应该信任您获得的结果。
根据有关getActualMaximum(..)
的文档,它指出:
例如,如果此实例的日期是2004年2月1日,则DAY_OF_MONTH字段的实际最大值为29,因为2004年是闰年,如果此实例的日期是2005年2月1日,则为28。
所以它应该可以工作,但你必须用一致的值来提供它。 2010年2月31日不正确,并且应用依赖于日期值的内容(如getActualMaximum
)无效。 它应该如何解决? 通过判断月份是错误的? 或者那天错了?
顺便说一句,因为每个人总是使用JodaTime .. :)
我敢肯定这不是通缉行为。 我同样确定在他们上课时没有人真正想过用例。 事实上,Calendar对内部状态以及它如何管理所有set方法中的所有潜在转换都有一个非常大的问题。
如果您不能在项目中使用JodaTime或JSR-310,那么在使用Calendar类时要进行大量的单元测试。 正如您在本案例中所看到的,日历代码的行为会有所不同,具体取决于您运行代码的某一天(或一天的哪个时间)。
也许setLenient(boolean lenient)
会为你排序。 当我运行下面的代码时,我得到一个例外。
如果没有,Joda是一个更好的答案。
import java.util.Calendar;
public class CalTest
{
public static void main(String[] args)
{
// today is 2010/05/31
Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, 1); // FEBRUARY
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));
System.out.println(cal.getTime());
}
}
我想提出现代的答案。
ZonedDateTime endOfFebruary2010 = LocalDate.of(2010, Month.MARCH, 1)
.atStartOfDay(ZoneId.systemDefault())
.minusNanos(1);
System.out.println(endOfFebruary2010);
在我的时区运行打印:
2010-02-28T23:59:59.999999999 + 01:00 [欧洲/哥本哈根]
无论您运行它的年份和月份,打印输出都是相同的。 对时区的依赖可能是不幸的,但可以通过指定您想要的时区来修补,例如ZoneId.of("Asia/Oral")
。 我正在使用并推荐java.time
,即现代Java日期和时间API。
如果你不可缺少地需要一个老式的java.util.Date
对象(仅在这种情况下),请转换:
Date oldFashionedDate = Date.from(endOfFebruary2010.toInstant());
System.out.println(oldFashionedDate);
Sun Feb 28 23:59:59 CET 2010
如果您只需要在某个月内计算天数(这是在一个重复的问题中提出的 ):
YearMonth ym = YearMonth.of(2011, Month.FEBRUARY);
int numDays = ym.lengthOfMonth();
System.out.println(numDays);
28
据我了解,你真正的问题是:
......我想知道这真的是想要的行为。 有什么想法吗 ?
我强烈认为,无需参与的GregorianCalendar
构造函数返回当前时间和当前时间。 并且Calender.set()
仅设置您明确设置的字段并尝试保持其他字段不变。 2010年2月31日溢出到3月,没有任何错误迹象,因为本月只有28天。 这些设计决策的结合使我得出了不可避免的结论:您观察到的行为是设计的。
如果你认为这是一个糟糕的设计,我们很多人都同意你的意见。 这也是为什么更换Calendar
和GregorianCalendar
用出来java.time
四年前了。 您永远不需要再次使用Calendar
。
java.time
在Java 7上运行良好。它至少需要Java 6 。
org.threeten.bp
的日期和时间类。 java.time
。 java.time
。 java.time
到Java 6和7(用于JSR-310的ThreeTen)。 原因应该是,MONTH具有类似枚举的逻辑结构。 您可以轻松填写和阅读数组/集合/列表。 由于国际化,它必须是可枚举的(间接访问)。 DAY只是一个可直接访问的Integer。 这就是区别。
日历从当前日期开始 - 2010年5月31日在您的示例中。 当您将月份设置为2月时,日期将更改为2010年2月31日,并标准化为2010年3月3日,因此cal.getActualMaximum(Calendar.DAY_OF_MONTH)将在3月返回31。
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2010);
c.set(Calendar.MONTH, Calendar.MAY);
c.set(Calendar.DAY_OF_MONTH, 31);
System.out.println(c.getTime());
c.set(Calendar.MONTH, Calendar.FEBRUARY);
System.out.println(c.getTime());
输出:
Mon May 31 20:20:25 GMT+03:00 2010
Wed Mar 03 20:20:25 GMT+03:00 2010
要修复代码,可以添加cal.clear(); 或在设定月份前设定第1..28天
问题是DAY_OF_MONTH
是从1开始的,第0
天是少了一天!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.