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

不送花的程式猿發表於2021-01-14

原文連結:坐上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時間戳與時區的旅行就到此結束了,希望大家也能從這次分享中得到有用的資訊~

相關文章