【问题标题】:How to aggregate by year-month-day on a different timezone如何在不同时区按年月日汇总
【发布时间】:2013-09-22 00:34:52
【问题描述】:

我有一个 MongoDB,它以 UTC 存储日期对象。好吧,我想在不同的时区 (CET) 中按年月日进行聚合。

这样做,适用于 UTC:

    BasicDBObject group_id = new BasicDBObject("_id", new BasicDBObject("year", new BasicDBObject("$year", "$tDate")).
                append("month", new BasicDBObject("$month", "$tDate")).
                append("day", new BasicDBObject("$dayOfMonth", "$tDate")).
                append("customer", "$customer"));

    BasicDBObject groupFields = group_id.
            append("eventCnt", new BasicDBObject("$sum", "$eventCnt")); 

    BasicDBObject group = new BasicDBObject("$group", groupFields);

或者,如果你使用命令行(未测试,我只测试了java版本):

{
    $group: {
        _id: {
            "year": {
                "$year", "$tDate"
            },
            "month": {
                "$month", "$tDate"
            },
            "day": {
                "$dayOfMonth", "$tDate"
            },
            "customer": "$customer"
        },
        "eventCount": {
            "$sum": "$eventCount"
        }
    }
}

如何在聚合框架内将这些日期转换为 CET?

例如,“2013-09-16 23:45:00 UTC”是“2013-09-17 00:45:00 CET”,这是不同的一天。

【问题讨论】:

    标签: mongodb aggregation-framework mongo-java


    【解决方案1】:

    我不是 CET 及其与 UTC 关系方面的专家,但以下代码(用于 shell)应该对 MongoDB 日期类型进行适当的转换(增加一个小时):

    db.dates.aggregate(
      {$project: {"tDate":{$add: ["$tDate", 60*60*1000]}, "eventCount":1, "customer":1}}
    )
    

    如果您在管道的其余部分之前运行该项目命令,则结果应该在 CET 中。

    【讨论】:

    • "例外:$add 只支持数字或日期类型,不支持字符串" :(
    • String [] date_add_array = {"$t_roundedDateHour", String.valueOf(3600*1000)}; BasicDBObject projectionsFields = new BasicDBObject("tDate", new BasicDBObject("$add", date_add_array).append("customer", 1).append("eventCount",1);
    • 聚合框架配置为管道,这意味着您可以传入一组聚合操作,它们将按顺序执行,其中一个的输出用作输入或下一个,并且很快。在 shell 中,它可能看起来像: db.collection.aggregate([{$project: ...}, {$group: ...}]);我用java的不多,但是这里有一个关于如何使用聚合管道的教程:docs.mongodb.org/ecosystem/tutorial/…
    • 另外,为了响应您之前的“异常”,您需要使用 mongodb 的内置日期类型才能使这些操作起作用,而不是字符串。如果即使您的一个字段是整个集合中的字符串,也会发生该异常。
    • 是的,我知道关于数据类型的事情(对于 Mongo,字符串与数字不同),这就是我打开另一个问题的原因:stackoverflow.com/questions/18868478/…
    【解决方案2】:

    您可以将timezone 提供给从 3.6 开始的日期运算符。

    将时区替换为您的时区。

    {
      "$group":{
        "_id":{
          "year":{"$year":{"date":"$tDate","timezone":"America/Chicago"}},
          "month":{"$month":{"date":"$tDate","timezone":"America/Chicago"}},
          "dayOfMonth":{"$dayOfMonth":{"date":"$tDate","timezone":"America/Chicago"}}
        },
        "count":{"$sum":1}
      }
    }
    

    【讨论】:

      【解决方案3】:

      搜索了几个小时后,这是对我有用的解决方案。这也很简单。只需通过减去以毫秒为单位的时区偏移量来转换时区。

      25200000 = 7 小时偏移量 // 420 分钟 * 60 秒 * 1000 英里

      $group: {
          _id = { 
              year: { $year : [{ $subtract: [ "$timestamp", 25200000 ]}] }, 
              month: { $month : [{ $subtract: [ "$timestamp", 25200000 ]}] }, 
              day: { $dayOfMonth : [{ $subtract: [ "$timestamp", 25200000 ]}] }
          },
          count = { 
              $sum : 1
          }
      };
      

      【讨论】:

      • 由于夏令时,这可能是错误的一半。大多数使用 CET 的国家/地区在某个随机日期在夏季切换到 CEST;那么差异是+2小时,而不是+1。例如,请参阅this
      • 我现在正在寻找解决此问题的方法,但尚未找到,但请不要按原样使用此解决方案,因为夏令时会导致此问题循环。
      【解决方案4】:

      例如使用 moment.js 来确定 CET 的当前时区偏移量,但这样您可以获得夏季和冬季偏移量

      var offsetCETmillisec = moment.tz.zone('Europe/Berlin').offset(moment())* 60 * 1000;
      
        $group: {
          _id: {
            'year': {'$year': [{ $subtract: [ '$createdAt', offsetCETmillisec ]}] },
            'month': {'$month': [{ $subtract: [ '$createdAt', offsetCETmillisec ]}] },
            'day': {'$dayOfMonth': [{ $subtract: [ '$createdAt', offsetCETmillisec ]}] }
          },
          count: {$sum: 1}
        }
      }
      

      【讨论】:

        【解决方案5】:

        MongoDB's documentation suggests 您将时区偏移量与时间戳一起保存:

        var now = new Date();
        db.data.save( { date: now,
                        offset: now.getTimezoneOffset() } );
        

        这当然不是理想的解决方案——但它是可行的,直到我们在 MongoDb 的聚合管道中拥有一个适当的 $utcOffset 函数。

        【讨论】:

          【解决方案6】:

          带有时区的解决方案是一个不错的解决方案,但在 3.6 版中,您还可以使用时区格式化输出,因此,您可以得到可以使用的结果:

          {
          "$project":{
              "year_month_day": {"$dateToString": { "format": "%Y-%m-%d", "date": "$tDate", "timezone": "America/Chicago"}}
          },
          "$group":{
              "_id": "$year_month_day",
              "count":{"$sum":1}
          }
          }
          

          确保你的“$match”也考虑时区,否则你会得到错误的结果。

          【讨论】:

            【解决方案7】:

            Mongo 以 UTC 格式存储日期, 所以这是让他们进入其他区域的程序

            • 检查 mongo 是否以 UTC 格式保存日期,插入一些记录等。
            • 使用 moment-timezone.js 例如 moment().tz('Europe/Zagreb').utcOffset() 函数获取时区偏移量,为您 指定时区
            • 为 $match 阶段准备 $gte 和 $lte(例如,用户输入日期 1.1.2019 - 13.1.2019。):
              • 如果偏移量为正,则在 $match 阶段减去() 秒;如果偏移量是负数,则在 $match 阶段添加()秒
            • 然后将日期标准化(因为 $match 阶段将以 UTC 格式返回它们)到您的区域,如下所示: -如果时区偏移为正 add() $project 阶段的那些秒; - 如果时区偏移为负,则在 $project 阶段减去() 秒。
            • $group 排在最后,这很重要(因为我们要对标准化结果进行分组,而不是 $match-ed)

            基本上是这样:将输入转换为 $match(UTC),然后标准化为您的时区。

            【讨论】:

              【解决方案8】:
              <?php
                  date_default_timezone_set('Asia/Karachi');
                  $date=getdate(date("U"));
                  $day = $date['mday'];
                  $month =$date['mon'];
                  $year = $date['year'];
                  $currentDate = $year.'-'.$month.'-'.$day;
              ?>
              

              【讨论】:

              • 您的“解决方案”究竟如何使 '2013-09-16 23:45:00 UTC' 转换为 2013-09-17 CET?
              • 你没有解释的另一件事是,如何在 mongoDB 聚合框架中“包含”你的代码,因为任何转换(或要设置的时区)都必须在 之前 将日期对象从小时、分钟、秒和毫秒中剔除。
              猜你喜欢
              • 1970-01-01
              • 2019-11-23
              • 1970-01-01
              • 2023-03-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多