¿Cómo lidiar con el problema de la zona horaria al almacenar fechas en utc usando mongod?


Tengo una colección mongodb donde cada documento tiene algunos atributos y una marca de tiempo utc. Necesito extraer datos de la colección y usar el marco de agregación porque uso los datos de la colección para mostrar algunos gráficos en la interfaz de usuario. Sin embargo, necesito hacer la agregación según la zona horaria del usuario. Suponiendo que conozco la zona horaria del usuario (pasada en la solicitud desde el navegador o de alguna otra manera), ¿hay alguna manera de usar el marco de agregación para agregar basado en ¿en la zona horaria del cliente?

Author: Hrishi, 2013-08-17

4 answers

Lo que está pidiendo se está discutiendo actualmente en MongoDB issue SERVER-6310.

Encontré esto en un enlace de un hilo de discusión.

El problema es común para cualquier agrupación por fecha, incluidas las bases de datos SQL y NoSQL. De hecho, recientemente me dirigí a este frente en RavenDB. Hay una buena descripción del problema y una solución de RavenDB aquí.

Los problemas de MongoDB discuten una solución alternativa, que es similar a lo que yo descrito en los comentarios anteriores. Precalculas las horas locales en las que estás interesado, y en su lugar las agrupas por ellas.

Será difícil cubrir todas las zonas horarias del mundo con cualquiera de los dos enfoques. Debe decidir sobre un pequeño puñado de zonas de destino que tengan sentido para su base de usuarios, como el enfoque por oficina que describí en el artículo de RavenDB.

ACTUALIZACIÓN: Este problema se resolvió en MongoDB en julio de 2017 (versión 3.5.11). La solución se describe en la primera enlace anterior, pero en resumen han introducido un nuevo formato de objeto para fechas en expresiones de agregación: { date: <dateExpression>, timezone: <tzExpression> } que le permite especificar una zona horaria para usar al agregar. Ver aquí para otro ejemplo en los documentos de Mongo.

 9
Author: Matt Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-05 16:48:45

Aparte del SERVIDOR-6310 mencionado por Matt Johnson, otra solución es usar el operador $project para agregar o restar de la zona horaria UTC para "cambiar la hora" a la zona local correcta. Resulta que puedes sumar o restar tiempo en milisegundos.

Por ejemplo, suponiendo que tengo un campo de Fecha llamado orderTime. Me gustaría preguntar por EDT. Eso es -4 horas desde UTC. Eso es 4 * 60 * 60 * 1000 milisegundos.

Entonces escribiría la siguiente proyección para obtener day_ordered en hora local de todos mis registros:

db.table.aggregate( 
    { $project : { orderTimeLocal : { $subtract : [ "$orderTime", 14400000] } } },
    { $project : { day_ordered : { $dayOfYear : "$orderTimeLocal" } } })
 36
Author: Astral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-08 11:13:14

Cada enfoque sugerido anteriormente funciona perfectamente bien, pero como hay una nueva versión de mongodb, desde 2.6 puede usar $let en el marco de agregación, esto le permitirá crear variables sobre la marcha, evitando así la necesidad de $project antes de agrupar. Ahora puede crear una variable con $let que contenga el tiempo localizado, y usarla en el operador $group.

Algo como:

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}
     }
 }])

Observe que utiliza $let dentro de la definición de un bloque / variable, y el valor de ese bloque / variable es el valor devuelto de la subexpresión "in", donde se utilizan los vars definidos anteriormente.

 16
Author: Sebastian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-06 08:50:18

Encontré una solución en el complemento mangosta para normalizar la zona horaria de las fechas almacenadas.

 1
Author: Emir Mamashov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-12 02:18:02