首页 > Java > java教程 > 正文

应用程序和后端开发中的边缘案例 - 日期和时间

WBOY
发布: 2024-08-09 09:23:32
原创
866 人浏览过

介绍

您可能认为处理日期和时间很容易。我们有一分钟持续 60 秒,一小时持续 60 分钟,一天持续 24 小时,一周持续 7 天,一个月持续 28 到 31 天,依此类推。

这里当然不需要火箭科学......

好吧,事实并非如此!

我们将展示您在应用程序和后端开发过程中可能遇到的与日期和时间相关的陷阱和陷阱。

测量单位

让我们从测量单位开始,从最小到最大。

秒和毫秒

每天使用的最小单位是秒。它也是 Unix 时间的基础。

但是,在某些编程语言(例如 Java)中,最常见的单位是毫秒(秒的 1/1000),例如 System.currentTimeMillis() 方法。

这种分歧可能会导致许多错误。

如果您从系统外部收到数值,即使在阅读文档之后,乍一看也可能不清楚它使用的测量单位!

查看短信和彩信内容提供商(数据库)列中的日期字段。两个文档都只说:

  • 收到消息的日期。

  • 类型:INTEGER(长整型)

但是,短信使用毫秒,而彩信使用秒。惊讶吗?好吧,这种情况可能会发生,特别是如果此类 API 是由不同的人设计的。

遇到这样的情况你会如何处理?那么如何才能避免它们呢?

幸运的是,我们可以制定几个通用规则:

  1. 始终确保传入数据的格式。
    请在野外检查它,因为文档可能不正确和/或过时。当您在开发过程中查找错误时,通常很容易发现错误,例如距未来 5 万年太远的日期。例如,当毫秒被视为秒时,就会出现此结果。

  2. 如果您对发送值的一方有任何影响(例如,系统正在设计但尚未有人使用),请考虑标准化文本格式。
    在这里,我强调标准化(例如 ISO 8601)而不是某些自定义格式(稍后我们将扩大这个主题)。几年后,原来团队中的任何人都可能不再为该项目工作,并且文本格式对于新开发人员来说很容易理解,因为他们不需要查看文档。
    然而,在性能关键型设备中,数字格式可能更好。

  3. **使用专用类来处理日期/时间/持续时间值,而不是原始整数。
    **在 Java 世界中,我们有一个 java.time 包,其中包含许多有用的类,例如 Instant 或 Duration。如果有一个整数叫做eg。 eventDuration,不知道它存储的是秒还是毫秒,甚至可能是天?

  4. 如果您必须处理无法完全重构的原始值(整数、长整数等),例如遗留代码,在变量/字段名称中包含测量单位 .
    例如,eventDurationSeconds 是明确的。

闰秒

您可能听说过闰年。它们与“正常”的不同之处在于多了一天。我们还有闰秒!

它们比非闰秒长吗?

嗯,这要看情况!

首先,让我们从一些理论开始。地球正在减速;实际上,这不是一个哲学陈述,而是一个经过科学证明的事实。它被称为 delta-T。

好的,那么这在实践中对我们意味着什么?嗯,我们的时间测量单位有一个标准化的长度。基本单位是秒,定义为:

9 192 631 770 个辐射周期的持续时间,对应于铯 133 原子基本未扰动基态的两个超精细能级之间的跃迁。 (来源:bipm.org)

该持续时间是恒定的,并告知所有其他从秒导出的单位,例如。一分钟由 60 秒组成,一小时由 60 分钟(3,600 秒)组成,依此类推。

但是,如果地球减速(白天变长),我们必须以某种方式适应这种减速,以确保我们的单位测量的时间与现实一致。这是通过插入额外的秒——闰秒来完成的。

一年中只有 2 个可用的闰秒时段:六月底和 12 月。最结束意味着最后一天(分别为 30 日或 31 日),即 UTC 23:59:59 之后(因此当地时间有所不同)。

在通常的最后几秒之后,会在进入第二天之前插入额外的闰秒。因此,我们可以得到 23:59:60(一分钟 61 秒——从 0 开始计数)。

Edge Cases in App and Backend Development — Dates & Times

由于减速不是恒定的,所以闰秒的插入是不规则的。最近一次(2021 年 4 月撰写本文时)发生在 2016 年 12 月,距今已有 4 年多了。不过,倒数第二次是在2015年6月,两者只相差1.5年。

在一些我们可以设置时间的地方——比如物理挂钟或一些框架API,可能无法观察和/或设置闰秒时间。

例如,老式的 Date Java 类甚至支持双闰秒 - 范围从 0 到 61,因此 62 秒是可能的!

但是,java.time 包中的现代 Instant 类不会向程序员公开闰秒。闰秒在一天的最后 1,000 秒内均匀拉伸(这些秒更长)。

请注意,理论上,闰秒也可以是负数。但目前还没有发生。

这意味着一分钟可能有 59 秒,这可能会导致巨大的问题。例如,如果某个操作计划在 23:59:59.001 发生,但结果发现所需的时间不存在......

幸运的是,与扩展可见秒类似,也可以缩小,对程序员完全透明。

我们知道第61秒可以存在,但是第62秒呢?嗯,文档说:

同一分钟内发生两个闰秒的可能性极小,但此规范遵循 ISO C 的日期和时间约定。

确实,在 C 规范中我们有一个 [0, 61] 范围。但为什么?同样的问题也困扰着 1992 年 tzdata(时区数据库)的作者 Paul Eggert。我们可以在档案中读到:

*“因为一年中确实有两个闰秒(在不同的日子),而 ANSI C 委员会的某人认为它们是在同一天出现的。” *— 来源 groups.google.com。

几十年前发生的轻微解释错误现在仍然可见,因为新的实现需要向后兼容这些标准。

让我们看看应用程序和后端开发中的其他边缘情况。

那些日子

分钟和小时不涉及任何意外情况,所以让我们跳到日子。

一天是什么?这取决于!你可以说它从00:00:00到23:59:60持续24小时(包括闰秒?)。好吧,无论后者是否普遍正确,一天并不总是持续 24 小时,因为它可能是 23、25 甚至 21 和 27!为什么有这些价值观?答案是……

夏令时

所谓的 DST 或“夏令时”会在温暖的月份提前时钟,以减少功耗。黑暗开始的时间比“正常”(非 DST)时间晚。如今,夏令时的必要性存在争议,因为它有很多缺点。由于这些原因,一些国家最近甚至停止了 DST(例如俄罗斯在 2014 年)。

夏令时有哪些问题?来看看吧!

我不会讨论诸如忘记手动调整挂钟或延迟公共交通之类的事情,而是,我将重点关注与后端和应用程序开发相关的方面。

您可能会认为,在夏季,我们将时钟提前 1 小时。但情况并非总是如此!世界上有些地方的时差为 3 小时(如凯西)或 30 分钟(如豪勋爵岛)。

夏季,并不完全像名字所暗示的那样,也可以覆盖春季或秋季的部分时间,但一般来说,它与温暖的月份有关。反过来,这取决于半球。

欧洲有夏天,澳大利亚有冬天,反之亦然。因此,从长远来看,澳大利亚的夏令时发生在欧洲的冬季!

此外,夏季的时间定义取决于司法管辖区。在欧盟的所有国家中,时间都会在同一时刻更改,因此例如柏林和伦敦之间的时差始终为 1 小时,无论我们是在夏季还是冬季。

但是让我们考虑一下悉尼和伦敦之间的时差。在这种情况下,这取决于一年中的哪一天!这是因为悉尼开始和停止遵守 DST 的日期与伦敦不同。

Edge Cases in App and Backend Development — Dates & Times
灵感:timeanddate.com

例如,从一月份开始,我们有 10 个小时的时差。悉尼当时遵守夏令时,但伦敦不遵守。 3月底,伦敦开始实行夏令时,而悉尼州保持不变,因此时差缩小至9个小时。然后到了四月份,悉尼停止遵守夏令时,所以我们有 8 个小时。我们有 3 种不同的偏移量。所以关于悉尼和伦敦时差的问题有3个答案!

美国、欧盟成员国或澳大利亚等国家在夏令时方面拥有稳定的管辖权。转变何时发生是预先众所周知的。然而,情况并非总是如此,因为一些国家可能会意外地改变法律。例如,2020 年斐济的规则发生了变化,公告仅在几个月前发布。

在一些正式庆祝斋月的伊斯兰国家,情况更为复杂。如果他们也遵守夏令时,后者可能会……暂停(在斋月期间)。

这意味着夏令时转换可能会发生多次(来回),甚至一年内发生几次。此外,斋月是根据伊斯兰(农历)日历计算的。预测用于计算斋月边界日期,它可能并不总是准确的。例如,2020 年巴勒斯坦的预测结果短了大约一周。更改仅提前几天应用。

大多数系统使用 IANA 时区数据库作为 DST 转换时刻的来源。该数据库通常每年都会有一些更新。

请注意,处理 DST 可能会对算法产生影响。让我们考虑几个典型场景。

如果我们将闹钟安排在特定的墙上时间(例如 02:30),则可能会发现这样的时刻不存在(当 DST 转换期间时间从 02:00 更改为 03:00 时)或当时间向后改变时,它会出现两次。比如如果不做备份,不给药或者给两次药,那后果可能就很可怕了。

如果用户位于观察 DST 的时区,另一个常见的影响包括周期性触发时间变化。例如,如果您安排在 bitrise.io 上的构建在 10:00 触发,它可能会突然在 11:00 开始触发。

发生这种情况是因为底层逻辑不知道 DST,并且用户可见的时间仅在渲染 UI 时计算。绝对时刻始终相同,但用户可见的时间会根据夏令时而变化。通常,这不是顾客所期望的。

几周

一周的第一天是哪一天?如果您住在欧洲,您可能会说星期一。在美国是周日,在阿拉伯国家是周六。这些事实可能会影响算法构建和/或渲染日历。

一年中的第几周呢?你可能认为一切都是从1月1日开始的。

正如您可能已经猜到的,情况并非总是如此!

根据 ISO 标准,一年的第一周必须包含星期四。例如,2021 年 1 月 1 日是星期五,因此属于上一年的最后一周。 2021年的第一周从1月4日开始!然而,并非所有语言环境都使用与 ISO 标准相同的规则。

几个月

世界上大多数人使用的公历有 12 个月,从一月到十二月。然而,其他类型的日历可能有更多的月份。例如,希伯来历可能有 13 个月。第 13 个(在 IT 界)被称为 12 月。

这些年

通常,一年有 365 天。然而,每一个能被 4 整除的年份(例如 2020、2024 等)都是闰年,有 366 天(二月有 29 天,而不是正常的 28 天)。但是,如果它能被 100 整除(2100、2200 等),则它不是闰年。但 ?如果能被 400(2000、2400 等)整除,则为闰年。

幸运的是,您不必(也不应该!)尝试自己实现此类区别。您应该使用给定编程语言的众所周知的日期类/函数库。

知名服务中存在许多与不正确的闰年计算相关的严重错误。甚至还有一个术语:闰年问题。

时区

当地时间取决于经度。虽然在某个特定地点是中午,但在地球的另一边也是午夜。

旅行时连续调整时钟是不切实际的,因此地球被划分为多个区域,覆盖具有相同当地官方时间的区域。

对于小国家或最大国家(如澳大利亚、美国或俄罗斯)的某些地理区域,区域边界通常等于国家边界。

Edge Cases in App and Backend Development — Dates & Times
来源:timeanddate.com

由于某些地方的政治和经济原因,官方时间与“合法”时间有很大差异(仅考虑经度/太阳时间)。例如,在西班牙,该区域与中欧大陆大部分地区相同,而不是英国,根据经度,该区域更接近。

大多数时区都有积分偏移(与 UTC — 标准时间的差异),例如。柏林 +1 小时(夏令时 +2)或布宜诺斯艾利斯 -3 小时(阿根廷,无夏令时)。然而,偏移量可以包括半小时,例如。在孟买(印度),我们的时间为 +5:30(无夏令时)。

如果这还不够,也可以住宿,就像在加德满都(尼泊尔)我们有+5:45h!

幸运的是,在撰写本文时,还没有更细粒度的偏移。然而,它们过去曾存在过,例如荷兰的+0:20。

由于我们的时钟有 24 小时这一事实,人们可能会认为这也是一系列可能的偏移量。 +12:00 和 -12:00 加起来就是 24。

然而,时区之间最远的时间距离是26小时!

这是可能的,因为我们有 +14:00 偏移量。大洋洲的几个国家都采用了这种方式,因为它们与澳大利亚关系密切,所以相差几个小时比超过 20 个小时更实际,这在大多数情况下会导致另一个日期。

让我们考虑一个航班转机查找器。对于往返航班,用户可以选择出发和回程日期/时间。可能很明显回程时间一定是在出发之后。

当然,这与事实相差甚远!

请记住,这些时间是在当地时区。

看一下这个例子:

  1. 00:30 从东京(出发时间+09:00)出发前往旧金山(出发时间-07:00)

  2. 当地时间前一天18:00抵达旧金山!

  3. 19:50 从旧金山返回(相对于最初出发时间仍早)

所以,你可以去某个地方,昨天就回来。看这个例子:

Edge Cases in App and Backend Development — Dates & Times

时区命名

您可能面临的应用程序/后端开发中的另一个潜在边缘情况与时区命名有关。

您可能会注意到,我们可以通过偏移量来调用时区,例如。 +01:00 或其标识符,例如。欧洲/柏林。请注意,后一种表示法具有多种好处:它包含有关 DST 转换的信息(柏林在夏季的偏移量为 +02:00),并且还保存历史数据。

例如,欧洲/华沙和欧洲/柏林区域现在似乎是相同的。它们全年都有相同的偏移量,夏令时转换也总是在同一时刻发生。

然而,过去并非总是如此!例如,1977年柏林根本没有夏令时,但华沙从4月3日到9月25日观察夏令时(与我们今天的完全不同)。

请注意,时区标识符是基于城市名称构建的。这是故意的,因为国家边界比城市名称更容易发生变化。

例如,克里米亚实际上已从乌克兰改为俄罗斯,但城市名称保持不变。无论我们需要当前数据还是历史数据,都可以使用欧洲/辛菲罗波尔区域。

日与夜间、周期与持续时间

假设某件事持续一天。因此,如果它从 09:15:00 AM 开始,那么我们可以添加 24 小时并获得结束时间(在本例中,仅是第二天的 09:15:00 AM)。

嗯,这并不总是那么容易!请记住,当时钟被人为地向前或向后调整时,我们可以进行夏令时转换。这意味着一天通常持续 24 小时,但偶尔也可能是 23、25 甚至更奇怪的值,例如 27 或 23 个半小时(你还记得凯西和豪勋爵吗?)。

在实现业务逻辑时,不要盲目地将天视为 24 小时,这一点很重要。您应该为此使用适当的日期/时间 API。例如,在 Java 中,我们有不同的类:

  • 期间,代表日历天 — 非常适合订阅有效性(以天、月或年为单位)

  • 持续时间,代表连续时间,例如。 24 小时,无论日历上的日期是什么。它非常适合测量操作的跨度,例如行程或自设备/程序启动以来的时间。

某些语言有不同的词来表示阳光明媚的状态(英语 - day)和连续 24 小时(英语 - nychthemeron,但并不常用)。

日期/时间表示:

Edge Cases in App and Backend Development — Dates & Times
来源:xkcd.com

与其他系统通信时(例如通过 REST API),就数据格式达成一致非常重要。如果您看到日期显示为 10/12/2021,则它可能采用美国格式(10 月 12 日)或英国格式(12 月 10 日)。

最好使用一些明确的表示!

对于日期和时间,我们有 ISO 8601 标准。请注意,不仅绝对日期/时间是标准化的,而且句点也是标准化的。使用正确的类型也很重要 - 本地(用于表示数据,如闹钟触发时间或出生日期)或分区(用于表示时间时刻,如事件开始或拍照)。

自定义的非标准化格式通常会导致混乱和错误,因此请避免使用它们。

在 UI 中显示日期和时间时,必须考虑用户的区域设置。显示格式取决于语言和区域。请看以下示例,它们都引用同一日期:

  • 21/4/18 — 美国英语

  • 18/04/2021 — 英国英语

  • 2021 年 4 月 18 日 — 罗马尼亚语

  • ١٨‏/٤‏/٢٠٢١ — 沙特阿拉伯,阿拉伯语,采用东阿拉伯数字

日期/时间库提供了预定义的格式类型,例如java.time中的FormatStyle。如果可能的话使用它们。否则,您可以使用标准化模式符号构建更多自定义格式。有关更多详细信息,请参阅 CLDR 文档。

值得注意的是,在月份、季度和星期几的情况下,也有独立的变体。它们仅在某些语言中有意义(在英语中无效)。例如,在波兰语中,四月被称为 kwiecień — 这是一个独立版本。但是,如果它与某一天(例如 4 月 18 日)相关,则文本将变为 18 kwietnia.

包起来

处理日期和时间并不简单。但是,如果您遵循上述标准并使用正确的类型,则可以避免巨大的错误。

我希望我对日期和时间边缘情况的见解对您的项目有用。祝你好运!

最初于 2021 年 4 月 26 日发布于 https://www.thedroidsonroids.com。

以上是应用程序和后端开发中的边缘案例 - 日期和时间的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!