У меня есть коллекция mongodb, в которой каждый документ имеет некоторые атрибуты и временную метку utc. Мне нужно извлечь данные из коллекции и использовать структуру агрегации, потому что я использую данные из коллекции для отображения некоторых диаграмм в пользовательском интерфейсе. Однако мне нужно выполнить агрегацию в соответствии с часовым поясом пользователя. Предполагая, что я знаю часовой пояс пользователя (переданный в запросе из браузера или каким-либо другим образом), есть ли способ использовать структуру агрегации для агрегирования на основе часового пояса [клиента]?
Как решить проблему с часовым поясом при хранении дат в utc с помощью mongod?
Ответы (3)
То, о чем вы просите, в настоящее время обсуждается в выпуске MongoDB SERVER-6310.
Я нашел это по ссылке из ветки обсуждения.
Проблема характерна для любой группировки по дате, включая базы данных SQL и базы данных NoSQL. Фактически, я недавно обратился к этому в RavenDB. Существует хорошее описание проблемы и решение RavenDB здесь.
В проблемах MongoDB обсуждается обходной путь, который похож на то, что я описал в комментариях выше. Вы предварительно вычисляете местное время, которое вас интересует, и вместо этого группируете его.
При любом подходе будет сложно охватить все часовые пояса мира. Вам следует определиться с небольшой горсткой целевых зон, которые имеют смысл для вашей пользовательской базы, например подход для каждого офиса, который я описал в статье RavenDB.
ОБНОВЛЕНИЕ: эта проблема была решена в MongoDB в июле 2017 года (версия 3.5.11). Решение описано в первой ссылке выше, но вкратце они ввели новый формат объектов для дат в выражениях агрегации. : { date: <dateExpression>, timezone: <tzExpression> }
, который позволяет указать часовой пояс для использования при агрегировании. См. здесь другой пример в документации Mongo.
Помимо SERVER-6310, упомянутого Мэттом Джонсоном, еще одним обходным путем является использование оператора $project
для добавления или вычитания из часового пояса UTC, чтобы «сдвинуть время» в правильную локальную зону. Оказывается, вы можете добавить или вычесть время в миллисекундах.
Например, предположим, что у меня есть поле даты с именем orderTime
. Я хотел бы запросить EDT. Это -4 часа от UTC. Это 4 * 60 * 60 * 1000 миллисекунд.
Поэтому я бы написал следующую проекцию, чтобы получить day_ordered
по местному времени для всех моих записей:
db.table.aggregate(
{ $project : { orderTimeLocal : { $subtract : [ "$orderTime", 14400000] } } },
{ $project : { day_ordered : { $dayOfYear : "$orderTimeLocal" } } })
"$lastActivity"
в объяснении, а не "$orderTime"
в ответе?
- person digitalextremist; 24.01.2015
4 * 60 * 60 * 1000
превращается в utcOffset * 60 * 60 * 1000
- person daino3; 14.07.2017
Все предложенные выше подходы прекрасно работают, но поскольку существует новая версия mongodb, начиная с 2.6, вы можете использовать $let
в структуре агрегации, это позволит вам создавать переменные на лету, избегая, таким образом, необходимости $project
перед группировкой. Теперь вы можете создать переменную с $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"
, где используются определенные выше переменные.
Foo_ByDate_MultiZone
индексируйте здесь. Если бы Монго допускал что-то подобное, вы бы сделали это на Карте, как я сделал в Raven. - person Matt Johnson-Pint   schedule 17.08.2013