坐上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时间戳与时区的旅行就到此结束了,希望大家也能从这次分享中得到有用的信息~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容