java日历返回错误的星期

问题描述 投票:0回答:2

我有以下测试,据我了解应该通过。是我遗漏了什么还是日历中的错误?

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 行似乎是多余的,因为我已经设置了年、月、日、小时、分钟、秒、毫秒字段,这些字段应该是完整日期时间的足够数据。

java calendar
2个回答
16
投票

你确实让我在这件事上感到困惑,但我想通了。

规格

来自 JavaDoc:

可以通过调用 set 方法来设置日历字段值。日历中设置的任何字段值在需要计算其时间值(从纪元开始的毫秒数)或日历字段的值之前都不会被解释。调用get、getTimeInMillis、getTime、add、roll都会涉及到这样的计算。

这意味着,每当您使用 set 更改值时,您应该告诉它通过使用 get 方法之一来传播更改来更新自身。但是,您的代码没有,除非您取消注释该代码。因此,您的代码中的某些内容显然与您在computeTime中的集合配合得不好。

哪里出了问题

所以...我们可以查看这个过时但仍然非常难以导航的 GregorianCalendar源副本,并准确分析在设置星期几后执行 getTime() 时发生的情况与所有其他的变化。

呃,复制起来很痛苦。我会引导你完成它。

首先,我们基本上知道

getTimeInMillis()
在第
computeTime()
行调用
505

您声明了一个 Calendar 实例。它有预定义的值。确切地说是今天。我们现在正处于九月的第三周。稍后重要!

现在我们在第

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 月第三周的星期日,并将我们的日期 = 22 覆盖到 15 号,即 6 月第三周的星期日。

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()
将确保你的时间不会成为意外的结果。

所以基本上,导致问题的原因是你有一半脏数据和一半新数据,这些数据自相矛盾并覆盖了一些东西。如果下周运行此代码,您

甚至不会发现该错误。 对于不方便的时间相关条件来说,这怎么样?


0
投票
切勿使用

Calendar

Date
SimpleDateFormat
 等。仅使用 
java.time 类。

java.time

获取您想要的日期。

LocalDate ld = LocalDate.of( 2014, Month.JUNE, 22 ) ;
获取同一周的星期日(等)。

LocalDate previousOrSameSunday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) ) ;
    
© www.soinside.com 2019 - 2024. All rights reserved.