论坛首页 Java企业应用论坛

你可能不知道的 Calendar 之 DAY_OF_WEEK

浏览 3412 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2016-08-31  

 

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_MONTHDAY_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.

原先的这段代码

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));
    }

5.参考

论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics