<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          都什么年代了你還在用 Date

          共 3847字,需瀏覽 8分鐘

           ·

          2022-01-12 16:32

          前言

          上篇文章搞清楚了時(shí)區(qū),這篇文章就主要來(lái)談一談 Java 中處理日期時(shí)間用什么 API 比較好。我本來(lái)不準(zhǔn)備寫(xiě)這篇文章的,因?yàn)槲矣X(jué)得 Java17 都特么出來(lái)了,大家對(duì) Java8 提供的時(shí)間日期 API 都很熟悉了。但是經(jīng)過(guò)我調(diào)研,很多中小公司還在用老版本的 Date 來(lái)處理時(shí)間日期,視 Java8 提供的時(shí)間日期 API 于無(wú)物,所以還是想來(lái)推薦一下新一代的時(shí)間日期 API,希望對(duì)大家有幫助。

          傳統(tǒng)的 Date

          老版本的 Date 相信大家都很熟悉了,這里就簡(jiǎn)單介紹幾個(gè)點(diǎn)

          可觀不可觸的時(shí)區(qū)

          對(duì)于老版本的 DateSimpleDateFormat 相信大家都很熟悉,值得注意的是,你是無(wú)法直接設(shè)置 Date 的時(shí)區(qū)信息的,但與之矛盾的是我們?cè)诖a中從數(shù)據(jù)庫(kù)讀取一個(gè)帶時(shí)區(qū)的時(shí)間,例如:2021-11-01 13:50:47.138494+00 ,它卻能夠自動(dòng)解析成當(dāng)前服務(wù)器所在時(shí)區(qū)的時(shí)間封裝在 Date 對(duì)象中。其實(shí)這是它的一個(gè)成員變量 private transient BaseCalendar.Date cdate 去做的事情,由于 Unix 時(shí)間戳是和時(shí)區(qū)無(wú)關(guān)的,所以在從數(shù)據(jù)庫(kù)讀取時(shí)它會(huì)將數(shù)據(jù)庫(kù)帶有時(shí)區(qū)時(shí)間轉(zhuǎn)換為 Unix 時(shí)間戳,然后在用這個(gè)時(shí)間戳轉(zhuǎn)換為當(dāng)前服務(wù)器所在時(shí)區(qū)的時(shí)間,并且攜帶著時(shí)區(qū)信息。

          其實(shí)我們可以看一下 Date 構(gòu)造方法源碼,他就是獲取的當(dāng)前 Unix 時(shí)間戳。

          public?Date()?{
          ????this(System.currentTimeMillis());
          }

          復(fù)雜的時(shí)區(qū)轉(zhuǎn)換計(jì)算

          如果我們需要顯示一個(gè) Date 對(duì)象在不同時(shí)區(qū)的時(shí)間,那么我們需要通過(guò) SimpleDateFormat 來(lái)實(shí)現(xiàn)

          SimpleDateFormat?sdf?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss");
          Date?date?=?new?Date();//默認(rèn)北京時(shí)區(qū)的時(shí)間
          System.out.println("北京時(shí)區(qū):"?+?sdf.getTimeZone());
          System.out.println("北京時(shí)區(qū)時(shí)間:"?+?sdf.format(date));
          sdf.setTimeZone(TimeZone.getTimeZone(ZoneId.of("Asia/Jakarta")));
          System.out.println("雅加達(dá)時(shí)區(qū):"?+?sdf.getTimeZone());
          System.out.println("雅加達(dá)時(shí)區(qū)時(shí)間:"?+?sdf.format(date));

          如果你想得到的不是字符串而是轉(zhuǎn)換為另一個(gè)時(shí)區(qū)的 Date 對(duì)象,那么你還需要再?gòu)淖址崔D(zhuǎn)到 Date ......這無(wú)疑是非常麻煩的

          復(fù)雜時(shí)間間隔計(jì)算

          要說(shuō) Date 最難受的地方之一就是兩個(gè)日期的時(shí)間差計(jì)算,之前 JDK 并沒(méi)有提供直接的 API 來(lái)計(jì)算兩個(gè) Date 的間隔。通常我們是把 Date 轉(zhuǎn)成時(shí)間戳之后進(jìn)行操作

          Date?date1?=?new?Date();
          TimeUnit.SECONDS.sleep(10);
          Date?date2?=?new?Date();
          long?time1?=?date1.getTime();
          long?time2?=?date2.getTime();
          System.out.println("間隔秒數(shù):"?+?(time2?-?time1)?/?1000);
          System.out.println("間隔小時(shí)數(shù):"?+?(time2?-?time1)?/?(1000?*?3600));
          //...

          是不是很麻煩?如果你并不覺(jué)得麻煩,那么你只是沒(méi)有見(jiàn)過(guò)更好的方式。我們來(lái)看看新版 API 怎么來(lái)做(后面會(huì)詳細(xì)介紹)

          Duration?duration?=?Duration.between(time2,?time1);
          long?days?=?duration.toDays();//獲取天數(shù)間隔
          long?hours?=?duration.toHours();//獲取小時(shí)間隔
          //...

          這樣是不是很簡(jiǎn)單!

          數(shù)據(jù)庫(kù)到底要不要存儲(chǔ)時(shí)區(qū)信息

          在談新版的時(shí)間 API 之前,我們先要搞清楚一個(gè)問(wèn)題,那就是你真的有必要把時(shí)區(qū)信息存儲(chǔ)到數(shù)據(jù)庫(kù)嗎?其實(shí)我覺(jué)得對(duì)于絕大多數(shù)的公司應(yīng)該都是不需要的,下我以我們公司印度尼西亞業(yè)務(wù)為例來(lái)分析這個(gè)問(wèn)題。

          存儲(chǔ)時(shí)區(qū)

          我們目前是先在代碼中獲取當(dāng)前服務(wù)器(我們服務(wù)器在亞馬遜 UTC-3 )所在時(shí)區(qū)的 Date 對(duì)象,在存入數(shù)據(jù)庫(kù)時(shí),在數(shù)據(jù)庫(kù)層面將其轉(zhuǎn)換為 UTC+8 時(shí)區(qū)的時(shí)間存儲(chǔ)到數(shù)據(jù)庫(kù)中,例如 2021-10-24 15:47:47.138494-03 存到數(shù)據(jù)庫(kù)中是 2021-10-25 02:47:47.138494+08,然后在前端頁(yè)面可以直接調(diào)用 API 根據(jù)帶時(shí)區(qū)的時(shí)間計(jì)算出前端設(shè)備當(dāng)?shù)貢r(shí)區(qū)的時(shí)間。

          xxx//假如設(shè)備在印度尼西亞,那么前端?API?獲得的時(shí)間就是?2021-10-25?01:47:47.138494

          不過(guò)不是很明白,我們既然存時(shí)區(qū),那么應(yīng)該也要存印度尼西亞的時(shí)區(qū)啊......畢竟我們的業(yè)務(wù)在印尼,也不在北京......

          不存儲(chǔ)時(shí)區(qū)

          正常來(lái)說(shuō)服務(wù)器所在地一般是唯一的,即使有 100 個(gè)服務(wù)實(shí)例來(lái)做負(fù)載均衡,總不可能出現(xiàn)一半服務(wù)器在美國(guó),一半在中國(guó)吧?那么代碼中的時(shí)間日期操作都是默認(rèn)使用服務(wù)器所在地時(shí)區(qū)(UTC-3),既然如此那么我們數(shù)據(jù)庫(kù)就可以不存儲(chǔ)時(shí)區(qū)信息,只存儲(chǔ)一個(gè)時(shí)間描述例如 2021-10-24 13:50:47.138494,它本身沒(méi)有時(shí)區(qū),但是我們都知道它的默認(rèn)時(shí)區(qū)就是 UTC-3

          這樣在前端代碼中只需要調(diào)用 API 的時(shí)候帶上帶上該時(shí)間的所屬時(shí)區(qū) UTC-3 即可算出前端設(shè)備當(dāng)?shù)貢r(shí)區(qū)的時(shí)間。

          xxx//假如設(shè)備在印度尼西亞,那么前端?API?獲得的時(shí)間就是?2021-10-25?01:47:47.138494

          而且這個(gè)不帶時(shí)區(qū)的時(shí)間不會(huì)受數(shù)據(jù)庫(kù)時(shí)區(qū)的影響,無(wú)論你把數(shù)據(jù)庫(kù)設(shè)置成哪個(gè)時(shí)區(qū),它都不會(huì)變化。這種方式就是整個(gè)業(yè)務(wù)里我們把所有時(shí)區(qū)都干掉,只留一個(gè)服務(wù)器時(shí)區(qū),當(dāng)有任何涉及時(shí)區(qū)的業(yè)務(wù)功能時(shí),只需要把源時(shí)間換算成服務(wù)器所在時(shí)區(qū)的時(shí)間即可。

          總結(jié)

          不存儲(chǔ)時(shí)區(qū)還有一種情況就是有些程序員喜歡存時(shí)間戳,不得不吐槽一下我覺(jué)得這是最 low 的方式之一(也許你能說(shuō)出它僅有的個(gè)別優(yōu)點(diǎn),但我不接受反駁)。雖然時(shí)間戳是和時(shí)區(qū)無(wú)關(guān)的,但是它的可讀性真的太差了......而且代碼中也不好進(jìn)行操作

          其實(shí)對(duì)比一下就能發(fā)現(xiàn)數(shù)據(jù)庫(kù)存不存儲(chǔ)時(shí)區(qū)信息,對(duì)于前后端的操作區(qū)別不大的。而且我覺(jué)得絕大多數(shù)業(yè)務(wù)場(chǎng)景,不存儲(chǔ)時(shí)區(qū)相對(duì)更簡(jiǎn)單!

          Java8 的時(shí)間日期 API

          LocalDate、LocalTime、LocalDateTime

          看源碼是一個(gè)好習(xí)慣,看源碼注釋更是一個(gè)好習(xí)慣。這三個(gè)類(lèi)的注釋說(shuō)的很清楚,首先這些類(lèi)是線程安全的,對(duì)于它的任何操作都會(huì)產(chǎn)生一個(gè)新的實(shí)例,這和 String 類(lèi)是一樣的。其次它不存儲(chǔ)或表示時(shí)區(qū)。相反,它是對(duì)日期時(shí)間的描述。

          值得注意的是,它不存儲(chǔ)時(shí)區(qū),但不代表它沒(méi)有時(shí)區(qū),細(xì)品這句話(huà)!通過(guò)一張圖來(lái)理解三者的關(guān)系

          下面以 LocalDateTime 為例簡(jiǎn)單介紹下用法

          LocalDateTime?time1?=?LocalDateTime.now();
          LocalDateTime?time2?=?LocalDateTime.now();

          time1.isAfter(time2);?time1.isBefore(time2);//比較時(shí)間
          time1.plusDays(1L);?time1.minusHours(1L);//加減時(shí)間日期
          LocalDateTime.parse("2021-11-19T15:16:17");//解析時(shí)間
          LocalDateTime.of(2019,?11,?30,?15,?16,?17);//指定日期時(shí)間
          LocalDateTime.now(ZoneId.of("Asia/Jakarta"));//其他時(shí)區(qū)相對(duì)此服務(wù)器時(shí)區(qū)的時(shí)間
          //...

          如果你可以不在數(shù)據(jù)庫(kù)中存儲(chǔ)時(shí)區(qū)信息的話(huà),那么請(qǐng)使用這個(gè)類(lèi)。如果你一定要存儲(chǔ),那么也請(qǐng)使用下面的 OffsetDateTime 而不要使用 Date

          OffsetDateTime

          帶有時(shí)區(qū)偏移量的日期時(shí)間類(lèi),相當(dāng)于 OffsetDateTime = LocalDateTime + ZoneOffset

          大多數(shù)情況下我們使用帶有偏移量的日期時(shí)間已經(jīng)能夠滿(mǎn)足需求。

          ZonedDateTime

          真正帶有完整時(shí)區(qū)信息的日期時(shí)間類(lèi)

          Duration、Period

          這兩個(gè)類(lèi)是代表一段時(shí)間或者說(shuō)是兩個(gè)時(shí)間的間隔,以 Duration 為例,試想在 Java8 之前你有一個(gè)業(yè)務(wù)要表示一個(gè)令牌的有效期為 7 天,那么通常的做法可能是存儲(chǔ)令牌的創(chuàng)建時(shí)間,然后在代碼中用系統(tǒng)當(dāng)前時(shí)間減去令牌創(chuàng)建時(shí)間和 7 天做比較,例如

          long?duration?=?System.currentTimeMillis()?-?token.getCreateTime().getTime();
          if?(duration?7?*?24?*?60?*?60?*?1000)?{?//令牌合法}

          但是在使用 Duration 之后,

          Duration?duration?=?Duration.between(token.getCreateTime(),?LocalDateTime.now());
          boolean?negative?=?duration.minus(effective).isNegative();//是否過(guò)期

          其次 Duration 在 SpringBoot 項(xiàng)目中,配置也很方便

          token:
          ??effective-time:?7d??#?d:天?,?h:小時(shí)?,?m:分鐘?,?s:秒

          在實(shí)體類(lèi)中,可以使用 @ConfigurationProperties 或者 @Value 將它直接映射成 Duration 對(duì)象,當(dāng)然這依賴(lài)于 SpringBoot 中提供的豐富的類(lèi)型轉(zhuǎn)換器。下一篇文章會(huì)介紹

          TemporalAdjuster 和 TemporalAdjusters

          第一眼看到這兩個(gè)類(lèi)是不是想起了熟悉的 CollectionCollections ,與之類(lèi)似這兩個(gè)類(lèi)是時(shí)間矯正器接口和時(shí)間矯正器的工具類(lèi)。新版日期時(shí)間類(lèi)幾乎都實(shí)現(xiàn)了 TemporalAdjuster ,以便于針對(duì)所有日期時(shí)間都可以對(duì)其進(jìn)行計(jì)算得到另外一個(gè)時(shí)間,例如

          LocalDateTime.now().with(TemporalAdjusters.firstDayOfMonth());//當(dāng)月第一天LocalDateTime.now().with(TemporalAdjusters.firstDayOfNextMonth());//下個(gè)月第一天LocalDateTime.now().with(TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.MONDAY));//第N個(gè)星期幾LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY));//下個(gè)星期幾//...

          有這么豐富的 API ,你還需要寫(xiě)一堆日期時(shí)間的工具類(lèi)嗎?

          Date 和 LocalDateTime 互轉(zhuǎn)

          不可否認(rèn)存在一種現(xiàn)象就是你的項(xiàng)目一直用的都是 Date,而 leader 又不愿意花費(fèi)時(shí)間精力去升級(jí),或者老的業(yè)務(wù)限制的情況,那么某些場(chǎng)景下你可以使用 Java8 提供的 Instant 將兩者互轉(zhuǎn)來(lái)簡(jiǎn)化一些業(yè)務(wù)代碼操作

          LocalDateTime.ofInstant(new?Date().toInstant(),?ZoneId.systemDefault());//Date?轉(zhuǎn)?LocalDateTime

          Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());//?LocalDateTime?轉(zhuǎn)?Date

          總結(jié)

          Java8 的時(shí)間日期工具還有很多用法,這里就不一一介紹了,總之 Date 有的功能 LocalDateTime 都有,Date 沒(méi)有的功能,LocalDateTime 還有很多。使用新版的日期時(shí)間,幾乎是不存在原來(lái)的 DateUtil 的。所以還需要我告訴你選誰(shuí)嗎~~

          結(jié)語(yǔ)

          人們總是對(duì)于自己熟悉的東西持有傾向,對(duì)于不熟悉的新事物往往會(huì)抵觸,曾經(jīng)我也不止一次的抵觸我親愛(ài)的架構(gòu)師讓我們更換新的技術(shù)組件,但后來(lái)我都愛(ài)上了這些新的技術(shù)。

          抵觸新技術(shù)不是一個(gè)優(yōu)秀程序員該有,這會(huì)阻礙你的成長(zhǎng)。新技術(shù)的出現(xiàn)往往是彌補(bǔ)老技術(shù)的缺陷,沒(méi)有哪個(gè)組織會(huì)花費(fèi)人力物力出一個(gè)廢物組件......所以每當(dāng)有新技術(shù)組件出現(xiàn)時(shí),請(qǐng)嘗試它,也許會(huì)有意想不到的收獲!


          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷五月丁香综合网 | 日韩美女福利视频 | 在线18禁 | 亚洲色无码专区观看在线观 | 亚洲AV在线免费观看 |