我有以下测试,据我了解应该通过。是我遗漏了什么还是日历中的错误?
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()
会影响输出?你确实让我在这件事上感到困惑,但我想通了。
可以通过调用 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()
将确保你的时间不会成为意外的结果。所以基本上,导致问题的原因是你有一半脏数据和一半新数据,这些数据自相矛盾并覆盖了一些东西。如果下周运行此代码,您
甚至不会发现该错误。 对于不方便的时间相关条件来说,这怎么样?
Calendar
、
Date
、
SimpleDateFormat
等。仅使用java.time 类。
LocalDate ld = LocalDate.of( 2014, Month.JUNE, 22 ) ;
获取同一周的星期日(等)。
LocalDate previousOrSameSunday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) ) ;