原文連結:坐上JDK8時間SDK的小船,帶你遨遊UNIX時間戳與時區的小太空~
一、背景:
最近有一個關於店鋪資料實時分析的需求,需要實時統計店鋪當天的資料:例如訪客數,瀏覽量、商品排行榜等。由於店鋪可以自主選擇店鋪所在時區(全球二十四個時區),而數倉統計後落庫的時間是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時間戳兩個時區的時間區別,可以看下面程式碼:
/***
* 對比同一時間戳,不同時區的時間顯示
* @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時區的時間戳,但是為了使得方法更加通用,引數分別為源時區和目標時區,可以相容更多的使用場景。
/***
* 獲取源時區的當前日期的零點零分,轉為目標時區對應的時間戳
* @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
補充:
當然,其他場景不一定就是拿當天的開始時間和結束時間,有可能僅僅是根據源時區當前時間獲取目標時區對應的時間戳。
這個也是非常簡單,直接看下面程式碼即可:
/***
* 獲取源時區的當前時間,轉為目標時區對應的時間戳
* @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時間戳與時區的旅行就到此結束了,希望大家也能從這次分享中得到有用的資訊~