Java時間操作類庫-Joda-Time
前言
上一周在做一個產品的需求的時候有個動態計算時間段(如現在是13:00,則時間段為15:10-17:10、17:10-19:10、19:10-21:10;即最早的出發時間為當前時間+參數【2h10min】,最遲的時間段為開始時間在20點前結束時間在20點后的時間段),期間大量使用到了日期時間類庫,本著熟悉日期時間類庫才有了這篇文章,文章最后我會把我如何實現的這個需求的一個算法貼出來。
一、JDK8以前版本中的時間類庫
1.1 原始時間類庫存在的缺陷與不足
我們在使用Java8之前的類庫時,都會在處理日期-時間的時候總是不爽,這其中包括且不限于以下的槽點:
在Java 1.0版本中,對時間、日期的操作完全依賴于 java.util.Data 類,只能以毫秒的精度表示時間,無法表示日期。
- 在易用性方面有著很大的缺陷,年份的起始時間選擇是1900年,月份是從0開始。
- toString 方法返回值不直觀,帶有時區。
在Java1.1 版本中,廢棄了很多Date 類中的很多方法,并且新增了 java.util.Calendar。但是與Date相同,Calendar 類也有類似的問題和設計缺陷,導致在使用這些類寫出的代碼也很容易出錯。
- 月份依然是從0開始計算。
- 常用的日期、時間操作需要同時使用Date、Canendar、SimpleDateFormat,比較繁瑣。
- 部分特性只存在于某一個類(解析和格式化日期或時間的DateFormat方法只存在于Date類中)。
- DateFormat 不是線程安全的,如果兩個線程嘗試使用同一個formatter 解析日期,可能會得到無法預期的結果。
- Date 和 Canendar 都是可變的。
1.2 關于SimpleDateFormat 線程不安全的原因
由于 parse 方法使用的貢獻變量 calendar 不是線程安全的。在 format (subFormat) 方法中進行了 calendar 的賦值,在 parse 進行了值得處理,因此在并發的情況下會造成 calendar 清理不及時,值被覆蓋的情況。
- /**
- * The {@link Calendar} instance used for calculating the date-time fields
- * and the instant of time. This field is used for both formatting and
- * parsing.
- *
- * <p>Subclasses should initialize this field to a {@link Calendar}
- * appropriate for the {@link Locale} associated with this
- * <code>DateFormat</code>.
- * @serial
- */
- protected Calendar calendar;
- @Override
- public StringBuffer format(Date date, StringBuffer toAppendTo,
- FieldPosition pos){
- pos.beginIndex = pos.endIndex = 0;
- return format(date, toAppendTo, pos.getFieldDelegate());
- }
- // Called from Format after creating a FieldDelegate
- private StringBuffer format(Date date, StringBuffer toAppendTo,
- FieldDelegate delegate) {
- // Convert input date to time field list
- calendar.setTime(date);
- // At this point the fields of Calendar have been set. Calendar
- // will fill in default values for missing fields when the time
- // is computed.
- pos.index = start;
- Date parsedDate;
- try {
- parsedDate = calb.establish(calendar).getTime();
- // If the year value is ambiguous,
- // then the two-digit year == the default start year
- if (ambiguousYear[0]) {
- if (parsedDate.before(defaultCenturyStart)) {
- parsedDate = calb.addYear(100).establish(calendar).getTime();
- }
- }
- }
- }
1.3 如何解決上述線程不安全問題?
- 使用ThreadLocal 為每個線程都創建一個線程獨享 SimpleDateFormat 變量;
- 需要的時候創建局部變量;
- 使用 org.apacle.commons.lang3.time.DateFormatUtils
- 使用Joda-Time (后面介紹)
二、Joda-Time 日期時間類庫
2.1 簡介
Joda-Time 是Joda提供的一個遵循Apache2.0 開源協議的 JDK以外的優質日期和時間開發庫。
- Joda除Joda-Time之外的項目有Joda-Money、Joda-Beans、Joda-Convert、Joda-Collect Joda官網
2.1.1 為什么使用Joda-Time
- 使用方便:Calendar 訪問“正常的”日期困難,并且缺乏簡單的防腐,Joda-Time 擁有簡單的字段訪問,比如獲得年的 getYear() 和 獲得星期 中的天 getDayOfWeek() 。
- 易于擴展:JDK支持通過使用子類實現多個日歷系統,但是這是非常笨重的,并且在實現中很難選出另一個日歷系統。Joda-Time 支持基于 Chronology 類實現多個可插拔的日歷系統。
- 功能全面:Joda-Time 提供了所有的日期和時間計算的必須功能,它提供了即裝即用的特性。
- 最新的時區計算:時區的實現基于公共時區信息數據庫,每年更新數次。新版本的Joda-Time 包括了這個數據庫的所有更改,應盡早進行必要的更新,手動更新區域數據很容易。
- 日歷支持:提供了8種日歷系統。
- 互通性:內部使用毫秒進行標識,這與JDK或者其他公共的時間表示相一致。
- 性能良好:支持針對所有訪問的域進行最小的計算。
- 良好的測試覆蓋率:有全方位的測試人員保證庫的質量、
- 具有完整文檔:有一個完整的用戶指南,該指南提供了一個概述,涵蓋常見的使用場景。javadoc 非常詳細,涵蓋API的其余部分。
- 發展:自2002年以來積極發展,是一個成熟的可靠的代碼庫,一些相關的項目目前也是可用的。
- 開源:遵循Apache 2.0開源協議發布。
2.1.2 Joda-Time 的關鍵優點
- LocalDate:只包含日期
- LocalTime:只包含時間
- Instant:時間軸上的時間點
- DateTime:時區中完整的日期和時間
- DateTimeZone:更好的時區
- Duration和Period:持續時間
- Interval:兩個時間點之間的時間
- 全面并且靈活的時間格式化與轉換
正因為Joda-Time 與 Java8 之前的時間類庫相比,具備了如此多的優點,所以 Joda-Time 成為事實上的標準的Java日期和時間庫。
2.2 特性解讀
2.2.1 Joda-Time和JDK的互操作性
互操作性是指:Joda 類能夠生成 java.util.Date 的實例(以及Calendar),這可以讓我們保留現有對JDK的依賴,又能夠使用Joda處理復雜的日期/時間計算。
Date To Joda-Time
- Date date = new Date();
- DateTime dateTime = new DateTime(date);
Canendar To Joda-Time
- Calendar calendar = Calendar.getInstance();
- DateTime dateTime = new DateTime(calendar);
Joda-Time To Date
- Date date = new Date();
- DateTime dateTime = new DateTime(date);
- Date date2 = dateTime.toDate();
Joda-Time To Calendar
- Calendar calendar = Calendar.getInstance();
- dateTime = new DateTime(calendar);
- Calendar calendar2 = dateTime.toCalendar(Locale.CHINA);
2.2.2 Joda的關鍵日期/時間概念理解
Joda 使用了以下概念,使得它們可以應用到任何日期/時間庫:
不可變性(Immutability)
Joda-Time與java.lang.String類似,它們的實例均無法修改(因為任意對其值改變的操作都會生成新的對象),這也代表了它們是線程安全的。
瞬時性(Instant)
如接口 org.joda.time.ReadableInstant 中所表示的那樣,Instant 表示的是一個精確的時間點,是從 epoch:1970-01-01T00:00:00Z 開始計算的毫秒數,這也的設計也使得其子類都可以與JDK Date 以及 Calendar 類兼容。
- /**
- * Defines an instant in the datetime continuum.
- * This interface expresses the datetime as milliseconds from 1970-01-01T00:00:00Z.
- * <p>
- * The implementation of this interface may be mutable or immutable.
- * This interface only gives access to retrieve data, never to change it.
- * <p>
- * Methods in your application should be defined using <code>ReadableInstant</code>
- * as a parameter if the method only wants to read the instant without needing to know
- * the specific datetime fields.
- * <p>
- * The {@code compareTo} method is no longer defined in this class in version 2.0.
- * Instead, the definition is simply inherited from the {@code Comparable} interface.
- * This approach is necessary to preserve binary compatibility.
- * The definition of the comparison is ascending order by millisecond instant.
- * Implementors are recommended to extend {@code AbstractInstant} instead of this interface.
- *
- * @author Stephen Colebourne
- * @since 1.0
- */
- public interface ReadableInstant extends Comparable<ReadableInstant> {
- /**
- * Get the value as the number of milliseconds since
- * the epoch, 1970-01-01T00:00:00Z.
- *
- * @return the value as milliseconds
- */
- long getMillis();
- ······
- }
DateTime 類繼承圖如下:

局部性(Partial)
瞬時性表達的是與epoch相對的時間上的一個精確時刻,而一個局部時間指的是一個時間的一部分片段,其可以通過一些方法使得時間產生變動(本質上還是生成了新的類),這樣可以把它當做重復周期中的一點,用到多個地方。
年表(Chronology)
Joda-Time的設計核心就是年表(org.joda.time.Chronology),從根本上將,年表是一種日歷系統,是一種計算時間的特殊方式,并且在其中執行日歷算法的框架。Joda-Time支持的8種年表如下所示:
- ISO(默認) - org.joda.time.chrono.ISOChronology
- GJ - org.joda.time.chrono.GJChronology
- Gregorian - org.joda.time.chrono.GregorianChronology
- Julian - org.joda.time.chrono.JulianChronology
- Coptic - org.joda.time.chrono.CopticChronology
- Buddhist - org.joda.time.chrono.BuddhistChronology
- Ethiopic - org.joda.time.chrono.EthiopicChronology
- Islamic - org.joda.time.chrono.IslamicChronology
以上的每一種年表都可以作為特定日歷系統的計算引擎,是可插拔的實現。
時區(Time zone)
具體定義詳見百科解釋,在實際編碼過程中任何嚴格的時間計算都必須涉及時區(或者相對于GMT),Joda-Time中對應的核心類為org.joda.time.DateTimeZone,雖然日常的使用過程中,并未涉及到對時區的操作,但是DateTimeZone如何對DateTime產生影響是比較值得注意的,此處不進行贅述。
2.3 具體使用方法
上面介紹我完了Joda-Time的一些概念,接下來具體使用我們來進行說明:
2.3.1 創建 Joda-Time 對象
瞬時性-ReadableInstant
- // 1.使用系統時間
- DateTime dateTime1 = new DateTime();
- // 2.使用jdk中的date
- Date jdkDate1 = new Date();
- DateTime dateTime2 = new DateTime(jdkDate1);
- // 3.使用毫秒數指定
- Date jdkDate2 = new Date();
- long millis = jdkDate.getTime();
- DateTime dateTime3 = new DateTime(millis);
- // 4.使用Calendar
- Calendar calendar = Calendar.getInstance();
- DateTime dateTime4 = new DateTime(calendar);
- // 5.使用多個字段指定一個瞬間時刻(局部時間片段)
- // year month day hour(midnight is zero) minute second milliseconds
- DateTime dateTime5 = new DateTime(2000, 1, 1, 0, 0, 0, 0);
- // 6.由一個DateTime生成另一個DateTime
- DateTime dateTime6 = new DateTime(dateTime1);
- // 7.有時間字符串生成DateTime
- String timeString = "2019-01-01T00:00:00-06:00";
- DateTime dateTime7 = DateTime.parse(timeString);
局部性-ReadablePartial
當程序中處理的日期、時間并不需要是完整時刻的時候,可以創建一個局部時間,比如只希望專注于年/月/日, 或者一天中的時間,或者是一周中的某天。Joda-Time中有表示這些時間的是org.joda.time.ReadablePartial接口,實現它的兩個類LocalDate和LocalTime是分別用來表示年/月/日和一天中的某個時間的。
- // 顯示地提供所含的每個字段
- LocalDate localDate = new LocalDate(2019, 1, 1);
- // 6:30:06 PM
- LocalTime localTime = new LocalTime(18, 30, 6, 0);
LocalDate是替代了早期Joda-Time版本中使用的org.joda.time.YearMonthDay,LocalTime是替代早期版本的org.joda.time.TimeOfDay。(均已被標注為過時狀態)。
時間跨度
Joda-Time提供了三個類用于表示時間跨度(在某些業務需求中,它們可能會非常有用)。
Duration
這個類表示以毫秒為單位的絕對精度,提供標準數學轉換的方法,同時把時間跨度轉換為標準單位。
Period
這個類表示以年月日單位表示。
Interval
這個類表示一個特定的時間跨度,使用一個明確的時刻界定這段時間跨度的范圍。Interval 為半開 區間,所以由其封裝的時間跨度包括這段時間的起始時刻,但是不包含結束時刻。
2.3.2 使用Joda-Time的方法處理時間
- DateTime today = new DateTime();
- // 獲取777秒之前的時間
- DateTime dateTime1 = today.minus(777 * 1000);
- // 獲取明天的時間
- DateTime tomorrow = today.plusDays(1);
- // 獲取當月第一天的日期
- DateTime dateTime2 = today.withDayOfMonth(1);
- // 獲取當前時間三個月后的月份的最后一天
- DateTime dateTime3 = today.plusMonths(3).dayOfMonth().withMaximumValue();
下面列出部分DateTime方法列表: plus/minus開頭的方法(比如:plusDay, minusMonths):用來返回在DateTime實例上增加或減少一段時間后的實例
- plus(long duration) 增加指定毫秒數并返回
- plusYears(int years) 增加指定年份并返回
- plusMonths(int months) 增加指定月份并返回
- plusWeeks(int weeks) 增加指定星期并返回
- plusDays(int days) 增加指定天數并返回
- plusHours(int hours) 增加指定小時并返回
- plusMinutes(int minutes) 增加指定分鐘并返回
- plusSeconds(int seconds) 增加指定秒數并返回
- plusMillis(int millis) 增加指定毫秒并返回
與之相反的是minus前綴的 plus是增加 minus是減少
with開頭的方法:用來返回在DateTime實例更新指定日期單位后的實例
- withCenturyOfEra(int centuryOfEra) 更新時間世紀單位并返回
- withYearOfCentury(int yearOfCentury)更新世紀年并返回
- withYear(int year) 更新時間年并返回
- withWeekyear(int weekyear) 更新時間周數并返回
- withMonthOfYear(int monthOfYear)更新時間月份并返回
- withDayOfYear(int dayOfYear) 更新時間天數并返回
- withDayOfMonth(int dayOfMonth) 更新時間天數并返回
- withDayOfWeek(int dayOfWeek) 更新時間天數并返回
- withHourOfDay(int hour) 更新時間小時并返回
- withMinuteOfHour(int minute) 更新時間分鐘并返回
- withSecondOfMinute(int second) 更新時間秒數并返回
- withMillisOfSecond(int millis) 更新時間毫秒并返回
- withMillisOfDay(int millis) 更新時間毫秒并返回
- withTimeAtStartOfDay() 獲取當天最早時間
判斷DateTime對象大小狀態的一些操作方法
- compareTo(DateTime d) 比較兩時間大小 時間大于指定時間返回 1 時間小于指定時間返回-1 相等返回0
- equals(DateTime d) 比較兩時間是否相等
- isAfter(long instant) 判斷時間是否大于指定時間
- isAfterNow() 判斷時間是否大于當前時間
- isBefore(long instant) 判斷時間是否小于指定時間
- isBeforeNow() 判斷時間是否小于當前時間
- isEqual(long instant) 判斷時間是否等于指定時間
- isEqualNow() 判斷時間是否等于當前時間
2.3.3 以Joda-Time的方式格式化時間
- // 傳入的格式化模板只需與JDK SimpleDateFormat兼容的格式字符串即可
- public static String convert(Date date,String dateFormat){
- return new DateTime(date).toString(dateFormat);
- }
- // 將JDK中的Date轉化為UTC時區的DateTime
- DateTime dateTime = new DateTime(new Date(), DateTimeZone.UTC);
- // 將String轉換為DateTime
- public static Date convertUTC2Date(String utcDate){
- DateTime dateTime =DateTime.parse(utcDate, DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
- return dateTime.toDate();
- }
更多使用方法請參考官方文檔。
三、JAVA 8中新的時間類庫
3.1 簡介
由于JDK之前版本的類庫的缺陷和糟糕的使用體驗,再加上已經成為事實標準Joda-Time的影響力,Oracle決定在JAVA API中提供高質量的日期和時間支持,這也就是整合了大部分Joda-Time特性的JDK 8新的時間類庫。(Joda-Time的作者實際參與開發,并且實現了JSR310的全部內容,新的API位于java.time下。常用的類有以下幾個:LocalDate、LocalTime、Instant、Duration和Period。)
由于JDK 8 新的時間類庫大量借鑒了Joda-Time的設計思想乃至命名,因此如果你是Joda-Time的使用者,那你可以無學習成本的使用新的API(當然,它們之間也存在些許差別需要注意到)。
3.2 使用方法
3.2.1 使用LocalDate 和LocalTime
首先是LocalDate,該類的實例是一個不可變對象,它只提供了簡單的日期,并不含當天的時間信息。另外,它也不附帶任何與時區相關的信息。
- // 使用指定的日期創建LocalDate
- LocalDate date = LocalDate.of(2019, 1, 1);
- // 獲取當前日期
- LocalDate today = LocalDate.now();
- // 獲取今日的屬性
- int year = date.getYear();
- Month month = date.getMonth();
- int day = date.getDayOfMonth();
- DayOfWeek dow = date.getDayOfWeek();
- int len = date.lengthOfMonth();
- boolean leap = date.isLeapYear();
- // 通過ChronoField的枚舉值獲取需要的屬性字段
- int year = date.get(ChronoField.YEAR);
接著是LocalTime,它表示了一天內的某個時刻。
- LocalTime time = LocalTime.of(18, 18, 18);
- int hour = time.getHour();
- int minute = time.getMinute();
- int second = time.getSecond();
LocalDate和LocalTime都可以通過使用靜態方法parse來解析字符串進行創建。
- LocalDate date = LocalDate.parse("2019-01-01");
- LocalTime time = LocalTime.parse("18:18:18");
也可以向parse方法傳遞一個DateTimeFormatter,該類的實例定義了如何格式化一個日期或者時間對象。它其實是老版java.util.DateFormat的替代品。
3.2.2 LocalDateTime
- // 直接創建LocalDateTime
- LocalDateTime dt1 = LocalDateTime.of(2019, Month.JANUARY, 1, 18, 18, 18);
- // 合并日期和時間
- LocalDate date = LocalDate.parse("2019-01-01");
- LocalTime time = LocalTime.parse("18:18:18");
- LocalDateTime dt2 = LocalDateTime.of(date, time);
- LocalDateTime dt3 = date.atTime(18, 18, 18);
- LocalDateTime dt4 = date.atTime(time);
- LocalDateTime dt5 = time.atDate(date);
- // 從LocalDateTime中提取LocalDate或者LocalTime
- LocalDate date1 = dt1.toLocalDate();
- LocalTime time1 = dt1.toLocalTime();
3.3.3 Instant
Instant類是為了方便計算機理解的而設計的,它表示一個持續時間段上某個點的單一大整型數,實際上它是以Unix元年時間(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算(最小計算單位為納秒)。
- // 傳遞一個秒數已創建該類的實例
- Instant.ofEpochSecond(3);
- // 傳遞一個秒數+納秒 2 秒之后再加上100萬納秒(1秒)
- Instant.ofEpochSecond(2, 1_000_000_000);
3.3.4 Duration與Period
Duration是用于比較兩個LocalTime對象或者兩個Instant之間的時間差值。
- Duration d1 = Duration.between(time1, time2);
- Duration d1 = Duration.between(dateTime1, dateTime2);
- Duration d2 = Duration.between(instant1, instant2);
Period是用于對年月日的方式對多個時間進行比較。
- Period tenDays = Period.between(LocalDate.of(2019, 1, 1), lcalDate.of(2019, 2, 2));
當然,Duration和Period類都提供了很多非常方便的工廠類,直接創建對應的實例。
- Duration threeMinutes = Duration.ofMinutes(3);
- Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
- Period tenDays = Period.ofDays(10);
- Period threeWeeks = Period.ofWeeks(3);
- Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
3.3.5 操作、解析和格式化日期
- // 直接使用withAttribute的方法修改
- LocalDate date1 = LocalDate.of(2019, 1, 1);
- LocalDate date2 = date1.withYear(2019);
- LocalDate date3 = date2.withDayOfMonth(1);
- LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 1);
所有聲明了Temporal接口的類LocalDate、LocalTime、LocalDateTime以及Instant,它們都使用get和with方法,將對象值的讀取和修改區分開,如果使用了不支持的字段訪問字段,會拋出一個UnsupportedTemporalTypeException異常。類似的,plus方法和minus方法都聲明于Temporal接口。通過這些方法,對TemporalUnit對象加上或者減去一個數字,我們能非常方便地將Temporal對象前溯或者回滾至某個時間段,通過ChronoUnit枚舉我們可以非常方便地實現TemporalUnit接口。
3.3.6 更多定制化的處理時間
向重載的with方法傳遞一個定制化的TemporalAdjuster對象,可以更加靈活地處理日期。時間和日期的API已經提供了大量預定義的TemporalAdjuster,可以通過TemporalAdjuster類的靜態工廠方法訪問它們。這些方法的名稱非常直觀,方法名就是問題描述。某些情況下,如果你需要定義自己的TemporalAdjuster,只需要聲明TemporalAdjuster接口并且自己實現對應的方法即可。
- LocalDate date1 = LocalDate.of(2014, 3, 18);
- LocalDate date2 = date1.with(TemporalAdjuster.nextOrSame(DayOfWeek.SUNDAY));
- LocalDate date3 = date2.with(TemporalAdjuster.lastDayOfMonth());
3.3.7 解析日期-時間對象
日常工作中,格式化以及解析日期-時間對象是另一個非常重要的功能,而新的java.time.format包就是特別為我們達到這個目的而設計的。這其中,最重要的類是DateTimeFormatter。所有的DateTimeFormatter實例都能用于以一定的格式創建代表特定日期或時間的字符串。(與老的java.util.DateFormat相比較,所有的DateTimeFormatter實例都是線程安全的)
- // 使用不同的格式器生成字符串
- LocalDate date = LocalDate.of(2019, 1, 1);
- String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
- String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
- // 生成LocalDate對象
- LocalDate date1 = LocalDate.parse("20190101", DateTimeFormatter.BASIC_ISO_DATE);
- LocalDate date2 = LocalDate.parse("2019-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
- // 使用特定的模式創建格式器
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
- LocalDate date1 = LocalDate.of(2019, 1, 1);
- String formattedDate = date1.format(formatter);
- LocalDate date2 = LocalDate.parse(formattedDate, formatter);
3.3.8 處理不同的時區和日歷系統
在新的日期-時間類庫中,為了最大程度上的減少在處理時區帶來的繁瑣和復雜而使用了新的java.time.ZoneId類(與其他日期和時間類一樣,ZoneId類也是無法修改的) 來替代老版的java.util.TimeZone。時區是按照一定的規則將區域劃分成標準時間相同的區間。在ZoneRules這個類中包含了40個這樣的實例。可以簡單地通過調用ZoneId的getRules()得到指定時區的規則。每個特定的ZoneId對象都由一個地區ID標識,地區ID都為“{區域}/{城市}”的格式。比如:
- ZoneId romeZone = ZoneId.of("Asia/Shanghai");
Java 8中在原先的TimeZone中加入了新的方法toZoneId,其作用是將一個老的時區對象轉換為ZoneId:
- ZoneId zoneId = TimeZone.getDefault().toZoneId();
得到的ZoneId對象后可以將它與LocalDate、LocalDateTime或者是Instant對象整合起來,構造為一個ZonedDateTime實例,它代表了相對于指定時區的時間點:
- LocalDate date = LocalDate.of(2019, Month.JANUARY, 1);
- ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
- LocalDateTime dateTime = LocalDateTime.of(2019, Month.JANUARY, 18, 13, 45);
- ZonedDateTime zdt2 = dateTime.atZone(romeZone);
- Instant instant = Instant.now();
- ZonedDateTime zdt3 = instant.atZone(romeZone);
通過ZoneId,還可以將LocalDateTime轉換為Instant:
- LocalDateTime dateTime = LocalDateTime.of(2019, Month.JANUARY, 18, 13, 45);
- Instant instantFromDateTime = dateTime.toInstant(romeZone);
同樣可以通過反向的方式得到LocalDateTime對象:
- Instant instant = Instant.now();
- LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
與Joda-Time所不同的是,Java8中的日期-時間類庫提供了4種其他的日歷系統,這些日歷系統中的每一個都有一個對應的日志類,分別是ThaiBuddhistDate、MinguoDate 、JapaneseDate 以及HijrahDate 。所有這些類以及LocalDate 都實現了ChronoLocalDate接口,能夠對公歷的日期進行建模。利用LocalDate對象,你可以創建這些類的實例。同樣的,利用它們提供的靜態工廠方法,你可以創建任何一個Temporal對象的實例。
- LocalDate date = LocalDate.of(2019, Month.JANUARY, 1);
- JapaneseDate japaneseDate = JapaneseDate.from(date);
參考資料
Joda-Time 簡介 Joda Time項目和java8時間api
動態計算時間段
需求:如現在是13:00,則時間段為15:10-17:10、17:10-19:10、19:10-21:10;即最早的出發時間為當前時間+參數【2h10min】,最遲的時間段為開始時間在20點前結束時間在20點后的時間段),求解共有多少個時間段?
分析:
- 第一個時間段的開始時間:當前時間+參數【2h10min】,中間的時間段是2h;
- 通過理解這句:最遲的時間段為開始時間在20點前結束時間在20點后的時間段,我們可以假設最大的時間變量為 max
- 假設當前時間為now,總共有n個時間段,可以推導出公式:now + (2h * n) + 10min <= max;
注意:計算過程都轉換成毫秒
- public class Test {
- // 毫秒
- static final long slot = 130 * 60 * 1000;
- private static List<TimeSelectItem> buildStartEndTime(Long now, Long max) {
- // now + (2h * n) + 10min <= max;
- Long n = (max - now - 60 * 1000) / (120 * 60 * 1000);
- System.out.println("max:" + max);
- System.out.println("now:" + now);
- System.out.println(" max - now:" + (max - now));
- System.out.println("n:" + n);
- List<TimeSelectItem> timeSelectItems = new ArrayList<>();
- Long startTimestamp = now + slot;
- Long endTimestamp = startTimestamp + 120 * 60 * 1000;
- for (int i = 1; i <= n; i++) {
- // 起始時間
- // startTimestamp = startTimestamp + i * (120 * 60 * 1000);
- // 結束時間
- endTimestamp = startTimestamp + (120 * 60 * 1000);
- System.out.println(startTimestamp);
- System.out.println(endTimestamp);
- TimeSelectItem item = new TimeSelectItem();
- DateTime dt = new DateTime(startTimestamp);
- int hour = dt.hourOfDay().get();
- int millis = dt.getMinuteOfHour();
- String startTag = hour + ":" + millis;
- DateTime dt1 = new DateTime(endTimestamp);
- int hour1 = dt1.hourOfDay().get();
- long millis1 = dt1.getMinuteOfHour();
- String enTag = hour1 + ":" + millis1;
- item.setDisplayName(startTag + " - " + enTag);
- item.setStartTimestamp(startTimestamp);
- item.setEndTimestamp(endTimestamp);
- timeSelectItems.add(item);
- startTimestamp = endTimestamp;
- }
- return timeSelectItems;
- }
- public static void main(String[] args) {
- Long start = DateTime.now().getMillis();
- Calendar c = Calendar.getInstance();
- c.setTime(new Date());
- c.set(Calendar.HOUR_OF_DAY, 20);
- c.set(Calendar.MINUTE, 0);
- c.set(Calendar.SECOND, 0);
- DateTime dt = new DateTime();
- dt.withHourOfDay(20);
- Long end = c.getTimeInMillis();
- // List<TimeSelectItem> list = buildStartEndTime(1614747600000L, 1614772800000L);
- List<TimeSelectItem> list = buildStartEndTime(1614834000000L, end);
- for (TimeSelectItem item : list ) {
- System.out.println(item);
- }
- }
- }
【編輯推薦】