GregorianCalendar 的奇怪行为
我刚刚遇到 GregorianCalendar 类的奇怪行为,我想知道我是否真的做了坏事。
仅当初始化日期的月份的实际最大值大于我要设置日历的月份时才会附加。
下面是示例代码:
// 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 天的月份(五月)而引起的,这与设置为二月(28 天)的月份混淆了。修复很简单(只需在设置年份和月份之前将 day_of_month 设置为 1 ),但我想知道这是否真的是想要的行为。有什么想法吗?
I just encountered a strange behaviour with the GregorianCalendar class, and I was wondering if I really was doing something bad.
This only appends when the initialization date's month has an actualMaximum bigger than the month I'm going to set the calendar to.
Here is the example code :
// 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
I know the problem is caused by the fact that the calendar initialization date is a 31 day month ( may ), which mess with the month set to february (28 days). The fix is easy ( just set day_of_month to 1 before setting year and month ), but I was wondering is this really was the wanted behaviour. Any thoughts ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
它正在获取当前日期/时间的实际最大值。 5月有31天,比2月28日多了3天,因此将转移到3月3日。
您需要致电
Calendar#clear()
获取/创建它后:这会导致:(
根据我的时区,这是正确的)
正如答案之一所述,
java.util.Calendar< /code> 和
Date
是史诗般的失败。在进行密集的日期/时间操作时,请考虑 JodaTime。It is getting the actual maximums of the current date/time. May has 31 days which is 3 more than 28 February and it will thus shift to 3 March.
You need to call
Calendar#clear()
after obtaining/creating it:This results in:
(which is correct as per my timezone)
As said in one of the answers, the
java.util.Calendar
andDate
are epic failures. Consider JodaTime when doing intensive date/time operations.是的,这就是它的工作原理。如果您从具有精确日期的
GregorianCalendar
开始,并通过使其不一致来修改它,那么您不应该相信您获得的结果。根据有关
getActualMaximum(..)
的文档,它指出:所以应该有效,但你必须为其提供一致的价值观。 2010 年 2 月 31 日 不正确,并且应用依赖于日期值的内容(例如
getActualMaximum
)无法工作。应该如何自行修复呢?通过确定那个月份是错误的?或者说日子不对?顺便说一句,正如每个人总是声明使用 JodaTime.. :)
Yes, this is how it is intended to work. If you start from a
GregorianCalendar
that has a precise date and you modify it by making it inconsistent then you shouldn't trust the results you obtain.According to the documentation about
getActualMaximum(..)
it states:So it is supposed to work but you have to feed it with consistent values. 31 February 2010 is not correct and applying things that relies on the date value (like
getActualMaximum
) can't work. How should it fix it by itself? By deciding that month is wrong? or that the day is wrong?By the way, as everyone always states use JodaTime.. :)
我确信这不是想要的行为。我同样确信没有人在上课时真正考虑过这个用例。事实上,Calendar 在内部状态以及它如何管理所有 set 方法中的所有潜在转换方面存在一个非常大的问题。
如果您无法在项目中使用 JodaTime 或 JSR-310,请在使用 Calendar 类时进行大量单元测试。正如您在本例中所看到的,日历代码的行为有所不同,具体取决于您运行代码的月份的哪一天(或一天中的什么时间)。
I'm sure it is not wanted behavior. I'm just equally sure no one really thought that use case through when they made the class. The fact of the matter is that Calendar has a very big problem with internal state and how it manages all of the potential transitions in all the set methods.
If you can't use JodaTime or JSR-310 in your project, unit test heavily when using the Calendar class. As you can see in this case Calendar code behaves differently depending on what day of the month (or what time of the day) you run the code.
也许
setLenient(boolean lenient)
会为你解决这个问题。当我运行下面的代码时出现异常。如果没有,乔达是一个更好的答案。
Maybe
setLenient(boolean lenient)
will sort it out for you. I get an exception when I run the code below.If not, Joda is a better answer.
我想贡献现代答案。
在我的时区运行,打印:
无论您在一年中的什么时间运行,打印输出都是相同的。对时区的依赖可能会令人遗憾,但可以通过指定所需的时区来修复,例如
ZoneId.of("Asia/Oral")
。我正在使用并推荐java.time
,现代 Java 日期和时间 API。如果您不可避免地需要一个老式的 java.util.Date 对象(并且仅在这种情况下),请转换:
如果您只需要计算某个月份的天数(这在 重复的问题):
据我了解,您真正的问题是:
我坚信无参数 GregorianCalendar 构造函数返回当前日期和当前时间是想要的行为。并且
Calender.set()
仅设置您显式设置的字段,并尝试保持其他字段不变。 2010 年 2 月 31 日溢出到 3 月,没有任何错误迹象,因为该月只有 28 天。这些设计决策的结合使我得出了不可避免的结论:您观察到的行为是设计使然的。如果您认为这是一个糟糕的设计,我们很多人都同意您的观点。这也是为什么四年前用
java.time
来替代Calendar
和GregorianCalendar
的原因。您将永远不需要再次使用日历
。保留:我们的工具仍然依赖于 Java 1.7
java.time
在 Java 7 上运行良好。它只需要至少 Java 6。链接
java.time< /代码>。
java.time< /code> 首次被描述。
java.time
向后移植到 Java 6 和7(JSR-310 为 ThreeTen)。I should like to contribute the modern answer.
Running in my time zone this prints:
The printout is the same no matter the time of year and month you run it. The dependency on time zone may be unfortunate, but can be mended by specifying which time zone you want, for example
ZoneId.of("Asia/Oral")
. I am using and recommendingjava.time
, the modern Java date and time API.If you indispensably need an old-fashioned
java.util.Date
object (and only in this case), convert:If you only needed a count of days in some month (this was asked in a duplicate question):
As I understand, your real question was:
I strongly believe that it is wanted behaviour that the no-arg
GregorianCalendar
constructor returns the current day and the current time of day. And thatCalender.set()
only sets the fields that you explicitly set and tries to keep other fields unchanged. And that February 31, 2010 overflows into March without any sign of error because there were only 28 days in the month. The combination of these design decisions leads me to the inevitable conclusion: the behaviour you observed is by design.If you think this is a poor design, we are many that agree with you. This was also why the replacement for
Calendar
andGregorianCalendar
came out withjava.time
four years ago now. You will never need to useCalendar
again.Reservation: Our tool is still dependent on Java 1.7
java.time
works nicely on Java 7. It just requires at least Java 6.org.threeten.bp
with subpackages.Links
java.time
.java.time
was first described.java.time
to Java 6 and 7 (ThreeTen for JSR-310).原因应该是,MONTH 具有类似枚举的逻辑结构。您可以轻松填充和读取数组/集合/列表。由于国际化,它必须是可枚举的(间接访问)。 DAY 只是一个可直接访问的整数。这就是区别。
The reason should be, that MONTH has an enumeration-like logical structure. You can easyly fill and read Arrays/Collections/Lists. Due to internationalization it has to be enumeratable (indirect access). DAY is just an direct accessible Integer. That's the difference.
日历从当天开始 - 在您的示例中为 2010 年 5 月 31 日。当您将月份设置为二月时,日期将更改为 2010 年 2 月 31 日,标准化为 2010 年 3 月 3 日,因此 cal.getActualMaximum(Calendar.DAY_OF_MONTH) 为 3 月返回 31。
输出:
要修复代码,您可以添加 cal.clear();或在设置月份之前设置日期 1..28
Calendar starts with current day - 31 may 2010 in your example. When you set month to February, date changes to 31 February 2010 which normalized to 3 March 2010, so cal.getActualMaximum(Calendar.DAY_OF_MONTH) returns 31 for March.
output:
To fix you code, you can add cal.clear(); or set day 1..28 before setting month
问题是
DAY_OF_MONTH
是从 1 开始的,而0
则少一天!The problem is that
DAY_OF_MONTH
is 1-based, day0
is one day less!