MongoDB $用持续时间减去日期,并考虑给定时区的DST更改

时间:2019-04-01 14:06:20

标签: mongodb time aggregation-framework mongoid dst

我们有一个特殊的需求,我们需要在时间字段上进行汇总,同时考虑到特定的“边距”并处理跨越DST边界的情况。

假设我们有一个项目,该项目在starts_at时间开始并在ends_at时间结束,我们实际上想将所有从starts_at - safety_marginends_at创建的事件进行分组>

所以我们有这样的文档

project: { "starts_at": "2019-04-01T10:28:05.711Z", "ends_at": "2019-01-29T10:28:05.711Z" }

safety_margin的当前常量为1周(在我们的MongoDB聚合中换算为毫秒)

在我们的汇总中,我们处于以下阶段

{ '$project': {
  safety_margin_starts_at: {
    '$subtract': ['$starts_at', safety_margin_duration]
  },
}

对于具有给定上下文的DST,这将完全失败:

  • 法国时区(4月1日之前为+2,4月1日之后为+1)
  • 一个项目始于1月1日或4月(因此FR中的+1偏移),并且具有安全裕度,所以它变为3月25日(偏移+2)

在我们的规格中,这将产生错误

project.starts_at               # => Mon, 01 Apr 2019 15:35:52 CEST +02:00
project.safety_margin_starts_at # => Mon, 25 Mar 2019 15:35:52 CET +01:00
# [Running our test]

expected 2019-03-25 13:35:52.357000000 +0000 to be within 0.1 of 2019-03-25 15:35:52 +0100
# The first figure is the one returned from the aggregation

我们的应用代码实际上进行了智能减法,其中starts_at - safety_margin_duration变为同一天/同一小时,而与扣除天数时的夏令时无关。是否有可能在MongoDB中重现此行为? 您是否有解决这些问题的技巧?

也许这可能是一个解决方案,但是我听说过一种新的方法来处理聚合时间,这可以解决我的问题吗?

2 个答案:

答案 0 :(得分:1)

处理带时区的日期的最佳实践是在UTC中执行所有数学运算。这意味着:

  • 如果用户输入的时间是当地时间,则在存储之前将日期/时间转换为UTC。
  • 执行所有UTC时间的操作。
  • 向用户呈现/演示日期/时间时,请转换为该时间所在的时区(用户查看时间或最初输入时间的时区)。

以下两个在本地(非UTC)时间上的操作之间也存在差异:

  • 增加24小时的时间
  • 为时间增加1天(时间可能会更改23、24或25小时,具体取决于是否超过了DST边界)

应用程序需要弄清楚它想要在哪里的两种行为。

鉴于问题中提供的代码,如果starts_atsafety_margin_starts_at是模型字段,则应将时间存储在UTC中。

Mongoid文档在此处提供有关时区的其他指导:https://docs.mongodb.com/mongoid/master/tutorials/mongoid-configuration/#time-zones

答案 1 :(得分:0)

示例文档中的日期采用UTC,没有DST,因此可以安全地减去毫秒。 我猜这是您的代码中导致错误的原因。

  

预计2019-03-25 13:35:52.357000000 +0000在2019-03-25 15:35:52 +0100的0.1范围内

13:35和15:35之间有2个小时的时差,这看起来不太正确。

这是汇总法国时间的方式:

{ '$project': {
  safety_margin_starts_at: {
    '$subtract': ['$starts_at', safety_margin_duration]
  },
},
{ '$addFields': {
        safety_margin_starts_at_fr: { $dateToString: {
            date: "$d",
            format: "%Y-%m-%dT%H:%M:%S.%L%z",
            timezone: "Europe/Paris"
        } }    
} }