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“需要登录”

java - 如何从Java日历中获取指定周的星期六日期? [关闭]

onActivityResult从日历返回时显示错误

日历返回错误的月份

python的calendar日历库使用方法