【问题标题】:How to deal with the timezone issue when storing dates in utc using mongod?使用mongod在UTC中存储日期时如何处理时区问题?
【发布时间】:2013-08-19 16:43:42
【问题描述】:

我有一个 mongodb 集合,其中每个文档都有一些属性和一个 utc 时间戳。我需要从集合中提取数据并使用聚合框架,因为我使用集合中的数据在用户界面上显示一些图表。但是,我需要根据用户的时区进行聚合。假设我知道用户的时区(通过浏览器的请求或以其他方式传递),有没有办法使用聚合框架根据 [client's] 时区进行聚合?

【问题讨论】:

  • 您能否更清楚地了解您正在尝试做什么,为什么您不能只转换为客户的时区聚合后?你总是可以使用 mapreduce 而不是聚合框架——它会更慢,但允许你需要做任何类型的临时计算
  • 实际上,我需要根据用户的时区为用户生成每周报告。对于报告,我需要使用聚合框架。问题是,我应该在聚合时考虑用户的时区。聚合后我不能这样做,因为这会导致错误的结果。 Map reduce 是一个选项,但我需要这个按需,我在搜索 StackOverflow 时看到它不应该用来代替查询。我希望有办法。
  • 我不太了解 MongoDB,无法回答,但我相信该方法类似于我为 RavenDB hereFoo_ByDate_MultiZone index here 描述的方法。如果 Mongo 允许类似的东西,你会在 Map 中这样做,就像我在 Raven 中那样。
  • 没有理由在 MongoDB 中需要这样做。这就是应用层的用途。在 UTC 中进行所有聚合,然后按其时区调整输出。如果用户也更改时区,将所有内容保持在 UTC 中将确保事情保持一致。
  • @Mason - 绝大多数时候,我同意你的观点,应用程序代码是最好的地方。这里的区别在于它是聚合的。要按天分组,您必须知道当天的界限。每个时区都有不同的开始和结束概念,无论是在原始 UTC 术语中,还是在处理 DST 的变化时。如果 Mongo 不允许您在索引地图中执行此操作,那么另一种方法是在进行聚合之前 预先计算不同区域中的多个本地时间并将它们与文档一起保存。不过这会变得很乱。

标签: mongodb timezone mongoid aggregation-framework


【解决方案1】:

您的要求目前正在MongoDB issue SERVER-6310 中讨论。

我在来自a discussion thread 的链接中找到了这个。

这个问题对于任何按日期分组都很常见,包括 SQL 数据库和 NoSQL 数据库。事实上,我最近在 RavenDB 中谈到了这个问题。有很好的问题描述和RavenDB解决方案here

MongoDB 问题讨论了一种解决方法,类似于我在上面的 cmets 中描述的。您预先计算您感兴趣的当地时间,然后按这些时间分组。

用任何一种方法都很难覆盖世界上的每个时区。您应该确定一小部分对您的用户群有意义的目标区域,例如我在 RavenDB 文章中描述的每个办公室的方法。

更新:2017 年 7 月(版本 3.5.11)在 MongoDB 中解决了此问题。 解决方案在上面的第一个链接中进行了描述,但简而言之,他们为日期引入了新的对象格式在聚合表达式中:{ date: <dateExpression>, timezone: <tzExpression> } 允许您指定聚合时使用的时区。有关 Mongo 文档中的另一个示例,请参阅 here

【讨论】:

  • 谢谢。希望 MongoDB 以后能提供这样的选择。
  • @Matt,在最新的 mongodb 2.6.x 中有没有可用的解决方案
  • @rams - 我没有检查,但问题仍然标记为打开/未解决。
【解决方案2】:

除了 Matt Johnson 提到的 SERVER-6310 之外,另一种解决方法是使用 $project 运算符添加或减去 UTC 时区,以将时间“转移”到正确的本地区域。事实证明,您可以以毫秒为单位增加或减少时间。

例如,假设我有一个名为 orderTime 的日期字段。我想查询 EDT。那是距 UTC 的 -4 小时。那是 4 * 60 * 60 * 1000 毫秒。

因此,我将编写以下投影以在当地时间获取 day_ordered 以获取我的所有记录:

db.table.aggregate( 
    { $project : { orderTimeLocal : { $subtract : [ "$orderTime", 14400000] } } },
    { $project : { day_ordered : { $dayOfYear : "$orderTimeLocal" } } })

【讨论】:

  • 谢谢星体。你的答案很完美。我真的不想为了做一个简单的时区调整而不得不使用 map reduce。
  • 这个答案很有意义。如果你想查询包含 EST 和 EDT 的“东部时间”,你会怎么做?
  • 解释中不是"$lastActivity",而不是答案中的"$orderTime"
  • 由于夏令时,这将不起作用。偏移量可能是 -4 或 -5,具体取决于日/月。
  • 同意 Ricardo Macario
【解决方案3】:

上面建议的每种方法都可以正常工作,但是由于有一个新版本的 mongodb,从 2.6 开始,您可以在聚合框架中使用 $let,这将让您动态创建变量,从而避免需要 @ 987654323@分组前。现在您可以使用$let 创建一个变量来保存本地化时间,并在$group 运算符中使用它。

类似:

db.test.aggregate([
   {$group: { 
        _id: { 
             $let: { 
                 vars: {  
                     local_time: { $subtract: ["$date", 10800000]} 
                 }, 
                 in: { 
                    $concat: [{$substr: [{$year: "$$local_time"}, 0, 4]}, 
                              "-", 
                              {$substr: [{$month: "$$local_time"}, 0, 2]}, 
                              "-", 
                              {$substr: [{$dayOfMonth: "$$local_time"}, 0, 2]}]
                 }
              }
         }, 
         count: {$sum: 1}
     }
 }])

请注意,您在块/变量的定义中使用了$let,并且该块/变量的值是子表达式"in" 的返回值,其中使用了上述定义的变量。

【讨论】:

  • 这在涉及夏令时的情况下也不起作用,因为您假设可以减去固定时间,此时正确的值可能会有所不同,具体取决于本地时间是否处于夏令时与否。
猜你喜欢
  • 1970-01-01
  • 2011-06-06
  • 2022-08-15
  • 2010-10-05
  • 2019-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多