java日历返回错误的星期
Posted
技术标签:
【中文标题】java日历返回错误的星期【英文标题】:java calendar returns wrong week 【发布时间】:2014-09-18 16:14:56 【问题描述】:根据我的理解,我有以下测试应该通过。是我遗漏了什么还是日历中的错误?
Calendar inputC = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US);
// Sunday
inputC.set(2014, Calendar.JUNE, 22, 0, 0, 0);
inputC.set(Calendar.MILLISECOND, 0);
// START code impl
// Given a date returns back the date with it's day being first day of week and resets time.
Calendar dc = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);
dc.set(inputC.get(Calendar.YEAR), inputC.get(Calendar.MONTH), inputC.get(Calendar.DAY_OF_MONTH), 0, 0 , 0);
dc.set(Calendar.MILLISECOND, 0);
// dc.getTimeInMillis();
// dc.set(Calendar.WEEK_OF_YEAR, inputC.get(Calendar.WEEK_OF_YEAR));
dc.set(Calendar.DAY_OF_WEEK, dc.getFirstDayOfWeek());
Date output = dc.getTime();
// END code impl
Calendar expectedSundayC = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.US);
expectedSundayC.set(2014, Calendar.JUNE, 22, 0, 0, 0);
expectedSundayC.set(Calendar.MILLISECOND, 0);
assertEquals(output, expectedSundayC.getTime());
输出: 2014-06-15T00:00:00Z 预计:2014-06-22T00:00:00Z
除非我取消注释以下两行之一,否则上述测试失败:
// dc.getTimeInMillis();
// dc.set(Calendar.WEEK_OF_YEAR, inputC.get(Calendar.WEEK_OF_YEAR));
为什么dc.getTimeInMillis()
会影响输出?
取消注释上面的第 2 行似乎是多余的,因为我已经设置了年、月、日、小时、分钟、秒、毫秒字段,这些字段应该是完整日期时间的足够数据。
【问题讨论】:
我觉得影响输出的是第二行,而不是dc.getTimeInMillis()
...您可以尝试将其设置为inputC.get(Calendar.WEEK_OF_YEAR) + 1
?
就像我说的,这两行中的任何一行都会影响输出。我只能取消注释 dc.getTimeInMillis()
并且测试通过了。
星期几和星期的第一天不是一回事。
你为什么要设置DAY_OF_WEEK
?
如果您阅读了被测试代码部分的注释,它说代码的范围是获取日期并将日期设置为一周的第一天。
【参考方案1】:
你当然给了我一个头条,但我想通了。
规格
From the JavaDoc:
可以通过调用 set 方法来设置日历字段值。在需要计算其时间值(距纪元的毫秒数)或日历字段的值之前,不会解释日历中设置的任何字段值。调用 get、getTimeInMillis、getTime、add 和 roll 涉及到这样的计算。
这意味着,每当您使用 set 更改您的值时,您应该告诉它通过使用其中一种 get 方法来更新自身以传播更改。但是,您的代码没有,除非您取消注释代码。因此,您的代码中的某些内容显然不能很好地与您在 computeTime 中的集合配合使用。
哪里出了问题
所以...我们可以查看这个过时但仍然非常难以导航的 GregorianCalendar 源副本,并准确分析执行 getTime( ) 在与所有其他通道一起设置星期几之后。
呃,从那里复制是一种痛苦。我会引导你完成它。
首先,我们基本上知道getTimeInMillis()
在505
行调用computeTime()
。
您声明了一个日历实例。它具有预定义的值。确切地说是今天的日期。我们也是在九月的第三周。以后很重要!
现在我们在511
行指定您的日期。这是您的一天设置为 22 的地方。
int day = fields[DAY_OF_MONTH];
523
线是事情变得疯狂并打破约会的地方!我们将其评估为false
并转到553
行的else。
if (! isSet[MONTH] && (! isSet[DAY_OF_WEEK] || isSet[WEEK_OF_YEAR]))
//evaluates to true
我们在555
处输入if 语句,因为设置了DAY_OF_WEEK
。我们有一个DAY_OF_WEEK_IN_MONTH
,但我们自己没有设置它。日历在实例化时执行。还记得九月的第三周吗?是的,它就在这里咬我们。它计算 6 月第 3 周的星期日,将我们的日期 = 22 覆盖到 15 日,即 6 月的第 3 个星期日。
if (isSet[DAY_OF_WEEK]) //yup we set this
int first = getFirstDayOfMonth(year, month);
// 3: YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
if (isSet[DAY_OF_WEEK_IN_MONTH]) //we didn't set this, but it's been set!
if (fields[DAY_OF_WEEK_IN_MONTH] < 0)
month++;
first = getFirstDayOfMonth(year, month);
day = 1 + 7 * (fields[DAY_OF_WEEK_IN_MONTH]);
else
day = 1 + 7 * (fields[DAY_OF_WEEK_IN_MONTH] - 1);
// 15 = 1 + 7 * (3 - 1)
int offs = fields[DAY_OF_WEEK] - first;
if (offs < 0)
offs += 7;
day += offs;
我们如何解决问题
检查您使用年、月和日调用的 set
方法的源 javadoc。
设置日历字段 YEAR、MONTH、DAY_OF_MONTH、HOUR_OF_DAY 和 MINUTE 的值。 保留其他字段的先前值。如果不需要,请先调用 clear()。
果然,在您的集合返回所需结果之前添加dc.clear();
。我没有详细介绍,但考虑到当前经历的奇怪行为,最好的选择是在每次更改后发送至getTime()
,以确保您的时间不会成为意外结果。
所以基本上,导致问题的原因是您有一半脏数据和一半新数据,这些数据自相矛盾并覆盖了内容。如果这段代码在下周运行,你甚至都不会发现这个错误。对于一个不方便的时间相关条件来说,这是怎么回事?
【讨论】:
呃,我处理日历的次数越多,我就越意识到整个事情需要被弃用......不过很好理解! @berry120:顺便说一句,Java 8 对 Date API 进行了改进,并且 Joda Time 对于处理时间的人来说是司空见惯的。两者都是非常强大的 Date API,它们处理不可变的时间瞬间(从而防止您一开始就陷入这种情况)。 @Makoto 哦,我知道,新的java.time
包是一个巨大 改进 - 因此我认为现在应该埋葬所有Calendar
!
顺便提一下,Calendar
对象的一个小用功能是setLenient()
方法——我总是将它设置为 false,然后它总是会捕获这样的事情并抛出异常(而不是试图猜一个更好的约会!)
很好的发现和解释。唯一的问题是,在我的机器上,GregorianCalendar 的实现是不同的。我机器上的 GregorianCalendar 有超过 3k 行,computeTime 函数是你链接的函数的 3 倍。我的java版本java version "1.7.0_45" Java(TM) SE Runtime Environment (build 1.7.0_45-b18) Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
以上是关于java日历返回错误的星期的主要内容,如果未能解决你的问题,请参考以下文章
Google 日历 API 在公共日历中插入事件时返回错误 401“需要登录”