你可能不知道的 `Calendar` 之 `DAY_OF_WEEK` ## 1.背景: 百度知道上有人咨询了 `Calendar 设置 Calendar.DAY_OF_WEEK不正确的问题` 参见 http://zhidao.baidu.com/question/748936560786113052 > 他的入职时间是 `"2006-02-14"`,要计算 `20年后的所在周的周六` 理论上, `2026-02-14`所在周的周六,正好就是 `2026-02-14` ![](http://i.imgur.com/GxNW6Yw.png) 他写的代码是 ```JAVA 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));` ```JAVA 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` or `WEEK_OF_YEAR`) has also been set. > So, avoid setting DAY_OF_WEEK without setting one of the week fields. 原先的这段代码 ```JAVA 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` 了 ![](http://i.imgur.com/ogukWEC.png) ## 3. 为毛中间插入了一段 `System.out.println(c.get(Calendar.DAY_OF_WEEK));` 结果就正确了呢? 我在问题里面也回答了, - 换成 `System.out.println(c.get(Calendar.ERA));` 结果也会对 - 换成 `System.out.println("啦啦啦啦:" + sdf.format(c.getTime()));` 结果也对 ![](http://i.imgur.com/sGDBDj9.png) **原因在于**, 调用这些方法的时候 ,`Calendar` 会调用 `java.util.Calendar.complete()` 方法, 这个方法会依照已经设置的参数,把 Calendar 的 17 个字段(包括 `WEEK_OF_MONTH` ) 都重新计算一下 , 此时, "2006-02-14" 20年后的日期是 2026-02-14 , 他的 `WEEK_OF_MONTH` 是当月的第二周, 都算完成了 ![](http://i.imgur.com/V2EOT0A.png) ![](http://i.imgur.com/9LeKOP6.png) ## 4.最佳实践 (推荐指南) 真心不建议自己来写 Calendar SimpleDateFormat 来操作 ,坑比较多. 建议使用 `apache commons-lang3` jar 或者 `joda-time` **示例: 对我来说就三步** ```JAVA 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](https://github.com/venusdrogon/feilong-core) **示例: 对我来说就三步** ```JAVA 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)); } ``` ## 5.参考 - http://zhidao.baidu.com/question/748936560786113052 - http://bugs.java.com/view_bug.do?bug_id=4655637