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

坐上JDK8时间SDK的小船,带你遨游UNIX时间戳与时区的小太空~的更多相关文章

  1. getdate — 取得日期/时间信息-----参数是一个 integer 的 Unix 时间戳

      <?php$today = getdate();print_r($today);?> Array ( [seconds] => 40 [minutes] => 58 [ho ...

  2. Unix时间戳转日期时间格式,C#、Java、Python各语言实现!

    之前有个Q上好友没事问我,怎么自己写Unix时间戳转日期时间?于是我就顺手写了个C#版本给他!最近想起来,就萌发多写几个语言的版本分享,权当练习思路外加熟悉另外两种语言. 先说转换步骤 先处理年份,从 ...

  3. 时间:UTC时间、GMT时间、本地时间、Unix时间戳

    转自:http://blog.csdn.net/u012102306/article/details/51538574 1.UTC时间 与 GMT时间 我们可以认为格林威治时间就是时间协调时间(GMT ...

  4. 关于时间:UTC时间、GMT时间、本地时间、Unix时间戳

    1.UTC时间 与 GMT时间我们可以认为格林威治时间就是时间协调时间(GMT=UTC),格林威治时间和UTC时间均用秒数来计算的. 2.UTC时间 与 本地时UTC + 时区差 = 本地时间时区差东 ...

  5. 【时间】Unix时间戳

    UNIX时间戳:Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp) 是从1970年1月1日(UTC/GMT的午夜)开始所经过的 ...

  6. Mac上安装Android SDK

    今天开始学习IOS,所以先买了个设备先,但是开始使用了苹果本,还是需要继续开发Android,因为那是我现在吃饭的东西,所以就需要在Mac上配置Android SDK,原以为安装SDK很简单,和Win ...

  7. python第三方库推荐 - 通过ntplib在windows上同步时间

    很多时候我们有通过程序脚本同步校正北京时间的需求. 在linux上同步时间比较方便,安装个ntpdate软件就行了. 但是在windows的要同步时间比较麻烦. 这时想到的就是从网络获取一个准确的时间 ...

  8. JavaScript总结-网页上显示时间

    网页上显示时间 代码如下: <TABLE> <TR> <TD><span id="localtime"></span> ...

  9. 在项目管理中如何保持专注,分享一个轻量的时间管理工具【Flow Mac版 - 追踪你在Mac上的时间消耗】

    在项目管理和团队作业中,经常面临的问题就是时间管理和优先级管理发生问题,项目被delay,团队工作延后,无法达到预期目标. 这个仿佛是每个人都会遇到的问题,特别是现在这么多的内容软件来分散我们的注意力 ...

随机推荐

  1. bootstrap table 控制checkbox在某些状态不显示

    首先columns:[{field:'column',checkbox:true}];然后设置$("#tableName").bootstrapTable('hideColumn' ...

  2. .pfx和.Cer 证书

    通常情况下,作为文件形式存在的证书一般有三种格式: 第一种:带有私钥的证书,由Public Key Cryptography Standards #12,PKCS#12标准定义,包含了公钥和私钥的二进 ...

  3. EF CodeFirst多个数据摸型映射到一张表与各一张表

    1. 多个实体映射到一张表 Code First允许将多个实体映射到同一张表上,实体必须遵循如下规则: 实体必须是一对一关系 实体必须共享一个公共键 我们通常有这样的需求,如:同一基类派生出的不同数据 ...

  4. 【题解】NOI 系列题解总集

    每次做一道 NOI 系列的估计都很激动吧,对于我这种萌新来说( P1731 [NOI1999]生日蛋糕 练习剪枝技巧,关于剪枝,欢迎看我的垃圾无意义笔记 这道题是有一定难度的,需要运用各种高科技剪枝( ...

  5. CentOS 7.6安装MariaDB10.4.8超详细教程

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品. Cent ...

  6. js中数组、字符串、日期、数学API方法一览

    以下内容摘选自 http://www.w3school.com.cn/jsref/jsref_obj_array.asp 点击方法新窗口打开详解 数组: 方法 描述 concat() 连接两个或更多的 ...

  7. 珍藏的C语言编程系列教程

    本文有332个文字,大小约为2KB,预计阅读时间1分钟 这是本人珍藏的C语言.C++系列教程. 相信每个Coder的第一门编程语言就是C语言吧, 现在也依然很热门,不谈了.直接上链接,感兴趣的直接存, ...

  8. 在Nuxt中使用react-id-swiper封装公共的轮播图组件(移动端

    首先就是引入swiper import Swiper from 'react-id-swiper': 一个轮播图首先要考虑到一种情况就是当只有一张图的时候是不是需要按轮播图来处理 一般情况下,一张图是 ...

  9. 图的遍历BFS

    图的遍历BFS 广度优先遍历 深度优先遍历 可以进行标记 树的广度优先遍历,我们用了辅助的队列 bool visited[MAX_VERTEX_NUM] //访问标记数组 //广度优先遍历 void ...

  10. 一个最简单的typescript工程

    初级: 1.新建一个文件夹~/a/ 2.cd ~/a/ 3.npm init -y          生成package.json 4.新建index.ts,内容:console.log(" ...