sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Qué tipo de marca de tiempo debo elegir en una base de datos PostgreSQL?

En primer lugar, el manejo del tiempo y la aritmética de PostgreSQL son fantásticos y la Opción 3 está bien en el caso general. Sin embargo, es una vista incompleta de la hora y las zonas horarias y se puede complementar:

  1. Almacenar el nombre de la zona horaria de un usuario como preferencia del usuario (por ejemplo, America/Los_Angeles , no -0700 ).
  2. Haga que los eventos/datos de tiempo del usuario se envíen localmente a su marco de referencia (lo más probable es que se desplacen de UTC, como -0700) ).
  3. En la aplicación, convierta la hora a UTC y almacenado usando un TIMESTAMP WITH TIME ZONE columna.
  4. Regresar solicitudes de hora locales a la zona horaria de un usuario (es decir, convertir de UTC a America/Los_Angeles ).
  5. Establezca la timezone de su base de datos a UTC .

Esta opción no siempre funciona porque puede ser difícil obtener la zona horaria de un usuario y, por lo tanto, el consejo de cobertura para usar TIMESTAMP WITH TIME ZONE para aplicaciones ligeras. Dicho esto, permítanme explicar algunos aspectos de fondo de esta Opción 4 con más detalle.

Al igual que la Opción 3, el motivo de WITH TIME ZONE es porque el momento en que algo sucedió es un absoluto momento en el tiempo. WITHOUT TIME ZONE produce un relativo zona horaria. Nunca, nunca, nunca mezcles marcas de tiempo absolutas y relativas.

Desde una perspectiva programática y de coherencia, asegúrese de que todos los cálculos se realicen utilizando UTC como zona horaria. Este no es un requisito de PostgreSQL, pero ayuda cuando se integra con otros lenguajes o entornos de programación. Establecer un CHECK en la columna para asegurarse de que la escritura en la columna de marca de tiempo tenga un desplazamiento de zona horaria de 0 es una posición defensiva que evita algunas clases de errores (por ejemplo, un script descarga datos en un archivo y otra cosa ordena los datos de tiempo usando una ordenación léxica). Nuevamente, PostgreSQL no necesita esto para hacer cálculos de fecha correctamente o para convertir entre zonas horarias (es decir, PostgreSQL es muy hábil para convertir horas entre dos zonas horarias arbitrarias). Para garantizar que los datos que ingresan a la base de datos se almacenen con un desplazamiento de cero:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

No es 100% perfecto, pero proporciona una medida anti-footshooting lo suficientemente fuerte que asegura que los datos ya estén convertidos a UTC. Hay muchas opiniones sobre cómo hacer esto, pero esta parece ser la mejor práctica según mi experiencia.

Las críticas al manejo de la zona horaria de la base de datos están justificadas en gran medida (hay muchas bases de datos que manejan esto con gran incompetencia), sin embargo, el manejo de las marcas de tiempo y las zonas horarias de PostgreSQL es bastante impresionante (a pesar de algunas "características" aquí y allá). Por ejemplo, una de esas características:

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Tenga en cuenta que AT TIME ZONE 'UTC' elimina la información de la zona horaria y crea un TIMESTAMP WITHOUT TIME ZONE relativo utilizando el marco de referencia de su objetivo (UTC ).

Al convertir de un TIMESTAMP WITHOUT TIME ZONE incompleto a una TIMESTAMP WITH TIME ZONE , la zona horaria que falta se hereda de su conexión:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

El resultado final:

  • almacenar la zona horaria de un usuario como una etiqueta con nombre (por ejemplo, America/Los_Angeles ) y no un desplazamiento de UTC (por ejemplo, -0700 )
  • use UTC para todo a menos que haya una razón convincente para almacenar una compensación distinta de cero
  • tratar todas las horas UTC distintas de cero como un error de entrada
  • nunca mezcle y combine marcas de tiempo relativas y absolutas
  • también use UTC como la timezone en la base de datos si es posible

Nota sobre el lenguaje de programación aleatorio:datetime de Python El tipo de datos es muy bueno para mantener la distinción entre tiempos absolutos y relativos (aunque frustrante al principio hasta que lo complementa con una biblioteca como PyTZ).

EDITAR

Déjame explicarte un poco más la diferencia entre relativo y absoluto.

El tiempo absoluto se utiliza para registrar un evento. Ejemplos:"Usuario 123 inició sesión" o "una ceremonia de graduación comienza el 28 de mayo de 2011 a las 2 p. m. PST". Independientemente de su zona horaria local, si pudiera teletransportarse al lugar donde ocurrió el evento, podría presenciar el evento. La mayoría de los datos de tiempo en una base de datos son absolutos (y por lo tanto deben ser TIMESTAMP WITH TIME ZONE , idealmente con una compensación de +0 y una etiqueta de texto que represente las reglas que rigen la zona horaria en particular, no una compensación).

Un evento relativo sería registrar o programar la hora de algo desde la perspectiva de una zona horaria aún por determinar. Ejemplos:"las puertas de nuestro negocio abren a las 8 a. m. y cierran a las 9 p. m.", "reunámonos todos los lunes a las 7 a. m. para un desayuno semanal" o "cada Halloween a las 8 p. m.". En general, el tiempo relativo se usa en una plantilla o fábrica para eventos, y el tiempo absoluto se usa para casi todo lo demás. Hay una rara excepción que vale la pena señalar y que debería ilustrar el valor de los tiempos relativos. Para eventos futuros que están lo suficientemente lejos en el futuro donde podría haber incertidumbre sobre el tiempo absoluto en el que podría ocurrir algo, use una marca de tiempo relativa. Aquí hay un ejemplo del mundo real:

Suponga que es el año 2004 y necesita programar una entrega para el 31 de octubre de 2008 a la 1:00 p. m. en la costa oeste de los EE. UU. (es decir, America/Los_Angeles /PST8PDT ). Si almacenó eso usando tiempo absoluto usando ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE , la entrega habría aparecido a las 2 p. m. porque el gobierno de EE. UU. aprobó la Ley de Política Energética de 2005 que cambió las reglas que rigen el horario de verano. En 2004 cuando estaba prevista la entrega, la fecha 10-31-2008 habría sido la hora estándar del Pacífico (+8000 ), pero a partir del año 2005+ las bases de datos de zonas horarias reconocieron que 10-31-2008 habría sido el horario de verano del Pacífico (+0700 ). El almacenamiento de una marca de tiempo relativa con la zona horaria habría resultado en un cronograma de entrega correcto porque una marca de tiempo relativa es inmune a la manipulación mal informada del Congreso. El límite entre el uso de tiempos relativos y absolutos para programar cosas es una línea confusa, pero mi regla general es que la programación para cualquier cosa en el futuro más allá de 3-6 meses debe usar marcas de tiempo relativas (programado =absoluto vs planeado =familiar ???).

El otro/último tipo de tiempo relativo es el INTERVAL . Ejemplo:"la sesión expirará 20 minutos después de que un usuario inicie sesión". Un INTERVAL se puede usar correctamente con marcas de tiempo absolutas (TIMESTAMP WITH TIME ZONE ) o marcas de tiempo relativas (TIMESTAMP WITHOUT TIME ZONE ). Es igualmente correcto decir, "una sesión de usuario expira 20 minutos después de un inicio de sesión exitoso (login_utc + session_duration)" o "nuestra reunión de desayuno matutino solo puede durar 60 minutos (recurring_start_time + meeting_length)".

Últimos fragmentos de confusión:DATE , TIME , TIME WITHOUT TIME ZONE y TIME WITH TIME ZONE son todos tipos de datos relativos. Por ejemplo:'2011-05-28'::DATE representa una fecha relativa ya que no tiene información de zona horaria que pueda usarse para identificar la medianoche. Del mismo modo, '23:23:59'::TIME es relativo porque no sabes ni la zona horaria ni el DATE representado por el tiempo. Incluso con '23:59:59-07'::TIME WITH TIME ZONE , no sabes cuál es la DATE sería. Y por último, DATE con una zona horaria no es de hecho un DATE , es un TIMESTAMP WITH TIME ZONE :

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Poner fechas y zonas horarias en las bases de datos es algo bueno, pero es fácil obtener resultados sutilmente incorrectos. Se requiere un esfuerzo adicional mínimo para almacenar la información de tiempo correcta y completamente, sin embargo, eso no significa que siempre se requiera un esfuerzo adicional.