时区的坑,不想再踩了

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了时区的坑,不想再踩了相关的知识,希望对你有一定的参考价值。

参考技术A 最近在使用date命令时,发现表示东8区(中国时区)要使用 GMT-8 ,但在Java中却需要使用 GMT+8 ,如下:

而在Java中,如下:

这就让人有点迷糊了,经过一段时间搜索,发现在时区表达形式上还有不少知识点呢!

众所周知,为了方便各地区本地时间之间的转换,人们将全球划分为了24个时区,以格林尼治天文台(GMT)为零时区,往东西两个方向分别有12个时区,所以自然有了以GMT为前缀的时区表示法,如下:

GMT+8 表示东8区,中国就是使用这个时区,而 GMT-8 表示西8区,如果格林尼治天文台的本地时间是2022-03-19的0点,那么 GMT+8 地区的本地时间就是2022-03-19的8点,而 GMT-8 的本地时间就是往前8小时,即2022-03-18的16点。

注意,上面的各地区本地时间的表述虽然不同,但它们实际是同一个时刻(绝对时间),要理解本地时间与绝对时间的区别。

GMT+8 正是Java中支持的时区表示法,那为啥Linux中却是 GMT-8 呢?实际上Linux中的 GMT-8 也可以写成 Etc/GMT-8 ,这才是它的标准名称,如下:

可以发现用 Etc/GMT-8 的话,Linux与Java的输出都是一样的了,是的, Etc/GMT-8 也是一种类似 GMT+8 的时区表示机制,只不过它的 +- 号是反的。

Ok,虽然上面的差异弄清楚了,但时区的表示形式还没有介绍完,接着往下看...

除了 GMT+8 表示方式外,我们还经常会看到 UTC+8 这样的表示方式,这是UTC时区表示法。

即生GMT何生UTC?这是由于GMT是以格林尼治天文台为时间基准,但地球不是完美球体且自转速度在变慢,所以地球自转速度并不均匀,这导致以格林尼治天文台为时间基准是不准的。

为了更准确度量时间,科学家们发明了UTC时间,以铯原子跃迁次数来度量时间,比GMT时间更准确,为了保证GMT的准确性,每隔几年GMT时间会做一次调整,以与UTC时间对齐。

因此,既然有了更准确的UTC,那么就有了以UTC为前缀的时区表示法,如中国时区可使用 UTC+8 。

各时区偏移量表示法一览表,如下:

除了用偏移量来表示时区,为了方便,人们还按区域/城市的方式来定义时区,如 Asia/Shanghai , Asia/Hong_Kong 都表示东8区,具体有哪些城市命名的时区,可以在时区数据库中查看。

另外,为了简化区域时区表示法,又定义了一套时区缩写,如CST是中国时区 China Standard Time 的缩写,可以在时区缩写中查看各种缩写定义。

注意,一般都不建议使用时区缩写,因为时区缩写的命名经常会重复,比如CST是 Central Standard Time (北美中部标准时间UTC -6)、 China Standard Time (中国标准时间UTC +8)、 Cuba Standard Time (古巴标准时间UTC -5)。

由于不同软件对CST的解释可能不同,导致会出现时间相差13或14个小时的情况,这在Java搭配mysql时经常出现,我还专门写了一篇文章mysql的timestamp会存在时区问题?,对于一定要使用时区缩写的场景,可以使用香港时区缩写 HKT ,它不重复且和上海处于同一个时区。

在Java中和时区相关的类有TimeZone、ZoneId,其中TimeZone是老的时区类,而ZoneId是新的时区类,它有ZoneOffset和ZoneRegion两个子类,分别代表偏移量表示法和区域表示法。

那它们都支持上述的哪些时区写法呢?写个Demo验证一下,如下:

输出如下:

虽然偏移量表示法与区域表示法都可以表示时区,但由于夏令时的存在,它们并不完全等同。

夏令时(Daylight Saving Time: DST),也叫 夏时制,是指为了节约能源,在天亮的早的夏季,人为将时间调快一小时,以充分利用光照资源,节约照明用电。

而中国在 1986 年至 1991 年也实行过夏令时,在1986~1991的每年从四月中旬第一个星期日的凌晨2时整(北京时间),将时钟拨快一小时,即将表针由2时拨至3时,夏令时开始;到九月中旬第一个星期日的凌晨2时整(北京夏令时),再将时钟拨回一小时,即将表针由2时拨至1时,夏令时结束。从1986年到1991年的六个年度,除1986年因是实行夏时制的第一年,从5月4日开始到9月14日结束外,其它年份均按规定的时段施行。

故会有下面看起来有点奇怪的现象:

为什么 Asia/Shanghai 输出为3点,而 GMT+8 输出为2点呢?原因是 1986-05-04 02:00:00 这个时间点中国正开始实行夏令时,时钟拨快了1小时。

而 GMT+8 为什么输出为2点呢?因为中国、马来西亚、菲律宾、新加坡的时区都是 GMT+8 ,只有中国在实行夏令时,而在 GMT+8 中没法感知到区域信息,那java只能以没有实行夏令时的方法来计算本地时间了。

正是由于夏令时的存在,导致程序可能出现诡异的现象甚至bug,如下:

输出如下:

为啥会这样呢?原因是本地时间虽然看起来没变,但 Asia/Shanghai 这个代表的时区却发生了变化。

我们可以将上面 printZonedDateTime 中时间格式由 yyyy-MM-dd HH:mm:ss VV 修改为 yyyy-MM-dd HH:mm:ss VV xxx 再执行,发现输出如下:

如上,夏令时导致 Asia/Shanghai 这个时区不一定是东8区了,也可能是东9区,故Java中,想将ZoneRegion转换为ZoneOffset,需要传递一个instant时刻参数,如下:

夏令时真是一种自欺欺人的做法,还好中国从1991年后就没再实行了!

Java 的这 100 个坑,我发誓不会再踩了

我常常在想,一个人具备怎样的素质和能力,才称得上高级工程师?估计有不少人会说,“基础过硬、熟练掌握一门编程语言、至少看过一个优秀开源项目的源代码、有过高并发工作经验、沟通能力强”。

上面这些都很对,这些也都是面试中经常考察的点。但从真实工作场景看,我觉得还缺少一环:业务开发能力。可能你会说,业务开发不就是 CRUD 吗,有啥难的?

的确,我们大部分人的日常工作就是做业务,不可能没事就去看源码、学数据结构与算法,这不现实。我们的能力,都是在业务开发中打磨出来的。

举个例子:一个产品,每天会有上千份订单状态或流程出现问题,要修复这些问题,就得花大量时间核对数据、确认订单状态,甚至没时间开发新需求。最后,项目负责人开启明细日志彻查问题,才发现是自调用方法导致事务没生效造成的坑。

在金融项目计算利息的代码中,因为用了 float 类型而不是 BigDecimal 类来保存和计算金额,结果给用户结算的每一笔利息,都多了几分钱。试想下,结算的上千个用户中,每个用户都有上千笔订单,如果等月终对账时才发现,可能已经损失了几百万。

再比如,用 RabbitMQ 做异步处理,处理失败的消息会不断地进入 MQ。问题爆发前,可能只影响了消息处理的时效性。待到 MQ 彻底瘫痪,面对堆积在一起、混杂了死信和正常消息的几百万条数据,除了清空 MQ 并用大量时间补正常的业务数据外,还能怎么办呢?

诸如以上种种,由一个小坑引发的重大事故不在少数。不仅会给公司造成损失,还会影响自己的职业发展。

其实,并非是我们不想解决问题,只是不知道问题到底出在哪里。要找到这些“定时炸弹”,第一步就要定位它们在哪儿、为什么会出现。

????朱晔整理的 Java 坑点脑图

说到这,我为你介绍一位高手,贝壳金服的资深架构师——朱晔。

他得过 3 次微软 MVP,做过 2 次创业公司 CTO,曾任职育碧软件、英孚教育、空中网、饿了么等公司。工作 15 年,始终没脱离编码工作,参与过在线教育、电商、游戏、O2O、互金、物流等领域 400+ 应用的架构设计和开发工作,熟悉微服务架构,擅长高并发、高可用架构。

工作中,他经常作为救火队员在一线分析和复盘问题,处理过大量生产事件和事故,积累了丰富的问题排查经验。

所以,他从自己接触过的 200+ 真实生产事件和事故中,整理提炼了 100 个 Demo,涉及 130 个坑点和 50 个最佳实践,都在他的专栏《Java 业务开发常见错误 100 例》中了,实不相瞒,这些坑我自己都踩过不少。

新人首单 ¥59.9  ,仅限「前 50 人」

原价 ¥129,相当于半价

在专栏中,每节课朱晔都会讲解 Java 业务开发的一个核心点,并对应 2-5 个案例,结合 Java 代码演示,按“知识介绍→还原业务场景→错误实现→正确实现→原理分析→总结”讲解,带你真正掌握规避高频坑点的方法,提升解决问题的能力。

代码篇:重点讲解开发层面的 20 个坑点,涉及多线程、数据访问、池技术、日志异常、日期时间、IO 和序列化、Spring 框架等。不仅会讨论代码的错误写法,还会纠正你在基础知识理解上的不足和误区。

 

设计篇:和你讨论项目技术设计时的坑点或误区,涉及接口、缓存、异步、生产就绪、代码重复、数据存储等六个方面的设计。通过场景化的案例,针对可能实际遇到的问题给出设计思路和最佳实践。

 

安全篇:为你介绍后端开发最容易忽略,但从开发层面来说,防控效果最好的 4 个安全问题,涉及客户端数据可信度、数据和代码的隔离、资源防刷兜底、敏感数据的处理等问题。

你还可以把专栏当作代码审核的 Checklist,上线时做到有备无患。

用正确的方法学习,才能事半功倍,当你遇到专栏内容之外的坑时,也能有清晰的解决思路。不吹不黑,我还给你们找了些读者的评价,供你参考:

申请了粉丝优惠

新人首单 ¥59.9  ,仅限「前 50 人」

原价 ¥129,相当于半价

通过上面的海报购买,还可以返现 ¥20。领取方式:添加微信:hiddenpps,记得备注你的极客昵称。  

温馨提示

订阅后可通过「极客时间 App」或「极客时间小程序」我的-已购,学习已订阅的专栏。

????点击「阅读原文」,扫码免费试读。

新人首单 ¥59.9  ,相当于半价哦

以上是关于时区的坑,不想再踩了的主要内容,如果未能解决你的问题,请参考以下文章

Java 的这 100 个坑,我发誓不会再踩了

Java 的这 100 个坑,我发誓不会再踩了

为 DateFormatter 设置全局时区

JavaScript 检查时区名称是不是有效

在 Rails 和 PostgreSQL 中完全忽略时区

如何在特定时区的php中查找星期几