日期和時間 API
爲什麼處理時間會如此之難呢?問題出在人類自身上
- Java 1.0 有一個
Date
類,事後證明它過於簡單了 - 當 Java 1.1 引入
Calendar
類之後,Date
類中的大部分方法就被棄用了。但是,Calendar
API 還不夠給力,它的實例是易變的,並且它沒有處理諸如閏秒這樣的問題。 - 第 3 次升級很吸引人,那就是 Java SE 中引入的
java.time
API ,它修正了過去的缺陷,並且應該會服役相當長的一段時間。
時間線( Instant
、Duration
)
在歷史上,基本的時間單位 秒
是從地球的自轉中推導出來的。地球自轉一週需要 24 個小時,即 24 * 60 * 60 = 86400 秒,因此,看起來這好像只是一個有關如何精確定義 1 秒的天文度量問題。遺憾的是,地球有輕微的顫動,所以需要更加精確的定義。1967 年,人們根
據銫 133 原子內在的特性推導出了與其歷史定義相匹配的秒的新的精確定義。自那以後,原子鐘網絡就一直被當作官方時間。
官方時間的監護者們時常需要將絕對時間與地球自轉進行同步。首先,官方的秒需要稍作調整,從 1972 年開始,偶爾需要插入“閏秒” (在理論上,偶爾也需要移除 1 秒,但是這還從來沒發生過 )。閏秒是個痛點,許多計算機系統使用“平滑”方式來人爲地在緊鄰閏秒之前讓時間變慢或變快,以保證每天都是 86400 秒。這種做法可以奏效,因爲計算機上的本地時間並非那麼精確,而計算機也慣於將自身時間與外部的時間服務進行同步
Java 的 Date 和 Time API 規範要求 Java 使用的時間尺度爲:
- 每天 86400 秒
- 每天正午與官方時間精確匹配
- 在其他時間點上,以精確定義的方式與官方時間接近匹配
在 Java 中, Instant
表示時間線上的某個點。 被稱爲“新紀元”的時間線原點被設置爲穿過倫敦格林威治皇家天文臺的本初子午線所處時區的 1970 年 1 月 1 日的午夜。這與 UNIX/POSIX 時間中使用的慣例相同。從該原點開始,時間按照每天 86400 秒向前或向回度量,精確到納秒。
Instant
的值向回可追溯 10 億年 ( Instant.MIN
)。最大的值 Instant.MAX
是公元 1 000 000 000 年的 12 月 31 日
靜態方法調用 Instant.now()
會給出當前的時刻。你可以按照常用的方式,用 equals
和 compareTo
方法來比較兩個 Instant
對象,因此你可以將 Instant
對象用作時間戳
爲了得到兩個時刻之間的時間差,可以使用靜態方法 Duration.between
Duration
是兩個時刻之間的時間量。 你可以通過調用 toNanos
、toMillis
、getSeconds
、toMinutes
、toHours
、toDays
獲得 Duration
按照傳統單位度量的時間長度
Instant instant1 = Instant.now();
System.out.println(instant1); // 2022-02-08T10:52:40.603Z
Thread.sleep(1234);
Instant instant2 = Instant.now();
Duration duration1 = Duration.between(instant1, instant2);
Duration duration2 = Duration.between(instant2, instant1);
System.out.println(duration1); // PT1.309S
System.out.println(duration2); // PT-1.309S
System.out.println(duration1.toMillis()); // 1309
Duration
對象的內部存儲所需的空間超過了一個 long
的值,因此秒數存儲在一個 long
中,而納秒數存儲在一個額外的 int
中。如果想要讓計算精確到納秒級,那麼實際上你需要整個 Duration
的存儲內容。如果不要求這麼高的精度,那麼你可以用 long
的值來執行計算,然後直接調用 toNanos
大約 300 年時間對應的納秒數纔會溢出 long
的範圍
如果想要檢查某個算法是否至少比另一個算法快 10 倍:
Duration timeElapsed = Duration.between(start, end);
Duration timeElapsed2 = Duration.between(start2, end2);
boolean overTenTimesFaster =
timeElapsed.multipliedBy(10).minus(timeElapsed2).isNegative();
// boolean overTenTimesFaster = timeElapsed.toNanos() * 10 < timeElapsed2.toNanos();
用於時間的 Instant
和 Duration
的算術運算
方法 | 描述 |
---|---|
plus 、 minus |
在當前的 Instant 或 Duration 上加上或減去一個 Duration |
plusNanos 、 plusMillis 、 plusSeconds 、 minusNanos 、 minusMillis 、 minusSeconds |
在當前的 Instant 或 Duration 上加上或減去給定時間單位 |
plusMinutes 、 plusHours 、 plusDays 、 minusMinutes 、 minusHours 、 minusDays |
在當前 Duration 上加上或減去給定時間單位的數值 |
multipliedBy 、 dividedBy 、 negated |
返回由當前的 Duration 乘以或除以給定 long 或 -1 而得到的 Duration 。注意,可以縮放 Duration ,但是不能縮放 Instant |
isZero 、 isNegative |
檢查當前的 Duration 是否是 0 或負值 |
Instant
和 Duration
類都是不可修改的類,所以諸如 multipliedBy
和 minus
這樣的方法都會返回一個新的實例
本地日期( LocalDate
、Period
)
讓我們從絕對時間轉移到人類時間。 Java API 中有兩種 人類時間 ,本地日期/時間 和 時區時間 。本地日期/時間包含日期和當天的時間,但是與時區信息沒有任何關聯。1903 年 6 月 14 日就是一個本地日期的示例( lambda 的發明者 Alonzo Church 在這一天誕生)。因爲這個日期既沒有當天的時間,也沒有時區信息,因此它並不對應精確的時刻。 與之相反的是, 1969 年 7 月 16 日 09:32:00 EDT (阿波羅 11 號發射的時刻)是一個時區日期/時間,表示的是時間線上的一個精確的時刻。
有許多計算並不需要時區,在某些情況下,時區甚至是一種障礙。 假設你安排每週 10:00 開一次會。如果你加 7 天到最後一次會議的時區時間上,那麼你可能會碰巧跨越了夏令時的時間調整邊界,這次會議可能會早一小時或晚一小時!
API 的設計者們推薦程序員不要使用時區時間,除非確實想要表示絕對時間的實例。生日、假日、 計劃時間等通常最好都表示成本地日期和時間。
LocalDate
是帶有年、月、 日的日期。爲了構建 LocalDate
對象,可以使用 now
或 of
靜態方法:
LocalDate today = LocalDate.now();
LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14);
alonzosBirthday = LocalDate.of(1903, Month.JUNE, 14);
與 UNIX 和 java.util.Date
使用的月從 0
開始計算而年從 1900
開始計算的不規則的慣用法不同,你需要提供通常使用的月份的數字。或者,你可以使用 java.time.Month
枚舉
最有用的操作 LocalDate
對象的方法:
方法 | 描述 |
---|---|
now 、 of |
這些靜態方法會構建一個 LocalDate ,要麼從當前時間構建,要麼從給定的年、月、日構建 |
plusDays 、 plusWeeks 、 plusMonths 、 plusYears |
在當前的 LocalDate 上加上一定量的天、星期、月或年 |
minusDays 、 minusWeeks 、 minusMonths 、 minusYears |
在當前的 LocalDate 上減去一定量的天、星期、月或年 |
plus 、 minus |
加上或減去一個 Duration 或 Period |
withDayOfMonth 、 withDayOfYear 、 withMonth 、 withYear |
返回一個新的 LocalDate 。其月的日期、年的日期、月或年修改爲給定的值 |
getDayOfMonth |
獲取月的日期(在 1 到 31 之間) |
getDayOfYear |
獲取年的日期(在 1 到 366 之間) |
getDayOfWeek |
獲取星期日期,返回 DayOfWeek 枚舉值 |
getMonth 、 getMonthValue |
獲取月份的 Month 枚舉值,或者是 1 - 12 之間的數 |
getYear |
獲取年份,在 -999 999 999 到 999 999 999 之間 |
until |
獲取 Period ,或者兩個日期之間按照給定的 ChronoUnits 計算的數值 |
isBefore 、 isAfter |
將當前的 LocalDate 與另一個 LocalDate 進行比較 |
isLeapYear |
如果當前是閏年,則返回 true |
程序員日是每年的第 256 天,如何很容易地計算出它:
LocalDate programmersDay = LocalDate.of(2022, 1, 1).plusDays(255);
兩個 Instant
之間的時長是 Duration
,而用於本地日期的等價物是 Period
,它表示的是流逝的年、月或日的數量。可以調用 birthday.plus(Period.ofYears(1))
來獲取下一年的生日。當然,也可以直接調用 birthday.plusYears(1)
util
方法會產生兩個本地日期之間的時長
LocalDate birthday = LocalDate.now();
birthday.plus(Period.ofYears(1));
LocalDate nextBirthday = birthday.plusYears(1);
Period period = birthday.until(nextBirthday);
System.out.println(period); // P1Y
System.out.println(period.getYears()); // 1
long days = birthday.until(nextBirthday, ChronoUnit.DAYS);
System.out.println(days); // 365
有些方法可能會創建出並不存在的日期。例如,在 1 月 31 日上加上 1 個月不應該產生 2 月 31 日。這些方法並不會拋出異常,而是會返回該月有效的最後一天
getDayOfWeek
會產生星期日期,即 DayOfWeek
枚舉的某個值。 DayOfWeek.MONDAY
的枚舉值爲 1
,而 DayOfWeek.SUNDAY
的枚舉值爲 7
DayOfWeek
枚舉具有便捷方法 plus
和 minus
,以 7
爲模計算星期日期
int value = DayOfWeek.MONDAY.getValue();
System.out.println(value); // 1
DayOfWeek dayOfWeek = DayOfWeek.SATURDAY.plus(3);
System.out.println(dayOfWeek); // TUESDAY
週末實際上在每週的末尾。這與 java.util.Calendar
所差異,在 Calendar
中,星期六的值爲 1
,而星期天的值爲 7
除了 LocalDate
之外,還有 MonthDay
、 YearMonth
、Year
類可以描述部分日期。例如, 12 月 25 日(沒有指定年份)可以表示成一個 MonthDay
對象
MonthDay monthDay = MonthDay.of(Month.DECEMBER, 5);
System.out.println(monthDay); // --12-05
日期調整器( TemporalAdjusters
)
TemporalAdjusters
類提供了大量用於常見調整的靜態方法。你可以將調整方法的結果傳遞給 with
方法。例如,某個月的第一個星期二可以像下面這樣計算:
LocalDate firstTuesday = LocalDate.of(year, month, 1).with(
TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
with
方法會返回一個新 LocalDate
對象, 不會修改原來的對象
TemporalAdjusters
類中的日期調整器:
方法 | 描述 |
---|---|
next(dayOfWeek) 、 previous(dayOfWeek) |
下一個或上一個給定的星期日期 |
nextOrSame(dayOfWeek) 、 previousOrSame(dayOfWeek) |
從給定的日期開始的下一個或上一個給定的星期日期 |
dayOfWeekInMonth(ordinal, dayOfWeek) |
月份中的第 n 個 weekday |
lastInMonth(DayOfWeek dayOfWeek) |
月份中的最後一個 weekday |
firstDayOfMonth() 、 firstDayOfNextMonth() 、 firstDayOfNextYear() 、 lastDayOfMonth() 、 lastDayOfYear() |
方法名所描述的日期 |
還可以通過實現 TemporalAdjuster
接口來創建自己的調整器。下面是用於計算下一個工作日的調整器:
LocalDate today = LocalDate.now();
TemporalAdjuster NEXT_WORKDAY = w -> {
LocalDate result = (LocalDate) w;
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
};
LocalDate backToWork = today.with(NEXT_WORKDAY);
注意, lambda 表達式的參數類型爲 Temporal
,它必須被強制轉型爲 LocalDate
。可以用 ofDateAdjuster
方法來避免這種強制轉型,該方法期望得到的參數是類型爲 UnaryOperator<LocalDate>
的 lambda 表達式
TemporalAdjuster NEXT_WORKDAY = TemporalAdjusters.ofDateAdjuster(w -> {
LocalDate result = w; // 無需轉換
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
});
本地時間( LocalTime
)
LocalTime
表示當日時刻,例如 15:30:00 。可以用 now
或 of
方法創建其實例:
LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // or LocalTime.of(22, 30, 0)
plus
和 minus
操作是按照一天 24 小時循環操作的
LocalTime
自身並不關心 AM/PM 。這種愚蠢的設計將問題拋給格式器去解決
LocalTime
的方法:
方法 | 描述 |
---|---|
now 、of |
這些靜態方法會構建一個 LocalTime ,要麼從當前時間構建,要麼從給定的小時和分鐘,以及可選的秒和納秒構建 |
plusHours 、plusMinutes 、plusSeconds 、plusNanos |
在當前的 LocalTime 上加上一定量的小時、分鐘、秒或納秒 |
minusHours 、minusMinutes 、minusSeconds 、minusNanos |
在當前的 LocalTime 上減去一定量的小時、分鐘、秒或納秒 |
plus 、minus |
加上或減去一個 Duration |
withHour 、withMinute 、withSecond 、withNano |
返回一個新的 LocalTime ,其小時、分鐘、秒和納秒修改爲給定的值 |
getHour 、getMinute 、getSecond 、getNano |
獲取當前 LocalTime 的小時、分鐘、秒或納秒 |
toSecondOfDay 、toNanoOfDay |
返回午夜到當前 LocalTime 的秒或納秒的數量 |
isBefore 、isAfter |
將當前的 LocalTime 與另一個 LocalTime 進行比較 |
還有一個表示日期和時間的 LocalDateTime
類。這個類適合存儲固定時區的時間點,例如,用於排課或排程。但是,如果你的計算需要跨越夏令時,或者需要處理不同時區的用戶,那麼就應該使用 ZonedDateTime
時區時間( ZonedDateTime
)
時區,可能是因爲完全是人爲創造的原因,它甚至比地球不規則的轉動引發的複雜性還要麻煩。在理性的世界中,我們都會遵循格林尼泊時間。有些人在 2:00 喫午飯,而有些人卻在 22:00 喫午飯。這就是中國的做法,中國橫跨了多個時區,但是使用了同一個時間(東八區 UTC+8)。在其他地方,時區顯得並不規則,並且還有國際日期變更線,而夏令時使事情變得更糟了
互聯網編碼分配管理機構( Internet Assigned Numbers Authority, IANA )保存着一個數據庫,裏面存儲着世界上所有已知的時區,它每年會更新數次,而批量更新會處理夏令時的變更規則。Java 使用了 IANA 數據庫
每個時區都有一個 ID ,例如 America/New_York
和 Europe/Berlin
。要想找出所有可用的時區,可以調用 ZoneId.getAvailableZoneIds()
屬於中國的時區 ID 有 6 個:
Asia/Chongqing
Asia/Shanghai
Asia/Urumqi
Asia/Macao
Asia/Hong_Kong
Asia/Taipei
給定一個時區 ID ,靜態方法 ZoneId.of(id)
可以產生一個 ZoneId
對象。可以通過調用 local.atZone(zoneId)
用這個對象將 LocalDateTime
對象轉換爲 ZonedDateTime
對象,或者可以通過調用靜態方法 ZonedDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond, zone)
來構造一個 ZonedDateTime
對象
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
// 1969-07-16T09:32-04:00[America/New_York]
這是一個具體的時刻,調用 apollo11launch.toInstant
可以獲得對應的 Instant
對象。反過來,如果你有一個時刻對象,調用 instant.atZone(ZoneId.of("UTC"))
可以獲得格林威治皇家天文臺的 ZonedDateTime
對象,或者使用其他 ZoneId
獲得地球上其他地方的 ZoneId
時區時間( ZonedDateTime
)對應時刻( Instant
)的相互轉換:
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
System.out.println("apollo11launch: " + apollo11launch); // apollo11launch: 1969-07-16T09:32-04:00[America/New_York]
Instant instant = apollo11launch.toInstant();
System.out.println("instant: " + instant); // instant: 1969-07-16T13:32:00Z
// 相同時刻
ZonedDateTime sameInstantZonedDateTime = apollo11launch.withZoneSameInstant(ZoneId.of("Asia/Chongqing"));
System.out.println(sameInstantZonedDateTime); // 1969-07-16T21:32+08:00[Asia/Chongqing]
System.out.println(sameInstantZonedDateTime.toInstant()); // 1969-07-16T13:32:00Z
// 相同本地時間
ZonedDateTime sameLocalZonedDateTime = apollo11launch.withZoneSameLocal(ZoneId.of("Asia/Chongqing"));
System.out.println(sameLocalZonedDateTime); // 1969-07-16T09:32+08:00[Asia/Chongqing]
System.out.println(sameLocalZonedDateTime.toInstant()); // 1969-07-16T01:32:00Z
UTC
代表 協調世界時
,這是英文 Coordinated Universal Time 和法文首字母縮寫的折中,它與這兩種語言中的縮寫都不一致。UTC 是不考慮夏令時的格林威治皇家天文臺時間
ZonedDateTime
的許多方法都與 LocalDateTime
的方法相同,它們大多數都很直接,但是夏令時帶來了一些複雜性
方法 | 描述 |
---|---|
now 、 of 、 ofInstant |
構建一個 ZonedDateTime ,要麼從當前時間構建, 要麼從一個 LocalDateTime 、一個 LocalDate 、與 ZoneId 一起的年 / 月 / 日 / 分鐘 / 秒 / 納秒,或從一個 Instant 和 ZoneId 中創建。這些都是靜態方法 |
plusDays 、 plusWeeks 、 plusMonths 、 plusYears 、 plusHours 、 plusMinutes 、 plusSeconds 、 plusNanos |
在當前的 ZonedDateTime 上加上一定量的時間單位 |
minusDays 、 minusWeeks 、 minuMonths 、 minusYears 、 minusHours 、 minuMinutes 、 minusSeconds 、 minusNanos |
在當前的 ZonedDateTime 上減去一定量的時間單位 |
plus 、 minus |
加上或減去一個 Duration 或 Period |
withDayOfMonth 、 withDayOfYear 、 withMonth 、 withYear 、 withHour 、 withMinute 、 withSecond 、 withNano |
返回一個新的 ZonedDateTime ,其某個時間單位被修改爲給定的值 |
withZoneSameInstant 、 withZoneSameLocal |
返回一個給定時區的新的 ZonedDateTime ,要麼表示同一個時刻,要麼表示同一個本地時間 |
getDayOfMonth |
獲取月的日期(在 1 - 31 之間) |
getDayOfYear |
獲取年的日期(在 1 - 366 之間) |
getDayOfWeek |
獲取星期日期,返回 DayOfWeek 枚舉的某個值 |
getMonth 、 getMonthValue |
獲取月份的 Month 枚舉值, 或者在 1 - 12 之間的數字 |
getYear |
獲取年份,在喃999 999 999 - 999 999 |
getHour 、 getMinute 、 getSecond 、 getNano |
獲取當前的 ZonedDateTime 的小時、分鐘、秒和納秒 |
getOffset |
獲取作爲 ZoneOffset 實例的距離 UTC 的偏移量。偏移量在 -12:00 ~+14:00 之間變化。有些時區有小數偏移量。偏移量會隨夏令時而發生變化 |
toLocalDate 、 toLocalTime 、 toInstant |
產生本地日期或本地時間 ,或者對應的 Instant 對象 |
isBefore 、 isAfter |
將當前的 ZonedDateTime 與另一個 ZonedDateTime 進行比較 |
當夏令時開始 ,時鐘向前撥快一小時。當夏令時結束時,時鐘要向回撥慢一小時,這樣同一個本地時間就會有出現兩次。當你構建位於這個時間段內的時間對象時,就會得到這兩個時刻中較早的一個。一個小時後的時間會具有相同的小時和分鐘,但是時區的偏移量會發生變化。
在調整跨越夏令時邊界的日期時特別注意。例如,如果你將會議設置在下個星期,不要直接加上一個 7
天的 Duration
,而是應該使用 Period
類。
警告:還有一個 OffsetDateTime
類,它表示與 UTC 具有偏移量的時間,但是沒有時區規則的束縛。這個類被設計用於專用應用,這些應用特別需要剔除這些規則的約束,例如某些網絡協議。對於人類時間,還是應該使用 ZonedDateTime
格式化和解析( DateTimeFormatter
)
DateTimeFormatter
類提供了三種用於打印日期/ 時間值的格式器:
- 預定義的格式器
Locale
相關的格式器- 帶有定製模式的格式器
預定義的格式器:
格式器 | 描述 | 示例 |
---|---|---|
BASIC_ISO_DATE |
年、月、日、時區偏移盤,中間沒有分隔符 | 19690716-0400 |
ISO_LOCAL_DATE ISO_LOCAL_TIME ISO_LOCAL_DATE_TIME |
分隔符爲 - : T |
1969-07-16 09:32:00 1969-07-16T09:32:00 |
ISO_OFFSET_DATE ISO_OFFSET_TIME ISO_OFFSET_DATE_TIME |
類似 ISO_LOCAL_XXX ,但是有時區偏移量 |
1969-07-16-04:00 09:32:00-04:00 1969-07-16T09:32:00-04:00 |
ISO_ZONED_DATE_TIME |
有時區偏移量和時區 ID | 1969-07-16T09:32:00-04:00[America/New_York] |
ISO_INSTANT |
在 UTC 中,用 Z 時區 ID 來表示 | 1969-07-16T13:32:00Z |
ISO_DATE ISO_TIME ISO_DATE_TIME |
類似 ISO_OFFSET_DATE 、ISO_OFFSET_TIME 、ISO_ZONED_DATE_TIME ,但是時區信息是可選的 |
1969-07-16-04:00 09:32:00-04:00 1969-07-16T09:32:00-04:00[America/New_York] |
ISO_ORDINAL_DATE |
LocalDate 的年和年日期 |
1969-197-04:00 |
ISO_WEEK_DATE |
LocalDate 的年、星期和星期日期 |
1969-W29-3-04:00 |
RFC_1123_DATE_TIME |
用於郵件時間戳的標準,編篡於 RFC822 ,並在 RFC1123 中將年份更新到 4 位 | Wed, 16 Jul 1969 09:32:00 -0400 |
使用標準的格式器:
ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
String format = DateTimeFormatter.BASIC_ISO_DATE.format(apollo11launch);
System.out.println(DateTimeFormatter.BASIC_ISO_DATE);
標準格式器主要是爲了機器刻度的時間戳而設計的。爲了向人類讀者表示日期和時間,可以使用 Locale
相關的格式器。 對於日期和時間而言,有 4 種與 Locale
相關的格式化風格,即 SHORT
、MEDIUM
、LONG
、FULL
,在 Locale.US
的情況下 :
風格 | 日期 | 時間 |
---|---|---|
SHORT |
7/16/69 | 9:32 AM |
MEDIUM |
Jul 16, 1969 | 9:32:00 AM |
LONG |
July 16, 1969 | 9:32:00 AM EDT |
FULL |
Wednesday, July 16, 1969 | 9:32:00 AM EDT |
靜態方法 ofLocalizedDate
、 ofLocalizedTime
、 ofLocalizedDateTime
可以創建這種格式器,這些方法使用了默認的 Locale
,爲了切換到不同的 Locale
,可以直接使用 withLocale
方法:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
String formatted = formatter.format(apollo11launch);
System.out.println(formatted); // July 16, 1969 9:32:00 AM EDT
formatted = formatter.withLocale(Locale.FRENCH).format(apollo11launch);
// 16 juillet 1969 09:32:00 EDT
System.out.println(formatted);
DayOfWeek
和 Month
枚舉都有 getDisplayName
方法,可以按照不同的 Locale
和格式給出星期日期和月份的名字:
for (DayOfWeek w : DayOfWeek.values()) {
System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}
// Mon Tue Wed Thu Fri Sat Sun
System.out.println();
for (Month m : Month.values()) {
System.out.print(m.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}
// Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
java.time.format.DateTimeFormatter
類被設計用來替代 java.text.DateFormat
。如果你爲了向後兼容性而需要後者的實例,那麼可以調用 formatter.toFormat()
最後,可以通過指定模式來定製自己的日期格式:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");
String formatted = formatter.format(apollo11launch);
System.out.println(formatted); // 星期三 1969-07-16 09:32
按照顯得晦澀且隨時間推移不斷擴充的規則,每個字母都表示一個不同的時間域,而字母重複的次數對應於所選擇的特定格式
常用的日期/時間格式的格式化符號,示例中的結果是在 Locale.US
的情況下 :
時間域或目的 | 示例 |
---|---|
ERA | G : AD GGGG : Anno Domini GGGGG : A |
YEAR_OF_ERA | yy : 69 yyyy : 1969 |
MONTH_OF_YEAR | M : 7 MM : 07 MMM : Jul MMMM : July MMMMM : J |
DAY_OF_MONTH | d : 6 dd : 06 |
DAY_OF_WEEK | e : 3 E : Wed EEEE : Wednesday EEEEE : W |
HOUR_OF_DAY | H : 9 HH : 09 |
CLOCK_HOUR_OF_AM_PM | K : 9 KK : 09 |
AMPM_OF_DAY | a : AM |
MINUTE_OF_HOUR | mm : 02 |
SECOND_OF_MINUTE | ss : 00 |
NANO_OF_SECOND | nnnnnn : 000000 |
時區 ID | VV : America/New_York |
時區名 | z : EDT zzzz : Eastern Daylight Time |
時區偏移盤 | x : -04 xx : -0400 xxx : -04:00 XXX : 與 xxx 相同,但是 Z 表示 0 |
本地化的時區偏移盤 | 0 : GMT-4 0000 : GMT-04:00 |
爲了解析字符串中的日期/時間值,可以使用衆多的靜態 parse
方法之一:
// 使用了標準的 ISO_LOCAL_DATE 格式器
LocalDate churchsBirthday = LocalDate.parse("1903-06-14");
// 使用的是一個定製的格式器
ZonedDateTime apollo11launch = ZonedDateTime.parse("1969-07-16 03:32:00-0400",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"));
與遺留代碼的互操作
Instant
類近似於 java.util.Date
。在 Java SE 中,這個類有兩個額外的方法:將 Date
轉換爲 Instant
的 toInstant
方法,以及反方向轉換的靜態的 from
方法
ZonedDateTime
近似於 java.util.GregorianCalendar
,在 Java SE 中,這個類有細粒度的轉換方法。toZonedDateTime
方法可以將 GregorianCalendar
轉換爲 ZonedDateTime
,而靜態的 from
方法可以執行反方向的轉換
另一個可用於日期和時間類的轉換集位於 java.sql
包中。你還可以傳遞一個 DateTimeFormatter
給使用 java.text.Format
的遺留代碼
類 | to 遺留類 | from 遺留類 |
---|---|---|
Instant 、java.util.Date |
Date.from(instant) |
date.toInstant() |
ZonedDateTime 、java.util.GregorianCalendar |
GregorianCalendar.from(zonedDateTime) |
cal.toZonedDateTime() |
Instant 、java.sql.Timestamp |
Timestamp.from(instant) |
timestamp.toInstant() |
LocalDateTime 、java.sql.Timestamp |
Timestamp.valueOf(localDateTime) |
timestamp.toLocalDateTime() |
LocalDate 、java.sql.Date |
Date.valueOf(localDate) |
date.toLocalDate() |
LocalTime 、java.sql.Time |
Time.valueOf(localTime) |
time.toLocalTime() |
DateTimeFormatter 、java.text.DateFormat |
formatter.toFormat() |
無 |
java.util.TimeZone 、ZoneId |
Timezone.getTimeZone(id) |
timeZone.toZoneId() |
java.nio.file.attribute.FileTime 、Instant |
FileTime.from(instant) |
fileTime.toInstant() |