0

我正在寻找一种 JavaScript 算法来将本地日历日期时间转换为自 Unix 纪元以来的 UTC 毫秒(或Date表示相同的对象)。对某些相对日历日期执行此操作的典型且通常有用的方法YYYY-MM-DD hh:mm:ss.zzz是:

# Close, but not quite monotonic
(new Date(YYYY, MM-1, DD, hh, mm, ss, zzz)).getTime()

JavaScript 实现有其处理 DST 的方法(阅读:夏令时、夏令时、必要时您自己的区域设置等效项),不幸的是它们不适用于手头的应用程序,而是需要单调的本地时间(但如有必要可能会失真)。

转换必须是单调的,因为对于两个相对的日历日期(即不知道时区、不知道 DST)A 和 B,转换函数 C 是这样的

If A is strictly earlier than B then C(A) ≤ C(B)
If A is simultaneous to B then       C(A) = C(B)
If A is strictly later than B then   C(A) ≥ C(B)

(这并不是指对时间获取函数的连续调用严格不递减的意义上的单调性——这个应用程序没有当前时间的概念,也不需要类似的东西。)

我已经开始着手自己的实现,但它可能会变得复杂,我认为也许其他人有更好的想法。

问题是:

  • 这已经实施了吗?
  • 如果没有,是否有比我在下面概述的更明智的方法来实现这一点?
    • 不连续性发现启发式方法是否适用于全球所有已知的 DST?

JavaScriptDate的行为

以下是 2012 年美国 DST 的极端情况。这些值是来自 Firefox 的实验结果。在这里,让函数 C 表示使用给定的本地日期和时间创建Date对象的结果,然后使用该getTime()方法检索 UTC 毫秒。

  • 跳过时间:2012 年 3 月 11 日,夏令时开始日期,当地时间 02:00 开始的时间被跳过:01:59 之后的分钟为 03:00。在 Firefox 中,较晚的输入时间可能会导致较早的解析时间;例如 01:45 < 02:15,但 C(01:45) > C(02:15),所以音阶不是单调的。
  • 双倍小时:在 2012 年 11 月 4 日,夏令时结束日期,从 01:00 开始的小时在当地时间出现两次:白天 01:59 之后的分钟是标准时间 01:00,然后是 01:59 之后的分钟标准时间是标准时间 02:00。在 Firefox 中,C(01:30) 对应于后面的重复,标准时间 01:30。
    • 只要保证解析行为有利于以后的时间,这不会破坏单调性。(我没有这个保证的文档,但它可能来自 ECMA-262 中的某种语言。)

必需/首选行为

在这里,让函数 C 表示具有所需行为的转换。

  • 跳过的小时:跳过的一分钟的日期应解析为接下来的第一个未跳过的分钟。例如,在 DST 开始日期,02:15、02:30 和 02:45 将解析为下一个未跳过的分钟 03:00;换句话说,C(02:15) = C(02:30) = C(02:45) = C(03:00)。
    • 未跳过的时间将保持不变:将 01:45 < 02:15 < 02:45 < 03:15 与 C(01:45) < C(02:15) = C(02:45) < C(03:15) 进行比较.
  • 双倍小时:一分钟多次出现的日期应仅解析为第一次出现;例如,DST 结束日期的 01:30 将被解析为 01:30 日光时间而不是标准时间,因为这是两者中较早的时间。
    • 和以前一样,只是保证了更早的时间。

这些规则大致基于 Vixie cron 的规则。但是,此应用程序不处理当前时间的概念,因此不具备监视时钟以了解时间变化所需的状态。它需要一些其他方法来确定是否以及何时将跳过或加倍时间。

顺便说一句,作为一个附加要求,实现不能假定它在美国语言环境中运行;它需要在国际范围内工作,并尽可能使用检测而不是配置。

实施思路

我认为可能用于检测日期是否不连续的一件事是测试本地日期跨度的宽度±1个日历日。如果两个日期的 UTC 时间之间的差异小于或大于 48 小时,则分别意味着某些时间已被跳过或加倍。

  • 如果跳过,我们可能会进一步确定是否跳过给定时间本身,如果在转换为 UTC 并返回之后,hh:mm:ss.zzz读取不同。如果是这样,则将时间解析为中断后的第一分钟。
  • 如果加倍,我们可能会在以后的重复中确定所有时间的范围。如果给定时间落在后面的重复中,则恢复到较早的时间;否则,它会被单独留下。

这两者都可能需要不连续点的确切位置,例如,这可以通过以 ±1 日期为界的二分搜索来完成。

这种启发式方法可能因多种原因而失败;虽然我的印象是它们不太可能,但夏季时间规则在世界范围内是奇怪且不一致的:

  • 如果在同一 3 个日历日内,任一方向上可能出现多个中断。(在美国,每年有两次,相隔几个月。我怀疑任何地方的调整量都超过了,比如说,四个小时。)
    • 如果不连续性是互补的,那么它们可能一开始就无法检测到。
    • 在任何情况下,简单的搜索都会假设在该范围内只有一个不连续点。
  • 如果单个不连续性可能导致(近)3 个日历日的持续时间。(在美国,每个不连续时间占一小时。我相当肯定夏季时间调整永远不会在任何地方都以天为单位。)
4

1 回答 1

0

至少对于当前的美国规则,与上述必需/首选行为一致的实现现在可用。总体而言,该机制并不是非常低效。在最坏的情况下(跳过的一分钟),它是 O(lg n),其中 n 是夏季和冬季之间变化的分钟数(我住的地方是 60 分钟),在所有其他情况下,它是 O(1)。

  • 输入是“面孔”(不知道 DST 的日历日期和时间)。
  • 输出是一个Date基于输入人脸设置的本地人脸。
    • 如果输入人脸恰好代表一个本地日期,则输出Date与将人脸的统计信息传递给Date构造函数一样。
    • 如果输入面表示零个本地日期(由于 DST 向前跳过),则输出Date反映跳过后的第一分钟。
    • 如果输入面表示两个本地日期(由于 DST 重复),则输出Date反映两者中较早的一个。

实施注意事项:

  • .getTimezoneOffset()方法用于确定两个日期是否位于不连续点的相对两侧。
  • Date通过检索该面之前 24 个本地小时的偏移量,可以找到该面可能靠近的任何不连续点之前的偏移量。
  • Date通过将其统计信息传递给Date构造函数以在本地进行解释,将面转换为 a 。
  • 如果转换后的本地人脸与Date输入人脸不同,则此人脸在本地时间不能直接表示;它已被跳过。
    • 转换后Date的被视为无效。
    • 不连续性之后的时区偏移量是通过检索Date输入人脸后 24 个本地人脸小时的偏移量来确定的。
    • 找到不连续性前后偏移之间的差异。从输入面之前的那段时间到之后的那段时间(根据定义,这两者都应该可以表示为本地日期),使用二进制搜索来定位不连续之后的第一分钟。代表这一分钟的ADate是输出。
  • 如果转换后的局部面Date与输入面相同,
    • 如果转换Date后具有不连续前的偏移量,则它是正确的。
    • 这包括任何不靠近 DST 变化的面孔;如果没有不连续性,那么当天的所有时间共享相同的偏移量。
    • 就算脸在翻倍时间,早期的解释是正确的。(这是否会发生可能取决于实现。)
    • 如果转换Date发生在不连续之后,
    • 如果它在不连续点后至少偏移差的时间是正确的。
    • 如果它在从不连续到之后一个偏移差的时间范围内,它是在后来的一倍时间解释中。通过减去偏移差可以找到正确的日期,从而产生更早的解释。
    • 如果早一个偏移差的a具有早偏移,则ADate被确定在该范围内。Date由于转换Date后的偏移量较晚,因此可以确定不连续发生的时间比那晚。
于 2013-01-09T20:47:00.663 回答