理解时间:闰年、闰秒、时间与时区的原理

协调世界时(UTC)

UTC是调和GMT与TAI的产物:UTC使用精确的国际原子时TAI作为计时基础、UTC使用国际时GMT作为修正目标、UTC使用闰秒作为修正手段。

我们通常所说的时间,通常就是指世界协调时间UTC,它与世界时GMT的差值在0.9秒内,在要求不严格的实践中,可以近似认为UTC时间与GMT时间是相同的,很多人也把它与GMT混为一谈。

前几天,出现了四年一遇的闰年2月29号,每到这一天,总会有一些土鳖软件出现大翻车。这种问题如果运气不好,可能要等上四年才会暴露出来。比如新鲜出炉的:某品牌的激光雷达和新西兰加油站都因为闰年Bug无法使用。

今天我们就来聊一聊闰年、闰秒、时间与时区的原理,以及在数据库与编程语言中的注意事项。

0x01 秒与计时

时间的单位是秒,但秒的定义并不是一成不变的。它有一个天文学定义,也有一个物理学定义。

世界时(UT1)

在最开始,秒的定义来源于日。秒被定义为平均太阳日的1/86400。而太阳日,则是由天文学现象定义的:两次连续正午时分的间隔被定义为一个太阳日;一天有86400秒,一秒等于86400分之一天,Perfect!以这一标准形成的时间标准,就称为世界时(Univeral Time, UT1),或不严谨地说,格林威治标准时(Greenwich Mean Time, GMT),下面就用GMT来指代它了。

这个定义很直观,但有一个问题:它是基于天文学现象的,即地球与太阳的周期性运动。不论是用地球的公转还是自转来定义秒,都有一个很尴尬的地方:虽然地球自转与公转的变化速度很慢,但并不是恒常的。譬如:地球的自转越来越慢,而地月位置也导致了每天的时长其实都不完全相同。这意味着作为物理基本单位的秒,其时长竟然是变化的。在衡量时间段的长短上就比较尴尬,几十年的一秒可能和今天的一秒长度已经不是一回事了。

原子时(TAI)

为了解决这个问题,在1967年之后,秒的定义变成了:铯133原子基态的两个超精细能级间跃迁对应辐射的9、192、631、770个周期的持续时间。秒的定义从天文学定义升级成为了物理学定义,其描述由相对易变的天文现象升级到了更稳定的宇宙中的基本物理事实。现在我们有了真正精准的秒啦:一亿年的偏差也不超过一秒。

当然,这么精确的秒除了用来衡量时间间隔,也可以用来计时。从1958-01-01 00:00:00开始作为公共时间原点,国际原子钟开始了计数,每计数9、192、631、770这么多个原子能级跃迁周期就+1s,这个钟走得非常准,每一秒都很均匀。使用这定义的时间称为国际原子时(International Atomic Time, TAI),下文简称TAI

冲突

在最开始,这两种秒是等价的:一天是 86400 天文秒,也等于 86400 物理秒,毕竟物理学这个定义就是特意去凑天文学的定义嘛。所以相应地,GMT也与国际原子时TAI也保持着同步。然而正如前面所说,天文学现象影响因素太多了,并不是真正的“天行有常”。随着地球自转公转速度变化,天文定义的秒要比物理定义的秒稍微长了那么一点点,这也就意味着GMT要比TAI稍微落后一点点。

那么哪种定义说了算,世界时还是原子时?如果理论与生活实践经验相违背,绝大多数人都不会选择反直觉的方案:假设一种极端场景,两个钟之间的差异日积月累,到最后出现了几分钟甚至几小时的差值:明明日当午,按GMT应当是12:00:00,但GMT走慢了,TAI显示的时间已经是晚上六点了,这就违背了直觉。在表示时刻这一点上,还是由天文定义说了算,即以GMT为准

当然,就算是天文定义说了算,也要尊重物理规律,毕竟原子钟走得这么准不是?实际上世界时与原子时之间的差值也就在几秒的量级。那么我们会自然而然地想到,使用国际原子时TAI作为基准,但加上一些闰秒(leap second)修正到GMT不就行了?既有高精度,又符合常识。于是就有了新的协调世界时(Coordinated Universal Time, UTC)

协调世界时(UTC)

UTC是调和GMT与TAI的产物:UTC使用精确的国际原子时TAI作为计时基础、UTC使用国际时GMT作为修正目标、UTC使用闰秒作为修正手段。

我们通常所说的时间,通常就是指世界协调时间UTC,它与世界时GMT的差值在0.9秒内,在要求不严格的实践中,可以近似认为UTC时间与GMT时间是相同的,很多人也把它与GMT混为一谈。

但问题紧接着就来了,按照传统,一天24小时,一小时60分钟,一分钟60秒,日和秒之间有86400的换算关系。以前用日来定义秒,现在秒成了基本单位,就要用秒去定义日。但现在一天不等于86400秒了。无论用哪头定义哪头,都会顾此失彼。唯一的办法,就是打破这种传统:一分钟不一定只有60秒了,它在需要的时候可以有61秒。

这就是闰秒机制,UTC以TAI为基准,因此走得也比GMT快。假设UTC和GMT的差异不断变大,在即将超过一秒时,让UTC中的某一分钟变为61秒,续的这一秒就像UTC在等GMT一样,然后误差就追回来了。每次续一秒时,UTC时间都会落后TAI多一秒,截止至今,UTC已经落后TAI三十多秒了。最近的一次闰秒调整是在2016年跨年:

国际标准时间UTC将在格林尼治时间2016年12月31日23时59分59秒(北京时间2017年1月1日7时59分59秒)之后,在原子时钟实施一个正闰秒,即增加1秒,然后才会跨入新的一年。

所以说,GMT和UTC还是有区别的,UTC里你能看到2016-12-31 23:59:60的时间,但GMT里就不会。

0x02 本地时间与时区

刚才讨论的时间都默认了一个前提:位于本初子午线(0度经线)上的时间。我们还需要考虑地球上的其他地方:毕竟美帝艳阳高照时,中国还在午夜呢。

本地时间,顾名思义就是以当地的太阳来计算的时间:正午就是12:00。太阳东升西落,东经120度上的本地时间比起本初子午线上就早了120° / (360°/24) = 8个小时。这意味着在北京当地时间12点整时,UTC时间其实是12-8=4,早晨4:00。

大家统一用UTC时间好不好呢?当然可以,毕竟中国横跨三个时区,也只用了一个北京时间。只要大家习惯就行。但大家都已经习惯了本地正午算12点了,强迫全世界人民用统一的时间其实违背了历史习惯。

时区的设置使得长途旅行者能够简单地知道当地人的作息时间,反正差不多都是朝九晚五上班。这就降低了沟通成本。于是就有了时区的概念。当然像新疆这种硬要用北京时间的结果就是,游客乍一看当地人11点12点才上班可能会有些懵。

但在大一统的国家内部,使用统一的时间也有助于降低沟通成本。假如一个新疆人和一个黑龙江人打电话,一个用的乌鲁木齐时间,一个用的北京时间,那就会鸡同鸭讲。都约着12点,结果实际差了两个小时。时区的选用并不完全是按照地理经度而来的,也有很多的其他因素考量(例如行政区划)

这就引出了时区的概念:时区是地球上使用同一个本地时间定义的区域。时区实际上可以视作从地理区域到时间偏移量的单射。

但其实有没有那个地理区域都不重要,关键在于时间偏移量的概念。UTC/GMT时间本身的偏移量为0,时区的偏移量都是相对于UTC时间而言的。这里,本地时间,UTC时间与时区的关系是:

本地时间 = UTC时间 + 本地时区偏移量。

比如UTC、GMT的时区都是+0,这意味着没有偏移量。中国所处的东八区偏移量就是+8,这意味着计算当地时间时,要在UTC时间的基础上增加8个小时。

夏令时(Daylight Saving Time, DST),可以视为一种特殊的时区偏移修正。指的是在夏天天亮较早的时候把时间调快一个小时(实际上不一定是一个小时),从而节省能源(灯火)

我国在1986年到1992年之间曾短暂使用过夏令时。欧盟从1996年开始使用夏令时,不过欧盟最近的民调显示,84%的民众希望取消夏令时。对程序员而言,夏令时也是一个额外的麻烦事,希望它能尽快被扫入历史的垃圾桶。

0x03 时间的表示

那么,时间又如何表示呢?使用TAI的秒数来表示时间当然不会有歧义,但使用不便。习惯上我们将时间分为三个部分:日期、时间、时区,而每个部分都有多种表示方法。

对于时间的表示,世界诸国人民各有各的习惯,例如,2006年1月2日,美国人就可能喜欢使用诸如January 2, 1999,1/2/1999这样的日期表示形式,而中国人也许会用诸如“2006年1月2日”,“2006/01/02”这样的表示形式。发送邮件时,首部中的时间则采用RFC2822中规定的Sat,24 Nov 2035 11:45:15 −0500格式。此外,还有一系列的RFC与标准,用于指定日期与时间的表示格式。

ANSIC       = “Mon Jan _2 15:04:05 2006”

UnixDate    = “Mon Jan _2 15:04:05 MST 2006”

RubyDate    = “Mon Jan 02 15:04:05 -0700 2006”

RFC822      = “02 Jan 06 15:04 MST”

RFC822Z     = “02 Jan 06 15:04 -0700” // RFC822 with numeric zone

RFC850      = “Monday, 02-Jan-06 15:04:05 MST”

RFC1123     = “Mon, 02 Jan 2006 15:04:05 MST”

RFC1123Z    = “Mon, 02 Jan 2006 15:04:05 -0700” // RFC1123 with numeric zone

RFC3339     = “2006-01-02T15:04:05Z07:00”

RFC3339Nano = “2006-01-02T15:04:05.999999999Z07:00”

不过在这里,我们只关注计算机中的日期表示形式与存储方式。而计算机中,时间最经典的表示形式,就是Unix时间戳。

Unix时间戳

比起UTC/GMT,对于程序员来说,更为熟悉的可能是另一种时间:Unix时间戳。UNIX时间戳是从1970年1月1日(UTC/GMT的午夜,在1972年之前没有闰秒)开始所经过的秒数,注意这里的秒其实是GMT中的秒,也就是不计闰秒,毕竟一天等于86400秒已经写死到无数程序的逻辑里去了,想改是不可能改的。

使用GMT秒数的好处是,计算日期的时候根本不用考虑闰秒的问题。毕竟闰年已经很讨厌了,再来一个没有规律的闰秒,绝对会让程序员抓狂。当然这不代表就不需要考虑闰秒的问题了,诸如ntp等时间服务还是需要考虑闰秒的问题的,应用程序有可能会受到影响:比如遇到‘时光倒流’拿到两次59秒,或者获取到秒数为60的时间值,一些实现简陋的程序可能就直接崩了。当然,也有一种将闰秒均摊到某一天全天的顺滑手段。

Unix时间戳背后的思想很简单,建立一条时间轴,以某一个纪元点(Epoch)作为原点,将时间表示为距离原点的秒数。Unix时间戳的纪元为GMT时间的1970-01-01 00:00:00,32位系统上的时间戳实际上是一个有符号四字节整型,以秒为单位。这意味它能表示的时间范围为:232 / 86400 / 365 = 68年,差不多从1901年到2038年。

当然,时间戳并不是只有这一种表示方法,但通常这是最为传统稳妥可靠的做法。毕竟不是所有的程序员都能处理好许多和时区、闰秒相关的微妙错误。使用Unix时间戳的好处就是时区已经固定死了是GMT了,存储空间与某些计算处理(比如排序)也相对容易。

在*nix命令行中使用date +%s可以获取Unix时间戳。而date -r @1500000000则可以反向将Unix时间戳转换为其他时间格式,例如转换为2017-07-14 10:40:00可以使用:

1  date -d @1500000000 ‘+%Y-%m-%d %H:%M:%S’    # Linux

2  date -r 1500000000 ‘+%Y-%m-%d %H:%M:%S’        # MacOS, BSD

在很久以前,当主板上的电池没电之后,系统的时钟就会自动重置成0;还有很多软件的Bug也会导致时间戳为0,也就是1970-01-01;以至于这个纪元时间很多非程序员都知道了。

当然,4字节Unix时间戳的上限2038年离今天(2024)年已经不是遥不可及了,还没及时改成8字节时间戳的软件到时候就要面临比闰天加油站罢工严峻得多的千年虫问题 —— 直接罢工了,比如直到今天还没有改过来的二傻子MySQL 。

PostgreSQL中的时间存储

通常情况下,Unix时间戳是传递/存储时间的最佳方式,它通常在计算机内部以整型的形式存在,内容为距离某个特定纪元的秒数。它极为简单,无歧义,存储占用更紧实,便于比较大小,且在程序员之间存在广泛共识。不过,Epoch+整数偏移量的方式适合在机器上进行存储与交换,但它并不是一种人类可读的格式(也许有些程序员可读)

PostgreSQL提供了丰富的日期时间数据类型与相关函数,它能以高度灵活的方式自动适配各种格式的时间输入输出,并在内部以高效的整型表示进行存储与计算。在PostgreSQL中,变量CURRENT_TIMESTAMP或函数now()会返回当前事务开始时的本地时间戳,返回的类型是TIMESTAMP WITH TIME ZONE,这是一个PostgreSQL扩展,会在时间戳上带有额外的时区信息。SQL标准所规定的类型为TIMESTAMP,在PostgreSQL中使用8字节的长整型实现。可以使用SQL语法AT TIME ZONE zone或内置函数timezone(zone,ts)将带有时区的TIMESTAMP转换为不带时区的标准版本。

通常最佳实践是,只要应用稍具规模或涉及到任何国际化的功能,要么按照PostgreSQL Wiki中推荐的最佳实践使用PostgreSQL自己提供的TimestampTZ扩展类型,要么使用TIMESTAMP类型并固定存储GMT/UTC时间。

PostgreSQL的时间戳实现用的是8字节,表示的时间范围从公元前4713年到29万年后,精度为1微秒,完全不用担心2038千年虫问题。

常见困惑:闰天

关于闰天,PostgreSQL处理得很好,但是需要特别注意的是对于闰年加减时间范围的运算规律。

例如,如果你在“2024-02-29”号往前减“一年”,结果是“2023-02-28”,但如果你减去365天,则是“2023-03-01”。

反过来,如果你都加一年,12个月或者加365天,结果都是明年的2月28号。这样处理肯定是比那些直接用年份+1来计算的二傻子软件靠谱多了。

常见困惑:时间戳互转

PostgreSQL 中一个经常让人困惑的问题就是TIMESTAMPTIMESTAMPTZ之间的相互转化问题。下面是一个附带说明的具体例子:

常见困惑:时区偏移量

当然,PostgreSQL中的时间戳也有一个与时区相关的设计比较违反直觉,就是在使用AT TIME ZONE的时候,应该尽可能避免使用 +8、-6这样的数字时区,而应该使用时区名。

这是因为当你在时区部分使用数值时,PostgreSQL会将其视作Interval类型进行处理,被解释为 “Fixed” Offsets from UTC,不常用,文档也不推荐使用。

举个例子,今天东八区中午 (SELECT ‘2024-01-15 12:00:00+08’::TIMESTAMPTZ) 转换为UTC时间戳,为“2024-01-15 04:00:00”(东八区的12点 = UTC 零时区的4点)这没有问题:

> SELECT ‘2024-01-15 12:00:00+08’::TIMESTAMPTZ AT TIME ZONE ‘+0’;

2024-01-15 04:00:00

现在,我们使用“+1”作为time zone参数,直观的想法应该是,+1代表东一区当前的时间,应该是“2024-01-15 05:00:00+1”但结果让人惊讶:反而是提前了一个小时。

> SELECT ‘2024-01-15 12:00:00+08’::TIMESTAMPTZ AT TIME ZONE ‘+1’;

2024-01-15 03:00:00

而如果我们反过来,使用-1作为西一区的时区名,结果也是错误的(预期应该是凌晨3点,结果返回了5点)

> SELECT ‘2024-01-15 04:00:00+00’::TIMESTAMPTZ AT TIME ZONE ‘-1’;

timezone

———————

2024-01-15 05:00:00

这里面的原因是,使用Interval而非时区名,处理逻辑是不一样的:

首先,东八区TIMESTAMPTZ“2024-01-15 12:00:00+08”被转换为UTC时间的TIMESTAMPTZ“2024-01-15 04:00:00+00”。

然后UTC时间的TIMESTAMPTZ“2024-01-15 04:00:00+00”被截断时区部分,成为“2024-01-15 04:00:00”,并拼接新时区参数+1成为一个新的TIMESTAMPTZ“2024-01-15 04:00:00+1”。

最后,这个新的时间戳被重新转换为不带时间戳的UTC时间“2024-01-15 03:00:00”。

本文来自微信公众号:非法加冯(ID:addvon),作者:冯若航

声明: 该内容为作者独立观点,不代表新零售资讯观点或立场,文章为网友投稿上传,版权归原作者所有,未经允许不得转载。 新零售资讯站仅提供信息存储服务,如发现文章、图片等侵权行为,侵权责任由作者本人承担。 如对本稿件有异议或投诉,请联系:wuchangxu@youzan.com
(0)
上一篇 2024年3月4日 10:30
下一篇 2024年3月4日 10:41

相关推荐

  • 水温80度:AI行业真假繁荣的临界点

    我们从来没拥有过这么成功的AI主导的产品。

    (这种分析统计并不那么准,但大致数量级是差不多的)

    这两个产品碰巧可以用来比较有两个原因:

    一个是它们在本质上是一种东西,只不过一个更通用,一个更垂直。

    蓝海的海峡

    未来成功的AI产品是什么样,大致形态已经比较清楚了,从智能音箱和Copilot这两个成功的AI产品上已经能看到足够的产品特征。

    未来科技 2024年6月5日
  • ChatGPT、Perplexity、Claude同时“罢工”,全网打工人都慌了

    美西时间午夜12点开始,陆续有用户发现自己的ChatGPT要么响应超时、要么没有对话框或提示流量过载,忽然无法正常工作了。

    因为发现AI用久了,导致现在“离了ChatGPT,大脑根本无法运转”。”

    等等,又不是只有一个聊天机器人,难道地球离了ChatGPT就不转了。

    大模型连崩原因猜想,谷歌躺赢流量激增6成

    GPT归位,人们的工作终于又恢复了秩序。

    未来科技 2024年6月5日
  • ChatGPT宕机8小时,谷歌Gemini搜索量激增60%

    ChatGPT一天宕机两次

    谷歌Gemini搜索量激增近60%

    ChatGPT在全球拥有约1.8亿活跃用户,已成为部分人群工作流程的关键部分。

    过去24小时内提交的关于OpenAI宕机的问题报告

    图片来源:Downdetector

    ChatGPT系统崩溃后,有网友在社交媒体X上发帖警告道:“ChatGPT最近发生的2.5小时全球中断,为我们所有依赖AI工具来支持业务的人敲响了警钟。

    未来科技 2024年6月5日
  • ChatGPT、Perplexity、Claude同时大崩溃,AI集体罢工让全网都慌了

    接着OpenAI也在官网更新了恢复服务公告,表示“我们经历了一次重大故障,影响了所有ChatGPT用户的所有计划。Generator调查显示,在ChatGPT首次故障后的四小时内,谷歌AI聊天机器人Gemini搜索量激增60%,达到327058次。

    而且研究团队表示,“Gemini”搜索量的增长与“ChatGPT故障”关键词的搜索趋势高度相关,显示出用户把Gemini视为ChatGPT的直接替代选项。

    未来科技 2024年6月5日
  • 深度对话苹果iPad团队:玻璃的传承与演变

    iPad最为原始的外观专利

    没错,这就是iPad最初被设想的样子:全面屏,圆角矩形,纤薄,就像一片掌心里的玻璃。

    2010年发布的初代iPad

    好在乔布斯的遗志,并未被iPad团队遗忘。

    初代iPad宣传片画面

    乔布斯赞同这一想法,于是快速将资源投入平板电脑项目,意欲打造一款与众不同的「上网本」,这就是iPad早年的产品定义。

    iPad进化的底色

    苹果发布会留下过很多「名场面」,初代iPad发布会的末尾就是一例。

    未来科技 2024年6月5日
  • 底层逻辑未通,影视业的AI革命正在褪色…

    GPT、Sora均为革命性产品,引发了舆论风暴,但它在上个月发布的“多模态语音对谈”Sky语音,却由于声音太像电影明星斯嘉丽·约翰逊,被正主强烈警告,被迫下架。

    华尔街日报也在唱衰,认为“AI工具创新步伐正在放缓,实用性有限,运行成本过高”:

    首先,互联网上已经没有更多额外的数据供人工智能模型收集、训练。

    03、

    如果说训练“数字人”、使用AI配音本质上瞄向的仍是影视行业固有的发展方向,那么还有另外一群人试图从根本上颠覆影视行业的生产逻辑和产品形态。

    但分歧点正在于此,电影公司希望通过使用AI技术来降低成本,但又不希望自己的内容被AI公司所窃取。

    未来科技 2024年6月5日
  • KAN会引起大模型的范式转变吗?

    “先变后加”代替“先加后变”的设计,使得KAN的每一个连接都相当于一个“小型网络”, 能实现更强的表达能力。

    KAN的主要贡献在于,在当前深度学习的背景下重新审视K氏表示定理,将上述创新网络泛化到任意宽度和深度,并以科学发现为目标进行了一系列实验,展示了其作为“AI+科学”基础模型的潜在作用。

    KAN与MLP的对照表:

    KAN使神经元之间的非线性转变更加细粒度和多样化。

    未来科技 2024年6月5日
  • 这个国家,也开始发芯片补贴了

    //mp.weixin.qq.com/s/tIHSNsqF6HRVe2mabgfp6Q
    [4]中国安防协会:欧盟批准430亿欧元芯片补贴计划:2030年产量占全球份额翻番.2023.4.19.https。//mp.weixin.qq.com/s/VnEjzKhmZbuBUFclzGFloA
    [6]潮电穿戴:印度半导体投资大跃进,一锤砸下1090亿,政府补贴一半.2024.3.5https。

    未来科技 2024年6月5日
  • 大模型的电力经济学:中国AI需要多少电力?

    这些报告研究对象(数字中心、智能数据中心、加密货币等)、研究市场(全球、中国与美国等)、研究周期(多数截至2030年)各不相同,但基本逻辑大同小异:先根据芯片等硬件的算力与功率,计算出数据中心的用电量,再根据算力增长的预期、芯片能效提升的预期,以及数据中心能效(PUE)提升的预期,来推测未来一段时间内智能数据中心的用电量增长情况。

    未来科技 2024年6月5日
  • 你正和20万人一起接受AI面试

    原本客户还担心候选人能否接受AI面试这件事,但在2020年以后,候选人进行AI面试的过程已经是完全自动化的,包括面试过程中AI面试官回答候选人的问题,AI面试官对候选人提问以及基于候选人的回答对候选人进行至多三个轮次的深度追问。

    以近屿智能与客户合作的校验周期至少3年来看,方小雷认为AI应用不太可能一下子爆发,包括近屿智能在内的中国AI应用企业或许要迎来一个把SaaS做起来的好机会。

    未来科技 2024年6月4日