2

我正在开发一项功能,该功能将计算出有多少工人在值班并为他们的轮班打卡(或不打卡)。

工作人员“打卡”和“下班”以及这些事件的时间戳将以 UTC 格式存储,因为这似乎是出现最多的建议。

但是班次从固定的当地时间开始,例如,一个班次总是从 07:00 开始,无论夏令时如何,并在例如 14:00 结束。

轮班工人被“分配”到轮班。

第一个要求是能够知道有多少工人“在这个时间点”值班(打卡)——一种状态检查。

第二个要求是能够获得过去一天的报告,例如单个工人(该工人是否在整个班次中值班,他是否迟到,他们是否迟到并要求支付加班费等)

所以不要假设一切都只会在一个时区中,我们的想法是将时区与班次的定义一起存储在数据库中(班次记录可能包含本地开始时间、结束时间和本地时区”姓名”。)

问题 1:存储本地时区的推荐格式是什么?有没有一种方法可以存储轮班的开始/结束时间,让我可以编写一个 SQL 查询,将这些时间与提供的 UTC 时间进行比较。

问题2:还有其他建议吗?我是否应该将时钟输入/输出时间存储为本地以与轮班开始/结束时间相同?或者我应该做一些魔术并将所有内容存储为UTC时间......但是如果将其存储为“2:00 UTC”,如何计算出当前的当地时间开始时间......将其转换为当地时间需要始终保持不变,例如当地时间 7:00,无论 DST 是什么......

4

1 回答 1

12

首先,了解SQL 标准几乎没有涉及日期时间。各种数据库产品的数据类型、定义和行为差异很大。

幸运的是,Postgres日期时间数据类型日期时间函数的处理非常丰富。但是您必须仔细研究文档并进行实验,以便了解行为。

07:00,不考虑夏令时

实际上,这不是不顾一切,而是尊重 夏令时 (DST)。DST 正在改变 7 AM 的含义,将其推迟或提前一小时。正确遵守夏令时是您问题的核心。

为了其他读者,关于术语的一个词:问题上下文中的“本地时间”一词是指日期和/或一天中的时间,而不考虑时区或与 UTC 的偏移量

因此,本地日期时间并不代表时间线上的一个点,而是可能点的粗略概念。在新西兰奥克兰,早上 7 点比在巴黎 FR 早得多。反过来,法国巴黎的早上 7 点比加利福尼亚州蒙特利尔的早上 7 点要早得多。因此,如果没有时区,“本地”值就没有意义。在 Postgres 中,这些“本地”类型是:

  • timestamp without time zone(日期和时间)
  • date (仅限日期)
  • time without time zone (仅限一天中的时间)

时间线上的实际时刻需要一个区域或偏移量。让我们称这些“分区”类型,因为没有更好的词。在 Postgres 中,这将是:

  • timestamp with time zone(日期和时间,调整为UTC
  • time with time zone(时间,调整为 UTC)

了解 Postgres从不保存任何时区信息非常重要。尽管有“带时区”的名称,但该时区并未保存为数据的一部分。相反,“with time zone”的意思是“<em>尊重 time zone”,因为 Postgres 将输入值调整为UTC

  • 对于分区类型,Postgres 在输入值中应用任何指定的区域/偏移量信息以从​​该区域/偏移量调整为 UTC。然后丢弃指定的区域/偏移信息。
  • 相反,对于“本地”类型,任何指定的区域/偏移信息都会被完全忽略

问题 1:存储本地时区的推荐格式是什么?

因此,根据您的业务需求和规则,除了日期时间值之外,您可能还需要单独记录区域/偏移量。Postgres 和 SQL 标准都没有为 zone 或 offset 指定数据类型。所以我建议存储为文本。

  • 与 UTC 的偏移
    • 使用由ISO 8601标准为指示符指定的格式。这些格式主要是 (a)Z表示 UTC(祖鲁语的缩写,表示 UTC)和 (b)±hh:mm表示+在 UTC 之前(如印度)和-减号字符,或者HYPHEN-MINUS)表示在 UTC 之后(如美洲)。
    • 虽然 ISO 8601 允许在小时内省略填充的零并省略冒号,但我建议您永远不要这样做。许多协议和软件实现都期望这些位,没有它们可能会中断。
    • 示例:对于印度,比 UTC 提前五个半小时,+05:30
  • 时区
    • continent/region在数据库中使用由 IANA 定义的格式的正式名称tzdata,以前称为 Olson 数据库。请参阅此最近的区域列表
    • 示例:对于印度,Asia/Kolkata

更多术语:与 UTC 的偏移量是 UTC 之前或之后的小时数和分钟数和秒数。时区是一个偏移量加上一组处理异常的规则,例如夏令时 (DST)。因此,当您知道时区时,总是更好地使用它。

要回答这个问题:
您的解决方案需要“本地”和“分区”类型。

要记录班次的定义,您需要一个“本地”时间,即time without time zone. 当您记录该班次通常应从早上 7 点开始时,您不希望 Postgres 更改或调整该值。

要记录特定班次的概念time without time zone,您将 (a) 开始时间记录为, (b) 日期记录为date。通过应用偏移量或区域,您可以确定 UTC 值。您可能还想/而不是记录 UTC 值本身。但请注意不要在未来太远,因为各地的政客都非常喜欢在几乎没有提前通知的情况下重新定义时区。

要记录工作实际打卡的时刻,您需要 UTC 时刻,timestamp with time zone类型。如果需要,您可以输入分区值,Postgres 将调整为 UTC。如问题中所述,在处理时间线上的实际时刻时,几乎总是最好在 UTC 中处理和存储数据。

对于这两种类型,您可能希望也可能不希望另外记录预期的时区。

Postgres 会话具有默认时区。我建议你永远不要依赖它。最好始终在 SQL 代码和/或输入数据中指定预期的时区。手动阅读数据时,您可能会发现将会话默认设置为 UTC 或区域很方便,但我不会在代码中这样做。

有没有一种方法可以存储轮班的开始/结束时间,让我可以编写一个 SQL 查询,将这些时间与提供的 UTC 时间进行比较。

如上所述,您会将班次的一般概念记录为“本地”。例如,“2015 年,杜塞尔多夫和底特律工厂在早上 6 点开始,而德里工厂在早上 7 点开始,但在 2016 年,所有三个工厂都在早上 7 点开始”。要记录任何一个实际班次,请以 UTC 记录,但您可能还想记录“本地”值以供人类阅读。

我是否应该将时钟输入/输出时间存储为本地以与轮班开始/结束时间相同?

不,不,当然不是。时间线上的任何实际点、真实时刻都应以 UTC 记录。根据需要使用timestamp with time zone并让 Postgres 将输入调整为 UTC。虽然通常我建议您的应用程序的编程事先使用 UTC。

或者我应该做一些魔术并将所有内容存储为UTC时间

不需要魔法,只需一致地处理您的数据以始终包含时区(或偏移)数据并调整为 UTC。在您的应用程序编程和数据库中直接获取这一点。例如,在Java中,传递对象而不仅仅是日期时间值的字符串。使用JDBC 4.2,数据库可以交换OffsetDateTime(和,可选地,InstantZonedDateTime)对象。(避免麻烦的java.util.Date&.Calendar类,现在是遗留的。)

Java(传统和现代)和标准 SQL 中的日期时间类型表

如果存储为“2:00 UTC”,如何计算班次的当前本地时间开始时间

如果您有 UTC 格式的日期和时间,您始终可以应用时区(或偏移量)来查看“本地”值。

日期时间处理是棘手的东西。所以想清楚,给自己时间去学习,练习,练习,再练习。提示:(a) 学习 24 小时制,(b) 在工作编程时,以 UTC 思考,在办公桌上放一个 UTC 时钟,忘记你自己的个人本地时区。主要在您自己的本地时区思考,并不断地与 UTC 来回转换,会让您发疯并导致错误。

于 2016-12-12T22:57:09.520 回答