1. 程式人生 > >坐上JDK8時間SDK的小船,帶你遨遊UNIX時間戳與時區的小太空~

坐上JDK8時間SDK的小船,帶你遨遊UNIX時間戳與時區的小太空~

# 原文連結:[坐上JDK8時間SDK的小船,帶你遨遊UNIX時間戳與時區的小太空~](https://blog.csdn.net/Howinfun/article/details/112639737) # 一、背景: 最近有一個關於店鋪資料實時分析的需求,需要實時統計店鋪當天的資料:例如訪客數,瀏覽量、商品排行榜等。由於店鋪可以自主選擇店鋪所在時區(全球二十四個時區),而數倉統計後落庫的時間是GMT+8時區對應的UNIX時間戳。因此,在我們呼叫中臺的介面時,不能直接取伺服器的UNIX時間戳作為傳參。 這麼一聽,如果之前沒深入過`UNIX時間戳`與`時區`的概念,可能大家都會有點懵逼了;其實我也是,特別是我是昨天晚上十點多才接收到這個資訊,內心就更加慌張了,畢竟原本昨天就要提測了,現在因為這個時間戳的原因而推遲了。下面我們先來了解UNIX時間戳和時區的概念,然後再繼續討論這個問題。 # 二、概念: **UNIX時間戳**:從1970年1月1日(UTC/GMT的午夜)開始所經過的秒數,不考慮閏秒。 也就是指格林威治時間1970年01月01日00時00分00秒開始到現在的總秒數。 > 對的,大家可以看到,其實UNIX時間戳說的是秒數,而通常我們講的是毫秒~ **時區**:為了克服時間上的混亂,1884年在華盛頓召開的一次國際經度會議(又稱國際子午線會議)上,規定將全球劃分為24個時區(東、西各12個時區)。規定英國(格林尼治天文臺舊址)為中時區(零時區)、東1—12區,西1—12區。每個時區橫跨經度15度,時間正好是1小時。最後的東、西第12區各跨經度7.5度,以東、西經180度為界。每個時區的中央經線上的時間就是這個時區內統一採用的時間,稱為區時,相鄰兩個時區的時間相差1小時。 > 例如:中國所在東8區的時間總比莫斯科所在東3區的時間多5個小時。 看完上面兩個定義,我們可以得出結論:時間戳是沒有時區之分的,僅僅是日期展示和時區有關係;同一個時間戳,在不同時區,顯示的日期是不一樣的。 所以上面的需求,如果直接用伺服器所在的時間戳來作為查詢時的時間傳參,那麼一般都是行不通的;除非店鋪的時區和我們伺服器的時區是一樣的(容器中的時區都是GMT+8,也就是東八區),不然店鋪的時間和伺服器的時間是有可能不一樣的。 ### 例子: 假設我們現在根據北京時間 2021-01-12 03:00:00,獲取到對應的時間戳是:1610391600000 (毫秒), 而這個時間戳對應的莫斯科時間為:2021-01-11 22:00:00,這顯然和東八區與東三區的時差(五個小時)是對得上的。 很明顯,上面的例子中,同一UNIX時間戳,不同時區的時間時不一樣的,甚至存在兩時區不在同一日;那至於上面的問題也就隨之被解答了,查詢店鋪的實時資料,那必須要拿到店鋪所在時區的當前時間了。 關於展示同一UNIX時間戳兩個時區的時間區別,可以看下面程式碼: ```java /*** * 對比同一時間戳,不同時區的時間顯示 * @author winfun * @param sourceTimezone sourceTimezone * @param targetTimezone targetTimezone * @return {@link Void } **/ public static void compareTimeByTimezone(String sourceTimezone,String targetTimezone){ // 當前時間伺服器UNIX時間戳 Long timestamp = System.currentTimeMillis(); // 獲取源時區時間 Instant instant = Instant.ofEpochMilli(timestamp); ZoneId sourceZoneId = ZoneId.of(sourceTimezone); LocalDateTime sourceDateTime = LocalDateTime.ofInstant(instant,sourceZoneId); // 獲取目標時區時間 ZoneId targetZoneId = ZoneId.of(targetTimezone); LocalDateTime targetDateTime = LocalDateTime.ofInstant(instant,targetZoneId); System.out.println("The timestamp is "+timestamp+",The DateTime of Timezone{"+sourceTimezone+"} is "+sourceDateTime+ ",The " + "DateTime of Timezone{"+targetTimezone+"} is "+targetDateTime); } ``` 其中一次的執行結果: ``` The timestamp is 1610594585422,The DateTime of Timezone{Europe/Moscow} is 2021-01-13 06:23:05.422,The DateTime of Timezone{Asia/Shanghai} is 2021-01-13 11:23:05.422 ``` 到此,我們應該可以將時間戳和時區很好地區分出來了。 # 三、需求分析: 上面已經很好地分析了UNIX時間戳與時區了,接下來繼續我們的需求分析~ 如果只是拿店鋪所在時區的當前時間,其實非常簡單,我們可以利用 LocalDateTime#now(ZoneId zone) 即可。 可是我上面的需求,到這一步還沒夠,因為數倉儲存的是GMT+8時區對應的UNIX時間戳;所以當我們拿到店鋪所在時區的時間後,還需要轉為GMT+8時區對應的UNIX時間戳。 由於我們是直接查詢當天,我們可以簡化為獲取店鋪當前的零點零分和23點59分,然後轉為GMT+8時區對應的時間戳;最後,利用 JDK8 中的 LocalDateTime 可以非常簡單的完成。 > 雖然我們最後需要的是GMT+8時區的時間戳,但是為了使得方法更加通用,引數分別為源時區和目標時區,可以相容更多的使用場景。 ```java /*** * 獲取源時區的當前日期的零點零分,轉為目標時區對應的時間戳 * @author winfun * @param sourceTimezone 源時區 * @param targetTimezone 目標時區 * @return {@link Void } **/ public static void getStartTimeFromSourceTimezoneAndConvertTimestampToTargetTimezone(String sourceTimezone,String targetTimezone){ // 獲取指定時區的當前時間 ZoneId sourceZoneId = ZoneId.of(sourceTimezone); LocalDateTime dateTime = LocalDateTime.now(sourceZoneId); LocalDate date = LocalDate.now(sourceZoneId); // 獲取上面時間的當天0點0分 LocalDateTime startTime = LocalDateTime.of(date, LocalTime.MIN); // 轉成目標時區對應的時間戳 ZoneId targetZoneId = ZoneId.of(targetTimezone); ZoneOffset targetZoneOffset = targetZoneId.getRules().getOffset(dateTime); Long gmt8Timestamp = startTime.toInstant(targetZoneOffset).toEpochMilli(); System.out.println("The Date of Timezone{"+sourceTimezone+"} is " + date + ",Thd StartTime of Timezone{"+sourceTimezone+ "} is,"+ startTime + ",convert to Timezone{"+targetTimezone+"} timestamp is "+gmt8Timestamp); } /*** * 獲取源時區的當前日期的23點59分,轉為目標時區對應的時間戳 * @author winfun * @param sourceTimezone 源時區 * @param targetTimezone 目標時區 * @return {@link Void } **/ public static void getEndTimeFromSourceTimezoneAndConvertTimestampToTargetTimezone(String sourceTimezone,String targetTimezone){ // 獲取指定時區的當前時間 ZoneId sourceZoneId = ZoneId.of(sourceTimezone); LocalDateTime dateTime = LocalDateTime.now(sourceZoneId); LocalDate date = LocalDate.now(sourceZoneId); // 獲取上面時間的當天23點59分 LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX); // 轉成目標時區對應的時間戳 ZoneId targetZoneId = ZoneId.of(targetTimezone); ZoneOffset targetZoneOffset = targetZoneId.getRules().getOffset(dateTime); Long gmt8Timestamp = endTime.toInstant(targetZoneOffset).toEpochMilli(); System.out.println("The Date of Timezone{"+sourceTimezone+"} is " + date + ",The EndTime of Timezone{"+sourceTimezone+ "} is"+ endTime + ", convert to Timezone{"+targetTimezone+"} timestamp is "+gmt8Timestamp); } ``` 其中一次執行結果: ``` The Date of Timezone{Europe/Moscow} is 2021-01-14,Thd StartTime of Timezone{Europe/Moscow} is,2021-01-14T00:00,convert to Timezone{Asia/Shanghai} timestamp is 1610553600000 The Date of Timezone{Europe/Moscow} is 2021-01-14,The EndTime of Timezone{Europe/Moscow} is2021-01-14T23:59:59.999999999, convert to Timezone{Asia/Shanghai} timestamp is 1610639999999 ``` ### 補充: 當然,其他場景不一定就是拿當天的開始時間和結束時間,有可能僅僅是根據源時區當前時間獲取目標時區對應的時間戳。 這個也是非常簡單,直接看下面程式碼即可: ```java /*** * 獲取源時區的當前時間,轉為目標時區對應的時間戳 * @author winfun * @param sourceTimezone 源時區 * @param targetTimezone 目標時區 * @return {@link Void } **/ public static void getTimeFromSourceTimezoneAndConvertToTargetTimezoneToTargetTimezone(String sourceTimezone,String targetTimezone){ // 獲取指定時區的當前時間 ZoneId sourceZoneId = ZoneId.of(sourceTimezone); LocalDateTime dateTime = LocalDateTime.now(sourceZoneId); /** * 轉成指定時區對應的時間戳 * 1、根據zoneId獲取zoneOffset * 2、利用zoneOffset轉成時間戳 */ ZoneId targetZoneId = ZoneId.of(targetTimezone); ZoneOffset offset = targetZoneId.getRules().getOffset(dateTime); Long timestamp = dateTime.toInstant(offset).toEpochMilli(); System.out.println("The DateTime of Timezone{"+sourceTimezone+"} is " + dateTime + ",convert to Timezone{"+targetTimezone+"} timestamp is "+timestamp); } ``` 其中一次執行結果: ``` The DateTime of Timezone{Europe/Moscow} is 2021-01-14T06:23:05.486,convert to Timezone{Asia/Shanghai} timestamp is 1610576585486 ``` # 四、最後 到此,這次驚險的UNIX時間戳與時區的旅行就到此結束了,希望大家也能從這次分享中得到有用的