java> java核心卷讀書筆記 - 日期和時間
概要
Java 1.0 Date
簡單
Java 1.1 Calendar
引入後,Date類大部分方法棄用。但例項易變,並且沒有處理諸如閏秒這樣的問題。
Java 3.0 java.time
修正了過去的缺陷
時間線
秒的來源
最初,根據地球自轉推導而來。地球自轉一週24h = 24x60x60 = 86400s。缺陷:地球有輕微顫動,1秒定義不精確。
1967年以來,原子鐘網路被當作官方時間:根據銫133原子內在特性推導而來。
絕對時間與地球同步自轉進行同步,官方時間的秒需要稍作調整,從1972年開始,偶爾需要插入“閏秒”。
Java的Date和Time API規範要求Java使用的時間尺度為:
- 每天86400秒
- 每天正午與官方時間精確匹配
- 在其他時間點上,以精確定義的方式與官方時間接近匹配
Instant和Duration類
Instant類
Instant表示時間線上的某個點。
“新紀元”時間線原點被設定為 穿過倫敦本初子午線所處時區的1970年1月1日的午夜,與UNIX/POSIX時間中使用的慣例相同。
從“新紀元”時間線原點開始,時間按照每天86400秒向前或向回度量,精確到納秒。Instant值可向回追溯10億年(Instant.MIN) << 宇宙年齡(約135億年),不過對所有實際應用來說,應該足夠。最大值Instant.MAX是公元1 000 000 000年的12月31日。
當前時間: Instant.now()
Instant物件含義:時間戳
比較2個Instant物件:equals和compareTo
如何度量演算法執行時間?
Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Durtion.between(start, end);
long millis = timeElapsed.toMillis();
Duration類
Duration是2個時刻之間的時間量。
Duration按傳統單位度量時間長度的常用方法
toNanos // 轉化納秒 toMillis // 轉化為毫秒 getSeconds // 轉化為秒 toMinutes // 轉化為分鐘 toHours // 轉化為小時 toDays // 轉化為天
例子,如果想要檢查某個演算法是否比另外一個演算法快10倍,可以這樣做
Duration timeElapse2 = Duration.between(start2, end2);
boolean overTenTimeFaster = timeElapse.multipliedBy(10).minus(timeElapsed2).isNegative();
// 等價於下面的語句
overTenTimeFaster = timeElapsed.toNanos() * 10 < timeElapsed2.toNanos();
注意: Instant和Duration都是不可修改的類,諸如mltipliedBy和minus都會返回一個新例項
本地時間LocalDate
Java中人類時間分為2種:本地時間,時區時間。
本地時間包含日期和當天的時間,與時區沒有任何關聯。
某些特殊情況下,時區可能回成為障礙。不推薦使用時區,除非先表達絕對時間。
LocalDate類
LocalDate是帶有年、月、日的本地日期,可以用now或of方法構建LocalDate物件。
java.util.Date使用從1900年0月開始計算(與Unix一致)
- LocalDate物件建立方法
LocalDate today = LocalDate.now(); // 今天的日期
LocalDate alonzosBirthday = LocalDate.of(1903.6.14); // 以1903年6月14日構建LocalDate物件
alnozosBirthday = LocalDay.of(1903, Mont.JUNE, 14); // 1903年6月14日
- LocalDate常用方法
方法 | 描述 |
---|---|
now, of | 靜態方法構建LocalDate物件,要麼從當前時間構建,那麼從給定年月日構建 |
plusDays, plusWeeks, plusMonths, plusYears | 在當前的LocalDate上加上一定量的天、星期、月或年 |
minusDays, minusWeeks, minusMonths, minusYears | 在當前的LocalDate上減去一定量的天、星期、月或年 |
plus, minus | 加上或減去一個Duration或Period |
withDayOfMont, 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,或者兩個日期之間按照給定的ChrounoUnits計算的數值 |
isBefore, isAfter | 將當前LocalDate與另一個LocalDate進行比較 |
isLeapYear | 如果是閏年,返回true;否則返回false |
注意:閏年指年份能被4整除,但是不能被100整除;或者能被400整除。
例子,程式設計師日是每年256天。計算方法:
LocalDate programmersDay = LocalDate.of(2020, 1, 1).plusDays(255);
日期間隔Period類
2個時間點Instant間隔是Duration,2個本地日期LocalDate直接的間隔是什麼呢?
是Period,表示流逝的年、月或日的數量。
利用Period計算日期,和LocalDate計算日期差異:例如,計算下一年生日
LocalDate birthday = LocalDate.now();
// 根據當前生日日期,計算明年生日日期3種方式
birthday.plusYears(1); // 正確
birthday.plusDays(365); // 錯誤,會有閏年問題
birthday.plus(Period.ofYears(1)); // LocalDate日期物件 + Period所代表的日期間隔(時長)
如何計算2個本地日期之間時長?
LocalDate day1 = LocalDate.of(2015.1.1);
LocalDate day2 = LocalDate.now();
day1.until(day2); // 相隔5年11個月0天
day1.until(day2, ChronoUnit.DAYS);// 計算具體相隔多少天
異常情況:
有些方法可能創建出並不存在的日期,如1月31+1個月,不會產生2月31日,也不會丟擲異常,而是返回該月最後一天。
例如,LocalDate.of(2016, 1, 31).plusMonths(1)
和LocalDate.of(2016, 3, 31).minusMonths(1)
都將產生2016年2月29日
日期調整器
略
本地時刻LocalTime
LocalTime表示當日時刻,如15:30:00。LocalTime預設24小時制,不關心AM/PM,交由格式器解決。
LocalTime建立
LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // 或者LocalTime.of(22, 30, 0) 表示時間24小時制 22時30分00秒
LocalTime常用方法
方法 | 描述 |
---|---|
now, of | 類方法,構建LocalTime物件。of方法從時分構建LocalTime物件,時分必選,秒、納秒可選 |
plusHour, plusMinutes, plusSeconds, plusNanos | 在當前LocalTime + 一定量小時、分鐘、秒、納秒 |
minusHour, minusMinutes, minusSeconds, minusNanos | 在當前LocalTime - 一定量小時、分鐘、秒、納秒 |
plus, minus | 加上或減去一個Duration |
withHour, withMinute, withSecond, withNano | 返回一個新LocalTime, 其小時、分鐘、秒、納秒修改為給定值 |
getHour, getMinute, getSecond, getNano | 獲取當前LocalDate的小時、分鐘、秒、納秒 |
toSecondOfDay, toNanoOfDay | 返回午夜到當前秒或納秒的數量 |
isBefore, isAfter | 將當前LocalTime與另外一個LocalTime進行比較 |
LocalDateTime類
適合儲存固定時區的時間點,如排課或排程。如果需要跨不同時區,或者跨越夏令時,應該使用ZonedDateTime類。
時區時間
時區時間有什麼用?
在北京是17點44分,然而在紐約是4點44分,而在倫敦卻是9點44分。在不同時區的人,如何約定好同一個時間舉辦一個會議? 這就需要用到時區時間ZonedDateTime類。
網際網路編碼分配管理機構(IANAN)儲存著一個數據庫,裡面儲存著世界上所有已知的時區 IANAN官網,每年更新幾次,批量更新會處理夏令時的變更規則。Java使用了IANAN資料庫。
時區ZonedDateTime類
時區ID
每個時區都有一個時區ID,例如Anmerica/New_York和Europe/Berlin。
目前,全世界有進600個ID。
- 找出所有可用時區
ZoneId.getAvailableZoneIds()
- 給定時區ID,構建ZondId物件
ZoneId.of(id);
- 將LocalDateTime物件轉換為ZonedDateTime物件
兩種方法
// 方法一
LocalDate local = local.now();
local.atZone(id);
// 方法二
ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneId);
// e.g.
ZonedDateTime apollolllaunch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of("America/New_York")); // 1969-07-16T09:32-04:00[America/New_York]
// 獲得Instant, ZonedDateTime -> Instant
Instant instant = apollolllaunch.toInstant();
// Instant -> ZonedDateTime
instant.atZone(ZoneId.of("UTC")); // 獲得格林威治皇家天文臺的ZonedDateTime物件
ZonedDateTime類
ZonedDateTime許多方法與LocalDateTime類的相同,不過夏令時帶來一些複雜性。
夏令時
夏令時,Daylight Saving Time,是一種節約能源而人為規定地方時間的制度,這一制度實行期間所採用的統一時間稱為“夏令時”。一般在夏季人為將時間提前1小時,冬季又調回1小時,每個國傢俱體規定不同。
夏令時開始時,時鐘要向前撥快一小時。夏令時結束時,時鐘要撥慢一小時。
// e.g. 2013年, 中歐地區3月31日 2:00 切換到夏令時. 如果試圖構建的時間是不存在的3月31日2:30,那麼實際上得到的是3:30
ZonedDateTime skipped = ZonedDateTime.of(
LocalDate.of(2013, 3, 31),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
); // 2013年3月31日 3:30
// 夏令時結束時, 時鐘要回撥慢一小時, 這樣同一個本地時間就會有出現2次. 當構建位於這個時間段內的時間物件時, 會得到這2個時刻中較早的一個
ZonedDateTime ambiguous = ZonedDateTime.of(
LocalDate.of(2013, 10, 27),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
); // 2013-10-27T02:30+02:00[Europe/Berlin]
ZonedDateTime anHourLater = ambiguous.plusHours(1); // 2013-10-27T02:30+01:00[Europe/Berlin]
// e.g 跨越夏令時邊界事需要特別注意. 例如, 如果將會議設定在下個星期, 不要直接+7天Duration, 而應該使用Period類
ZonedDateTime nextMeeting = meeting.plus(Duration.ofDays(7)); // 錯誤做法
ZonedDateTime nextMeeting = meeting.plus(Period.ofDays(7)); // 正確做法
注意:offsetDateTime類,UTSC具有偏移量的時間,沒有時區規則限制,用於專用應用,如某些網路協議。人類時區時間應該使用ZonedDateTime。
格式化和解析
格式器DateTimeFormatter
DateTimeFormatter類提供3種列印日期/時間值的格式器:
- 預定義的格式器
- Local相關的格式器
- 帶有定製模式的格式器
注意:java.time.format.DateTimeFormatter類被設計用來替代java.util.DateFormat。如果為向後相容需要使用後者,可以呼叫formatter.toFormat()。
示例:使用標準的格式器,直接呼叫DateTimeFormatter.format方法
ZonedDateTime apollolllaunch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0, ZoneId.of("America/New_York")); // 1969-07-16T09:32-04:00[America/New_York]
String formatted = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(apollolllaunch ); // 1969-07-16T09:32:00-04:00
System.out.println(formatted);
標準格式器主要是為了機器刻度的時間戳而設計的,向人類表示日期和時間,可以使用Locale相關格式器。
有4種與Locale相關的格式化風格:SHORT, MEDIUM, LONG, FULL.
Locale格式化風格
- 建立Locale相關格式化風格
// 建立DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
// 使用預設Locale
String formatted = formatter.format(apollolllaunch ); // 1969年7月16日 上午09時32分00秒
System.out.println(formatted);
// 使用withLocale, 切換到不同Locale
formatted = formatter.withLocale(Locale.FRENCH).format(apollolllaunch ); // 16 juillet 1969 09:32:00 EDT
// 按不同Locale和格式給出星期日期和月份名字
for (DayOfWeek w: DayOfWeek.values()) {
// 列印星期幾的英文簡稱: Mon Tue Wed Thu Fri Sat Sun
System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
}