鴻蒙開源第三方組件—日期和時間處理組件JodaTime-ohos
前言
基于安卓平臺的日期和時間處理組件JodaTime-ohos(https://github.com/dlew/joda-time-android), 實現鴻蒙化遷移和重構。代碼已經開源到(https://gitee.com/isrc_ohos/joda-time_ohos),歡迎各位下載使用并提出寶貴意見!
背景
JodaTime-ohos是一個日期和時間處理組件,可以獲取標準時間、當前時間、相對時間、格式化時間等多種形式的時間,并支持對各類時間進行計算和判斷。該組件易于使用、可擴展性強、擁有全面的功能集 ,支持多種日歷系統,被廣泛應用于時間顯示類應用。
組件效果展示
(1)顯示“標準時間”“一段時間”“格式化時間”“一段時間的相對表示”“一段時間的相對的字符串表示”“相對時間”
- 標準時間:程序執行時刻的時間表示,見圖1中的a。
- 格式化時間:將日期/時間格式轉換為預先定義的日期/時間格式,見圖1中的b。
- 一段時間:在程序執行時刻的基礎上,增加一段時間后的起始時間和結果時間顯示,見圖1中的c。
- 一段時間的相對表示:未來或者在過去的某個時間段表示,見圖1中的d。
- 一段時間的相對字符串表示:在程序執行時刻的基礎上,增加一段時間的結果時間顯示,見圖1中的e。
- 相對時間:在程序執行時刻的基礎上,增加一段時間后的結果時間和時間段表示,見圖1中的f。
圖1 顯示“標準時間”“一段時間”“格式化時間”“一段時間的相對表示”“一段時間的相對的字符串表示”“相對時間”
(2)顯示“當前時間”、“是否為今天”
- 當前時間:程序執行時刻的年月日表示。同時也可以給出在當前時間的基礎上增加一段 時間后的結果表示,如圖2的a所示。
- 是否為今天:可以判斷程序執行的日期是否為今天、是否為昨天、是否為明天,如圖2的b所示。

圖2 顯示“當前時間”、“是否為今天”
(3)顯示“一段時間的格式化表示”
“一段時間的格式化表示”:將一段時間用對應的單位表示出來。

圖3 顯示“一段時間的格式化表示”
Sample解析
1、工程結構

圖4 Sample的工程結構
從上圖可以看出,Sample的工程結構主要分為三個部分: 圖中第①部分的MainAbilitySlice主要負責構建組件應用的主頁面布局。主頁面上有9個按鈕,點擊不同的按鈕,會跳轉到不同的導航界面,每個界面顯示不同的時間格式,如圖5所示。

圖5 組件應用的主頁面布局
圖中第②部分的內容包含多個AbilitySlice,每一個AbilitySlice都對應①中的一個導航界面。界面中會包含特定格式的時間顯示,如圖6所示。

圖6 按鈕導航界面
圖中第③部分內容包含兩個文件:MainAbility是一切應用的入口,通過setMainRoute()設置路由,將組件應用的主界面設置為MainAbilitySlice。JodaTime則是為了創建示例接收時區變換的廣播。
- // JodaTime
- try {
- JodaTimeAndroid.init(this); //init方法用于創建示例接收時區變換的廣播
- } catch (RemoteException e) {
- e.printStackTrace();
- }
2、時間的獲取
JodaTime-ohos可以為開發者提供9種不同格式的時間,下面我們以sampleGetRelativeTimeSpanStringslice為例,展示“一段時間的相對表示”時間的獲取,效果如上述圖1中的d所示。其余幾種時間格式的使用方法同理,不再贅述。
(1)導入相關類
在sampleGetRelativeTimeSpanStringslice導入DateTime類,并得到實例化對象,該類是時間日期獲取工具類。
- //導入相關類DateTime
- import org.joda.time.DateTime;
- ......
- //實例化類對象
- DateTime now = DateTime.now();
(2)設置時間顯示布局
在sampleGetRelativeTimeSpanStringslice中創建一個DirectionalLayout布局,用于顯示獲取到的時間。
- DirectionalLayout directionalLayout = new DirectionalLayout(this);
- ......
- @Override
- protected void onStart(Intent intent) {
- super.onStart(intent);
- //設置Layout的寬和高
- directionalLayout.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
- directionalLayout.setHeight(ComponentContainer.LayoutConfig.MATCH_PARENT);
- directionalLayout.setPadding(32, 32, 80, 80); //設置Layout填充距離
- ShapeElement element = new ShapeElement();
- element.setRgbColor(new RgbColor(255, 255, 255));
- directionalLayout.setBackground(element); //設置Layout背景
- ......
- }
(3)設置時間顯示控件
由于需要在布局中顯示多個時間結果,所以此處創建一個List,內部元素為String類型,每個String元素表示一個時間結果。
- List<String> text = new ArrayList<String>();
(4)獲取日期和時間值
通過上述創建的DateTime 類對象,分別調用類中不同的方法如plusMinutes()、minusMinutes(),在起始時間點的基礎上增加或減少一個固定的時間;后使用DateUtils類的getRelativeTimeSpanString()方法將獲得到日期和時間值以對應的單位(時、分或秒)表示出來。
- text.add("Short future: " + DateUtils.getRelativeTimeSpanString(this, now.plusMinutes(25)));//目前時間值基礎上增加分鐘數
- text.add("Medium future: " + DateUtils.getRelativeTimeSpanString(this, now.plusHours(5)));//目前時間值基礎上增加小時數
- text.add("Long future: " + DateUtils.getRelativeTimeSpanString(this, now.plusDays(3)));//目前時間值基礎上增加天數
- text.add("Short past: " + DateUtils.getRelativeTimeSpanString(this, now.minusMinutes(25)));//目前時間值基礎上減少分鐘數
- text.add("Medium past: " + DateUtils.getRelativeTimeSpanString(this, now.minusHours(5)));//目前時間值基礎上減少小時數
- text.add("Long past: " + DateUtils.getRelativeTimeSpanString(this, now.minusDays(3)));//目前時間值基礎上減少天數
(5)添加控件到布局中
將獲取到的日期和時間值添加到時間顯示布局DirectionalLayout中。
- directionalLayout.addComponent(textT);
3、日期和時間常用類
第2小節的例子通過DateTime來獲取時間,通常情況下,在使用JodaTime_ohos組件的過程中,使用最多的日期時間獲取類共有5種,分別是:Instant類、DateTime類、LocalDate類、LocalTime類和LocalDateTime類。根據要獲取日期和時間值的格式不同,需要引入的類也會有所差別,以下分別對這幾個類的功能做簡單介紹:
Instant:用來表示時間軸上一個瞬時的點,即一個事件發生的時間戳,可以忽略其使用的日歷系統或所在時區。
- DateTime類:是JodaTime_ohos的核心類,能夠確定在時間軸上的位置。使用此類的時間格式有標準時間(sampleDateTimeslice)、格式化時間(sampleFormatDateTimeslice)、一段時間間隔(sampleDateRangeslice)、相對時間(sampleGetRelativeDateTimeStringslice)、一段時間的相對表示(sampleGetRelativeTimeSpanStringslice)、一段相對時間的字符串表示(sampleGetRelativeTimeSpanStringWithPrepositionslice)。
- LocalDate類:表示本地的日期,不包含時間部分,因此適合強調日期的情況如紀念日。使用此類的時間格式有當前時間(sampleLocalDateslice)、今日時間(sampleIsTodayslice)。
- LocalTime:表示本地的時間,不包含日期部分,因此適合強調時間的情況如上下班時間。
- LocalDateTime:表示本地的日期和時間,因此適用于同時強調日期和時間的情況,如考試時間。
上述五個類都是不可變的類,不論怎樣對它進行修改和處理,所有日期和時間相關操作的 API 都將返回一個全新的JodaTime_ohos 實例,類似Java的String類。除了上述5種常用類,本Sample中還使用到了Duration類,涉及到此類的時間格式是一段時間的格式化表示(sampleFormatDurationslice)。
Library解析
JodaTime-ohos組件的整個library分為五個部分,如圖7所示。

圖7 library組成結構
- JodaTimeAndroid類負責初始化相關操作,在開始使用JodaTime_ohos組件之前,都需要通過此類的init()方法進行初始化操作,否則將不能正常訪問組件中的類和方法;
- ResourceZoneInfoProvide類負責監控時區變化,但是此處需要注意的是,鴻蒙里沒有提示時區變化的intent;
- TimeZoneChangedReceiver類則負責處理監控到的時區變化;
- DateUtils類主要負責將獲取到的日期和時間值進行處理得到多種格式。
其中,DateUtils類是最核心的時間格式處理類,是經過封裝的工具類。如圖8所示,在Sample中使用到的日期和時間類是DateTime類、LocalDate類和Duration類,共三種,在通過DateUtils類中相應方法將獲取到日期和時間值進行處理之后,可以得到多達7種格式的日期和時間表現形式。

圖8 時間格式處理核心類
1、方法重載實現多時間格式
在DateUtils類中,包含很多時間格式轉換的方法,其中有些方法通過方法重載存在多種參數形式,在使用時可以根據需求傳入不同個數的參數,從而得到不同格式的日期和時間,增加了程序的可讀性。
以getRelativeTimeSpanString()方法為例,此方法用于獲得相對時間的跨度,如“20分鐘前”、“20分鐘內”。 通過方法重載的方式,調用此方法根據參數的不同能夠得到兩種,分別有兩個參數和三個參數兩種形式:
(1)兩個參數
當傳入參數分別是time和flags時,得到的時間格式是一段時間的相對表示,效果如上述圖1.1中的d圖所示。在獲取到傳入的原始時間值之后,進行時、分、秒的判斷,從而完成相應的處理。
- public static CharSequence getRelativeTimeSpanString(Context context, ReadableInstant time, int flags) throws NotExistException, WrongTypeException, IOException {
- boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;
- //將傳入的原始時間值轉換為DateTime類型
- DateTime now = DateTime.now(time.getZone()).withMillisOfSecond(0);
- //獲取原始時間值
- DateTime timeDt = new DateTime(time).withMillisOfSecond(0);
- boolean past = !now.isBefore(timeDt);
- Interval interval = past ? new Interval(timeDt, now) : new Interval(now, timeDt);
- ...
- //進行時、分、秒的判斷,完成相應處理
- if (Minutes.minutesIn(interval).isLessThan(Minutes.ONE)) {...}
- else if (Hours.hoursIn(interval).isLessThan(Hours.ONE)) {...}
- else if (Days.daysIn(interval).isLessThan(Days.ONE)) {...}
- else if (Weeks.weeksIn(interval).isLessThan(Weeks.ONE)) {...}
- ...
- return String.format(format, count);//返回處理結果
- }
(2)三個參數:
當傳入參數分別是time、flags和withPrepositon時,得到的時間格式是一段相對時間的字符串表示,效果如上述圖1.1中的e圖所示。先獲取傳入的原始時間值,將其轉換為LocalDate格式,之后分別針對時、分、秒進行判斷,從而完成處理返回相應的時間格式。
- public static CharSequence getRelativeTimeSpanString(Context ctx, ReadableInstant time, boolean withPreposition) throws NotExistException, WrongTypeException, IOException {
- String result;
- LocalDate now = LocalDate.now();//實例化LocalDate對象,獲取本地時間
- LocalDate timeDate = new LocalDate(time);//將傳入的原始時間值轉換為LocalDate類型
- int prepositionId;
- //針對時、分、秒進行判斷,完成處理
- if (Days.daysBetween(now, timeDate).getDays() == 0) {...}
- else if (Years.yearsBetween(now, timeDate).getYears() != 0) {...}
- else {...}
- if (withPreposition) {
- result = ctx.getResourceManager().getElement(prepositionId).getString(result);
- }
- return result;//返回處理結果
- }
2、同方法中通過判斷實現多時間格式
除了上述通過方法重載實現不同時間格式的情況,DateUtils類中還有另一種實現多時間格式的方式,即在同一個方法中,通過判斷不同類型的原始時間數據進行相應的處理,從而得到不同類型的時間值。
以一段時間的格式化表示的sampleFormatDurationslice類為例,效果如上述圖1.4。開發者在Sample中使用的時候,在分別通過standardSeconds()、standardMinutes()、standardHours()方法按需獲取不同時間值即秒、分鐘、小時之后,再調用DateUtils中的formatDuration()方法:
- text.add("Seconds: " + DateUtils.formatDuration(this, Duration.standardSeconds(25)));//獲取25秒的標準時間值
- text.add("Minutes: " + DateUtils.formatDuration(this, Duration.standardMinutes(5)));//獲取5分鐘的標準時間值
- text.add("Hours: " + DateUtils.formatDuration(this, Duration.standardHours(3)));//獲取3小時的標準時間值
在formatDuration()方法中,若原始時間數據包含小時,則進行第一個判斷,返回小時類型的時間處理結果;若原始時間數據包含分鐘,則進行第二個判斷,返回分鐘類型的時間處理結果;若原始時間數據包含秒,則不進入判斷直接返回秒類型的時間處理結果。
- public static CharSequence formatDuration(Context context, ReadableDuration readableDuration) throws IOException, NotExistException, WrongTypeException {
- ResourceManager res = context.getResourceManager();
- Duration duration = readableDuration.toDuration();//將傳入的原始時間值轉換為Duration類型
- final int hours = (int) duration.getStandardHours();//獲取傳入的原始時間值
- if (hours != 0) {//判斷傳入時間值中是否包含小時
- return //返回時間值包含小時的時間格式res.getElement(net.danlew.android.joda.ResourceTable.Plural_joda_time_android_duration_hours).getPluralString(hours, hours);
- }
- final int minutes = (int) duration.getStandardMinutes();
- if (minutes != 0) {//判斷傳入時間值中是否包含分鐘
- return //返回時間值包含分鐘的時間格式res.getElement(net.danlew.android.joda.ResourceTable.Plural_joda_time_android_duration_minutes).getPluralString(minutes, minutes);
- }
- final int seconds = (int) duration.getStandardSeconds();
- return //傳入時間值中包含秒則直接返回處理后的結果res.getElement(net.danlew.android.joda.ResourceTable.Plural_joda_time_android_duration_seconds).getPluralString(seconds, seconds);
- }