一, 概述
Java 官方推荐使用 Calendar 来替换 Date 的使用.
Calendar 与 Date 之间可以自由的进行转换, 转换的纽带是 time.
使用 Calendar 的 getTime()方法可以得到一个 Date 类型的对象, 这个对象底层是使用 Date 的第二个带 Long 型参数的构造器创建的, 这个 Long 型参数是 Calendar 中的 time 字段中保存的值, 这个 time 字段的值是在具体的实现类中定义赋值的.
比如 GregorianCalendar 中的实现 computeTime(), 这个方法的目的就是将 field 值转换为 time 值, 这个涉及到 Calendar 中的两种模式, 之后会有介绍.
而通过 Calendar 的 setTime(Date date)方法可以将一个 Date 对象转换为一个 Calendar 对象, 这个方法以一个 Date 对象为参数, 底层调用的 setTimeInMillis(long millis)方法, 将 date.getTime()的值作为参数, 再底层会将这个 Long 型参数值赋值给 time 字段, 这时会重计算 field 值.
Calendar 与 Date 的转换:
- public class CalendarTest {
- public static void main(String[] args) {
- //Calendar--->Date
- Calendar c = Calendar.getInstance();
- Date d = c.getTime();
- //Date--->Calendar
- Date d1 = new Date();
- Calendar c1 = Calendar.getInstance();
- c1.setTime(d1);
- System.out.println(d);
- System.out.println(c1.get(Calendar.YEAR)+"年"+(c1.get(Calendar.MONTH)+1)+"月"+c1.get(Calendar.DATE)+"日");
- }
- }
执行结果:
Sat Jul 08 10:39:14 CST 2017
二, Calendar 中的 time 与 field
Calendar 中有两种描述时间内容的域, 一种就是 time, 它用来保存 Calendar 对象所代表的时间点据 1970 年 1 月 1 日 00:00:00 的毫秒数, 另一种就是 field, 它是一个数组, 它表示的并不是一个内容, 而是 Calendar 内部定义的最多静态常量字段.
而这一般情况下是同步的, 即表述的是同一时间点, 但也有可能会出现不同步的情况:
起初, field 没有设置, time 也是无效的
如果 time 被设置, 所有的 field 都会自动被设置为同步的时间点
如果某一 field 被单独设置, time 会自动失效
更确切的说, 当我们通过 Calendar.getInstance()方法获取一个全新的 Calendar 对象时, 它所代表的时间点是通过 time 来设置的, 而这个 time 的值是通过 System.currentTimeMillis()得到的, 通过 time 定义 Calendar,isTimeSet 为 true, 表示 time 值是最新的 (真的),areFieldsSet 为 false, 表示 field 字段的值都是旧的(假的), 因为当我们重新设置了 time 值之后, Calendar 所代表的时间点就发生了变化(这里是首次, 相当于从无到有, 也算是变化, 之后当我们为 Calendar 的 time 重新设置一个新值时, Calendar 的时间点就会再次发生变化, 它会指向最新的 time 值所代表的时间点), 而这时 field 中还表示的是原来的时间点内容, 然后会调用 computeFields() 方法进行所有字段值的重计算, 确保 field 中的值与 time 同步, 并同时将 areFieldsSet 和 areAllFieldsSet 设置为 true, 表示所有的 field 代表的时间值也是最新的了(真). 其实我们每次更改 time 值都会自动触发重计算, 来确保两个域所描述的时间点一致(即同步), 这也就是上面 b 所述的内容.
但是如果我们通过 set(int field, int value)单独对 field 中的某行一字段进行更改时, 首先会触发一个验证, areFieldsSet 为真而 areAllFieldsSet 为 false 时, 表示只有一部分 field 是最新的情况, 即存在部分 field 属于旧的情况, 针对这种情况会触发 field 的重新计算; 之后会将 isTimeSet 设置为 false,areFieldsSet 设置为 false, 将 isSet[field]设置为 true(将当前 field 设置为真), 这种情况下, 当我们使用 getTime()获取 time 值所代表的时间点时, 由于 isTimeSet 为 false, 会触发 time 的重计算, 这个计算依据是根据 field 的值进行的, 之后将 isTimeSet 设置为 true, 同样我们在通过 get(int field)获取某个 field 值时也会先验证 isTimeSet 是否为 true, 如果为 false, 同样会触发 time 的重计算, 然后验证 areFieldsSet 为 false, 则触发其余 field 的重计算.
time 的重计算是依据 field 的, 确切的说是依据部分 field 的, 而有一部分 field 也是在 field 的基础上再计算的, 所以可以说有一部分 field 是固定的, 是和 time 息息相关的.
以上种种所述全部是 Calendar 内部的实现规则, 对外而言, 我们只需要简单的调用即可, 所有这些都被隐藏在内部, 从而保证我们通过对外方法获取到的直接就是正确的值.
过程实例:
- public class CalendarTest {
- public static void main(String[] args) throws ParseException {
- System.out.println("------- 初始情况 -------");
- Calendar c = Calendar.getInstance();
- System.out.println(c.getTime());
- System.out.println(c.get(Calendar.DATE));
- System.out.println(c.get(Calendar.HOUR));
- System.out.println("------- 重设置 time-------");
- c.setTime(new SimpleDateFormat("yyyyMMdd").parse("20170501"));
- System.out.println(c.getTime());
- System.out.println(c.get(Calendar.DATE));
- System.out.println(c.get(Calendar.HOUR));
- System.out.println("------- 重设置 field-------");
- c.set(Calendar.MONTH, 4);
- System.out.println(c.getTime());
- System.out.println(c.get(Calendar.DATE));
- System.out.println(c.get(Calendar.HOUR));
- System.out.println("总结: time 与 field 所代表时间点同步, 所有的不同步全部在内部处理完成");
- }
- }
执行结果:
------- 初始情况 -------
- Sat Jul 08 13:08:34 CST 2017
- 8
- 1
------- 重设置 time-------
- Mon May 01 00:00:00 CST 2017
- 1
- 0
------- 重设置 field-------
- Mon May 01 00:00:00 CST 2017
- 1
- 0
总结: time 与 field 所代表时间点同步, 所有的不同步全部在内部处理完成
三, Calendar 中的两种解析模式
lenient: 该模式下可以自动规则化用户赋值给 Calendar 的不规则值, 比如 1 月 32 日会被解析为 2 月 1 日
non-lenient: 该模式下不会自动解析不规则的输入, 而是一旦发现不规则输入, 就会报出异常
这也叫 Calendar 的容错性, lenient 的开启与关闭使用 setLenient(boolean lenient)方法来设置, true 表示开启容错性(默认情况),false 表示关闭该功能.
实例:
- public class CalendarTest {
- public static void main(String[] args) {
- Calendar c = Calendar.getInstance();
- c.set(Calendar.MONTH, 8);
- c.set(Calendar.DAY_OF_MONTH, 33);
- System.out.println(c.getTime()+"\n");
- c.setLenient(false);
- c.set(Calendar.MONTH, 8);
- c.set(Calendar.DAY_OF_MONTH, 33);
- System.out.println(c.getTime());
- }
- }
执行结果:
- Tue Oct 03 13:18:48 CST 2017
- Exception in thread "main" java.lang.IllegalArgumentException: DAY_OF_MONTH
- at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2583)
- at java.util.Calendar.updateTime(Calendar.java:2606)
- at java.util.Calendar.getTimeInMillis(Calendar.java:1118)
- at java.util.Calendar.getTime(Calendar.java:1091)
- at JdkTest.main(JdkTest.java:87)
从上面的例子中可以看出, 默认情况下, 我们为 Calendar 的月份赋值为 8 即九月份, 日期赋值为 33 即下一月 3 号, 输出为 10 月 3 日, 容错性将这种不符合规则的输入规则化处理了, 而关闭容错性之后, 同样的赋值只会报异常 java.lang.IllegalArgumentException(非法参数异常).
四, Calendar 的使用
实例:
- public class CalendarTest {
- public static void main(String[] args) throws ParseException {
- // 通过 SimpleDateFormat 解析日期字符串
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hh:mm:ss.SSS");
- Date date = sdf.parse("20170502 13:33:23.433");
- // 将 Date 格式日期转换成 Calendar
- Calendar c = Calendar.getInstance();
- c.setTime(date);
- // 获取时间值
- System.out.println(c.getTime());
- System.out.println("年份为"+c.get(Calendar.YEAR));
- System.out.println("月份为"+c.get(Calendar.MONTH));
- System.out.println("日期为"+c.get(Calendar.DATE));
- System.out.println("日期为"+c.get(Calendar.DAY_OF_MONTH));
- System.out.println("日期为"+c.get(Calendar.DAY_OF_WEEK));
- System.out.println("日期为"+c.get(Calendar.DAY_OF_WEEK_IN_MONTH));
- System.out.println("日期为"+c.get(Calendar.DAY_OF_YEAR));
- System.out.println("时为"+c.get(Calendar.HOUR));
- System.out.println("时为"+c.get(Calendar.HOUR_OF_DAY));
- System.out.println("分为"+c.get(Calendar.MINUTE));
- System.out.println("秒为"+c.get(Calendar.SECOND));
- System.out.println("毫秒为"+c.get(Calendar.MILLISECOND));
- System.out.println("星期为"+c.get(Calendar.WEEK_OF_MONTH));
- System.out.println("星期为"+c.get(Calendar.WEEK_OF_YEAR));
- System.out.println("历型为"+c.get(Calendar.ERA));
- System.out.println("zone 为"+c.get(Calendar.ZONE_OFFSET));
- // 设置
- c.set(Calendar.MONTH, Calendar.APRIL);
- System.out.println("修改后月份为"+c.get(Calendar.MONTH));
- c.set(1999, 0, 23);
- System.out.println(c.getTime());
- c.set(2000, 1, 12, 13, 33, 14);
- System.out.println(c.getTime());
- c.set(2001, 2, 13, 14, 13);
- System.out.println(c.getTime());
- // 运算
- System.out.println("----- 运算 -----");
- c.add(Calendar.YEAR, 12);
- System.out.println(c.getTime());
- c.add(Calendar.MONTH, -1);
- System.out.println(c.getTime());
- c.roll(Calendar.DATE, true);
- System.out.println(c.getTime());
- c.add(Calendar.DATE, 1);
- System.out.println(c.getTime());
- //roll 与 add 运算对比
- c.set(2000, 1, 29);
- System.out.println(c.getTime());
- c.roll(Calendar.DATE, 1);
- System.out.println(c.getTime());
- c.set(2000, 1, 29);
- c.add(Calendar.DATE, 1);
- System.out.println(c.getTime());
- }
- }
执行结果为:
Tue May 02 13:33:23 CST 2017
年份为 2017
月份为 4
日期为 2
日期为 2
日期为 3
日期为 1
日期为 122
时为 1
时为 13
分为 33
秒为 23
毫秒为 433
星期为 1
星期为 18
历型为 1
zone 为 28800000
修改后月份为 3
- Sat Jan 23 13:33:23 CST 1999
- Sat Feb 12 13:33:14 CST 2000
- Tue Mar 13 14:13:14 CST 2001
----- 运算 -----
- Wed Mar 13 14:13:14 CST 2013
- Wed Feb 13 14:13:14 CST 2013
- Thu Feb 14 14:13:14 CST 2013
- Fri Feb 15 14:13:14 CST 2013
- Tue Feb 29 14:13:14 CST 2000
- Tue Feb 01 14:13:14 CST 2000
- Wed Mar 01 14:13:14 CST 2000
对比上面最后的两行输出, 可以看出 add 与 roll 的运算规则其实是不同的, roll 的运算不会影响大规则 (这里的大规则指的是月份的改变) 的改变, 而 add 会影响.
综上所述, 我们可以简单的认识一下 Calendar 类, 并学会简单的使用它, 对于其内部实现, 还需要认真敲一敲源码.
来源: http://www.jianshu.com/p/9742bb685d72