Как агрегировать по году-месяцу-дню в другом часовом поясе

У меня есть 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"
        }
    }
}

Как преобразовать эти даты в среднеевропейское время в рамках агрегации?

Например, «2013-09-16 23:45:00 UTC» — это «2013-09-17 00:45:00 CET», это другой день.


person SQL.injection    schedule 17.09.2013    source источник


Ответы (8)


Я не эксперт по CET и его связи с UTC, но следующий код (для оболочки) должен выполнить правильное преобразование (добавление часа) в тип даты MongoDB:

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

Если вы запустите эту команду проекта перед остальной частью вашего конвейера, результаты должны быть в CET.

person 3rf    schedule 17.09.2013
comment
исключение: $add поддерживает только числовые типы или типы даты, а не String :( - person SQL.injection; 18.09.2013
comment
Строка [] 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); - person SQL.injection; 18.09.2013
comment
Платформа агрегации настроена как конвейер, что означает, что вы можете передать массив операций агрегации, и они будут выполняться по порядку, вывод одной из них будет использоваться как ввод, другой и так далее. В оболочке это может выглядеть примерно так: db.collection.aggregate([{$project: ...}, {$group: ...}]); Я не часто использую Java, но здесь есть руководство по использованию конвейера агрегации: docs.mongodb.org/ecosystem/tutorial/ - person 3rf; 18.09.2013
comment
Кроме того, в ответ на ваше более раннее исключение вам нужно использовать встроенные типы дат mongodb, чтобы эти операции работали, а не строки. Это исключение может произойти, если хотя бы одно из ваших полей является строкой во всей коллекции. - person 3rf; 18.09.2013
comment
да, я знаю, что такое типы данных (для Mongo строка не совпадает с числом), поэтому я открыл еще один вопрос: stackoverflow.com/questions/18868478/ - person SQL.injection; 18.09.2013
comment
Спасибо. Часовой пояс браузера можно получить с помощью (new Date()).getTimezoneOffset() — разница в минутах с UTC. Восточное полушарие отрицательное. - person Daniel Flippance; 23.11.2015
comment
Это, вероятно, неправильно в половине случаев из-за перехода на летнее время. Большинство стран, использующих CET, переключаются на CEST на летнее время в случайную дату; тогда разница +2 часа, а не +1. См., например, это. - person Waldo; 26.02.2016

Вы можете предоставить 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}
  }
}
person s7vr    schedule 20.03.2018

После нескольких часов поиска это решение сработало для меня. Это также очень просто. Просто преобразуйте часовой пояс, вычитая смещение часового пояса в миллисекундах.

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
    }
};
person Trevor Meier    schedule 19.11.2014
comment
Это, вероятно, неправильно в половине случаев из-за перехода на летнее время. Большинство стран, использующих CET, переключаются на CEST на летнее время в случайную дату; тогда разница +2 часа, а не +1. См., например, это. - person Waldo; 25.05.2016
comment
Я ищу решение этой проблемы прямо сейчас и еще не нашел его, но, пожалуйста, не используйте это решение как есть, потому что переход на летнее время приведет к зацикливанию. - person Augie Gardner; 27.12.2017

Используйте, например, 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}
  }
}
person helgetan    schedule 10.09.2016

документация MongoDB предлагает сохранить смещение часового пояса вместе с отметкой времени:

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

Это, конечно, не идеальное решение, но оно работает до тех пор, пока в конвейере агрегации MongoDb нет подходящей функции $utcOffset.

person Waldo    schedule 25.05.2016

Решение с часовым поясом хорошее, но в версии 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» также учитывает часовой пояс, иначе вы получите неправильные результаты.

person Fernando Ulisses dos Santos    schedule 17.03.2019

Mongo хранит даты в формате UTC, так что это процедура, чтобы получить их в другой зоне.

  • проверьте, что монго сохраняет даты в формате UTC, вставьте некоторые записи и т. д.
  • получить смещение часового пояса с помощью функций moment-timezone.js, например, moment().tz('Europe/Zagreb').utcOffset(), для указанного вами часового пояса
  • Prepare $gte and $lte for $match stage (eg user input for dates 1.1.2019 - 13.1.2019.):
    • If offset is positive subtract() those seconds in $match stage; If offset is negative add() those seconds in $match stage
  • Затем нормализуйте даты (поскольку стадия $match вернет их в формате UTC) для вашей зоны следующим образом: -если смещение часового пояса положительное, добавьте() эти секунды на стадии $project; -если смещение часового пояса отрицательное, вычтите() эти секунды на этапе $project.
  • $group идет последним, это важно (потому что мы хотим сгруппировать нормализованные результаты, а не $matched)

В основном это так: сдвиньте входные данные на $match(UTC), а затем нормализуйте их по вашему часовому поясу.

person TomoMiha    schedule 15.10.2019

person    schedule
comment
как именно ваше решение преобразует «2013-09-16 23:45:00 UTC» в 2013-09-17 CET? - person SQL.injection; 17.09.2013
comment
еще одна вещь, которую вы не объясняете, это то, как включить свой код в структуру агрегации mongoDB, потому что любое преобразование (или установка часового пояса) должно быть выполнено до объекта даты, который будет вырван из часы, минуты, секунды и миллисекунды. - person SQL.injection; 17.09.2013