坐上JDK8時間SDK的小船,帶你遨游UNIX時間戳與時區(qū)的小太空~·

一、背景:

最近有一個關(guān)于店鋪數(shù)據(jù)實時分析的需求,需要實時統(tǒng)計店鋪當(dāng)天的數(shù)據(jù):例如訪客數(shù),瀏覽量、商品排行榜等。由于店鋪可以自主選擇店鋪所在時區(qū)(全球二十四個時區(qū)),而數(shù)倉統(tǒng)計后落庫的時間是GMT+8時區(qū)對應(yīng)的UNIX時間戳。因此,在我們調(diào)用中臺的接口時,不能直接取服務(wù)器的UNIX時間戳作為傳參。

這么一聽,如果之前沒深入過 UNIX時間戳 與 時區(qū) 的概念,可能大家都會有點懵逼了;其實我也是,特別是我是昨天晚上十點多才接收到這個信息,內(nèi)心就更加慌張了,畢竟原本昨天就要提測了,現(xiàn)在因為這個時間戳的原因而推遲了。下面我們先來了解UNIX時間戳和時區(qū)的概念,然后再繼續(xù)討論這個問題。

二、概念:

UNIX時間戳:從1970年1月1日(UTC/GMT的午夜)開始所經(jīng)過的秒數(shù),不考慮閏秒。

也就是指格林威治時間1970年01月01日00時00分00秒開始到現(xiàn)在的總秒數(shù)。

對的,大家可以看到,其實UNIX時間戳說的是秒數(shù),而通常我們講的是毫秒~

時區(qū):為了克服時間上的混亂,1884年在華盛頓召開的一次國際經(jīng)度會議(又稱國際子午線會議)上,規(guī)定將全球劃分為24個時區(qū)(東、西各12個時區(qū))。規(guī)定英國(格林尼治天文臺舊址)為中時區(qū)(零時區(qū))、東1—12區(qū),西1—12區(qū)。每個時區(qū)橫跨經(jīng)度15度,時間正好是1小時。最后的東、西第12區(qū)各跨經(jīng)度7.5度,以東、西經(jīng)180度為界。每個時區(qū)的中央經(jīng)線上的時間就是這個時區(qū)內(nèi)統(tǒng)一采用的時間,稱為區(qū)時,相鄰兩個時區(qū)的時間相差1小時。

例如:中國所在東8區(qū)的時間總比莫斯科所在東3區(qū)的時間多5個小時。

看完上面兩個定義,我們可以得出結(jié)論:時間戳是沒有時區(qū)之分的,僅僅是日期展示和時區(qū)有關(guān)系;同一個時間戳,在不同時區(qū),顯示的日期是不一樣的。

所以上面的需求,如果直接用服務(wù)器所在的時間戳來作為查詢時的時間傳參,那么一般都是行不通的;除非店鋪的時區(qū)和我們服務(wù)器的時區(qū)是一樣的(容器中的時區(qū)都是GMT+8,也就是東八區(qū)),不然店鋪的時間和服務(wù)器的時間是有可能不一樣的。

例子:

假設(shè)我們現(xiàn)在根據(jù)北京時間 2021-01-12 03:00:00,獲取到對應(yīng)的時間戳是:1610391600000 (毫秒),

而這個時間戳對應(yīng)的莫斯科時間為:2021-01-11 22:00:00,這顯然和東八區(qū)與東三區(qū)的時差(五個小時)是對得上的。

很明顯,上面的例子中,同一UNIX時間戳,不同時區(qū)的時間時不一樣的,甚至存在兩時區(qū)不在同一日;那至于上面的問題也就隨之被解答了,查詢店鋪的實時數(shù)據(jù),那必須要拿到店鋪所在時區(qū)的當(dāng)前時間了。

關(guān)于展示同一UNIX時間戳兩個時區(qū)的時間區(qū)別,可以看下面代碼:

```

/***

* 對比同一時間戳,不同時區(qū)的時間顯示

* @author winfun

* @param sourceTimezone sourceTimezone

* @param targetTimezone targetTimezone

* @return {@link Void }

**/

public static void compareTimeByTimezone(String sourceTimezone,String targetTimezone){

? ? // 當(dāng)前時間服務(wù)器UNIX時間戳

? ? Long timestamp = System.currentTimeMillis();

? ? // 獲取源時區(qū)時間

? ? Instant instant = Instant.ofEpochMilli(timestamp);

? ? ZoneId sourceZoneId = ZoneId.of(sourceTimezone);

? ? LocalDateTime sourceDateTime = LocalDateTime.ofInstant(instant,sourceZoneId);

? ? // 獲取目標(biāo)時區(qū)時間

? ? 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);

}

```

其中一次的執(zhí)行結(jié)果:

```

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

```

到此,我們應(yīng)該可以將時間戳和時區(qū)很好地區(qū)分出來了。

三、需求分析:

上面已經(jīng)很好地分析了UNIX時間戳與時區(qū)了,接下來繼續(xù)我們的需求分析~

如果只是拿店鋪所在時區(qū)的當(dāng)前時間,其實非常簡單,我們可以利用 LocalDateTime#now(ZoneId zone) 即可。

可是我上面的需求,到這一步還沒夠,因為數(shù)倉保存的是GMT+8時區(qū)對應(yīng)的UNIX時間戳;所以當(dāng)我們拿到店鋪所在時區(qū)的時間后,還需要轉(zhuǎn)為GMT+8時區(qū)對應(yīng)的UNIX時間戳。

由于我們是直接查詢當(dāng)天,我們可以簡化為獲取店鋪當(dāng)前的零點零分和23點59分,然后轉(zhuǎn)為GMT+8時區(qū)對應(yīng)的時間戳;最后,利用 JDK8 中的 LocalDateTime 可以非常簡單的完成。

雖然我們最后需要的是GMT+8時區(qū)的時間戳,但是為了使得方法更加通用,參數(shù)分別為源時區(qū)和目標(biāo)時區(qū),可以兼容更多的使用場景。

```

/***

* 獲取源時區(qū)的當(dāng)前日期的零點零分,轉(zhuǎn)為目標(biāo)時區(qū)對應(yīng)的時間戳

* @author winfun

* @param sourceTimezone 源時區(qū)

* @param targetTimezone 目標(biāo)時區(qū)

* @return {@link Void }

**/

public static void getStartTimeFromSourceTimezoneAndConvertTimestampToTargetTimezone(String sourceTimezone,String targetTimezone){

? ? // 獲取指定時區(qū)的當(dāng)前時間

? ? ZoneId sourceZoneId = ZoneId.of(sourceTimezone);

? ? LocalDateTime dateTime = LocalDateTime.now(sourceZoneId);

? ? LocalDate date = LocalDate.now(sourceZoneId);

? ? // 獲取上面時間的當(dāng)天0點0分

? ? LocalDateTime startTime = LocalDateTime.of(date, LocalTime.MIN);

? ? // 轉(zhuǎn)成目標(biāo)時區(qū)對應(yīng)的時間戳

? ? 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);

}

/***

* 獲取源時區(qū)的當(dāng)前日期的23點59分,轉(zhuǎn)為目標(biāo)時區(qū)對應(yīng)的時間戳

* @author winfun

* @param sourceTimezone 源時區(qū)

* @param targetTimezone 目標(biāo)時區(qū)

* @return {@link Void }

**/

public static void getEndTimeFromSourceTimezoneAndConvertTimestampToTargetTimezone(String sourceTimezone,String targetTimezone){

? ? // 獲取指定時區(qū)的當(dāng)前時間

? ? ZoneId sourceZoneId = ZoneId.of(sourceTimezone);

? ? LocalDateTime dateTime = LocalDateTime.now(sourceZoneId);

? ? LocalDate date = LocalDate.now(sourceZoneId);

? ? // 獲取上面時間的當(dāng)天23點59分

? ? LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);

? ? // 轉(zhuǎn)成目標(biāo)時區(qū)對應(yīng)的時間戳

? ? 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);

}

```

其中一次執(zhí)行結(jié)果:

```

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

```

補充:

當(dāng)然,其他場景不一定就是拿當(dāng)天的開始時間和結(jié)束時間,有可能僅僅是根據(jù)源時區(qū)當(dāng)前時間獲取目標(biāo)時區(qū)對應(yīng)的時間戳。

這個也是非常簡單,直接看下面代碼即可:

```

/***

* 獲取源時區(qū)的當(dāng)前時間,轉(zhuǎn)為目標(biāo)時區(qū)對應(yīng)的時間戳

* @author winfun

* @param sourceTimezone 源時區(qū)

* @param targetTimezone 目標(biāo)時區(qū)

* @return {@link Void }

**/

public static void getTimeFromSourceTimezoneAndConvertToTargetTimezoneToTargetTimezone(String sourceTimezone,String targetTimezone){

? ? // 獲取指定時區(qū)的當(dāng)前時間

? ? ZoneId sourceZoneId = ZoneId.of(sourceTimezone);

? ? LocalDateTime dateTime = LocalDateTime.now(sourceZoneId);

? ? /**

? ? * 轉(zhuǎn)成指定時區(qū)對應(yīng)的時間戳

? ? * 1、根據(jù)zoneId獲取zoneOffset

? ? * 2、利用zoneOffset轉(zhuǎn)成時間戳

? ? */

? ? 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);

}

```

其中一次執(zhí)行結(jié)果:

```

The DateTime of Timezone{Europe/Moscow} is 2021-01-14T06:23:05.486,convert to Timezone{Asia/Shanghai} timestamp is 1610576585486

```

四、最后

到此,這次驚險的UNIX時間戳與時區(qū)的旅行就到此結(jié)束了,希望大家也能從這次分享中得到有用的信息~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容