跳到文章

漫谈时间(一)——时间、历法和时区

由于做的项目经常要和“时间”打交道,零零碎碎地了解了一些相关知识,今天整理一下,分享给大家😁本篇文章主要介绍时间、历法、时区等背景知识,不会有代码。后续的文章我会使用 Java, Javascript, C# 来说明在编程活动中如何处理时间, comming soon…

背景

古代人们为了可以对事件排先后顺序及进行比较,需要一种度量方式来描述,以便更好地思考宇宙。俗话说,当一无所知时,观察、探索是最好的方式。于是人们发现头顶上的太阳有规律地从东边升起,西边落下;月亮有阴晴圆缺的周期变化;季节有节奏地变化。人们记录每个事物变化的周期,相应地有了“日”,“月”,“年”的概念。

历法(Calendar)

有了概念,就需要给出这些概念的基本定义以及相应的操作(是不是很 OOP 啊,哈哈哈),于是就有了历法。历法是用年、月、日等时间单位计算时间的方法。

历法主要分为阳历(Solar Calendar)、阴历(Lunar Calendar)和阴阳历(Lunisolar Calendar)三种。

  • 阳历是根据地球上所呈现出太阳直射点的周期性变化,所制定的历法,这个周期为365.2422日,称为回归年。阳历有多种,比如我们熟知的公历(Gregorian Calendar),公历把一年定为365日,划分成12个月,每个月从28日到31日不等。由于一个回归年比公历中的历年多出了0.2422日,这剩下的时间每4年累计一天,所以我们就有了闰年的概念。还记得我们小学时都做过的算数吗?“1700年是不是闰年?那2000年呢?”,答案是1700年不是闰年,2000年是闰年。那时候我的数学老师只是简单地和我解释,“像1700年这种能被100整除的年份,还需要被400整除才是闰年。”那时候我懂得少,也就默默接受了,并没有进一步深入了解。不过,现在看到这篇文章的读者只要进行一个简单的计算(0.2422*400=96.88),应该就能明白为什么1700不是闰年了。

  • 阴历与阳历相对,主要根据月亮绕地球运行一周时间为一个月,称为朔望月(朔,新月,定为每月的初一;望,满月,定为每月的十五),大约29.5306日,分为大月30日、小月29日。一年有12个朔望月,约354或355日。值得注意的是,我们国家传统上使用的农历并不是阴历,实际上农历是一种阴阳历。

  • 阴阳历综合了阳历和阴历,是兼顾月相周期和太阳周期运动所安排的历法。阴阳历既保证“年”与地球绕日周期的一致,又保证“月”与月亮周期的一致。比如我国的农历,在阴历的基础上,引入二十四节气,作为农历的阳历部分。农历广泛应用于生日标记、各种民俗活动等方面。比如中秋节(农历八月十五)就是八月的“望”,是阴历的体现;而清明节(每年4月5号左右)就是二十四节气之一,是阳历的体现。农历是我们中华文化的象征之一。

取法自然与人为规定

一开始古代人们使用日落日出的周期来作为一日的时长,古埃及发明了24小时制,古巴比伦发明了60分和60秒的用法。现在人们发现地球自转并不够稳定,而且在逐渐变慢。于是,我们不再以日为作为标准计时单位,转为定义更小更精确的单位,即是“秒”。“秒”使用原子钟来定义,稳定性、精确性毋庸置疑。将一天定为24小时,1440分,86400秒。

年、月、日取法自然,时、分、秒人为规定。

时区(Time Zone)

我们作为太阳的子民,“日出而作,日落而息。”但不同经度的地方的日出、日落时间必定有所偏差,为了照顾大家的生活习惯,出现了时区的概念。世界按统一标准划分时区,每个地区有着自己的时间(地方时)。

一开始时区以格林威治时间为基准,往东增加,往西减少,中间有一条国际日期变更线。后来由原子钟报时的、更精确的 UTC (协调世界时)时间出现了,UTC 尽量接近格林威治时间。由于 GMT 时间不够准确,GMT 现在已经不被科学界所确定。所以,说到北京时间的时区,现在我们更多说的是 “UTC+8” ,而不是 “GMT+8” 。

ISO 8601

好了,现在我们有了公历这一套通用的历法,又有了时区,那么如何准确地表示时间呢?国际标准 ISO 8601 应运而生。

标准说明起来比较麻烦,举例说明一下,详情参考ISO 8601

  • 2017-11-26T14:00:02Z

表示UTC时间2017年11月26号14点0分2秒,其中 “T” 是日期和时间的分界,“Z” 代表时间是 UTC 时间。也可以表示成"2017-11-26T14:00:02+00:00"

  • 2004-05-03T17:30:08+08:00

表示 “UTC+8” 时区的2004年5月3号17点30分8秒。

  • P1Y3M5DT6H7M30S

时间间隔的表示法,这里表示间隔为1年3月5天6小时7分30秒。时间间隔有正负之分,如果为负,则在前面加负号表示。例如 -P1Y3M5DT6H7M30S

Unix Time

编程的各位经常会接触到 Unix 时间戳这个概念,它的标准定义为:从1970-01-01T00:00:00Z起至现在的总秒数。由于 Unix Time 是相对于 UTC 时间的,所以同一个 Unix Time 在不同时区的地方时是不同的,比如说:

对于 1511708653 来说:

  • 在 UTC 时间是 “2017-11-26T15:04:13Z”
  • UTC+8 时区的地方时是 “2017-11-26T23:04:13+08:00”
  • UTC-8 时区的地方时是 “2017-11-26T07:04:13-08:00”

但它们指的都是同一时刻,比如双十一剁手的时候,中国时0点开始,日本那边就是1点开始。

这也就是为什么程序中喜欢使用 Unix Time 来作为基本时间的原因,程序关注是否在同一个时刻,而地方时只是一个 UI 展现层而已。

有趣的是,Unix Time 在程序中广泛使用,带来了一个历史遗留问题,著名的 “Year 2038 problem(2038年问题)” ,有时也被称作 “Unix Millennium Bug(千年虫)"

最后值得注意的是, Unix Time 的单位是秒,而不是毫秒,部分人(包括我在内)可能误以为 Unix Time 的单位是毫秒。可能是由于 Javascript 中并没有现成的获取当前时区对应的 Unix Time 的方法,只有获取 1970-01-01T00:00:00Z 到现在的毫秒数的方法吧,哈哈哈哈。

注:博客中所有文章都开源,本篇文章在这里,如果大家发现文章哪里写的不好、不对的,欢迎提 pull request。(啊,我真的是懒到家了…