时间(date time)应该是一个精确的点,以便能够进行比较。但是如何能让他成为一个精确的点呢?一般的,如果需要确认一个精确的点就需要指定一个参照点,而后加上一个偏移量以便说明时间的具体所在。最简单的情况就是一个固定的参照点加上一个固定的偏移量这种表示方式了。
为什么这回没有太长不读版,因为这回本来就是要讲故事的,没有中间精彩的过程怎么衬托结局的意义。
定义参照点和偏移量
参照点的定义包括两个部分,第一个部分,如何度量;第二个部分,在哪儿度量。而偏移量关心也有两点,第一,怎么偏;第二,这个偏移量能管多大范围。
GMT 和时区
为了寻找固定的参照点,人们想了各种办法。一开始,从度量上,人们希望使用真正的太阳相对地球的角度定义某个地点的时间(真太阳时),但是后来发现太阳相对地球的运动并不均匀,还在逐渐变慢,并且由于存在黄赤交角,这种定义的方式下每一天长短并不均匀,计量困难。于是人们考虑一个 平太阳,它沿着天赤道匀速率运行,其速率为太阳一年内的平均速率,并且和太阳同时经过近地点和远地点。我们定义这个平太阳连续经过上中天的时间称为 平太阳日。然后将这个平太阳日分为 24 个小时,每一个小时 60 分钟,每一分钟 60 秒。这种计算方法叫做 平太阳时,简称 平时(mean time),一年一算。虽然这种方法计算简单了不少但是它和真太阳时是有误差的。
说完早期度量方法后再说说在哪儿量,而这个地点就是格林尼治。好了,现在翻译乱到不行的一个词出场了,它就是 GMT(Greenwich Mean Time)。不介绍上面的概念的话我们就没有办法把这个东西说清楚。因为它的翻译真的是乱七八糟网上查到的有这么多种:格林尼治时间、格林尼治平时、格林尼治标准时、世界时、格林尼治平太阳时、……。这里面最挫的一个翻译是 格林尼治标准时,你告诉我你英语是怎么学的 标准 俩字你怎么整出来的?!但是去他们的翻译,我们直接从字面上解释就是,英国原格林尼治皇家天文台(本初子午线定界处)的 平太阳时。可以看到 GMT 的度量结果是全年不变的。
好了,参照点选好了,那么我们来开始规定偏移量吧,这样我们就能够精确的进行各地时间的表示了。但是,地球表面陆地大部分都是连续的啊,走不了多远就晚看见太阳一秒钟。如果按这样走几步就要和 GMT 换算一下 offset 会死人的。于是规定了一系列的区域,在一个区域内,采用来自同一个地点的 offset 作为标准时,称这个区域为 时区。你看,我可没有跟你说时区是按照经线划分的啊,我说的是地区。因为一个经线可能纵穿 N 个国家。这个可以参照 这张图 。这也是为什么当我们打开时区的列表的时候,明明是同一个偏移量,偏偏要列出好多好多的时区;而明明是同一经度,偏移量却不一样的原因。例如(北京-重庆-乌鲁木齐,offset 为 8 小时,而按乌鲁木齐所在的经度划分的话则 offset 为 6 小时)。
在进行下一个问题之前,来一个小测验。格林尼治位于 GMT+00:00 时区之内,但是呢,我现在是一个权力很大的政治家,我说,格林尼治当地的表现在必须向前拨一个小时,否则就扣工资。那么是否 GMT+00:00 时区就变成了 GMT+01:00 时区了呢?
请继续向后拉
答案是:当然不是,你格林尼治当地时间变了整个时区就变了么?切,自恋!还有情绪?还有情绪咱们换一个名字不把这个时区叫 GMT 了你舒服了没有(很遗憾的现在已经换了)。这个小测验进一步说明了 GMT 这个词到底用的多乱。它一会儿代表一个参照系统(格林尼治平太阳时),一会儿代表 GMT+00:00 时区。大家一定要睁大自己雪亮的眼睛,想清楚你看到的 GMT 到底是个什么鬼。当然,别把 GMT 和格林尼治当地时间划等号。
偏移量的修正--夏令时
太好了,有了固定参照,有了偏移量,尘埃落定,天下太平了。但是人类就是在折腾自己的过程中不断进步的。从 1895 年开始,N 片论文阐述了早睡早起不但精神百倍而且还非常节约能源的观点。1916 年,德国和奥匈帝国首次使用了夏令时。从此,若干国家和地区引入了夏令时以节约能源的消耗。因此,一个地区的 GMT offset 有可能会发生改变!而且不同地区的夏令时开始和结束时间有可能是不同的更增加了复杂性。
最让人疯狂的事情来了,英国也引入了夏令时。这样英国全境在夏令时段内实际上是 GMT+01:00 时区而非 GMT+00:00 时区。而这个地区当然包括格林尼治!那么现在的问题就是:请问现在北京(目前我们没有夏令时)是在 GMT+08:00 呢,还是在 GMT+07:00 呢。
请继续向后拉……
答案是:没有变化 北京还是在 GMT+08:00。根据前一个小测验的结果,格林尼治当地时间变化了不代表 GMT+00:00 时区发生变化,更和你要不要少烧一点儿燃料没有关系。格林尼治当地在夏令时的时候使用的是 BST(British Standard Time:GMT+01:00),不要搞混了啊。
一秒有多长--度量方式的变化
按理说,现在参照点也有了,偏移量也有了,还有一个夏令时来做当地时间的修正,这总可以消停了吧。不能!为什么呢?因为有人问了,一秒到底有多长?
你告诉他,按照平太阳时,一秒就是一个平太阳时的 1/3600 但是跨年一个平太阳日的长度并非不变的。麻烦了。我们干脆找一天定一个吧。你别说还真找到一个:1900年1月1日12时起算的回归年的 31,556,925.9747 分之一。这个日子怎么定的,这是从一个叫做 Newcomb 的太阳历中得到的,详细的信息请参见 这里。哎,人类的历史真是“不严肃”。那么,现在怎么量这个时间呢?根据 1967 年召开的国际度量衡大会,对秒的定义为:铯133原子基态的两个超精细能阶间跃迁对应辐射的9,192,631,770个周期的持续时间(原子钟)。这个定义提到的铯原子必须在绝对零度时是静止的,而且在地面上的环境是零磁场。在这样的情况下被定义的秒,与方才说道的根据历书定义的秒是等效的。
当度量的定义和 GMT 有差异的时候,按照新定义计算的时间和原定义的差异就开始产生并积累了。大家说,算了,不用 GMT 了(我靠好随便有没有)。我们找一个良辰吉时用新的标准吧。于是找到了 GMT 1958 年 1 月 1 日 00:00:00 秒作为同年 00:00:00 的原子时。
但是并没有解决误差积累的问题啊,毕竟 GMT 用了那么长时间一下子替换掉你知道要折腾多久吗?于是,科学改不了但是人可以改啊,这样吧,我们用新的秒定义,但是为了尽可能的使用 GMT 那套东西,我们让新的参照尽量贴近原来 GMT 规定的参照不就好了吗?误差大了,加个闰秒什么的,不就平了吗?于是人们给这个参照起了一个新的名字:UTC(Coordinated Universal Time,欸?有没有搞错,字母顺序不对吧,哦,那是由于法语是 Temps Universel Coordonné,两家就妥协了以下,UTC 算了,谁都不亏= =)。
因此,UTC 和 GMT 的误差不会超过 1 秒。GMT 停用,因此,正确的写法应当是 UTC+00:00,对于那些非得用 GMT 的(秒级别的误差对我没有影响)……算了你接着用吧,但是你得知道,你写的是 GMT(UTC)+00:00 括号里的内容请大家自行脑补。
结论
时间可以表示为一个参照和一个偏移的组合,目前这种组合是:
UTC 加/减 偏移量,再以当地的夏令时进行修正(如果有且当前时间就在夏令时生效时间段内的话)
这么复杂我写程序的时候要注意什么么
说了这么多,实际上大部分的程序基本类库已经处理了上述描述的问题。我们需要关心的就是使用正确的方式进行时间的存储,交换,比较与展示。
存储
如果没有什么特殊情况的话一定要存 UTC+00:00 为参照的时间。为什么呢?因为简单:
- UTC+00:00 这个参照点是普遍约定了的;
- 它不受任何夏令时等政策性指令的影响;
- 与其他时区时间的转化最简单。按照上一节的结论直接加上时区的偏移值与夏令时的修正值就好了。
但是大家需要注意的是,数据在存储的时候是否从存储层面保持着上述语意,并且将存储读取出来之后其数据结构是否还维持着这些语义。在这里举一个小例子。
数据存储在 SQL Server 的类型为 datetime 的列中。存储的时候应用程序保证传递的值为 UTC+00:00 参照下的时间,而读取出来使用了
DateTime
类型之后出现了时间转换的问题。
- 首先,datetime 类型是没有 offset 信息的,即数据存储的时候已经不具备 UTC+00:00 的语意;
- 其次,数据从 ADO.NET 或者 ORM 读取完毕后为
DateTime
类型,这种类型对于时区的处理是非常弱的。由于数据没有 UTC+00:00 的信息,导致DateTime.Kind
为DateTimeKind.Unspecified
类型而非DateTimeKind.Utc
,导致在进行转换的时候会按照本地时区进行转换从而发生错误。
交换
请使用 UTC+00:00 为参照进行数据的交换,如果需要进行序列化,请使用 ISO8601 标准中的 Combined date and time inUTC 进行文本形式的序列化。
- 二进制的序列化不同的系统时间存在差异。例如,有的平台用从 1900/01/01 开始的 tick 数目表示时间,但是其他平台的起始点却可能不同;
- 其他系统一般应该能够处理 UTC+00:00 时间的序列化形式。而对于有偏移的序列化形式能否正确处理需要查阅相关的文档;
- 使用 ISO8601 Combined date and time inUTC 的形式可以避免遗漏某一个部分导致的问题。例如,仅仅包含日期的数据可能由于遗漏偏移信息导致换算错误。
比较
请务必使用能够正确的处理偏移的数据结构进行比较操作。如果需要比较的时间只包含日期时间以及时区信息(而不是有关 UTC 的绝对偏移量),则务必首先将其转换为 UTC 绝对偏移量再进行比较。
- 使用绝对偏移量的原因是因为时区除了一般的 UTC 偏移量之外还需要考虑当前时间是否在指定时区的夏令时段。
对于 .NET 或者 .NET Core 的应用程序,请务必使用
DateTimeOffset
不要使用DateTime
。因为后者无法在比较和运算中正确的处理时区和偏移。
展示
在展示的过程中如果需要将时间转换为本地时间或者指定时区的时间,请务必注意转换过程是否应用了相应时区的夏令时。
总结
日期时间非常小,参照偏移来定稿
参照就用UTC,偏移时区少不了
时区处处都不同,小心政策夏令时
存储交换作比较,转换形式最重要
展示时候留个心,时区信息勿忘掉
希望大家能够在自己的程序中将时间运用自如。
网友评论