最近有一个关于店铺数据实时分析的需求,须要实时统计店铺当天的数据:例如访客数,浏览量、商品排行榜等。因为店铺能够自主选择店铺所在时区(全球二十四个时区),而数仓统计后落库的时间是GMT+8时区对应的UNIX时间戳。所以,在咱们调用中台的接口时,不能直接取服务器的UNIX时间戳做为传参。java
这么一听,若是以前没深刻过UNIX时间戳
与时区
的概念,可能你们都会有点懵逼了;其实我也是,特别是我是昨天晚上十点多才接收到这个信息,心里就更加慌张了,毕竟本来昨天就要提测了,如今由于这个时间戳的缘由而推迟了。下面咱们先来了解UNIX时间戳和时区的概念,而后再继续讨论这个问题。服务器
UNIX时间戳:从1970年1月1日(UTC/GMT的午夜)开始所通过的秒数,不考虑闰秒。
也就是指格林威治时间1970年01月01日00时00分00秒开始到如今的总秒数。.net
对的,你们能够看到,其实UNIX时间戳说的是秒数,而一般咱们讲的是毫秒~unix
时区:为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1—12区,西1—12区。每一个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每一个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。code
例如:中国所在东8区的时间总比莫斯科所在东3区的时间多5个小时。blog
看完上面两个定义,咱们能够得出结论:时间戳是没有时区之分的,仅仅是日期展现和时区有关系;同一个时间戳,在不一样时区,显示的日期是不同的。接口
因此上面的需求,若是直接用服务器所在的时间戳来做为查询时的时间传参,那么通常都是行不通的;除非店铺的时区和咱们服务器的时区是同样的(容器中的时区都是GMT+8,也就是东八区),否则店铺的时间和服务器的时间是有可能不同的。get
假设咱们如今根据北京时间 2021-01-12 03:00:00,获取到对应的时间戳是:1610391600000 (毫秒),
而这个时间戳对应的莫斯科时间为:2021-01-11 22:00:00,这显然和东八区与东三区的时差(五个小时)是对得上的。class
很明显,上面的例子中,同一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); /** * 转成指定时区对应的时间戳 * 一、根据zoneId获取zoneOffset * 二、利用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时间戳与时区的旅行就到此结束了,但愿你们也能从此次分享中获得有用的信息~