成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

建議收藏:徹底弄透Java處理GMT/UTC日期時(shí)間

開(kāi)發(fā) 后端
本文內(nèi)容較多,文字較長(zhǎng),預(yù)計(jì)超2w字,旨在全面的徹底幫你搞定Java對(duì)日期時(shí)間的處理,建議你可收藏,作為參考書留以備用。

 你好,我是A哥(YourBatman)。

本系列的目的是明明白白、徹徹底底的搞定日期/時(shí)間處理的幾乎所有case。上篇文章 鋪設(shè)所有涉及到的概念解釋,例如GMT、UTC、夏令時(shí)、時(shí)間戳等等,若你還沒(méi)看過(guò),不僅強(qiáng)烈建議而是強(qiáng)制建議你前往用花5分鐘看一下,因?yàn)槿掌跁r(shí)間處理較為特殊,實(shí)戰(zhàn)必須基于對(duì)概念的了解,否則很可能依舊霧里看花。

  • ❝說(shuō)明:日期/時(shí)間的處理是日常開(kāi)發(fā)非常常見(jiàn)的老大難,究其原因就是對(duì)日期時(shí)間的相關(guān)概念、應(yīng)用場(chǎng)景不熟悉,所以不要忽視它❞上篇概念,本文落地實(shí)操,二者相輔相成,缺一不可。本文內(nèi)容較多,文字較長(zhǎng),預(yù)計(jì)超2w字,旨在全面的徹底幫你搞定Java對(duì)日期時(shí)間的處理,建議你可收藏,作為參考書留以備用。

本文提綱


版本約定

JDK:8

✍正文

上文鋪了這么多概念,作為一枚Javaer最關(guān)心當(dāng)然是這些“概念”在Java里的落地。平時(shí)工作中遇到時(shí)間如何處理?用Date還是JDK 8之后的日期時(shí)間API?如何解決跨時(shí)區(qū)轉(zhuǎn)換等等頭大問(wèn)題。A哥向來(lái)管生管養(yǎng),管殺管埋,因此本文就帶你領(lǐng)略一下,Java是如何實(shí)現(xiàn)GMT和UTC的?

眾所周知,JDK以版本8為界,有兩套處理日期/時(shí)間的API:


雖然我一直鼓勵(lì)棄用Date而支持在項(xiàng)目中只使用JSR 310日期時(shí)間類型,但是呢,由于Date依舊有龐大的存量用戶,所以本文也不落單,對(duì)二者的實(shí)現(xiàn)均進(jìn)行闡述。

Date類型實(shí)現(xiàn)

java.util.Date在JDK 1.0就已存在,用于表示日期 + 時(shí)間的類型,縱使年代已非常久遠(yuǎn),并且此類的具有職責(zé)不單一,使用很不方便等諸多毛病,但由于十幾二十年的歷史原因存在,它的生命力依舊頑強(qiáng),用戶量巨大。

先來(lái)認(rèn)識(shí)下Date,看下這個(gè)例子的輸出:

  1. @Test 
  2. public void test1() { 
  3.     Date currDate = new Date(); 
  4.     System.out.println(currDate.toString()); 
  5.     // 已經(jīng)@Deprecated 
  6.     System.out.println(currDate.toLocaleString()); 
  7.     // 已經(jīng)@Deprecated 
  8.     System.out.println(currDate.toGMTString()); 

運(yùn)行程序,輸出:

  1. Fri Jan 15 10:22:34 CST 2021 
  2. 2021-1-15 10:22:34 
  3. 15 Jan 2021 02:22:34 GMT 

第一個(gè):標(biāo)準(zhǔn)的UTC時(shí)間(CST就代表了偏移量 +0800)第二個(gè):本地時(shí)間,根據(jù)本地時(shí)區(qū)顯示的時(shí)間格式 第三個(gè):GTM時(shí)間,也就是格林威治這個(gè)時(shí)候的時(shí)間,可以看到它是凌晨2點(diǎn)(北京時(shí)間是上午10點(diǎn)哦)

第二個(gè)、第三個(gè)其實(shí)在JDK 1.1就都標(biāo)記為@Deprecated過(guò)期了,基本禁止再使用。若需要轉(zhuǎn)換為本地時(shí)間 or GTM時(shí)間輸出的話,請(qǐng)使用格式化器java.text.DateFormat去處理。

時(shí)區(qū)/偏移量TimeZone

在JDK8之前,Java對(duì)時(shí)區(qū)和偏移量都是使用java.util.TimeZone來(lái)表示的。

一般情況下,使用靜態(tài)方法TimeZone#getDefault()即可獲得當(dāng)前JVM所運(yùn)行的時(shí)區(qū),比如你在中國(guó)運(yùn)行程序,這個(gè)方法返回的就是中國(guó)時(shí)區(qū)(也叫北京時(shí)區(qū)、北京時(shí)間)。

有的時(shí)候你需要做帶時(shí)區(qū)的時(shí)間轉(zhuǎn)換,譬如:接口返回值中既要有展示北京時(shí)間,也要展示紐約時(shí)間。這個(gè)時(shí)候就要獲取到紐約的時(shí)區(qū),以北京時(shí)間為基準(zhǔn)在其上進(jìn)行帶時(shí)區(qū)轉(zhuǎn)換一把:

  1. @Test 
  2. public void test2() { 
  3.     String patternStr = "yyyy-MM-dd HH:mm:ss"
  4.     // 北京時(shí)間(new出來(lái)就是默認(rèn)時(shí)區(qū)的時(shí)間) 
  5.     Date bjDate = new Date(); 
  6.  
  7.     // 得到紐約的時(shí)區(qū) 
  8.     TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York"); 
  9.     // 根據(jù)此時(shí)區(qū) 將北京時(shí)間轉(zhuǎn)換為紐約的Date 
  10.     DateFormat newYorkDateFormat = new SimpleDateFormat(patternStr); 
  11.     newYorkDateFormat.setTimeZone(newYorkTimeZone); 
  12.     System.out.println("這是北京時(shí)間:" + new SimpleDateFormat(patternStr).format(bjDate)); 
  13.     System.out.println("這是紐約時(shí)間:" + newYorkDateFormat.format(bjDate)); 

運(yùn)行程序,輸出:

  1. 這是北京時(shí)間:2021-01-15 11:48:16 
  2. 這是紐約時(shí)間:2021-01-14 22:48:16 

(11 + 24) - 22 = 13,北京比紐約快13個(gè)小時(shí)沒(méi)毛病。

  • ❝注意:兩個(gè)時(shí)間表示的應(yīng)該是同一時(shí)刻,也就是常說(shuō)的時(shí)間戳值是相等的❞那么問(wèn)題來(lái)了,你怎么知道獲取紐約的時(shí)區(qū)用America/New_York這個(gè)zoneId呢?隨便寫個(gè)字符串行不行?

答案是當(dāng)然不行,這是有章可循的。下面我介紹兩種查閱zoneId的方式,任你挑選:

方式一:用Java程序把所有可用的zoneId打印出來(lái),然后查閱

  1. @Test 
  2. public void test3() { 
  3.     String[] availableIDs = TimeZone.getAvailableIDs(); 
  4.     System.out.println("可用zoneId總數(shù):" + availableIDs.length); 
  5.     for (String zoneId : availableIDs) { 
  6.         System.out.println(zoneId); 
  7.     } 

運(yùn)行程序,輸出(大部分符合規(guī)律:/前表示所屬州,/表示城市名稱):

  1. 可用zoneId總數(shù):628 
  2. Africa/Abidjan 
  3. Africa/Accra 
  4. ... 
  5. Asia/Chongqing // 亞洲/重慶 
  6. Asia/Shanghai // 亞洲/上海 
  7. Asia/Dubai // 亞洲/迪拜 
  8. ... 
  9. America/New_York // 美洲/紐約 
  10. America/Los_Angeles // 美洲/洛杉磯 
  11. ... 
  12. Europe/London // 歐洲/倫敦 
  13. ... 
  14. Etc/GMT 
  15. Etc/GMT+0 
  16. Etc/GMT+1 
  17. ... 

值得注意的是并沒(méi)有 Asia/Beijing 哦。

  • ❝說(shuō)明:此結(jié)果基于JDK 8版本,不同版本輸出的總個(gè)數(shù)可能存在差異,但主流的ZoneId一般不會(huì)有變化❞

方式二:zoneId的列表是jre維護(hù)的一個(gè)文本文件,路徑是你JDK/JRE的安裝路徑。地址在.\jre\lib目錄的名為tzmappings的文本文件里。打開(kāi)這個(gè)文件去ctrl + f找也是可以達(dá)到查找的目的的。

這兩種房子可以幫你找到ZoneId的字典方便查閱,但是還有這么一種情況:當(dāng)前所在的城市呢,在tzmappings文件里根本沒(méi)有(比如沒(méi)有收錄),那要獲取這個(gè)地方的時(shí)間去顯示怎么破呢?雖然概率很小,但不見(jiàn)得沒(méi)有嘛,畢竟全球那么多國(guó)家那么多城市呢~

Java自然也考慮到了這一點(diǎn),因此也是有辦法的:指定其時(shí)區(qū)數(shù)字表示形式,其實(shí)也叫偏移量(不要告訴我這個(gè)地方的時(shí)區(qū)都不知道,那就真沒(méi)救了),如下示例

  1. @Test 
  2. public void test4() { 
  3.     System.out.println(TimeZone.getTimeZone("GMT+08:00").getID()); 
  4.     System.out.println(TimeZone.getDefault().getID()); 
  5.  
  6.     // 紐約時(shí)間 
  7.     System.out.println(TimeZone.getTimeZone("GMT-05:00").getID()); 
  8.     System.out.println(TimeZone.getTimeZone("America/New_York").getID()); 

運(yùn)行程序,輸出:

  1. GMT+08:00 // 效果等同于Asia/Shanghai 
  2. Asia/Shanghai 
  3. GMT-05:00 // 效果等同于America/New_York 
  4. America/New_York  

值得注意的是,這里只能用GMT+08:00,而不能用UTC+08:00,原因下文有解釋。

設(shè)置默認(rèn)時(shí)區(qū)

一般來(lái)說(shuō),JVM在哪里跑,默認(rèn)時(shí)區(qū)就是哪。對(duì)于國(guó)內(nèi)程序員來(lái)講,一般只會(huì)接觸到東八區(qū),也就是北京時(shí)間(本地時(shí)間)。隨著國(guó)際合作越來(lái)越密切,很多時(shí)候需要日期時(shí)間國(guó)際化處理,舉個(gè)很實(shí)際的例子:同一份應(yīng)用在阿里云部署、在AWS(海外)上也部署一份供海外用戶使用,此時(shí)同一份代碼部署在不同的時(shí)區(qū)了,怎么破?

倘若時(shí)區(qū)不同,那么勢(shì)必影響到程序的運(yùn)行結(jié)果,很容易帶來(lái)計(jì)算邏輯的錯(cuò)誤,很可能就亂套了。Java讓我們有多種方式可以手動(dòng)設(shè)置/修改默認(rèn)時(shí)區(qū):

  1. API方式:強(qiáng)制將時(shí)區(qū)設(shè)為北京時(shí)區(qū)TimeZone.setDefault(TimeZone.getDefault().getTimeZone("GMT+8"));
  2. JVM參數(shù)方式:-Duser.timezone=GMT+8
  3. 運(yùn)維設(shè)置方式:將操作系統(tǒng)主機(jī)時(shí)區(qū)設(shè)置為北京時(shí)區(qū),這是推薦方式,可以完全對(duì)開(kāi)發(fā)者無(wú)感,也方便了運(yùn)維統(tǒng)一管理

據(jù)我了解,很多公司在阿里云、騰訊云、國(guó)內(nèi)外的云主機(jī)上部署應(yīng)用時(shí),全部都是采用運(yùn)維設(shè)置統(tǒng)一時(shí)區(qū):中國(guó)時(shí)區(qū),這種方式來(lái)管理的,這樣對(duì)程序來(lái)說(shuō)就消除了默認(rèn)時(shí)區(qū)不一致的問(wèn)題,對(duì)開(kāi)發(fā)者友好。

讓人惱火的夏令時(shí)

你知道嗎,中國(guó)曾經(jīng)也使用過(guò)夏令時(shí)。

  • ❝什么是夏令時(shí)?戳這里❞

離現(xiàn)在最近是1986年至1991年用過(guò)夏令時(shí)(每年4月中旬的第一個(gè)周日2時(shí) - 9月中旬的第一個(gè)星期日2時(shí)止):1986年5月4日至9月14日1987年4月12日至9月13日1988年4月10日至9月11日1989年4月16日至9月17日1990年4月15日至9月16日1991年4月14日至9月15日

夏令時(shí)是一個(gè)“非常煩人”的東西,大大的增加了日期時(shí)間處理的復(fù)雜度。比如這個(gè)靈魂拷問(wèn):若你的出生日期是1988-09-11 00:00:00(夏令時(shí)最后一天)且存進(jìn)了數(shù)據(jù)庫(kù),想一想,對(duì)此日期的格式化有沒(méi)有可能就會(huì)出問(wèn)題呢,有沒(méi)有可能被你格式化成1988-09-10 23:00:00呢?

針對(duì)此拷問(wèn),我模擬了如下代碼:

  1. @Test 
  2. public void test5() throws ParseException { 
  3.     String patterStr = "yyyy-MM-dd"
  4.     DateFormat dateFormat = new SimpleDateFormat(patterStr); 
  5.  
  6.     String birthdayStr = "1988-09-11"
  7.     // 字符串 -> Date -> 字符串 
  8.     Date birthday = dateFormat.parse(birthdayStr); 
  9.     long birthdayTimestamp = birthday.getTime(); 
  10.     System.out.println("老王的生日是:" + birthday); 
  11.     System.out.println("老王的生日的時(shí)間戳是:" + birthdayTimestamp); 
  12.  
  13.     System.out.println("==============程序經(jīng)過(guò)一番周轉(zhuǎn),我的同時(shí) 方法入?yún)鱽?lái)了生日的時(shí)間戳============="); 
  14.     // 字符串 -> Date -> 時(shí)間戳 -> Date -> 字符串 
  15.     birthday = new Date(birthdayTimestamp); 
  16.     System.out.println("老王的生日是:" + birthday); 
  17.     System.out.println("老王的生日的時(shí)間戳是:" + dateFormat.format(birthday)); 

這段代碼,在不同的JDK版本下運(yùn)行,可能出現(xiàn)不同的結(jié)果,有興趣的可copy過(guò)去自行試試。

關(guān)于JDK處理夏令時(shí)(特指中國(guó)的夏令時(shí))確實(shí)出現(xiàn)過(guò)問(wèn)題且造成過(guò)bug,當(dāng)時(shí)對(duì)應(yīng)的JDK版本是1.8.0_2xx之前版本格式化那個(gè)日期出問(wèn)題了,在這之后的版本貌似就沒(méi)問(wèn)題了。這里我提供的版本信息僅供參考,若有遇到類似case就升級(jí)JDK版本到最新吧,一般就不會(huì)有問(wèn)題了。

  • ❝發(fā)生這個(gè)情況是在JDK非常小的版本號(hào)之間,不太好定位精確版本號(hào)界限,所以僅供參考❞

總的來(lái)說(shuō),只要你使用的是較新版本的JDK,開(kāi)發(fā)者是無(wú)需關(guān)心夏令時(shí)問(wèn)題的,即使全球仍有很多國(guó)家在使用夏令時(shí),咱們只需要面向時(shí)區(qū)做時(shí)間轉(zhuǎn)換就沒(méi)問(wèn)題。

Date時(shí)區(qū)無(wú)關(guān)性

類Date表示一個(gè)特定的時(shí)間瞬間,精度為毫秒。既然表示的是瞬間/時(shí)刻,那它必然和時(shí)區(qū)是無(wú)關(guān)的,看下面代碼:

  1. @Test 
  2. public void test6() { 
  3.     String patterStr = "yyyy-MM-dd HH:mm:ss"
  4.     Date currDate = new Date(System.currentTimeMillis()); 
  5.  
  6.     // 北京時(shí)區(qū) 
  7.     DateFormat bjDateFormat = new SimpleDateFormat(patterStr); 
  8.     bjDateFormat.setTimeZone(TimeZone.getDefault()); 
  9.     // 紐約時(shí)區(qū) 
  10.     DateFormat newYorkDateFormat = new SimpleDateFormat(patterStr); 
  11.     newYorkDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York")); 
  12.     // 倫敦時(shí)區(qū) 
  13.     DateFormat londonDateFormat = new SimpleDateFormat(patterStr); 
  14.     londonDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); 
  15.  
  16.     System.out.println("毫秒數(shù):" + currDate.getTime() + ", 北京本地時(shí)間:" + bjDateFormat.format(currDate)); 
  17.     System.out.println("毫秒數(shù):" + currDate.getTime() + ", 紐約本地時(shí)間:" + newYorkDateFormat.format(currDate)); 
  18.     System.out.println("毫秒數(shù):" + currDate.getTime() + ", 倫敦本地時(shí)間:" + londonDateFormat.format(currDate)); 

運(yùn)行程序,輸出:

  1. 毫秒數(shù):1610696040244, 北京本地時(shí)間:2021-01-15 15:34:00 
  2. 毫秒數(shù):1610696040244, 紐約本地時(shí)間:2021-01-15 02:34:00 
  3. 毫秒數(shù):1610696040244, 倫敦本地時(shí)間:2021-01-15 07:34:00 

也就是說(shuō),同一個(gè)毫秒值,根據(jù)時(shí)區(qū)/偏移量的不同可以展示多地的時(shí)間,這就證明了Date它的時(shí)區(qū)無(wú)關(guān)性。

確切的說(shuō):Date對(duì)象里存的是自格林威治時(shí)間( GMT)1970年1月1日0點(diǎn)至Date所表示時(shí)刻所經(jīng)過(guò)的毫秒數(shù),是個(gè)數(shù)值

讀取字符串為Date類型

這是開(kāi)發(fā)中極其常見(jiàn)的一種需求:client請(qǐng)求方扔給你一個(gè)字符串如"2021-01-15 18:00:00",然后你需要把它轉(zhuǎn)為Date類型,怎么破?

問(wèn)題來(lái)了,光禿禿的扔給我個(gè)字符串說(shuō)是15號(hào)晚上6點(diǎn)時(shí)間,我咋知道你指的是北京的晚上6點(diǎn),還是東京的晚上6點(diǎn)呢?還是紐約的晚上6點(diǎn)呢?

因此,對(duì)于字符串形式的日期時(shí)間,只有指定了時(shí)區(qū)才有意義。也就是說(shuō)字符串 + 時(shí)區(qū) 才能精確知道它是什么時(shí)刻,否則是存在歧義的。

也許你可能會(huì)說(shuō)了,自己平時(shí)開(kāi)發(fā)中前端就是扔個(gè)字符串給我,然后我就給格式化為一個(gè)Date類型,并沒(méi)有傳入時(shí)區(qū)參數(shù),運(yùn)行這么久也沒(méi)見(jiàn)出什么問(wèn)題呀。如下所示:

  1. @Test 
  2. public void test7() throws ParseException { 
  3.     String patterStr = "yyyy-MM-dd HH:mm:ss"
  4.  
  5.     // 模擬請(qǐng)求參數(shù)的時(shí)間字符串 
  6.     String dateStrParam = "2020-01-15 18:00:00"
  7.  
  8.     // 模擬服務(wù)端對(duì)此服務(wù)換轉(zhuǎn)換為Date類型 
  9.     DateFormat dateFormat = new SimpleDateFormat(patterStr); 
  10.     System.out.println("格式化器用的時(shí)區(qū)是:" + dateFormat.getTimeZone().getID()); 
  11.     Date date = dateFormat.parse(dateStrParam); 
  12.     System.out.println(date); 

運(yùn)行程序,輸出:

  1. 格式化器用的時(shí)區(qū)是:Asia/Shanghai 
  2. Wed Jan 15 18:00:00 CST 2020 

看起來(lái)結(jié)果沒(méi)問(wèn)題。事實(shí)上,這是因?yàn)槟J(rèn)情況下你們交互雙發(fā)就達(dá)成了契約:雙方均使用的是北京時(shí)間(時(shí)區(qū)),既然是相同時(shí)區(qū),所以互通有無(wú)不會(huì)有任何問(wèn)題。不信你把你接口給海外用戶調(diào)試試?

對(duì)于格式化器來(lái)講,雖然說(shuō)編程過(guò)程中一般情況下我們并不需要給DateFormat設(shè)置時(shí)區(qū)(那就用默認(rèn)時(shí)區(qū)唄)就可正常轉(zhuǎn)換。但是作為高手的你必須清清楚楚,明明白白的知道這是由于交互雙發(fā)默認(rèn)有個(gè)相同時(shí)區(qū)的契約存在。

SimpleDateFormat格式化

Java中對(duì)Date類型的輸入輸出/格式化,推薦使用DateFormat而非用其toString()方法。

DateFormat是一個(gè)時(shí)間格式化器抽象類,SimpleDateFormat是其具體實(shí)現(xiàn)類,用于以語(yǔ)言環(huán)境敏感的方式格式化和解析日期。它允許格式化(日期→文本)、解析(文本→日期)和規(guī)范化。

  • ❝劃重點(diǎn):對(duì)語(yǔ)言環(huán)境敏感,也就是說(shuō)對(duì)環(huán)境Locale、時(shí)區(qū)TimeZone都是敏感的。既然敏感,那就是可定制的❞

對(duì)于一個(gè)格式化器來(lái)講,模式(模版)是其關(guān)鍵因素,了解一下:

日期/時(shí)間模式:格式化的模式由指定的字符串組成,未加引號(hào)的大寫/小寫字母(A-Z a-z)代表特定模式,用來(lái)表示模式含義,若想原樣輸出可以用單引號(hào)''包起來(lái),除了英文字母其它均不解釋原樣輸出/匹配。下面是它規(guī)定的模式字母(其它字母原樣輸出):

這個(gè)表格里出現(xiàn)了一些“特殊”的匹配類型,做如下解釋:

Text:格式化(Date -> String),如果模式字母的數(shù)目是4個(gè)或更多,則使用完整形式;否則,如果可能的話,使用簡(jiǎn)短或縮寫形式。對(duì)于解析(String -> Date),這兩種形式都一樣,與模式字母的數(shù)量無(wú)關(guān)

  1. @Test 
  2. public void test9() throws ParseException { 
  3.     String patternStr = "G GG GGGGG E EE EEEEE a aa aaaaa"
  4.     Date currDate = new Date(); 
  5.  
  6.     System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地區(qū)模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); 
  7.     System.out.println("====================Date->String===================="); 
  8.     DateFormat dateFormat = new SimpleDateFormat(patternStr, Locale.CHINA); 
  9.     System.out.println(dateFormat.format(currDate)); 
  10.  
  11.     System.out.println("====================String->Date===================="); 
  12.     String dateStrParam = "公元 公元 公元 星期六 星期六 星期六 下午 下午 下午"
  13.     System.out.println(dateFormat.parse(dateStrParam)); 
  14.  
  15.     System.out.println("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地區(qū)模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); 
  16.     System.out.println("====================Date->String===================="); 
  17.     dateFormat = new SimpleDateFormat(patternStr, Locale.US); 
  18.     System.out.println(dateFormat.format(currDate)); 
  19.  
  20.     System.out.println("====================String->Date===================="); 
  21.     dateStrParam = "AD ad bC Sat SatUrday sunDay PM PM Am"
  22.     System.out.println(dateFormat.parse(dateStrParam)); 

運(yùn)行程序,輸出:

  1. ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓中文地區(qū)模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 
  2. ====================Date->String==================== 
  3. 公元 公元 公元 星期六 星期六 星期六 下午 下午 下午 
  4. ====================String->Date==================== 
  5. Sat Jan 03 12:00:00 CST 1970 
  6. ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓英文地區(qū)模式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 
  7. ====================Date->String==================== 
  8. AD AD AD Sat Sat Saturday PM PM PM 
  9. ====================String->Date==================== 
  10. Sun Jan 01 00:00:00 CST 1970 

觀察打印結(jié)果,除了符合模式規(guī)則外,還能在String -> Date解析時(shí)總結(jié)出兩點(diǎn)結(jié)論:

  1. 英文單詞,不分區(qū)大小寫。如SatUrday sunDay都是沒(méi)問(wèn)題,但是不能有拼寫錯(cuò)誤
  2. 若有多個(gè)part表示一個(gè)意思,那么last win。如Sat SatUrday sunDay最后一個(gè)生效

對(duì)于Locale地域參數(shù),因?yàn)橹形牟淮嬖诟袷健⒖s寫方面的特性,因此這些規(guī)則只對(duì)英文地域(如Locale.US生效)

  • Number:格式化(Date -> String),模式字母的數(shù)量是數(shù)字的【最小】數(shù)量,較短的數(shù)字被零填充到這個(gè)數(shù)量。對(duì)于解析(String -> Date),模式字母的數(shù)量將被忽略,除非需要分隔兩個(gè)相鄰的字段
  • Year:對(duì)于格式化和解析,如果模式字母的數(shù)量是4個(gè)或更多,則使用特定于日歷的長(zhǎng)格式。否則,使用日歷特定的簡(jiǎn)短或縮寫形式
  • Month:如果模式字母的數(shù)量是3個(gè)或更多,則被解釋為文本;否則,它將被解釋為一個(gè)數(shù)字。
  • 通用時(shí)區(qū):如果該時(shí)區(qū)有名稱,如Pacific Standard Time、PST、CST等那就用名稱,否則就用GMT規(guī)則的字符串,如:GMT-08:00
  • RFC 822時(shí)區(qū):遵循RFC 822格式,向下兼容通用時(shí)區(qū)(名稱部分除外)
  • ISO 8601時(shí)區(qū):對(duì)于格式化,如果與GMT的偏移值為0(也就是格林威治時(shí)間嘍),則生成“Z”;如果模式字母的數(shù)量為1,則忽略小時(shí)的任何分?jǐn)?shù)。例如,如果模式是“X”,時(shí)區(qū)是“GMT+05:30”,則生成“+05”。在進(jìn)行解析時(shí),“Z”被解析為UTC時(shí)區(qū)指示符。一般時(shí)區(qū)不被接受。如果模式字母的數(shù)量是4個(gè)或更多,在構(gòu)造SimpleDateFormat或應(yīng)用模式時(shí)拋出IllegalArgumentException。

這個(gè)規(guī)則理解起來(lái)還是比較費(fèi)勁的,在開(kāi)發(fā)中一般不太建議使用此種模式。若要使用請(qǐng)務(wù)必本地做好測(cè)試

SimpleDateFormat的使用很簡(jiǎn)單,重點(diǎn)是了解其規(guī)則模式。最后關(guān)于SimpleDateFormat的使用再?gòu)?qiáng)調(diào)這兩點(diǎn)哈:

  1. SimpleDateFormat并非線程安全類,使用時(shí)請(qǐng)務(wù)必注意并發(fā)安全問(wèn)題
  2. 若使用SimpleDateFormat去格式化成非本地區(qū)域(默認(rèn)Locale)的話,那就必須在構(gòu)造的時(shí)候就指定好,如Locale.US
  3. 對(duì)于Date類型的任何格式化、解析請(qǐng)統(tǒng)一使用SimpleDateFormat

JSR 310類型

曾經(jīng)有個(gè)人做了個(gè)很有意思的投票,統(tǒng)計(jì)對(duì)Java API的不滿意程度。最終Java Date/Calendar API斬獲第二爛(第一爛是Java XML/DOM),體現(xiàn)出它爛的點(diǎn)較多,這里給你例舉幾項(xiàng):

定義并不一致,在java.util和java.sql包中竟然都有Date類,而且呢對(duì)它進(jìn)行格式化/解析類竟然又跑到j(luò)ava.text去了,精神分裂啊

java.util.Date等類在建模日期的設(shè)計(jì)上行為不一致,缺陷明顯。包括易變性、糟糕的偏移值、默認(rèn)值、命名等等

java.util.Date同時(shí)包含日期和時(shí)間,而其子類java.sql.Date卻僅包含日期,這是什么神繼承?


  1. @Test 
  2. public void test10() { 
  3.     long currMillis = System.currentTimeMillis(); 
  4.  
  5.     java.util.Date date = new Date(currMillis); 
  6.     java.sql.Date sqlDate = new java.sql.Date(currMillis); 
  7.     java.sql.Time time = new Time(currMillis); 
  8.     java.sql.Timestamp timestamp = new Timestamp(currMillis); 
  9.  
  10.     System.out.println("java.util.Date:" + date); 
  11.     System.out.println("java.sql.Date:" + sqlDate); 
  12.     System.out.println("java.sql.Time:" + time); 
  13.     System.out.println("java.sql.Timestamp:" + timestamp); 

運(yùn)行程序,輸出:

  1. java.util.Date:Sat Jan 16 21:50:36 CST 2021 
  2. java.sql.Date:2021-01-16 
  3. java.sql.Time:21:50:36 
  4. java.sql.Timestamp:2021-01-16 21:50:36.733 
  • 國(guó)際化支持得并不是好,比如跨時(shí)區(qū)操作、夏令時(shí)等等

Java 自己也實(shí)在忍不了這么難用的日期時(shí)間API了,于是在2014年隨著Java 8的發(fā)布引入了全新的JSR 310日期時(shí)間。JSR-310源于精品時(shí)間庫(kù)joda-time打造,解決了上面提到的所有問(wèn)題,是整個(gè)Java 8最大亮點(diǎn)之一。

JSR 310日期/時(shí)間 所有的 API都在java.time這個(gè)包內(nèi),沒(méi)有例外。


當(dāng)然嘍,本文重點(diǎn)并不在于討論JSR 310日期/時(shí)間體系,而是看看JSR 310日期時(shí)間類型是如何處理上面Date類型遇到的那些case的。

時(shí)區(qū)/偏移量ZoneId

在JDK 8之前,Java使用java.util.TimeZone來(lái)表示時(shí)區(qū)。而在JDK 8里分別使用了ZoneId表示時(shí)區(qū),ZoneOffset表示UTC的偏移量。

值得提前強(qiáng)調(diào),時(shí)區(qū)和偏移量在概念和實(shí)際作用上是有較大區(qū)別的,主要體現(xiàn)在:

  1. UTC偏移量?jī)H僅記錄了偏移的小時(shí)分鐘而已,除此之外無(wú)任何其它信息。舉個(gè)例子:+08:00的意思是比UTC時(shí)間早8小時(shí),沒(méi)有地理/時(shí)區(qū)含義,相應(yīng)的-03:30代表的意思僅僅是比UTC時(shí)間晚3個(gè)半小時(shí)
  2. 時(shí)區(qū)是特定于地區(qū)而言的,它和地理上的地區(qū)(包括規(guī)則)強(qiáng)綁定在一起。比如整個(gè)中國(guó)都叫東八區(qū),紐約在西五區(qū)等等
  • ❝中國(guó)沒(méi)有夏令時(shí),所有東八區(qū)對(duì)應(yīng)的偏移量永遠(yuǎn)是+8;紐約有夏令時(shí),因此它的偏移量可能是-4也可能是-5哦❞

綜合來(lái)看,時(shí)區(qū)更好用。令人惱火的夏令時(shí)問(wèn)題,若你使用UTC偏移量去表示那么就很麻煩,因?yàn)樗勺儯阂荒陜?nèi)的某些時(shí)期在原來(lái)基礎(chǔ)上偏移量 +1,某些時(shí)期 -1;但若你使用ZoneId時(shí)區(qū)去表示就很方便嘍,比如紐約是西五區(qū),你在任何時(shí)候獲取其當(dāng)?shù)貢r(shí)間都是能得到正確答案的,因?yàn)樗鼉?nèi)置了對(duì)夏令時(shí)規(guī)則的處理,也就是說(shuō)啥時(shí)候+1啥時(shí)候-1時(shí)區(qū)自己門清,不需要API調(diào)用者關(guān)心。

UTC偏移量更像是一種寫死偏移量數(shù)值的做法,這在天朝這種沒(méi)有時(shí)區(qū)規(guī)則(沒(méi)有夏令時(shí))的國(guó)家不會(huì)存在問(wèn)題,東八區(qū)和UTC+08:00效果永遠(yuǎn)一樣。但在一些夏令時(shí)國(guó)家(如美國(guó)、法國(guó)等等),就只能根據(jù)時(shí)區(qū)去獲取當(dāng)?shù)貢r(shí)間嘍。所以當(dāng)你不了解當(dāng)?shù)匾?guī)則時(shí),最好是使用時(shí)區(qū)而非偏移量。

ZoneId

 

它代表一個(gè)時(shí)區(qū)的ID,如Europe/Paris。它規(guī)定了一些規(guī)則可用于將一個(gè)Instant時(shí)間戳轉(zhuǎn)換為本地日期/時(shí)間LocalDateTime。

上面說(shuō)了時(shí)區(qū)ZoneId是包含有規(guī)則的,實(shí)際上描述偏移量何時(shí)以及如何變化的實(shí)際規(guī)則由java.time.zone.ZoneRules定義。ZoneId則只是一個(gè)用于獲取底層規(guī)則的ID。之所以采用這種方法,是因?yàn)橐?guī)則是由政府定義的,并且經(jīng)常變化,而ID是穩(wěn)定的。

對(duì)于API調(diào)用者來(lái)說(shuō)只需要使用這個(gè)ID(也就是ZoneId)即可,而無(wú)需關(guān)心更為底層的時(shí)區(qū)規(guī)則ZoneRules,和“政府”同步規(guī)則的事是它領(lǐng)域內(nèi)的事就交給它嘍。如:夏令時(shí)這條規(guī)則是由各國(guó)政府制定的,而且不同國(guó)家不同年一般都不一樣,這個(gè)事就交由JDK底層的ZoneRules機(jī)制自行sync,使用者無(wú)需關(guān)心。

ZoneId在系統(tǒng)內(nèi)是唯一的,它共包含三種類型的ID:

  1. 最簡(jiǎn)單的ID類型:ZoneOffset,它由'Z'和以'+'或'-'開(kāi)頭的id組成。如:Z、+18:00、-18:00
  2. 另一種類型的ID是帶有某種前綴形式的偏移樣式ID,例如'GMT+2'或'UTC+01:00'。可識(shí)別的(合法的)前綴是'UTC', 'GMT'和'UT'
  3. 第三種類型是基于區(qū)域的ID(推薦使用)。基于區(qū)域的ID必須包含兩個(gè)或多個(gè)字符,且不能以'UTC'、'GMT'、'UT' '+'或'-'開(kāi)頭。基于區(qū)域的id由配置定義好的,如Europe/Paris

概念說(shuō)了一大推,下面給幾個(gè)代碼示例感受下吧。

1、獲取系統(tǒng)默認(rèn)的ZoneId:

  1. @Test 
  2. public void test1() { 
  3.     // JDK 1.8之前做法 
  4.     System.out.println(TimeZone.getDefault()); 
  5.     // JDK 1.8之后做法 
  6.     System.out.println(ZoneId.systemDefault()); 
  7.  
  8. 輸出: 
  9. Asia/Shanghai 
  10. sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null

二者結(jié)果是一樣的,都是Asia/Shanghai。因?yàn)閆oneId方法底層就是依賴TimeZone,如圖:


2、指定字符串得到一個(gè)ZoneId:

  1. @Test 
  2. public void test2() { 
  3.     System.out.println(ZoneId.of("Asia/Shanghai")); 
  4.     // 報(bào)錯(cuò):java.time.zone.ZoneRulesException: Unknown time-zone ID: Asia/xxx 
  5.     System.out.println(ZoneId.of("Asia/xxx")); 

很明顯,這個(gè)字符串也是不能隨便寫的。那么問(wèn)題來(lái)了,可寫的有哪些呢?同樣的ZoneId提供了API供你獲取到所有可用的字符串id,有興趣的同學(xué)建議自行嘗試:

  1. @Test 
  2. public void test3() { 
  3.     ZoneId.getAvailableZoneIds(); 

3、根據(jù)偏移量得到一個(gè)ZoneId:

  1. @Test 
  2. public void test4() { 
  3.     ZoneId zoneId = ZoneId.ofOffset("UTC", ZoneOffset.of("+8")); 
  4.     System.out.println(zoneId); 
  5.     // 必須是大寫的Z 
  6.     zoneId = ZoneId.ofOffset("UTC", ZoneOffset.of("Z")); 
  7.     System.out.println(zoneId); 
  8.  
  9. 輸出: 
  10. UTC+08:00 
  11. UTC 

這里第一個(gè)參數(shù)傳的前綴,可用值為:"GMT", "UTC", or "UT"。當(dāng)然還可以傳空串,那就直接返回第二個(gè)參數(shù)ZoneOffset。若以上都不是就報(bào)錯(cuò)

注意:根據(jù)偏移量得到的ZoneId內(nèi)部并無(wú)現(xiàn)成時(shí)區(qū)規(guī)則可用,因此對(duì)于有夏令營(yíng)的國(guó)家轉(zhuǎn)換可能出問(wèn)題,一般不建議這么去做。

4、從日期里面獲得時(shí)區(qū):

  1. @Test 
  2. public void test5() { 
  3.     System.out.println(ZoneId.from(ZonedDateTime.now())); 
  4.     System.out.println(ZoneId.from(ZoneOffset.of("+8"))); 
  5.  
  6.     // 報(bào)錯(cuò):java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: 
  7.     System.out.println(ZoneId.from(LocalDateTime.now())); 
  8.     System.out.println(ZoneId.from(LocalDate.now())); 

雖然方法入?yún)⑹荰emporalAccessor,但是只接受帶時(shí)區(qū)的類型,LocalXXX是不行的,使用時(shí)稍加注意。

ZoneOffset

距離格林威治/UTC的時(shí)區(qū)偏移量,例如+02:00。值得注意的是它繼承自ZoneId,所以也可當(dāng)作一個(gè)ZoneId來(lái)使用的,當(dāng)然并不建議你這么去做,請(qǐng)獨(dú)立使用。

時(shí)區(qū)偏移量是時(shí)區(qū)與格林威治/UTC之間的時(shí)間差。這通常是固定的小時(shí)數(shù)和分鐘數(shù)。世界不同的地區(qū)有不同的時(shí)區(qū)偏移量。在ZoneId類中捕獲關(guān)于偏移量如何隨一年的地點(diǎn)和時(shí)間而變化的規(guī)則(主要是夏令時(shí)規(guī)則),所以繼承自ZoneId。

1、最小/最大偏移量:因?yàn)槠屏總魅氲氖菙?shù)字,這個(gè)是有限制的哦

  1. @Test 
  2. public void test6() { 
  3.     System.out.println("最小偏移量:" + ZoneOffset.MIN); 
  4.     System.out.println("最小偏移量:" + ZoneOffset.MAX); 
  5.     System.out.println("中心偏移量:" + ZoneOffset.UTC); 
  6.     // 超出最大范圍 
  7.     System.out.println(ZoneOffset.of("+20")); 
  8.  
  9. 輸出: 
  10. 最小偏移量:-18:00 
  11. 最小偏移量:+18:00 
  12. 中心偏移量:Z 
  13.  
  14. java.time.DateTimeException: Zone offset hours not in valid range: value 20 is not in the range -18 to 18 

2、通過(guò)時(shí)分秒構(gòu)造偏移量(使用很方便,推薦):

  1. @Test 
  2. public void test7() { 
  3.     System.out.println(ZoneOffset.ofHours(8)); 
  4.     System.out.println(ZoneOffset.ofHoursMinutes(8, 8)); 
  5.     System.out.println(ZoneOffset.ofHoursMinutesSeconds(8, 8, 8)); 
  6.  
  7.     System.out.println(ZoneOffset.ofHours(-5)); 
  8.  
  9.     // 指定一個(gè)精確的秒數(shù)  獲取實(shí)例(有時(shí)候也很有用處) 
  10.     System.out.println(ZoneOffset.ofTotalSeconds(8 * 60 * 60)); 
  11.  
  12. // 輸出: 
  13. +08:00 
  14. +08:08 
  15. +08:08:08 
  16. -05:00 
  17. +08:00 

看來(lái),偏移量是能精確到秒的哈,只不過(guò)一般來(lái)說(shuō)精確到分鐘已經(jīng)到頂了。

設(shè)置默認(rèn)時(shí)區(qū)

ZoneId并沒(méi)有提供設(shè)置默認(rèn)時(shí)區(qū)的方法,但是通過(guò)文章可知ZoneId獲取默認(rèn)時(shí)區(qū)底層依賴的是TimeZone.getDefault()方法,因此設(shè)置默認(rèn)時(shí)區(qū)方式完全遵照TimeZone的方式即可(共三種方式,還記得嗎?)。

讓人惱火的夏令時(shí)

因?yàn)橛邢牧顣r(shí)規(guī)則的存在,讓操作日期/時(shí)間的復(fù)雜度大大增加。但還好JDK盡量的屏蔽了這些規(guī)則對(duì)使用者的影響。因此:推薦使用時(shí)區(qū)(ZoneId)轉(zhuǎn)換日期/時(shí)間,一般情況下不建議使用偏移量ZoneOffset去搞,這樣就不會(huì)有夏令時(shí)的煩惱啦。

JSR 310時(shí)區(qū)相關(guān)性

java.util.Date類型它具有時(shí)區(qū)無(wú)關(guān)性,帶來(lái)的弊端就是一旦涉及到國(guó)際化時(shí)間轉(zhuǎn)換等需求時(shí),使用Date來(lái)處理是很不方便的。

JSR 310解決了Date存在的一系列問(wèn)題:對(duì)日期、時(shí)間進(jìn)行了分開(kāi)表示(LocalDate、LocalTime、LocalDateTime),對(duì)本地時(shí)間和帶時(shí)區(qū)的時(shí)間進(jìn)行了分開(kāi)管理。LocalXXX表示本地時(shí)間,也就是說(shuō)是當(dāng)前JVM所在時(shí)區(qū)的時(shí)間;ZonedXXX表示是一個(gè)帶有時(shí)區(qū)的日期時(shí)間,它們能非常方便的互相完成轉(zhuǎn)換。

  1. @Test 
  2. public void test8() { 
  3.     // 本地日期/時(shí)間 
  4.     System.out.println("================本地時(shí)間================"); 
  5.     System.out.println(LocalDate.now()); 
  6.     System.out.println(LocalTime.now()); 
  7.     System.out.println(LocalDateTime.now()); 
  8.  
  9.     // 時(shí)區(qū)時(shí)間 
  10.     System.out.println("================帶時(shí)區(qū)的時(shí)間ZonedDateTime================"); 
  11.     System.out.println(ZonedDateTime.now()); // 使用系統(tǒng)時(shí)區(qū) 
  12.     System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York"))); // 自己指定時(shí)區(qū) 
  13.     System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 自己指定時(shí)區(qū) 
  14.     System.out.println("================帶時(shí)區(qū)的時(shí)間OffsetDateTime================"); 
  15.     System.out.println(OffsetDateTime.now()); // 使用系統(tǒng)時(shí)區(qū) 
  16.     System.out.println(OffsetDateTime.now(ZoneId.of("America/New_York"))); // 自己指定時(shí)區(qū) 
  17.     System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 自己指定時(shí)區(qū) 

運(yùn)行程序,輸出:

  1. ================本地時(shí)間================ 
  2. 2021-01-17 
  3. 09:18:40.703 
  4. 2021-01-17T09:18:40.703 
  5. ================帶時(shí)區(qū)的時(shí)間ZonedDateTime================ 
  6. 2021-01-17T09:18:40.704+08:00[Asia/Shanghai] 
  7. 2021-01-16T20:18:40.706-05:00[America/New_York] 
  8. 2021-01-17T01:18:40.709Z 
  9. ================帶時(shí)區(qū)的時(shí)間OffsetDateTime================ 
  10. 2021-01-17T09:18:40.710+08:00 
  11. 2021-01-16T20:18:40.710-05:00 
  12. 2021-01-17T01:18:40.710Z 

本地時(shí)間的輸出非常“干凈”,可直接用于顯示。帶時(shí)區(qū)的時(shí)間顯示了該時(shí)間代表的是哪個(gè)時(shí)區(qū)的時(shí)間,畢竟不指定時(shí)區(qū)的時(shí)間是沒(méi)有任何意義的。LocalXXX因?yàn)樗哂袝r(shí)區(qū)無(wú)關(guān)性,因此它不能代表一個(gè)瞬間/時(shí)刻。

另外,關(guān)于LocalDateTime、OffsetDateTime、ZonedDateTime三者的跨時(shí)區(qū)轉(zhuǎn)換問(wèn)題,以及它們的詳解,因?yàn)閮?nèi)容過(guò)多放在了下文專文闡述,保持關(guān)注。

讀取字符串為JSR 310類型

一個(gè)獨(dú)立的日期時(shí)間類型字符串如2021-05-05T18:00-04:00它是沒(méi)有任何意義的,因?yàn)闆](méi)有時(shí)區(qū)無(wú)法確定它代表那個(gè)瞬間,這是理論當(dāng)然也適合JSR 310類型嘍。

遇到一個(gè)日期時(shí)間格式字符串,要解析它一般有這兩種情況:

1.不帶時(shí)區(qū)/偏移量的字符串:要么不理它說(shuō)轉(zhuǎn)換不了,要么就約定一個(gè)時(shí)區(qū)(一般用系統(tǒng)默認(rèn)時(shí)區(qū)),使用LocalDateTime來(lái)解析

  1. @Test 
  2. public void test11() { 
  3.     String dateTimeStrParam = "2021-05-05T18:00"
  4.     LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStrParam); 
  5.     System.out.println("解析后:" + localDateTime); 
  6.  
  7. 輸出: 
  8. 解析后:2021-05-05T18:00 

2.帶時(shí)區(qū)字/偏移量的符串:

  1. @Test 
  2. public void test12() { 
  3.     // 帶偏移量 使用OffsetDateTime  
  4.     String dateTimeStrParam = "2021-05-05T18:00-04:00"
  5.     OffsetDateTime offsetDateTime = OffsetDateTime.parse(dateTimeStrParam); 
  6.     System.out.println("帶偏移量解析后:" + offsetDateTime); 
  7.  
  8.  // 帶時(shí)區(qū) 使用ZonedDateTime  
  9.     dateTimeStrParam = "2021-05-05T18:00-05:00[America/New_York]"
  10.     ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStrParam); 
  11.     System.out.println("帶時(shí)區(qū)解析后:" + zonedDateTime); 
  12.  
  13. 輸出: 
  14. 帶偏移量解析后:2021-05-05T18:00-04:00 
  15. 帶時(shí)區(qū)解析后:2021-05-05T18:00-04:00[America/New_York] 

請(qǐng)注意帶時(shí)區(qū)解析后這個(gè)結(jié)果:字符串參數(shù)偏移量明明是-05,為毛轉(zhuǎn)換為ZonedDateTime后偏移量成為了-04呢???

這里是我故意造了這么一個(gè)case引起你的重視,對(duì)此結(jié)果我做如下解釋:


如圖,在2021.03.14 - 2021.11.07期間,紐約的偏移量是-4,其余時(shí)候是-5。本例的日期是2021-05-05處在夏令時(shí)之中,因此偏移量是-4,這就解釋了為何你顯示的寫了-5最終還是成了-4。

JSR 310格式化

針對(duì)JSR 310日期時(shí)間類型的格式化/解析,有個(gè)專門的類java.time.format.DateTimeFormatter用于處理。

DateTimeFormatter也是一個(gè)不可變的類,所以是線程安全的,比SimpleDateFormat靠譜多了吧。另外它還內(nèi)置了非常多的格式化模版實(shí)例供以使用,形如:

  1. @Test 
  2. public void test13() { 
  3.     System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now())); 
  4.     System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now())); 
  5.     System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now())); 
  6.  
  7. 輸出: 
  8. 2021-01-17 
  9. 22:43:21.398 
  10. 2021-01-17T22:43:21.4 

若想自定義模式pattern,和Date一樣它也可以自己指定任意的pattern 日期/時(shí)間模式。由于本文在Date部分詳細(xì)介紹了日期/時(shí)間模式,各個(gè)字母代表什么意思以及如何使用,這里就不再贅述了哈。

  • ❝雖然DateTimeFormatter支持的模式比Date略有增加,但大體還保持一致,個(gè)人覺(jué)得這塊無(wú)需再花精力。若真有需要再查官網(wǎng)也不遲❞
  1. @Test 
  2. public void test14() { 
  3.     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US); 
  4.  
  5.     // 格式化輸出 
  6.     System.out.println(formatter.format(LocalDateTime.now())); 
  7.  
  8.     // 解析 
  9.     String dateTimeStrParam = "第1季度 2021-01-17 22:51:32"
  10.     LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStrParam, formatter); 
  11.     System.out.println("解析后的結(jié)果:" + localDateTime); 

Q/q:季度,如3; 03; Q3; 3rd quarter。

最佳實(shí)踐

  • 棄用Date,擁抱JSR 310

每每說(shuō)到JSR 310日期/時(shí)間時(shí)我都會(huì)呼吁,保持慣例我這里繼續(xù)啰嗦一句:放棄Date甚至禁用Date,使用JSR 310日期/時(shí)間吧,它才是日期時(shí)間處理的最佳實(shí)踐。

另外,在使用期間關(guān)于制定時(shí)區(qū)(默認(rèn)時(shí)區(qū)時(shí))依舊有一套我心目中的最佳實(shí)踐存在,這里分享給你:

  • 永遠(yuǎn)顯式的指定你需要的時(shí)區(qū),即使你要獲取的是默認(rèn)時(shí)區(qū)
  1. // 方式一:普通做法 
  2. LocalDateTime.now(); 
  3.  
  4. // 方式二:最佳實(shí)踐 
  5. LocalDateTime.now(ZoneId.systemDefault()); 

如上代碼二者效果一模一樣。但是方式二是最佳實(shí)踐。

理由是:這樣做能讓代碼帶有明確的意圖,消除模棱兩可的可能性,即使獲取的是默認(rèn)時(shí)區(qū)。拿方式一來(lái)說(shuō)吧,它就存在意圖不明確的地方:到底是代碼編寫者忘記指定時(shí)區(qū)欠考慮了,還是就想用默認(rèn)時(shí)區(qū)呢?這個(gè)答案如果不通讀上下文是無(wú)法確定的,從而造成了不必要的溝通維護(hù)成本。因此即使你是要獲取默認(rèn)時(shí)區(qū),也請(qǐng)顯示的用ZoneId.systemDefault()寫上去。

  • 使用JVM的默認(rèn)時(shí)區(qū)需當(dāng)心,建議時(shí)區(qū)和當(dāng)前會(huì)話保持綁定

這個(gè)最佳實(shí)踐在特殊場(chǎng)景用得到。這么做的理由是:JVM的默認(rèn)時(shí)區(qū)通過(guò)靜態(tài)方法TimeZone#setDefault()可全局設(shè)置,因此JVM的任何一個(gè)線程都可以隨意更改默認(rèn)時(shí)區(qū)。若關(guān)于時(shí)間處理的代碼對(duì)時(shí)區(qū)非常敏感的話,最佳實(shí)踐是你把時(shí)區(qū)信息和當(dāng)前會(huì)話綁定,這樣就可以不用再受到其它線程潛在影響了,確保了健壯性。

❝說(shuō)明:會(huì)話可能只是當(dāng)前請(qǐng)求,也可能是一個(gè)Session,具體case具體分析❞

✍總結(jié)

通過(guò)上篇文章 對(duì)日期時(shí)間相關(guān)概念的鋪墊,加上本文的實(shí)操代碼演示,達(dá)到弄透Java對(duì)日期時(shí)間的處理基本不成問(wèn)題。

兩篇文章的內(nèi)容較多,信息量均比較大,消化起來(lái)需要些時(shí)間。一方面我建議你先搜藏留以當(dāng)做參考書備用,另一方面建議多實(shí)踐,代碼這東西只有多寫寫才能有更深體會(huì)。

后面會(huì)再用3 -4篇文章對(duì)這前面這兩篇的細(xì)節(jié)、使用場(chǎng)景進(jìn)行補(bǔ)充,比如如何去匹配ZoneId和Offset的對(duì)應(yīng)關(guān)系,LocalDateTime、OffsetDateTime、ZonedDateTime跨時(shí)區(qū)互轉(zhuǎn)問(wèn)題、在Spring MVC場(chǎng)景下使用的最佳實(shí)踐等等,敬請(qǐng)關(guān)注,一起進(jìn)步。

本文是 A哥(YourBatman)原創(chuàng)文章,未經(jīng)允許/開(kāi)白不得轉(zhuǎn)載,謝謝合作。

 

責(zé)任編輯:姜華 來(lái)源: BAT的烏托邦
相關(guān)推薦

2017-07-27 15:50:19

Java時(shí)間日期

2024-06-17 09:40:45

UTCDay.js庫(kù)時(shí)間轉(zhuǎn)換

2023-12-27 08:16:54

Sessiontoken安全性

2022-03-24 07:38:07

注解SpringBoot項(xiàng)目

2021-01-26 09:25:02

Nginx開(kāi)源軟件服務(wù)器

2022-08-24 11:54:10

Pandas可視化

2021-05-27 05:34:22

Git開(kāi)源控制系統(tǒng)

2020-12-18 08:03:00

插件MyBatis Executor

2025-05-22 08:21:28

2024-02-22 14:51:38

Java字符串

2022-05-18 11:35:17

Python字符串

2021-10-12 13:35:30

C++Set紅黑樹(shù)

2021-03-02 07:13:54

Java8版本升級(jí)

2010-07-19 15:37:48

Perl日期時(shí)間

2019-09-03 10:55:20

Python函數(shù)lambad

2020-03-12 09:06:05

數(shù)據(jù)挖掘聚類分析學(xué)習(xí)

2022-07-20 00:15:48

SQL數(shù)據(jù)庫(kù)編程語(yǔ)言

2022-07-20 09:05:06

Python編程語(yǔ)言

2020-07-06 11:53:08

TCP三次握手協(xié)議

2025-04-02 09:10:00

LinuxShell腳本
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 人干人人 | 在线色网站 | 国产乱xxav | 91综合网| 国产精品久久久久久 | 久精品久久 | 一二区视频 | 久久草视频 | 日韩精品中文字幕一区二区三区 | 亚洲精品中文字幕av | 91精品国产乱码久久久久久久久 | 欧美视频1| www312aⅴ欧美在线看 | 99久久精品免费视频 | 欧美一级三级在线观看 | a成人| 日韩专区中文字幕 | 国产精品一区二区三区四区五区 | 日韩成人在线视频 | 一区二区三区亚洲精品国 | 国产一区欧美 | 久久精品亚洲国产 | 中文字幕综合 | 性色av一区二区三区 | 亚洲免费在线视频 | 亚洲精品一区中文字幕乱码 | 亚洲444eee在线观看 | 在线播放国产一区二区三区 | 成人午夜电影网 | 伊人一区 | 夜夜草天天草 | 日日天天 | 国产精品色 | 国产一区二区激情视频 | 色av一区二区 | 奇米影视首页 | 狠狠操狠狠色 | 高清国产午夜精品久久久久久 | 成人免费看电影 | 国产在线激情视频 | 亚洲第一在线 |