TSQL: ¿Cómo convertir la hora local a UTC? (SQL Server 2008)

82

Estamos tratando con una aplicación que necesita manejar datos de hora global de diferentes zonas horarias y configuraciones de horario de verano. La idea es almacenar todo en formato UTC internamente y solo convertir de un lado a otro para las interfaces de usuario localizadas. ¿Ofrece SQL Server algún mecanismo para tratar las traducciones en un momento, un país y una zona horaria?

Este debe ser un problema común, por lo que me sorprende que Google no muestre nada utilizable.

¿Algún consejo?

BuschnicK
fuente
Tengo mi servidor mssql vinculado a un servidor mysql. Me pregunto si es posible ejecutar mysql CONVERT_TZ (time, srczone, dstzone) en las consultas :-) Extraño, falta esta función; está integrado en Linux.
Leif Neland
1
@BuschnicK Vea mi respuesta a continuación. De hecho, creo que puedes aceptarlo incluso para que sea más fácil de encontrar para otros.
Piotr Owsiak
La respuesta corta: no hay una forma integrada de hacer esto antes de SQL Server 2016, por lo que requerirá un código personalizado en versiones anteriores.
lehiester
La respuesta media: toda la funcionalidad de compensación horaria antes de SQL Server 2016 solo funcionaba con compensaciones absolutas, sin soporte para las compensaciones variables que ocurren en la mayoría de las zonas horarias debido al horario de verano. Las consultas no tienen ninguna forma de acceder a las compensaciones de tiempo, excepto por lo que sea la compensación actual de la hora local del servidor, que es inútil para intentar automatizar la conversión.
lehiester

Respuestas:

49

Pasaron 7 años y ... de
hecho, existe esta nueva característica de SQL Server 2016 que hace exactamente lo que necesita.
Se llama AT TIME ZONE y convierte la fecha en una zona horaria especificada considerando los cambios de DST (horario de verano).
Más información aquí: https://msdn.microsoft.com/en-us/library/mt612795.aspx

Piotr Owsiak
fuente
19
Ya no estoy trabajando en esto, ni en ese proyecto, ni en SQL Server, ni en la misma empresa y ni siquiera en el mismo país ;-) Así que no me ayudará, pero lo votaré a favor de las personas que encuentren esta pregunta. ahora.
BuschnicK
2
@BuschnicK sí, me imagino, pero llegué aquí buscando una solución al mismo problema que tuviste, así que decidí publicar una respuesta ahora que hay una solución real: D
Piotr Owsiak
5
La pregunta es para SQL Server 2008. Actualice la pregunta si acepta esta respuesta. Thx
Robert
1
Para convertir a UTC, puede hacer 'AT TIME ZONE' UTC '.
Krzyserious
1
@Piotr Owsiak: Sí, pero asume que la hora de entrada es UTC, lo cual no tiene sentido si desea convertir la hora local a UTC ... Así que pasaron 7 años y todavía no creen que valga la pena manejar esto correctamente. ..
Stefan Steiger
65

Esto funciona para fechas que actualmente tienen el mismo desplazamiento UTC que el host de SQL Server; no tiene en cuenta los cambios de horario de verano. Reemplace YOUR_DATEcon la fecha local para convertir.

SELECT DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), YOUR_DATE);

shaig
fuente
2
Gracias, esta es una buena idea, pero solo funciona exactamente para una zona horaria: la de la máquina local. Sin embargo, necesitamos que funcione para zonas horarias arbitrarias ...
BuschnicK
51
Esto no tiene en cuenta el horario de verano
Gabriel McAdams
10
¡No! La diferencia depende de la fecha exacta. Depende del horario de verano.
usr
3
En mi caso, solo necesitaba la zona horaria 1, ¡y esto funcionó muy bien! Gracias.
M Thelen
10
Esto funciona bien si sabes que corres hoy, no históricamente, gracias
David Adlington
22

Si bien algunas de estas respuestas lo llevarán al estadio de béisbol, no puede hacer lo que está tratando de hacer con fechas arbitrarias para SqlServer 2005 y anteriores debido al horario de verano. Usar la diferencia entre el UTC actual y el local actual me dará el desplazamiento tal como existe hoy. No he encontrado una manera de determinar cuál habría sido la compensación para la fecha en cuestión.

Dicho esto, sé que SqlServer 2008 proporciona algunas funciones de fecha nuevas que pueden abordar ese problema, pero las personas que usan una versión anterior deben conocer las limitaciones.

Nuestro enfoque es mantener UTC y realizar la conversión en el lado del cliente, donde tenemos más control sobre la precisión de la conversión.

user890155
fuente
16

Para SQL Server 2016 y versiones posteriores, y Azure SQL Database, use la AT TIME ZONEinstrucción integrada .

Para las ediciones anteriores de SQL Server, puede usar mi proyecto de soporte de zona horaria de SQL Server para convertir entre zonas horarias estándar de IANA, como se enumeran aquí .

UTC a Local es así:

SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'America/Los_Angeles')

Local a UTC es así:

SELECT Tzdb.LocalToUtc('2015-07-01 00:00:00', 'America/Los_Angeles', 1, 1)

Las opciones numéricas son un indicador para controlar el comportamiento cuando los valores de la hora local se ven afectados por el horario de verano. Estos se describen en detalle en la documentación del proyecto.

Matt Johnson-Pinta
fuente
1
Cómo hombre ... este proyecto de Matt es genial y no requiere CLR. Matt merece mucho crédito por esto +100
buckley
14

SQL Server 2008 tiene un tipo llamado datetimeoffset. Es realmente útil para este tipo de cosas.

http://msdn.microsoft.com/en-us/library/bb630289.aspx

Luego, puede usar la función SWITCHOFFSETpara moverla de una zona horaria a otra, pero manteniendo el mismo valor UTC.

http://msdn.microsoft.com/en-us/library/bb677244.aspx

Robar

Rob Farley
fuente
6
SWITCHOFFSET no tiene en cuenta el horario de verano, por lo que solo es útil en algunas situaciones.
robocat
2
No. Pero la pregunta era sobre poder manejar el cambio a cualquier zona horaria solicitada.
Rob Farley
De la pregunta "diferentes zonas horarias y configuraciones de horario de verano". Nosotros también buscamos una solución para los horarios locales. Tu sugerencia no resuelve el problema del horario de verano, ¿verdad?
robocat
Siempre que necesite detectar la zona horaria en un cliente y luego tener las horas de retorno de la base de datos en la zona horaria especificada, la función SWITCHOFFSET es muy útil.
Rob Farley
3
@RobFarley La detección de la zona horaria en el cliente y el uso de SWITCHOFFSET aún pueden hacer las cosas mal. Necesita saber si la fecha y la hora que está convirtiendo tiene o no aplicado el horario de verano. Simplemente detectar la zona horaria y aplicar la compensación actual a UTC puede ser una hora menos, y eso es en un caso simple en el que todas sus conversiones están en el mismo país. No todos los países cambian a / desde el horario de verano en las mismas fechas. SWITCHOFFSET funciona bien si almacena la hora local y conoce la diferencia entre la zona original y la de destino dentro del mismo país.
Jamie Ver
12

Aquí está el código para convertir una zona DateTimeen otra zonaDateTime

DECLARE @UTCDateTime DATETIME = GETUTCDATE();
DECLARE @ConvertedZoneDateTime DATETIME;

-- 'UTC' to 'India Standard Time' DATETIME
SET @ConvertedZoneDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE 'India Standard Time'
SELECT @UTCDateTime AS UTCDATE,@ConvertedZoneDateTime AS IndiaStandardTime

-- 'India Standard Time' to 'UTC' DATETIME
SET @UTCDateTime = @ConvertedZoneDateTime AT TIME ZONE 'India Standard Time' AT TIME ZONE 'UTC'
SELECT @ConvertedZoneDateTime AS IndiaStandardTime,@UTCDateTime AS UTCDATE

Nota : AT TIME ZONE funciona solo en SQL Server 2016+ y la ventaja es que considera automáticamente la luz del día cuando se convierte a una zona horaria en particular

KarthikeyanMlp
fuente
2
Me encanta esto, aunque solo sea por el hecho de que muestra que puedes encadenar varias AT TIME ZONEllamadas (¿frases?) Juntas. Simplemente elegante. Dije anteriormente que stackoverflow.com/a/44579178/112764 satisfacía mis necesidades, pero esto es aún mejor. Felicitaciones importantes.
NateJ
DECLARE @UTCDateTime DATETIME = GETUTCDATE(); DECLARE @ConvertedZoneDateTime DATETIME; -- 'UTC' to 'India Standard Time' to 'Eastern Standard Time' DATETIME SET @ConvertedZoneDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE 'India Standard Time' AT TIME ZONE 'Eastern Standard Time' SELECT @UTCDateTime AS UTCDATE,@ConvertedZoneDateTime AS EasternStandardTime Sí, puede encadenar varias AT TIME ZONEllamadas, pero From y To es suficiente para cualquier conversión y la mayoría de las que necesitábamos
KarthikeyanMlp
4

Tiendo a inclinarme hacia el uso de DateTimeOffset para todo el almacenamiento de fecha y hora que no está relacionado con un evento local (es decir, reunión / fiesta, etc., de 12 p.m. a 3 p.m. en el museo).

Para obtener el DTO actual como UTC:

DECLARE @utcNow DATETIMEOFFSET = CONVERT(DATETIMEOFFSET, SYSUTCDATETIME())
DECLARE @utcToday DATE = CONVERT(DATE, @utcNow);
DECLARE @utcTomorrow DATE = DATEADD(D, 1, @utcNow);
SELECT  @utcToday [today]
        ,@utcTomorrow [tomorrow]
        ,@utcNow [utcNow]

NOTA: Siempre usaré UTC al enviar por cable ... JS del lado del cliente puede llegar fácilmente a / desde UTC local. Ver: new Date().toJSON()...

El siguiente JS manejará el análisis de una fecha UTC / GMT en formato ISO8601 a una fecha y hora local.

if (typeof Date.fromISOString != 'function') {
  //method to handle conversion from an ISO-8601 style string to a Date object
  //  Date.fromISOString("2009-07-03T16:09:45Z")
  //    Fri Jul 03 2009 09:09:45 GMT-0700
  Date.fromISOString = function(input) {
    var date = new Date(input); //EcmaScript5 includes ISO-8601 style parsing
    if (!isNaN(date)) return date;

    //early shorting of invalid input
    if (typeof input !== "string" || input.length < 10 || input.length > 40) return null;

    var iso8601Format = /^(\d{4})-(\d{2})-(\d{2})((([T ](\d{2}):(\d{2})(:(\d{2})(\.(\d{1,12}))?)?)?)?)?([Zz]|([-+])(\d{2})\:?(\d{2}))?$/;

    //normalize input
    var input = input.toString().replace(/^\s+/,'').replace(/\s+$/,'');

    if (!iso8601Format.test(input))
      return null; //invalid format

    var d = input.match(iso8601Format);
    var offset = 0;

    date = new Date(+d[1], +d[2]-1, +d[3], +d[7] || 0, +d[8] || 0, +d[10] || 0, Math.round(+("0." + (d[12] || 0)) * 1000));

    //use specified offset
    if (d[13] == 'Z') offset = 0-date.getTimezoneOffset();
    else if (d[13]) offset = ((parseInt(d[15],10) * 60) + (parseInt(d[16],10)) * ((d[14] == '-') ? 1 : -1)) - date.getTimezoneOffset();

    date.setTime(date.getTime() + (offset * 60000));

    if (date.getTime() <= new Date(-62135571600000).getTime()) // CLR DateTime.MinValue
      return null;

    return date;
  };
}
Rastreador1
fuente
+1 Yo también me cambié a DateTimeOffset. Evita una serie de problemas con las conversiones locales UTC +. Sin embargo, por razones similares, también recomiendo enviar valores con un desplazamiento por cable (a través de JSON).
user2864740
Como nota, hago lo que tú haces exactamente al revés. Para DateTimes que están vinculados a un evento local, almaceno un DateTimeOffset. Para un DateTime que no está vinculado a un evento local, almaceno un DateTime en UTC. El primero tiene dos puntos de datos relevantes (cuándo es en hora local y qué hora local es esa), el último solo uno (cuándo es)
Martijn
@Martijn Pero la zona horaria no le da la ubicación y, de todos modos, debe almacenarla por separado.
Tracker1
@ tracker1 Eso solo funciona cuando la ubicación tiene una forma de conocer su zona horaria, e incluso así es un completo dolor de cabeza convertir.
Martijn
3

Sí, hasta cierto punto como se detalla aquí .
El enfoque que utilicé (antes de 2008) es realizar la conversión en la lógica empresarial de .NET antes de insertarla en la base de datos.

AdaTheDev
fuente
1

Puede usar la función GETUTCDATE () para obtener la fecha y hora UTC Probablemente pueda seleccionar la diferencia entre GETUTCDATE () y GETDATE () y usar esta diferencia para ajustar sus fechas a UTC

Pero estoy de acuerdo con el mensaje anterior, que es mucho más fácil controlar la fecha y hora correcta en la capa empresarial (en .NET, por ejemplo).

Bogdan_Ch
fuente
12
¡No! La diferencia depende de la fecha exacta. Depende del horario de verano.
usr
3
No tiene en cuenta el horario de verano. Estuve usando soluciones como esta durante algún tiempo y causó problemas importantes. Debe determinar si la fecha con la que está comparando está en DST.
Jeff Davis
-1

Uso de muestra:

SELECT
    Getdate=GETDATE()
    ,SysDateTimeOffset=SYSDATETIMEOFFSET()
    ,SWITCHOFFSET=SWITCHOFFSET(SYSDATETIMEOFFSET(),0)
    ,GetutcDate=GETUTCDATE()
GO

Devoluciones:

Getdate SysDateTimeOffset   SWITCHOFFSET    GetutcDate
2013-12-06 15:54:55.373 2013-12-06 15:54:55.3765498 -08:00  2013-12-06 23:54:55.3765498 +00:00  2013-12-06 23:54:55.373
Robert Cantor
fuente