1.背景:
百度知道上有人咨询了 Calendar 设置 Calendar.DAY_OF_WEEK不正确的问题
参见 http://zhidao.baidu.com/question/748936560786113052
他的入职时间是
"2006-02-14"
,要计算20年后的所在周的周六
理论上, 2026-02-14
所在周的周六,正好就是 2026-02-14
他写的代码是
public class Test {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date=sdf.parse("2006-02-14");
System.out.println(sdf.format(date));
//********************************************************************
Calendar c=Calendar.getInstance();
c.setTime(date);
c.add(Calendar.YEAR, 20);
c.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
//********************************************************************
System.out.println("入职20周年纪念日派对日期:"+sdf.format(c.getTime()));
}
}
看上去没毛病,但是结果死活就是 2026-02-21
但是,在中间插入一行代码 System.out.println(c.get(Calendar.DAY_OF_WEEK));
public class Test {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date=sdf.parse("2006-02-14");
System.out.println(sdf.format(date));
//********************************************************************
Calendar c=Calendar.getInstance();
c.setTime(date);
c.add(Calendar.YEAR, 20);
System.out.println(c.get(Calendar.DAY_OF_WEEK));//有就正确,没有就错误:输出2026-02-21
c.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY); //正确日期2026-02-14
//********************************************************************
System.out.println("入职20周年纪念日派对日期:"+sdf.format(c.getTime()));
}
}
结果就是正确的 2026-02-14
是不是很神奇?颠覆世界观,觉得不可思议?
2.原因
原因在于,当你设置 DAY_OF_WEEK
的时候, 你需要设置 WEEK_OF_MONTH
或者 DAY_OF_WEEK_IN_MONTH
或者 WEEK_OF_YEAR
, 否则会使用老的WEEK_OF_MONTH
字段
When computing time (milliseconds), GregorianCalendar leaves some fields inconsistent.
Then, after the last set(DAY_OF_WEEK), an invalid (older) WEEK_OF_MONTH value is used in the last getTime() call.When you set DAY_OF_WEEK, the calendar expects a week field (
WEEK_OF_MONTH
,DAY_OF_WEEK_IN_MONTH
orWEEK_OF_YEAR
) has also been set.
So, avoid setting DAY_OF_WEEK without setting one of the week fields.
原先的这段代码
public class Test {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date=sdf.parse("2006-02-14");
System.out.println(sdf.format(date));
//********************************************************************
Calendar c=Calendar.getInstance();
c.setTime(date);
c.add(Calendar.YEAR, 20);
c.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
//********************************************************************
System.out.println("入职20周年纪念日派对日期:"+sdf.format(c.getTime()));
}
}
由于只设置了 Calendar.DAY_OF_WEEK
,那么计算的时间结果是 20年后的第3周的周六 (因为没有设置WEEK_OF_MONTH
,那么取的是老的"2006-02-14" 的WEEK_OF_MONTH
)
而 "2006-02-14" 是所在月的第3周, 那么 20年后的第3周的周六 结果就是 2026-02-21
了
3. 为毛中间插入了一段 System.out.println(c.get(Calendar.DAY_OF_WEEK));
结果就正确了呢?
我在问题里面也回答了,
- 换成
System.out.println(c.get(Calendar.ERA));
结果也会对 - 换成
System.out.println("啦啦啦啦:" + sdf.format(c.getTime()));
结果也对
原因在于,
调用这些方法的时候 ,Calendar
会调用 java.util.Calendar.complete()
方法,
这个方法会依照已经设置的参数,把 Calendar 的 17 个字段(包括 WEEK_OF_MONTH
) 都重新计算一下 ,
此时, "2006-02-14" 20年后的日期是 2026-02-14 , 他的 WEEK_OF_MONTH
是当月的第二周, 都算完成了
4.最佳实践 (推荐指南)
真心不建议自己来写 Calendar SimpleDateFormat 来操作 ,坑比较多.
建议使用 apache commons-lang3
jar 或者 joda-time
示例: 对我来说就三步
public static void main(String[] args) throws ParseException{
//1.转成date
Date date = DateUtils.parseDate("2006-02-14", "yyyy-MM-dd");
//2.20年后的日期
Date d20 = DateUtils.addYears(date, 20);
//3.20年后的日期所在周的 周六 ,
Calendar calendar = DateUtils.toCalendar(d20);
calendar.set(DAY_OF_WEEK, SATURDAY);
System.out.println("入职20周年纪念日派对日期:" + DateFormatUtils.format(calendar.getTime(), "yyyy-MM-dd"));
}
你也可以使用 feilong-core jar
示例: 对我来说就三步
public static void main(String[] args){
//1.转成date
Date date2 = DateUtil.toDate("2006-02-14", DatePattern.COMMON_DATE);
//2.20年后的日期
Date d20 = DateUtil.addYear(date2, 20);
//3.20年后的日期所在周的 周六
System.out.println("入职20周年纪念日派对日期:" + DateUtil.toString(getLastDateOfThisWeek(d20), DatePattern.COMMON_DATE));
}