一篇文章了解全球化系统UTC时间(包括DB放置和接口定义)中日期和时间处理的问题,应该不受用户时区或服务器时区的影响。前端输入和显示时间要根据具体的业务场景和精度调整。对于没有时间的日期,需要明确区分“纪念日”和“精度低的绝对时间”两种用法。大多数时候看到的日期都是后者,应该也是用“DateTime判断时区”来实现的1.日期和时间的重要性处理一直是计算机系统中一个看似简单的问题,但实际上它经常爆炸。例如每隔几年弹出的“2000年Bug问题”的各种变种,通常是因为系统在设计之初没有设计好日期时间数据的存储方式,或者低估了产品设计的生命周期.选择的数据结构不够。Y2K:年长的程序员都知道Y2K。在2000年之前,很多系统都是用2位数字来表示年份,所以99年是它可以表示的最大值。因此,这些系统中并没有定义1999之后的年份,甚至可能会出现各种奇怪的情况,比如“1900”、“1:00”、“19:0”(为什么?有兴趣的读者自己猜).如果说千年虫是时间维度上缺乏前瞻性设计造成的,那么另一个缺乏前瞻性的问题则是空间维度上的问题,即产品全球化、跨时区带来的问题。在全球化的产品中,如果时间处理不遵循统一的标准,整个系统就会充满时间转换,难以理解和维护。各种接口的对接文档都要写清楚“这个接口的时间是什么时区?怎么处理?”如果后端服务需要在机房跨多个大洲部署,由于服务器时区不同,需要做Extensive改造。不幸的是,在大多数情况下,产品不会从一开始就具有“全球化”属性。所以一开始产研团队不会关注全球化的设计问题,容易留下缺乏前瞻性的设计问题。一般情况下,我们不鼓励“过度设计”。不过,日期和时间的设计最不怕“过度”。这是因为在技术上实现前瞻性时间日期方案的成本并不高;但如果最初的设计还不够,后期的升级和数据迁移工作就会很痛苦。2.如何表示时间和日期?2.1时间和日期的传递:微服务之间、前后端之间使用字符串。推荐使用字符串来传递日期和时间。字符串清晰易读,易于手动调试,并且通常具有完全可以接受的开销。(对于时间数据量大的接口,建议考虑使用UnixTimestamp。)如果使用字符串,就不用自己发明格式了。有一个非常明确的国际标准:ISO8601(维基百科:https://en.wikipedia.org/wiki/ISO_8601)下面的例子是符合规范的通用格式:onlydate:2022-02-09UTCdatetime:2022-02-09T12:36:42Z特定时区的日期时间:2022-02-09T20:36:42+08:00精度更高的时间:2022-02-09T12:36:42.123456789Z注意,使用的字符串格式在MySQL中(如2022-02-0912:36:42)不符合规范,不推荐使用。2.2时间和日期的存储:注意MySQL中的DateTime不同的数据库对时间和日期相关对象的处理有很大差异。这里只说MySQL,因为坑不小。MySQL的DateTime数据在存储时不包含时区信息,因此读取时不会做任何时区转换。同时,每个MySQL连接会话都有“会话时区”的概念,但这个概念只影响MySQL的NOW()等与当前时间相关的函数的行为,对保存在数据。例如:SETtime_zone='+00:00';UPDATEtabSETdatetime_colume='2020-01-0100:00:00';SETtime_zone='+08:00';--更改一个session时区SELECTdatetime_columeFROMtab;--返回值仍然是'2020-01-0100:00:00',与写入数据一致,与session时间无关--------SETtime_zone='+00:00';现在选择();--假设返回'2022-01-0100:00:00'UPDATEtabSETdatetime_colume=NOW();--存储的是'2022-01-0100:00:00'SETtime_zone='+08:00';--更改会话时区SELECTNOW();--'2022-01-0108:00:00'SELECTdatetime_columeFROMtab根据时区;--'2022-01-0100:00:00'已经写好了2.3时间日期的计算:语言原生DateTime类型所有语言一般都提供原生DateTime数据类型来表示绝对日期时间,都支持上述ISO8601规范解析和格式化。在处理相对时区时,各种语言通常会使用操作系统的时区数据库来转换为绝对时区。在联网的情况下,操作系统需要定期更新时区数据库。2.4UnixTimestampUnixTimestamp可以用于存储、计??算和传输,可以说是通用的。只是不适合表示纪念日。它以数值表示绝对时间与Unix纪元时间(定义为1970-01-01T00:00:00Z)之间的秒差。UnixTimestamp本身已经表达了绝对时间,不需要时区信息。在使用UnixTimestamp时,要特别注意选择合适的数值类型,这会影响时间表示的范围。如果你不小心,你可能会种植新的Y2K。使用带符号的int32,最多2038。MySQL的TIMESTAMP类型也是。当一个Y2K变体使用signedint64,使用9位decimal定点小数位时,就是Golang的UnixNano(),可以表示1678到2262的年份,一般不会用浮点数来表示。因为浮点数的精度不固定3、产品透视图的日期时间设计本着不重复的原则,我们可以按照下表划分产品中的所有日期时间对象:timezone,需要根据用户所在的时区进行转换②表示世界上唯一确定的时间点④表示全球都可以理解的重复时间?不存在的场景以下五个场景一一解释。3.1表示世界上唯一确定的时间点(表中②)包含“年、月、日-时、分、秒-时区”。这样一来,历史长河中的一个明确的时间点就可以完全确定下来了。这个时间点是完全客观的,与来访用户所在的地理位置、服务器所在的地理位置等无关。在产品表现中,时间的显示通常会根据观看者所在的时区进行重新调整。用法示例:单个事件的发生时间。比如2022年冬奥会开幕式时间:2022年2月4日20:00,+0800时区。观看电视转播的英国人会看到开幕式将于2022年2月4日中午12点整播出。这反映了观看者对时间的转换。3.2表示本地定时点(表中①)包含“年、月、日-时、分、秒”。因为没有时区信息,它本身不能确定一个精确的时间点,只能在特定情况下确定。意义。所谓特定情况是因为业务场景包含时区信息,是公认的共识。所以本质上它仍然代表一个绝对时间。在产品性能方面,由于对时区的共识,无需根据观看者的时区调整时间显示。使用示例:在非国际化的产品中,用户所在的时区是明确知道的,所以去除时区是最简单的处理方式,可以省去很多麻烦。对时区还有其他传统的理解。例如:飞机的起降时间,酒店的入住和退房时间,必须按照飞机起降的当地时区,酒店所在地来表示。在所有订票网站上,无论访问用户在哪个时区,都会按此规则显示时间。3.3表示重复时间(表中③和④)与前两类相比,“日期”信息"被删除以描述重复性计划。它可能指定也可能不指定时区,但基于人们对时区的共识。用法示例:每周三8:00+0800,如果这可以是跨国会议,大家可以理解正确的时间。这时,产品性能要注意根据观看者调整显示。对于每周三8:00出发的航班,默认为出发地时区。产品展示不必根据观看者的时区调整显示。3.4周年纪念日期(表中⑤)日期对象几乎只有一个有意义的用途:代表纪念日/假期。它不会包含时区信息。认为“日期”只能用于“纪念日”就有点绝对了。但我确实查了很多,没有看到不是“阵亡将士纪念日”的日期。举个例子:小吴的生日是3月11日,那么无论是在中国还是在美国,他的生日都是3月11日。12月25日在西方每年都是圣诞节,各国也在12月25日庆祝,虽然他们不在同一时区。在产品性能方面,无需根据时区调整日期的显示。从本质上讲,“周年纪念”的逻辑其实是人脑不严谨造成的一种习惯,一种不严谨、不客观的习惯。没有包含时区信息来满足这种松散的习惯。3.5区分“纪念日”和“低精度绝对时间”上面提到,日期对象不能包含时区。你可能会问,我需要表达“北京时间2022年3月22日”吗?答案是:这不是日期,而是“低精度的绝对时间”。在很多情况下,当你想使用日期时,你可能实际上需要一个“低精度的绝对时间”。在飞书HRSuite的业务中,经常会遇到这种场景。比如美国的同学和日本的同学都在2022年3月22日从公司离职,离职事宜由同一个北京的HR处理。可见,从我们用户的角度理解的“某事件的日期”,其实是我们忽略了时间的准确性。在产品全球化之前,我们通过一些默认的简化(比如把时间填成00:00:00)来忽略时间精度的问题。一旦面临产品的全球化,就需要及时补足,提高精准度。完成时间和提高精度的方式需要根据具体的产品形态来考虑和明确定义。比如上面的离职场景,需要根据公司对离职的定义进行补充,可以是当天当地时间23:59:59,也可以是当天下班时间,比如17:00:00。再比如,对于跨团队的业务,比如同学的上级汇报线从美国领导调到日本领导,那么为了避免歧义,通常会约定一个明确的有效时区,比如统一根据到公司总部所在地的时间。计算。4.日期时间的技术实现4.1DateTime判断时区适用于以上①②③④四种场景。所有后端暴露的接口中的时间对象均以UTC时间表示。同时,所有后端在存储、计算、传输时间时也统一使用UTC时间。由于DB在存储时间的时候会丢失时区信息,所以要保证丢失的时区是明确约定的,没有歧义的,即UTC。这样,DB中的所有时间字段也是没有歧义的。界面内部产生的时间,比如CreatedAt和UpdatedAt时间,放置前需要转换成UTC时间。如果直接使用MySQL的NOW()函数,需要确保MySQLSession的时区设置正确。前端或BFF负责何时处理用户输入,以及何时显示给客户看。包括两个步骤:处理“低精度时间”问题。例如:员工变动的生效时间,用户只能设置精度为“天”。那么如果不是跨境,可以补全用户会话时区的00:00:00作为精确生效时间;如果是跨境的,就看客户怎么定义了,产品对客户的灵活性如何:比如可以用客户公司总部所在时区的00:00:00为准有效时间。时区转换。请注意,此处不一定使用用户的会话时区进行转换。比如上面提到的飞机、火车、酒店的预订时间,要按照当地时区进行换算。以上两点在产品设计中一定要明确,不能模糊。有几点不要太认真:由于历史原因,DB已经保存为北京时间,所以我们可以约定+0800时区为我们所有后端接口的时间。只要使用明确的绝对时区,就不会出现歧义,也不一定非得是UTC。时间转换也可以在后端接口的网关层处理。不要认真考虑它是否被认为是BFF。我们需要的是严格禁止时区转换逻辑深入到后端下层。4.2无时区日期适用于上述⑤,为周年纪念场景。输入或显示日期时不会以任何方式处理。日期对象直接保存在数据库中。只有真正的纪念日才有必要使用这种方法,而且要非常小心。例如,保存联系人的生日时。5.时区的特殊处理5.1时区的不确定性用绝对时差来表示时区,例如:“东八区”表示比协调世界时(UTC)早8小时的时区。这是一个客观的时区。很多时候,我们关心的是城市或地区的时区。例如:Asia/Shanghai表示中国时间;三字母缩写EST表示东部标准时间。请注意,这些地理定义的时区的时差会发生变化,变化的因素包括:可能受当地政策影响,或受夏令时影响。对于历史时间,地理时区可以确定客观时区,因为没有人会重新定义已经过去的时间。对于未来的时间,地理时区并不能确定客观时区。因此,如果未来的事件是根据非绝对时区来承包的,那么它很可能会发生变化。而且,我们的产品需要处理这种变化。例如,中国员工在“每天早上8:00”发起跨境会议。在美国,由于夏令时的变化,冬季的会议时间与夏季不同。反之,美国员工在“每天早上8:00”发起的跨国会议,由于美国夏令时的变化,中国员工夏季和冬季的时间也不同。5.2夏令时部分国家在夏季,时间会向前调整一小时(提前一小时)。这体现在同一地区在冬季和夏季使用不同的绝对时区。这是因为夏天的白天很长,经过调整,白天会提早上班,这样下班后的黎明时间就会更长。注意,不是10点到9点上班的调整,而是全社会提前一个小时重新定义10点。一个具体的例子,在美国:2021年3月14日凌晨1点59分59秒后,下一秒是凌晨3点00分。因此,美国时间March14,20212:10:00AM实际上并不存在。为了兼容性,根据RFC5545,如果时间表在这个不存在的时间,将被视为3:10:00。2021年11月7日凌晨1:59:59后,下一秒为凌晨1:00:00。所以美国时间2021年11月7日凌晨1:10:00实际上出现了两次。为了避免歧义,根据RFC5545,看到这个时间的时候,会认为是更早的时间点。所以,除非你用他国的时区来预约,否则美国老板是不可能让你在重叠的第二个小时内开会的。6.阅读更多并参考维基百科:ISO8601-Astandardforexpressingvarioustimeobjectswithstringshttps://en.wikipedia.org/wiki/ISO_8601RFC3339-AgeneralimplementationoftimeanddateontheInternetProposalhttps://www.rfc-editor.org/rfc/rfc3339RFC5545-iCalendar互联网日历应用规范https://datatracker.ietf.org/doc/html/rfc5545Stackoverflow:夏令时和时区最佳实践[关闭]-技术实施建议https://stackoverflow.com/questions/2532729/daylight-saving-time-and-time-zone-best-practicesStackoverflow:如何存储重复日期并牢记夏令时-技术实施建议https://medium.com/@vivekmadurai/how-to-deal-with-date-and-time-across-time-zones-39b1bd747f35Medium:如何处理跨时区的日期和时间-技术实施建议https://medium.com/@vivekmadurai/how-to-deal-with-date-and-time-across-time-zones-39b1bd747f35Microsoft365:行为和f日期和时间字段的格式选项-Microsoft的时间和日期字段文档https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/customize/behavior-format-date-time-field?六ew=op-9-1TimeChange2021inUnitedStates-如何调整2021年美国夏令时https://www.timeanddate.com/time/change/usa?year=2021
