1. 程式人生 > >joda.time之如何獲取到兩個時間的差值(正確的使用Period類)

joda.time之如何獲取到兩個時間的差值(正確的使用Period類)

前言

此前Java處理時間日期所使用的 Date 和 Calendar 被詬病不已,Calendar 的主要問題物件可變,而像時間和日期這樣的類應該是不可變的,另外其概念模型也有不明確的地方,月份計算從0開始等等。

JodaTime開源時間/日期庫是很好的替代,另外Java8中也推出了新的java.time庫,設計理念與JodaTime相似。

Joda-Time 令時間和日期值變得易於管理、操作和理解。易於使用是 Joda 的主要設計目標。Joda-Time主類 DateTime 和JDK舊有類 Date 和 Calendar之間可以互相轉換。從而保證了與JDK框架的相容。

本文主要介紹了下Period類及如何正確的兩個時間的差值 通過分析構造方法的方式進行展開,詳細見下文

常用操作

org.joda.time大部分用法計算機程式的思維邏輯使用Joda-Time優雅的處理日期時間這裡面都有很好的介紹不再贅述,下面主要看下處理時間段(兩個日期的差值)的Period類

Period簡介

由一組持續時間欄位值指定的不可變時間段

一段時間分為多個欄位如年月日來表示,這些欄位由PeriodType類定義,PeriodType預設值為standard型別,支援年,月,周,日,小時,分鐘,秒和毫秒

建構函式

第一種:直接宣告
public Period() {
	super(0L, (PeriodType)null, (Chronology)null);
}
第二種:傳入年、月、周、日、時、分、秒、毫秒等值 此處一定要注意中間的周這個值的定義
public Period(int var1, int var2, int var3, int var4) {
	super(0, 0, 0, 0, var1, var2, var3, var4, PeriodType.standard());
}
第三種:獲取兩個ReadableInstant實現類(如DateTime)之間的時間段
public Period(ReadableInstant var1, ReadableInstant var2, PeriodType var3) {
	super(var1, var2, var3);
}
複製程式碼

正確的獲取兩個時間之間的差值

網上有很多的案例說獲取兩個時間之間的差距年月日等資訊的解決方案:

計算兩個時間的差值

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就可以自動計算出來,兩個時間之間有多少月、多少天、多少小時等。

這裡有一個問題沒有說明 拿一個案例來說

DateTime d1 = new DateTime(2018,1,27,0,30);
DateTime d2 = new DateTime(2018,2,26,2,30);
Period p0 = new Period(d1, d2);
System.out.println(p0.getYears() + "年" + p0.getMonths() + "月"  + p0.getWeeks() + "周" + p0.getDays() + "天");
複製程式碼

輸出為:

0年0月4周2天
複製程式碼

是不是跟預期想要得到的30天不一致,下面看下原因

使用new Period(ReadableInstant var1, ReadableInstant var2)進行Period的例項化
這個構造方法預設使用的PeriodType
public Period(ReadableInstant var1, ReadableInstant var2) { 
   super(var1, var2, (PeriodType)null);
}
這時候指定的PeriodType為null,再繼續向下看Period的上級抽象父類BasePeriod的構造方法
protected BasePeriod(ReadableInstant var1, ReadableInstant var2, PeriodType var3) {
   //1. 檢查一下var3對應的值
   var3 = this.checkPeriodType(var3);
   if (var1 == null && var2 == null) {
   	//6. 這裡將iType賦值為var3指定的Period
   	this.iType = var3;
       this.iValues = new int[this.size()];
    } else {
    	long var4 = DateTimeUtils.getInstantMillis(var1);
       long var6 = DateTimeUtils.getInstantMillis(var2);
       Chronology var8 = DateTimeUtils.getIntervalChronology(var1, var2);
       this.iType = var3;
       this.iValues = var8.get(this, var4, var6);
    }
}
protected PeriodType checkPeriodType(PeriodType var1) {
   //2. 呼叫DateTimeUtils工具類的getPeriodType()傳入var1 也就是上一程式碼塊的var3(也就是null)
   return DateTimeUtils.getPeriodType(var1);
}
public static final PeriodType getPeriodType(PeriodType var0) {
   //3. 在這裡就可以看到 傳入的null最終轉化為了PeriodType.standard()
   return var0 == null ? PeriodType.standard() : var0;
}
//4. 在PeriodType看到靜態方法 例項standard並返回對應的Period
public static PeriodType standard() {
   PeriodType var0 = cStandard;
   if (var0 == null) {
   	//5. 這裡可以看到在Standard中指定了年月周天時分秒(有興趣的可以看下PeriodType的例項的設計)
   	//這裡的貓膩就在於Standard中指定了周!!!
  		var0 = new PeriodType("Standard", new DurationFieldType[]{DurationFieldType.years(), DurationFieldType.months(), DurationFieldType.weeks(), DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis()}, new int[]{0, 1, 2, 3, 4, 5, 6, 7});
   	cStandard = var0;
   }

   return var0;
}
複製程式碼

所以獲取兩個日期的差值如年月日的正確姿勢應該如下

DateTime d1 = new DateTime(2018,1,27,0,30);
DateTime d2 = new DateTime(2018,2,26,2,30);
//指定PeriodType為yearMonthDayTime
Period p2 = new Period(d1, d2, PeriodType.yearMonthDayTime());
System.out.println(p2.getYears() + "年" + p2.getMonths() + "月"+ p2.getWeeks() + "周" + p2.getDays() + "天");
複製程式碼

輸入結果:

0年0月0周30天
複製程式碼

查詢官方文件

public Period(ReadableInstant startInstant,
              ReadableInstant endInstant)
複製程式碼

Most calculations performed by this method have obvious results. The special case is where the calculation is from a "long" month to a "short" month. Here, the result favours increasing the months field rather than the days. For example, 2013-01-31 to 2013-02-28 is treated as one whole month. By contrast, 2013-01-31 to 2013-03-30 is treated as one month and 30 days (exposed as 4 weeks and 2 days). The results are explained by considering that the start date plus the calculated period result in the end date.

大致的意思是說有的兩點:

  1. 2013-01-31 to 2013-03-30 is treated as one month and 30 days (exposed as 4 weeks and 2 days)

從2013-01-31到2013-03-30被視為有一個月和30天(其中30天暴露為4周和2天)

上面已經分析過原因及實現,以及如果獲取到想要的值

  1. 從大月到小月的計算需要注意,比如2013-01-31到2013-02-28是一個月

    2013-01-29到2013-02-28也是一個月

    2013-01-28到2013-02-28還是一個月

    2013-01-27到2013-02-28是一個月零一天

    這個也是需要注意的點

總結

筆者也是在專案中使用joda.time來處理兩個時間的差值的時候使用Period這個處理時間段的類,但是在使用的過程中出現了跟預期值不同的問題,隨後翻閱資料更正了用法總結了一下,希望能夠幫助到大家。需要使用到更多的用法可以參考官方文件:User Guide

參考資料

joda.time官方API

計算機程式的思維邏輯