计算机程序的思惟逻辑 (33) - Joda-Time

本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》(马俊昌著),由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买:京东自营连接 html

Joda-Time

上节介绍了JDK API中的日期和时间类,咱们提到了JDK API的一些不足,并提到,实践中有一个普遍使用的日期和时间类库,Joda-Time,本节咱们就来介绍Joda-Time。俗话说,工欲善其事,必先利其器,Joda-Time就是操做日期和时间的一把利器。java

Joda-Time的官网是www.joda.org/joda-time/。它的基本概念和工做原理与上节介绍的是相似的,好比说,都有时刻和年历的概念,都有时区和Locale的概念,主要工做,都是在毫秒和年月日等年历信息之间进行相互转换。编程

Joda-Time的主要类和Java API的类也有一个粗略的对应关系:设计模式

|Joda-Time |Java API |说明 | | ------------- |-------------| |Instant |Date| 时刻| |DateTime |Calendar| 年历| |DateTimeZone |TimeZone| 时区| |DateTimeFormatter| DateFormat| 格式化|安全

须要说明的是,这只是一个很是粗略的对应,并不严谨,Joda-Time也还有很是多的其余类。bash

虽然基本概念是相似的,但API的设计却有很大不一样,Joda-Time的API更容易理解和使用,代码也更为简洁,下面咱们会经过例子来讲明。微信

另外,与Date/Calendar的设计有一个很大的不一样,Joda-Time中的主要类都被设计为了避免可变类,咱们以前介绍过不可变类,包装类/String都是不可变类,不可变类有一个很大的优势,那就是简单、线程安全,全部看似的修改操做都是经过建立新对象来实现的。学习

本文并不打算全面介绍Joda-Time的每一个类,相反,咱们主要经过一些例子来讲明其基本用法,体会其方便和强大,同时,学习其API的设计理念。spa

建立对象

新建一个DateTime对象,表示当前日期和时间:线程

DateTime dt = new DateTime();
复制代码

新建一个DateTime对象,给定年月日时分秒等信息:

//2016-08-18 15:20
DateTime dt = new DateTime(2016,8,18,15,20);

//2016-08-18 15:20:47
DateTime dt2 = new DateTime(2016,8,18,15,20,47);

//2016-08-18 15:20:47.345
DateTime dt3 = new DateTime(2016,8,18,15,20,47,345);
复制代码

获取日历信息

与Calendar不一样,DateTime为每一个日历字段都提供了单独的方法,取值的范围也都是符合常识的,易于理解和使用,来看代码:

//2016-08-18 15:20:47.345
DateTime dt = new DateTime(2016,8,18,15,20,47,345);
System.out.println("year: "+dt.getYear());
System.out.println("month: "+dt.getMonthOfYear());
System.out.println("day: "+dt.getDayOfMonth());
System.out.println("hour: "+dt.getHourOfDay());
System.out.println("minute: "+dt.getMinuteOfHour());
System.out.println("second: "+dt.getSecondOfMinute());
System.out.println("millisecond: " +dt.getMillisOfSecond());
System.out.println("day_of_week: " +dt.getDayOfWeek());
复制代码

输出为:

year: 2016
month: 8
day: 18
hour: 15
minute: 20
second: 47
millisecond: 345
day_of_week: 4
复制代码

每一个字段的输出都符合常识,且保持一致,都是从1开始,好比dayOfWeek,周四就是4, 易于理解。

格式化

Java API中,格式化必须使用一个DateFormat对象,而Joda-Time中,DateTime本身就有一个toString方法,能够接受一个pattern参数,看例子:

//2016-08-18 14:20:45.345
DateTime dt = new DateTime(2016,8,18,14,20,45,345);
System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));
复制代码

输出为:

2016-08-18 14:20:45
复制代码

Joda-Time也有与DateFormat相似的类,看代码:

DateTime dt = new DateTime(2016,8,18,14,20);
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
System.out.println(formatter.print(dt));
复制代码

输出为:

2016-08-18 14:20
复制代码

这里有两个类,一个是DateTimeFormat,另外一个是DateTimeFormatter。DateTimeFormatter是具体的格式化类,提供了print方法将DateTime转换为字符串。DateTimeFormat是一个工厂类,专门生成具体的格式化类,除了forPattern方法,它还有一些别的工厂方法,本文就不介绍了。

程序设计的一个基本思惟是关注点分离,程序通常老是比较复杂的,涉及方方面面,解决的思路就是分解,将复杂的事情尽可能分解为不一样的方面,或者说关注点,各个关注点之间耦合度要尽可能低。

具体来讲,对应到Java,每一个类应该只关注一点。上面的例子中,由于生成DateTimeFormatter的方式比较多,就将生成DateTimeFormatter这个事单独拿了出来,就有了工厂类DateTimeFormat,只关注生产DateTimeFormatter,Joda-Time中还有别的工厂类,好比ISODateTimeFormat,工厂类是一种常见的设计模式

除了将DateTime转换为字符串,DateTimeFormatter还能够将字符串转化为DateTime,代码以下:

DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
DateTime dt = formatter.parseDateTime("2016-08-18 14:20");
复制代码

与上节介绍的格式化类不一样,Joda-Time的DateTimeFormatter是线程安全的,能够安全的被多个线程共享。

设置和修改时间

上节介绍Calendar时提到,修改时期和时间有两种方式,一种是直接设置绝对值,另外一种是在现有值的基础上进行相对增减操做,DateTime也支持这两种方式。

不过,须要注意的是,DateTime是不可变类,修改操做是经过建立并返回新对象来实现的,原对象自己不会变。

咱们来看一些例子。

调整时间为下午3点20

DateTime dt = new DateTime();
dt = dt.withHourOfDay(15).withMinuteOfHour(20);
复制代码

DateTime有不少withXXX方法来设置绝对时间。DateTime中很是方便的一点是,方法的返回值是修改后的DateTime对象,能够接着进行下一个方法调用,这样,代码就很是简洁,也很是容易阅读,这种一种流行的设计风格,称为流畅接口 (Fluent Interface),相比之下,使用Calendar,就必需要写多行代码,比较臃肿,下面咱们会看到更多例子。

另外,注意须要将最后的返回值赋值给dt,不然dt的值不会变。

三小时五分钟后

DateTime dt = new DateTime().plusHours(3).plusMinutes(5);
复制代码

DateTime有不少plusXXX和minusXXX方法,用于相对增长和减小时间。

今天0点

DateTime dt = new DateTime().withMillisOfDay(0);
System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss.SSS"));
复制代码

当前时间为2016-08-18,因此输出为

2016-08-18 00:00:00.000
复制代码

withMillisOfDay直接设置当天毫秒信息,会同时将时分秒等信息进行修改。

下周二上午10点整

DateTime dt = new DateTime().plusWeeks(1).withDayOfWeek(2)
        .withMillisOfDay(0).withHourOfDay(10);
复制代码

明天最后一刻

DateTime dt = new DateTime().plusDays(1).millisOfDay().withMaximumValue();
System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss.SSS"));
复制代码

当前时间为2016-08-18,因此输出为

2016-08-19 23:59:59.999
复制代码

这里说明一下,plusDays(1)容易理解,设为次日。millisOfDay()的返回值比较特别,它是一个属性,具体类为DateTime的一个内部类Property,这个属性表明当天毫秒信息,这个属性有一些方法,能够接着对日期进行修改,withMaximumValue就是将该属性的值设为最大值。

这样,代码是否是很是简洁?除了millisOfDay,DateTime还有不少相似属性。咱们来看更多的例子。

本月最后一天最后一刻

DateTime dt = new DateTime().dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue();
复制代码

下个月第一个周一的下午5点整

DateTime dt = new DateTime().plusMonths(1).dayOfMonth().withMinimumValue()
        .plusDays(6).withDayOfWeek(1).withMillisOfDay(0).withHourOfDay(17);
复制代码

咱们稍微解释下:

new DateTime().plusMonths(1).dayOfMonth().withMinimumValue()
复制代码

将时间设为了下个月的第一天。.plusDays(6).withDayOfWeek(1)将时间设为第一个周一。

时间段的计算

JDK API中没有关于时间段计算的类,而Joda-Time包含丰富的表示时间段和用于时间段计算的方法,咱们来看一些例子。

计算两个时间之间的差

Joda-Time有一个类,Period,表示按日历信息的时间段,看代码:

DateTime start = new DateTime(2016,8,18,10,58);
DateTime end = new DateTime(2016,9,19,12,3);
Period period = new Period(start,end);        
System.out.println(period.getMonths()+"月"+period.getDays()+"天"
        +period.getHours()+"小时"+period.getMinutes()+"分");
复制代码

输出为:

1月1天1小时5分
复制代码

只要给定起止时间,Period就能够自动计算出来,两个时间之间有多少月、多少天、多少小时等。

若是只关心一共有多少天,或者一共有多少周呢?Joda-Time有专门的类,好比Years用于年,Days用于日,Minutes用于分钟,来看一些例子。

根据生日计算年龄

年龄只关心年,可使用Years,看代码:

DateTime born = new DateTime(1990,11,20,12,30);
int age = Years.yearsBetween(born, DateTime.now()).getYears();
复制代码

计算迟到分钟数

假定早上9点是上班时间,过了9点算迟到,迟到要统计迟到的分钟数,怎么计算呢?看代码:

int lateMinutes = Minutes.minutesBetween(
        DateTime.now().withMillisOfDay(0).withHourOfDay(9),
        DateTime.now()).getMinutes(); 
复制代码

单独的日期和时间类

咱们一直在用DateTime表示完整的日期和时间,但在年龄的例子中,只须要关心日期,在迟到的例子中,只须要关心时间,Joda-Time分别有单独的日期类LocalDate和时间类LocalTime。

使用LocalDate计算年龄

LocalDate born = new LocalDate(1990,11,20);
int age = Years.yearsBetween(born, LocalDate.now()).getYears();
复制代码

使用LocalTime计算迟到时间

int lateMinutes = Minutes.minutesBetween(
        new LocalTime(9,0),
        LocalTime.now()).getMinutes();
复制代码

LocalDate和LocalTime能够与DateTime进行相互转换,好比:

DateTime dt = new DateTime(1990,11,20,12,30);
LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();
DateTime newDt = DateTime.now().withDate(date).withTime(time);
复制代码

与JDK API的互操做

Joda-Time中的类能够方便的与JDK中的类进行相互转换。

JDK -> Joda

Date、Calendar能够方便的转换为DateTime对象:

DateTime dt = new DateTime(new Date());
DateTime dt2 = new DateTime(Calendar.getInstance());
复制代码

也能够方便的转换为LocalDate和LocalTime对象:

LocalDate.fromDateFields(new Date());
LocalDate.fromCalendarFields(Calendar.getInstance());
LocalTime.fromDateFields(new Date());
LocalTime.fromCalendarFields(Calendar.getInstance());
复制代码

Joda -> JDK

DateTime对象也能够方便的转换为JDK对象:

DateTime dt = new DateTime();
Date date = dt.toDate();
Calendar calendar = dt.toCalendar(Locale.CHINA);
复制代码

LocalDate也能够转换为Date对象:

LocalDate localDate = new LocalDate(2016,8,18);
Date date = localDate.toDate();
复制代码

小结

本节介绍了Joda-Time,一个方便和强大的日期和时间类库,本文并未全面介绍,主要是经过一些例子展现了其基本用法。

咱们也介绍了Joda-Time之因此易用的一些设计思惟,好比,关注点分离,为方便操做,提供单独的功能明确的类和方法,设计API为流畅接口,设计为不可变类,使用工厂类等。

下一节,咱们来讨论一个有趣的话题,那就是随机。


未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深刻浅出,老马和你一块儿探索Java编程及计算机技术的本质。用心原创,保留全部版权。

相关文章
相关标签/搜索