JavaSE 8—新的時間和日期API

jopen 10年前發布 | 23K 次閱讀 Java

為什么我們需要一個新的時間日期API

Java開發中一直存在一個問題,JDK提供的時間日期API一直對開發者沒有提供良好的支持。

比如,已有的的類(如java.util.DateSimpleDateFormatter)不是線程安全的,會在并發情況下留下一些隱患,這不是開發者在編寫處理日期的代碼塊時想要的效果。

某些日期時間處理類也表現出了相當不合理的設計,比如在java.util.Date類中,年從1900開始,月從1開始,日從0開始——就表現的不是很直觀。

這些問題導致我們會選擇一些第三方的日期時間處理庫,比如:Joda-Time

為了在JDK中解決這些問題并提供更好的方式,Java SE8中設計了新的時間日期處理API。

這個項目為JSR-310并由Joda-Time(Stephen Colebourne)和Oracle共同設計,并會放在Java SE 8的java.time包下。

核心思路

這個新的API由三個核心思路組成:

  • 不可改變值的類。一個嚴重的問題是,對于Java中已經存在的格式化處理類(Formatter)不是線程安全的。這使開發人員在日常開發中需要編寫線程安全的日期處理代碼變得很麻煩。新的API保證所有的核心類中的值是不可變的,避免了并發情況下帶來的不必要的問題。

  • 領域驅動設計。新的API模型可以精確的表示出DateTime的差異性。在以前的Java庫中這一點就表現的非常差。比如,java.util.Date他表示一個時間點,從Unix時代開始就是以毫秒數的形式保存,但是你調用它的toString方法時,結果卻顯示它是有時區概念的,這就容易讓開發者產生歧義。

    領域驅動設計的重點是從長遠好處出發且簡單易懂, 當你需要把你以前的處理時間的模塊代碼移植到Java8上時你就需要考慮一下領域模型的設計了。

  • 區域化時間體系。新的API允許人們在時區不同的時間體系下使用。比如日本或者泰國,他們不必要遵循 ISO-8601。新API為大多數開發者減少了很多額外的負擔,我們只需要使用標準的時間日期API。

LocalDate類和LocalTime類

LocalDate類和LocalTime類很有可能是你使用新API時第一個遇見的類。他們本地化的原因是他們能夠根據系統環境來表示日期和時間,就像放在桌上的日歷或者掛在墻上的時鐘。還有一個混合類是LocalDateLocalTime組合而成的,叫LocalDateTime

當你不知道你所運行環境的時區時,你應該使用這些本地化的類。比如桌面JavaFX程序就是其中之一。甚至可以在處于不同時區的分布式系統中使用這些類。

現有的時間日期API中的類都不是線程安全的,開發者需要處理潛在的并發問題——這不是大部分的開發者想要的。

創建對象

在新的API中所有的核心類都可以由工廠方法很方便的構建。當我通過某些類自身的字段來構建它時,可以使用of方法;當我通過從另外一個類型的轉換來構建它時,可以使用from方法。同樣也可以通過parse方法來由一個String參數構建它。參見代碼1.
代碼1

LocalDateTime timePoint = LocalDateTime.now(
    );     // The current date and time
LocalDate.of(2012, Month.DECEMBER, 12); // from values
LocalDate.ofEpochDay(150);  // middle of 1970
LocalTime.of(17, 18); // the train I took home today
LocalTime.parse("10:15:30"); // From a String

在Java SE 8 中我們可以使用Java標準的getter方法來獲取想要的值,參見代碼2

代碼2

LocalDate theDate = timePoint.toLocalDate();
Month month = timePoint.getMonth();
int day = timePoint.getDayOfMonth();
timePoint.getSecond();

你也可以對對象的值進行運算操作。因為新的API中所有的類型都是不可變的,他們都是調用了with方法并返回一個新對象,相當于使用了setter賦值。對于每一個字段都有提供了基本的運算方法。參見代碼3

代碼3

// Set the value, returning a new object
LocalDateTime thePast = timePoint.withDayOfMonth(
    10).withYear(2010);

/* You can use direct manipulation methods, 
    or pass a value and field pair */
LocalDateTime yetAnother = thePast.plusWeeks(
    3).plus(3, ChronoUnit.WEEKS);

新的API提供的一個調節器的概念–用來封裝通用的處理邏輯的一段代碼。你對任意時間使用WithAdjuster來設置一個或者多個字段,或者可以使用PlusAdjuster來對字段進行增量或者減法操作. 值類型也可以被當做調節器使用,用來更新字段的值. 新的API定義了一些內置的調節器, 但是如果你希望實現某些特定的業務邏輯,你也可以自己實現一個調節器. 參見代碼4。

代碼4

import static java.time.temporal.TemporalAdjusters.*;

LocalDateTime timePoint = ...
foo = timePoint.with(lastDayOfMonth());
bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY));

// Using value classes as adjusters
timePoint.with(LocalTime.now()); 

截取

新的API提供了表示不同精度的時間點類型來表示日期、時刻、日期+時刻。

API提供的truncatedTo方法適用于這種場景:他允許你從一個字段中截取出一個值。參見代碼5。

代碼5

LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);

時區

我們參考了以前的對于時區的很復雜的抽象方式。時區就是一個規則的集合,同樣的時區遵守同樣的時間標準規定,時區大約有40條規則。時區是由世界統一時間(UTC)來定義的。 他們基本上是同步的但是也有細小的差別。時區有兩種命名定義: 簡寫型, 比如, “PLT”,完整型,“Asia/Karachi.”。當你設計程序的時候,你應該考慮是在什么時區下運行。

  • ZoneId 是時區的標示符. 每一個ZoneId都表示這些時區遵循同樣的規則。 當你編碼時你可以考慮使用例如“PLT”,“Asia/Karachi,”這種字符串來創建ZoneId。下面是一段完整的使用ZoneId的代碼,參見代碼6。

代碼6

// You can specify the zone id when creating a zoned date time
ZoneId id = ZoneId.of("Europe/Paris");
ZonedDateTime zoned = ZonedDateTime.of(dateTime, id);
assertEquals(id, ZoneId.from(zoned));
  • ZoneOffset 是一段時間內代表格林尼治時間/世界同一時間與時區之間的差異。他能表示一個特殊的差異時區偏移量。參見代碼7。

代碼7

ZoneOffset offset = ZoneOffset.of("+2:00");

時區類

  • ZonedDateTime是一個帶有時區的日期時間類。他能表示出與任意時區的某個時間點的時差. 如果你希望代表一個日期和時間不依賴特定服務器環境, 你就應該使用ZonedDateTime.

代碼8

ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
  • OffsetDateTime 是一個帶有偏移量的時間日期類。如果你的服務器處在不同的時區,他可以存入數據庫中也可以用來記錄某個準確的時間點。
  • OffsetTime 是一個帶有偏移量的時間類。參見代碼9
    代碼9
OffsetTime time = OffsetTime.now();
// changes offset, while keeping the same point on the timeline
OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(offset);
// changes the offset, and updates the point on the timeline
OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(offset);
// Can also create new object with altered fields as before
changeTimeWithNewOffset.withHour(3).plusSeconds(2);

Java中已經存在了表示時區的類—java.util.TimeZone—但是他不能用在Java SE 8中,因為在JSR-310中所有的時間日期類是不可變的,時區類確是可變的。

Period類

Periods類用來表示例如“三個月零一天”這種描述一段時間的值。這是目前看來與其他類不同的表示一段時間而不是時間點的類。 參見代碼10。

代碼10

// 3 years, 2 months, 1 day
Period period = Period.of(3, 2, 1);

// You can modify the values of dates using periods
LocalDate newDate = oldDate.plus(period);
ZonedDateTime newDateTime = oldDateTime.minus(period);
// Components of a Period are represented by ChronoUnit values
assertEquals(1, period.get(ChronoUnit.DAYS)); 

Duration類

Duration類也是用來描述一段時間的, 他和Period類似,但是不同于Period的是,它表示的精度更細。參見代碼11。

// A duration of 3 seconds and 5 nanoseconds
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);

我們可以對其進行加減或者with函數操作,也可以使用它來修改一個時間日期對象的值。

Java SE 8 的java.time包中新的時間日期API的功能可用性和安全性都大大提升了。 新的API很好用,可以適應大部分的場景。

Chronology系列類

由于我們需要支持無ISO日期年表表示的環境, Java SE 8 首次引入了Chronology類, 在這種環境下使用。 他們也實現了核心的時間日期接口。
Chronology:
– ChronoLocalDate
– ChronoLocalDateTime
– ChronoZonedDateTime

這些類應該使用在具有高度國際化的應用中需要使用本地化的時間體系時,沒有這種需求的話最好不要使用它們。有一些時間體系中甚至沒有一個月,一個星期這樣的概念,我們只能通過更加通用抽象的字段進行運算。

其余的API改動

Java SE 8 還提供了一些其他場景下使用的類. MonthDay類表示一個月中的某幾天,表示節日的時候可以使用。YearMonth類可以用來表示在比如信用卡的生效和失效時間(譯者注:信用卡失效時間以月為單位)。

Java SE 8中也提供了對于JDBC的新的類型支持, 但是是一個非公開的修改。

這些類也可以和某些數據庫類型進行映射。如下表,描述了這些類對于ANSI SQL的對應關系

ANSI SQL Java SE 8
DATE LocalDate
TIME LocalTime
TIMESTAMP TIMESTAMP
TIMESTAMP WITH TIMEZONE OffsetDateTime

總結

Java SE 8提供的java.time中新的日期和時間API。很大的提升了安全性,功能也更為強大。新的API架構模型,會讓開發人員在各種各樣場景下很方面的使用。

原文鏈接: oracle 翻譯: ImportNew.com - 胡 勁寒
譯文鏈接: http://www.importnew.com/9635.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!