Java編程的邏輯 (33) - Joda-Time

本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營連接http://item.jd.com/12299018.htmlhtml


Joda-Time
上節介紹了JDK API中的日期和時間類,咱們提到了JDK API的一些不足,並提到,實踐中有一個普遍使用的日期和時間類庫,Joda-Time,本節咱們就來介紹Joda-Time。俗話說,工欲善其事,必先利其器,Joda-Time就是操做日期和時間的一把利器。編程

Joda-Time的官網是http://www.joda.org/joda-time/。它的基本概念和工做原理與上節介紹的是相似的,好比說,都有時刻和年曆的概念,都有時區和Locale的概念,主要工做,都是在毫秒和年月日等年曆信息之間進行相互轉換。設計模式

Joda-Time的主要類和Java API的類也有一個粗略的對應關係:安全

Joda-Time
Java API
說明
Instant Date 時刻
DateTime Calendar 年曆
DateTimeZone TimeZone 時區
DateTimeFormatter DateFormat 格式化

須要說明的是,這只是一個很是粗略的對應,並不嚴謹,Joda-Time也還有很是多的其餘類。微信

雖然基本概念是相似的,但API的設計卻有很大不一樣,Joda-Time的API更容易理解和使用,代碼也更爲簡潔,下面咱們會經過例子來講明。 學習

另外,與Date/Calendar的設計有一個很大的不一樣,Joda-Time中的主要類都被設計爲了避免可變類,咱們以前介紹過不可變類,包裝類/String都是不可變類,不可變類有一個很大的優勢,那就是簡單、線程安全,全部看似的修改操做都是經過建立新對象來實現的。spa

本文並不打算全面介紹Joda-Time的每一個類,相反,咱們主要經過一些例子來講明其基本用法,體會其方便和強大,同時,學習其API的設計理念。線程

建立對象
設計

新建一個DateTime對象,表示當前日期和時間:3d

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編程及計算機技術的本質。用心寫做,原創文章,保留全部版權。

相關文章
相關標籤/搜索