最近在使用java.time.LocalDate时踩了坑, 归根到底是对jdk中的时间操作不够清晰
踩坑场景
以下这一段代码, 你认为能正常返回结果吗? 如果不能, 那么问题在哪里?
/**
* 基于当前时间, 进行一定的偏移, 返回偏移后的日期
* @param offsetMillis 便宜量 (毫秒)
* @return 日期
*/
public static LocalDate nowLocalDate(long offsetMillis) {
long time = System.currentTimeMillis();
time += offsetMillis;
long days = time / TimeUnit.DAYS.toMillis(1);
return LocalDate.ofEpochDay(days);
}
问题描述
这一段代码, 假设参数
offsetMillis为0的情况下, 在00:00~08:00这段时间返回的结果都是错误的, 是前一日的日期
原理剖析
System.currentTimeMillis() 获取到的时间戳是在UTC时区下当前时间与1970年1月1日0点的毫秒差, 北京时间所处的时区为CST时区相比UTC时区早8小时 (北京时间 2022年1月27日凌晨1点时, UTC时区下为2022年1月26日下午17点, 所以获取到当前时间戳后, 必须要先+8h转换为CTS的时间戳, 再进行天数计算
事实上, 时间戳是没有时区概念的, 它指的就是从
历史上的某一时刻至今所经历的时光, 这是全球统一的, 你在美国经历了2小时, 他在中国也是经历了2小时, 不会因为所处时区不同而不同
历史上的某一时刻是: 1970-01-01 00:00:00 (UTC)
由于历史时刻是以UTC时区为参照的, 所以加上时间戳以后得到的当前时间也必然是UTC时区的时间
既然时间戳是基于UTC时区的, 那么为什么我们在东八区开发过程中经常使用它, 却没有产生问题?
我们在代码中使用 System.
currentTimeMillis()有几大场景
- 统计耗时: 比如在一段逻辑前后获取时间戳, 并将两者差作为耗时
- 记录时刻: 很多时候我们不需要结构化的时间, 仅仅需要记录一个时刻, 用作后续的时刻对比或结构化
- 构造Date: 有时我们不得不通过一个给定的时间戳, 将它结构化为Date对象以便后续操作
细品上面这些场景, 确实大部分场景是跟时区无关的, 所以并不会有任何影响, 而第三个场景中, 构造java.util.Date 对象时, Date的构造函数所定义的时间戳参数是基于GMT时区
而GMT是格林尼治的标准时间, 巧了, 格林尼治正好处在零时区, 所以GMT = UTC+0h
也不知道是巧合, 还是有意为之, 哈哈
总结
写了这么久的
System.currentTimeMillis(), 一直以为它返回的时间戳是基于当前系统时区(CTS), 没想到它是基于UTC的, 一度陷入沉思, 有被自己菜到, 哈哈...
需要重新系统性的学习一下Java的时间模块了!