Java 时间戳

转载自:https://lotabout.me/2019/Timestamp-revealed/

叙述

海上生明月,天涯共此时。在计算机的世界里,怎么才能“共此时”呢?

分析

时间如何存储

存储的首先需要有唯一性。

你朋友说要在 22:00 给你打电话,结果 21:00 电话就来了,心里咒骂了一阵,才想起来朋友在日本,日本 22:00 时北京正好是 21:00。虽然平时可能不太注意得到,但是如果想让时间唯一,是需要加上时区的。用 时间+时区 来存储时间似乎是一个好选择。

存储的数据最好能方便比较。

你可能很难一眼看出 10:00 CST 和 11:00 IOT 哪个时间更早。但如果统一换算成协调世界时(UTC)或是其它什么时区,就很容易比较了。也就是说存储的基准最好一致。

再着嘛,最好节省空间。

直接的想法是记录年月日时,但是一个标准的时间字符串 (如 2019-10-15 20:48:19.128) 就占用了 23 个字节,比较浪费。所以计算机中也使用一种称为 epoch time 的存储方式,存储的是当前时间(转换为 UTC) 距离 Unix epoch (1970-01-01 00:00:00) 的毫秒数,例如上例可表示为 1571172499000。这样要表示日常生活中的时间,通常只需要 4 个字节(32位) 或是 8 个字节(64位) 即可。当然,存储节省了,能表示的时间范围也小了,例如 32 位的 epoch time 最多只能表示到 2038-01-19

下面是列举了一些系统的时间表示方式:

  • MySQL 中的 TIMESTAMP 类型以 YYYY-MM-DD hh:mm:ss 表示当前时间对应的UTC时间[1], 占 19 个字节。
    • 5.6.4 之后的版本可通过 TIMESTAMP(n) 指定保留 n 位毫秒数[2]
  • Java 中的 Date 类型内部以 long 型(64位)存储当前时间(UTC)距 epoch time 的毫秒数。
  • 大数据格式 Parquet 以 int96 的类型存储当前时间(UTC)距 epoch time 的纳秒数。

当然后面我们会看到,为了更准确处理各种情形,也会直接用 年月日时分秒+时区 的方式存储。

时间如何解析

假设我们以 epoch time 作为存储格式,现在拿到 2019-10-15 20:48:19 这样一个时间,要如何转换成相应的 epoch time 呢?注意,这个时间字符串是不带时区的!

原始时区信息缺失是时间处理不一致的重要根源之一,不同的系统/工具应对的方式不同。

例如 java.sql.Timestamp.valueOf 会认为解析的字符串就是 UTC 时间。Java 创建 Timestamp类型的初衷是对标 MySQL 的 TIMESTAMP 类型,两者在解析时都认为输入是 UTC 时间也就不足为奇了。

String timeStr = "2019-10-15 10:10:10.001";
Timestamp timestamp = java.sql.Timestamp.valueOf(timeStr);
System.out.println(timestamp);
System.out.println(timestamp.getTime());

// 2019-10-15 10:10:10.001 # 北京时间下运行
// 1571105410001 # (2019-10-15 10:10:10.001 in GMT)

// 2019-10-15 10:10:10.001 # 东京时间下运行
// 1571101810001 # (2019-10-15 10:10:10.001 in GMT)

上例中将系统调成北京时间(CST)还是东京时间(JST),输出的内容不变。

而 java.util.Date 以及对应的 java.text.DateFormat 都允许指定时区,默认选取系统的时区进行解析。下面以 SimpleDateFormat 为例 [3]

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String timeStr = "2019-10-15 10:10:10.001";
java.util.Date date = format.parse(timeStr);
System.out.println(date);
System.out.println(date.getTime());

// Tue Oct 15 10:10:10 CST 2019 # 北京时间下运行
// 1571105410001 # (2019-10-15 2:10:10.001 in GMT)

// Tue Oct 15 10:10:10 JST 2019 # 东京时间下运行
// 1571101810001 # (2019-10-15 1:10:10.001 in GMT)

由于使用了系统当前所在的时区,上面的代码在北京时间(CST)和东京时间(JST)下执行,得到的毫秒数是不同的。

时间如何展示

展示的终极问题:要以当前的时区展示?还是以原时区展示?这是与业务相关的。

  • 如果要判断一笔交易在几点进行,则可能按发生地时区展示/计算更合理(例如认为凌晨发生的交易是欺诈的可能性高,则境外的交易就需要按境外的时区算几点)
  • 如果要展示一篇博客何时发布,以读者所在的时区展示可能更理想

正因为这个决定跟业务相关,系统的实现者只能为两种需求都提供对应的机制。显然以 epoch time 存储是不行的,因为它不带原始时区。MySQL 的存储也同样不行,虽然以字符串存储,但依旧不包含原始时区[4]。这里我们简单记录 Java 提供的处理机制。

Java 8 的 java.time 包中提供了许多时间处理的类,让我们按需自取。如 LocalDateLocalTime 和 LocalDateTime 内部以年月日、时分秒的形式保存了日期和时间,不包含任何时区的信息。而 ZonedDateTime 则是 DateTime 加上时区,用于处理与时区相关的所有操作,包括时区间的时间转换。

Java 8 中的 ZonedDateTime 人如其名,内部提供了额外的字段保留时区:

String timeStr = "2019-10-15T10:10:10.001+02:00[Europe/Paris]";
ZonedDateTime datetime = ZonedDateTime.parse(timeStr);
System.out.println(datetime);
System.out.println(datetime.toInstant().getEpochSecond());

// 2019-10-15T10:10:10.001+02:00[Europe/Paris] # 北京时间下运行
// 1571127010 # 2019-10-15 08:10:10 GMT

可以看到,尽管在北京时间下运行,输出里仍然保留了原始输入的时区:巴黎时间。

而如果希望将巴黎时间展示为当前的时区,则可以如下操作:

String timeStr = "2019-10-15T10:10:10.001+02:00[Europe/Paris]";
ZonedDateTime parisTime = ZonedDateTime.parse(timeStr);
ZonedDateTime shanghaiTime = parisTime.withZoneSameInstant(ZoneId.systemDefault());
System.out.println(shanghaiTime);
System.out.println(parisTime.toInstant().getEpochSecond());

// 2019-10-15T16:10:10.001+08:00[Asia/Shanghai]
// 1571127010

可以看到,当前时区是上海,巴黎 10:10 时,上海是 16:10

小结

时间处理,尤其是在不同系统中传递时间信息,一般会涉及三个问题:

  1. 数据解析,时间数据如何解析成内部格式?如何补全时区信息?
  2. 数据存储,存储带不带原始时间的时区?
  3. 数据展示,要展示原始时区?当前时区?还是其它时区?

考虑一下,Java 中的 LocalDateTime 是不带时区的,但是如果将对应数据存入 MySQL,则需要转换成 epoch time,那么如何补全时区信息呢?

展开阅读全文

Python数据分析与挖掘

01-08
92讲视频课+16大项目实战+源码+¥800元课程礼包+讲师社群1V1答疑+社群闭门分享会=99元   为什么学习数据分析?       人工智能、大数据时代有什么技能是可以运用在各种行业的?数据分析就是。       从海量数据中获得别人看不见的信息,创业者可以通过数据分析来优化产品,营销人员可以通过数据分析改进营销策略,产品经理可以通过数据分析洞察用户习惯,金融从业者可以通过数据分析规避投资风险,程序员可以通过数据分析进一步挖掘出数据价值,它和编程一样,本质上也是一个工具,通过数据来对现实事物进行分析和识别的能力。不管你从事什么行业,掌握了数据分析能力,往往在其岗位上更有竞争力。    本课程共包含五大模块: 一、先导篇: 通过分析数据分析师的一天,让学员了解全面了解成为一个数据分析师的所有必修功法,对数据分析师不在迷惑。   二、基础篇: 围绕Python基础语法介绍、数据预处理、数据可视化以及数据分析与挖掘......这些核心技能模块展开,帮助你快速而全面的掌握和了解成为一个数据分析师的所有必修功法。   三、数据采集篇: 通过网络爬虫实战解决数据分析的必经之路:数据从何来的问题,讲解常见的爬虫套路并利用三大实战帮助学员扎实数据采集能力,避免没有数据可分析的尴尬。   四、分析工具篇: 讲解数据分析避不开的科学计算库Numpy、数据分析工具Pandas及常见可视化工具Matplotlib。   五、算法篇: 算法是数据分析的精华,课程精选10大算法,包括分类、聚类、预测3大类型,每个算法都从原理和案例两个角度学习,让你不仅能用起来,了解原理,还能知道为什么这么做。
©️2020 CSDN 皮肤主题: 终极编程指南 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值