Howard Chen's Blog

CODE IS POETRY

Time Zone & Daylight Saving Best Practice

每当别人问起怎么存储时间信息的时候,我都会告诉别人,用统一的UTC时间来存储。结果,我一直都错了。这里整理了一份对于时间存储的列表,希望能对你的时间存储行为带来最佳实践。

一定要这样做:

  • 当需要一个确定时刻,保留一个持续化的并和夏令时无关的统一时间。GMT和UTC都适用于这种情况,但UTC会好一些。GMT是一个时区,而UTC是一个标准。
  • 如果选择存储本地时间的话,请把本地时间和UTC时间的时间偏移也记录下来。注意,时间偏移值在一年中的某些月份可能会变。这样后面使用的时候才能被计算成确切时间。
  • 有时,你也需要考虑把UTC和本地时间都存储下来。常常,我们会考虑把他们分布存储到两个字段里面,但有些平台上,他们可以被存储在一个平台里面,如MSSQL 的datetimeoffset字段(https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql); 而这个不是sql标准字段。
  • 当需要把日期存储为数字类型,使用Unix Time (https://en.wikipedia.org/wiki/Unix_time) 如需要精确的时间,请考虑替换为毫秒级别。这个数字基于UTC,没有做任何timezone调整。Java: new Date().getTime(); JS: new Date().getTime() 或 +new Date都可以。
  • 如果以后需要修改这个时间戳,请顺带记录上原来时区的ID,用以判断时间偏移和原来存储时间比较是否变化了。
  • 如果是为计划未来的定期任务,存储本地时间会比存储UTC更好,因为通常会夏令时的改动。比如冬令时设置你每天的闹钟为早上8点,转换为UTC时间以后,在夏令时期间,你就会上班迟到一小时了:) https://stackoverflow.com/questions/19626177/how-to-store-repeating-dates-keeping-in-mind-daylight-savings-time/19627330#19627330
  • 当你计划一整天的时间,不要转成UTC或者其他任何时区来记录。
    • 数据库里面这种字段尽量不要包含时刻信息。保留到天即可。
    • 如果数据库或存储平台不允许这样做,那么尽量在读取时忽略时刻的意义。如果确实不确定,那么我们尽量采用正午12:00,而不要用午夜0:00来显示这个时刻。
  • 注意时区差并不是永远都是整数。比如Indian Standard Time is UTC+05:30, and Nepal uses UTC+05:45
  • 相比UTC或者GMT,很多商业规则使用Civil time会更多。安排计划时需要把UTC时间戳转换到本地时区。
  • 时区(Timezone)和时间偏移值(Time Offsets)不一样,时区不会变,而时间偏移值在一年中可能会变可能会变。比如洛杉矶的时区是IANA,在夏令时的时候时间偏移值为UTC-7,冬令时的时间偏移值为UTC-8,和它相邻的亚利桑那州不存在夏令时,时间偏移值一直保持UTC-7。
  • 考虑时间类型(确定时间,海外时间,相对时间,历史时间,计划循环时间),你需要采用不同的存储方式保证时间不会缺失。
  • 让你的操作系统,数据库和应用程序保持一致。
  • 服务器上,设置硬件时钟和操作系统时钟为UTC时间,非本地时间。
  • 服务器端代码,包含网站,永远不要指望服务器本地时间能有意义。
  • 根据你的应用,把时间的设置分成若干种类,对于不同的种类设计适当的存储方案。不要做全局设置。
  • 服务器使用网络时间协议
  • 考虑在FAT32/16的设备上使用本地时间。理由比较奇葩。比如在DOS时代,个人电脑都没有网络功能,时间都是使用Bios时间,存储在3.5寸软盘上。那时人们完全不会关心软盘上面存储的时间,而会使用铅笔在盘面上写下处理时间。因此这些时间不会准确。回到现在,很多移动设备,比如相机等还在使用FAT类型格式; 当你在另一个时区时,这些时间不会被改变,如果存储时间戳,就会导致存储时间的混乱。所以存储本地时间会更好。
  • 当设置周期时间时,比如跟美剧,每周一集更新那种,记住时间会因为夏令时,在不同时区会有不同。
  • 使用>=判断下限,用<判断上限。

千万不要这样做:

  • 不要混淆时区(America/New York)和时区偏移(UTC-5:00),他们是两种概念。https://stackoverflow.com/tags/timezone/info
  • 不要在老浏览器,ES5.1及以前版本使用javascript的Date对象,因为他们在使用夏令时计算的时候会有问题。ES6已经修复。
  • 不要使用客户端时间,至少我不能保证你浏览器上时间时正确的。
  • 不要再告诉别人,都用UTC时间了,虽然这种事我也做过。正确的做法是根据场景,选择对用的存储方式。

最后总结

在涉及到时间存储的问题上,一定要谨慎。把场景列出来以后再考虑适当的存储方式。一个简单的例子,我想过滤出今天所有的日志信息。想想,可能会出现几种不同的场景?所以一般通用的解决方案会把local_time, utc_time, local_tz都存下来。现今最长的timezone是“America/Argentina/ComodRivadavia”, 共32个字符。

Proudly powered by Hexo and Theme by Hacker
© 2019 Howard Chen