好用java库(一):java date/time api:joda-time

基于java的项目,最大的一个好处是有很多开源,优秀的第三方jar包拿过来直接使用,但是引用第三方包时一定要小心审核,确认包的作者或组织的权威性,以免未知的第三方包对项目的性能,安全和正确性的影响。作为一个java coder,有些包你不得不了解下,它们真的可以帮你在项目中节省很多时间去写自己的utils包,况且成熟的社区维护的第三方工具包比自己动手写的专业多了。所以我想写个系列性的文章,介绍下平时在用,而且值得推荐给大家的一些好的第三方jar包。

时间,日期的处理在应用项目中是经常要用到的一块,如果你还是个稍微追求感觉的程序员,应该早就受不了jdk中java.util.Date这个类,莫名奇妙的构造方法,再加上一堆的deprecated方法,让你觉得为何不把这个类给取消算了。举个例子,现在实例化一个日期对象(2013-1-6):

1 Date today = new Date(2013,1,6);

不好意思,其实返回的日期是:3913-2-6,有病吧......

我们也经常要用到一些时间变换处理的地方,如返回这个月的最后一个星期二,当月的最后一天,每个月的第一天的凌晨三点进行批处理任务,这个时候我们一般会用jdk中的Calendar 。虽然Calendar的出现改善了一些情况,但是为了处理一些时间变换时写出各种DAY_OF_WEEK,DAY_OF_MONTH,MONTH....和add,set,roll之间的组合调用时,这种不直观的api仍然使我们容易犯糊涂。

 Joda-Time 是一个非常优秀的时间日期处理工具,正是它的强大易用性,作者好像成了JSR310的Leader,下面我们就来大概介绍一下里面强大的api。

实例化日期/时间对象及操作对象

初始化:

DateTime dt = new DateTime(2013,1,7,12,23,58,874);

 

改变对象:

dt = dt.withDayOfMonth(20);//设置成20号
dt = dt.minusDays(2);//前面两天
dt = dt.plusWeeks(1);//后面一个星期

 

注意:「DateTime」对象是不可变对象(Immutable),所以每次操作改变对象时实质上是返回的一个全新对象。

字段属性:

DateTime dt2 = new DateTime();
Property prop = dt.dayOfMonth();//得到字段属性
int i = prop.getDifference(dt2);//两个日期之间相差几天

 

格式化(format)和解析(parse)

「格式化和解析日期/时间」这种需求在时间处理中大家都会碰到,在标准jdk中使用的是SimpleDateFormat,但是大家都知道这个玩艺用起来真是爱恨交加。首先SimpleDateFormat的实例化是重量级的,这就会促使我们用单例模式使用它,可是这个类又是「线程不安全的」,所以要synchronized调用。

joda-time对这类需求支持很强大:

DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS");
String s = fmt.print(dt);
dt = fmt.parseDateTime("2013-01-07 22:39:13 782");

 

如果你不想每次格式化输出的时候都要去关联一个DateTimeFormatter,可以直接这样:

DateTime dt = new DateTime();
String str = dt.toString("yyyy-MM-dd HH:mm:ss SSS");

 

这里面的原理其实就是在调用toString的时候把传进去的format string参数生成一个DateTimeFormatter,然后再格式化输出。你可能考虑担心每次都会实例化DateTimeFormatter对象会影响性能,其实内部已经做了缓存了。

private static final Map<String, DateTimeFormatter> cPatternedCache = new HashMap<String, DateTimeFormatter>(7);

 如果你以为joda-time只有这些对相对jdk改善的功能,那么太小看它了,且看里面一些jdk没有的高级api功能。

Interval/Duration/Period

 它们三个之间很有相似性,都有“时间间隔”的意思,我们先看一段代码,再讲下它们之间的差异性。

        DateTime start = new DateTime(2011,12,2,15,33);//开始时间
        DateTime end = new DateTime(2013,3,1,12,34);//结束时间
        Interval inteval = new Interval(start,end);
        Duration duration = new Duration(start,end);
        Period period = inteval.toPeriod();
        
        boolean between = inteval.contains(new DateTime(2013,2,1,12,34));//判断指定时间在这段时间间隔里: True
        long millSeconds = duration.getMillis();//得到两个时间相差的豪秒数:39301260000
        long millSeconds1 = period.getMillis();//得到两个时间相差的豪秒数:0

 

上面的三个返回结果中,第一个好理解,「2013-2-1 12:34」是中「2011-12-2 15:33」和「2013-3-1 12:34」之间的一个时间。第二个返回这两个时间相差的毫秒数(39301260000),也比较好理解。可是第三个结果返回 0 ,就比较晕了,其实Interval,Duration,Period都有「时间间隔」的意思在里面,但是这里定义三个类肯定是有三种语义在里面的,要理解这三者的区别,只要搞清楚这三个不同的类里包含的「信息」就知道差异了。

  • Interval   : 它包含了「开始时间」和「结束时间」信息,包括各自的时区。所以可以根据Interval对象得到「开始时间」和「结束时间」这两个信息。
  • Duration : 它没有任何「开始时间」和「结束时间」的概念,只是保存了一个具体的时间间隔,也就是一个以毫秒为单元的整数。
  • Period     : 它和「Duration」差不多意思,也是没有包含「开始时间」和「结束时间」的信息,但是它和「Duration」不同的是,它所指的时间间隔不是以毫秒为单元,而是「年」,「月」,「天」,「小时」,「分」,「秒」,「毫秒」为相对单元。举个例子:「Duration」就像是“我比你大30天”的语义,而「Period」就是“我大你一个月”。它们两种语义是不同的,“大你30天”是个绝对值,而“大你一个月”就是相对的,因为「一个月」可能是30天,也可能是31天,也可能会碰到2月分的28天,所以说是相对的。

再看上面代码中的输出结果,就知道为什么duration和period调用getMillis会出现不同的结果了:duration.getMillis返回的是绝对相差毫秒数,而period.getMillis返回0是因为「开始时间」和「结束时间」间隔的相对时间是:1年2个月3个星期5天21个小时1分钟。

  

posted @ 2013-01-07 23:09  海鸟  阅读(3371)  评论(0编辑  收藏  举报