Búsqueda de sitios web

zona horaria - Cómo inicializar una Fecha de JavaScript en una zona horaria particular


Tengo una fecha y hora en una zona horaria particular como una cadena y quiero convertir esto a la hora local. Pero, no sé cómo establecer la zona horaria en el objeto Date.

Por ejemplo, tengo 28 de febrero de 2013 7:00 PM ET, entonces puedo

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);  

Hasta donde sé, puedo configurar la hora UTC o la hora local. Pero, ¿cómo puedo configurar la hora en otra zona horaria?

Intenté usar sumar/restar el desfase con respecto a UTC, pero no sé cómo contrarrestar el horario de verano. No estoy seguro si voy en la dirección correcta.

¿Cómo puedo convertir la hora de una zona horaria diferente a la hora local en JavaScript?

Respuesta #1

Fondo

El objeto Date de JavaScript rastrea el tiempo en UTC internamente, pero normalmente acepta entradas y produce salidas en la hora local del ordenador en el que se está ejecutando. Tiene muy pocas facilidades para trabajar con el tiempo en otras zonas horarias.

La representación interna de un objeto Date es un solo número, es decir, timestamp, que representa el número de milisegundos que han transcurrido desde 1970-01-01 00:00:00 UTC, sin tener en cuenta los segundos intercalares.

No hay zona horaria ni formato de cadena almacenado en el objeto Date en sí.

Cuando se utilizan varias funciones del objeto Date, se aplica la zona horaria local del ordenador a la representación interna. Si la función produce una cadena, entonces se puede tener en cuenta la información de configuración regional del ordenador para determinar cómo producir esa cadena. Los detalles varían según la función, y algunos son específicos de la implementación.

Las únicas operaciones que el objeto Date puede realizar con zonas horarias no locales son:

  • Puede analizar una cadena que contenga un desplazamiento numérico UTC de cualquier zona horaria. Utiliza esto para ajustar el valor que se está analizando y almacena el equivalente en UTC. La hora local original y el desplazamiento no se retienen en el objeto Date resultante. Por ejemplo:

      var d = new Date("2020-04-13T00:00:00.000+08:00");
      d.toISOString()  //=> "2020-04-12T16:00:00.000Z"
      d.valueOf()      //=> 1586707200000  (this is what is actually stored in the object)
    
  • En entornos que han implementado la API de Internacionalización de ECMASCript (también conocida como "Intl"), un objeto Date puede producir una cadena específica de la localidad ajustada a un identificador de zona horaria determinado. Esto se logra mediante la opción timeZone de toLocaleString y sus variantes. La mayoría de las implementaciones admitirán identificadores de zona horaria IANA, como 'America/New_York'. Por ejemplo:

      var d = new Date("2020-04-13T00:00:00.000+08:00");
      d.toLocaleString('en-US', { timeZone: 'America/New_York' })
      //=> "4/12/2020, 12:00:00 PM"
      // (midnight in China on April 13th is noon in New York on April 12th)
    

    La mayoría de los entornos modernos admiten el conjunto completo de identificadores de zona horaria de IANA (consulte la tabla de compatibilidad aquí). Sin embargo, tenga en cuenta que el único identificador requerido para ser compatible con Intl es 'UTC', por lo que debería verificar cuidadosamente si necesita admitir navegadores antiguos o entornos atípicos (por ejemplo, dispositivos IoT ligeros).

Bibliotecas

Existen varias bibliotecas que se pueden usar para trabajar con zonas horarias. Aunque todavía no pueden hacer que el objeto Date se comporte de manera diferente, generalmente implementan la base de datos de zonas horarias estándar de IANA y proporcionan funciones para usarla en JavaScript. Las bibliotecas modernas utilizan los datos de zonas horarias proporcionados por la API Intl, pero las bibliotecas más antiguas suelen tener sobrecarga, especialmente si se ejecutan en un navegador web, ya que la base de datos puede volverse un poco grande. Algunas de estas bibliotecas también permiten reducir selectivamente el conjunto de datos, ya sea por las zonas horarias que se admiten y/o por el rango de fechas con el que se puede trabajar.

Aquí están las bibliotecas a considerar:

Bibliotecas basadas en Intl

El nuevo desarrollo debe elegir una de estas implementaciones, que dependen de la API Intl para sus datos de zona horaria:

  • Luxon (sucesor de Moment.js)
  • date-fns-tz (extensión para date-fns)
  • Day.js (al usar su complemento de zona horaria)

Bibliotecas No Internacionales

Estas bibliotecas se mantienen, pero llevan la carga de empaquetar sus propios datos de zona horaria, los cuales pueden ser bastante grandes.

  • js-joda/timezone (extensión para js-joda)
  • moment-timezone* (extensión para Moment.js)
  • date-fns-timezone (extensión para las versiones antiguas 1.x de date-fns)
  • GranFácil/ZonaHoraria
  • tz.js

* Aunque anteriormente se recomendaban Moment y Moment-Timezone, el equipo de Moment ahora prefiere que los usuarios elijan Luxon para nuevos desarrollos.

Bibliotecas descontinuadas

Estas bibliotecas han sido oficialmente descontinuadas y ya no deberían ser utilizadas.

  • TiempoDePared-js
  • TimeZoneJS

Propuestas Futuras

La propuesta Temporal de TC39 tiene como objetivo proporcionar un nuevo conjunto de objetos estándar para trabajar con fechas y horas en el lenguaje JavaScript. Esto incluirá soporte para un objeto consciente de la zona horaria.

Errores Comunes

Hay varios enfoques que a menudo se intentan, los cuales están equivocados y generalmente deberían evitarse.

Re-Parsing

new Date(new Date().toLocaleString('en', {timeZone: 'America/New_York'}))

El enfoque anterior utiliza correctamente la API Intl para crear una cadena en una zona horaria específica, pero luego pasa incorrectamente esa cadena de nuevo al constructor Date. En este caso, el análisis será específico de la implementación y puede fallar por completo. Si tiene éxito, es probable que el objeto Date resultante ahora represente un instante de tiempo incorrecto, ya que la zona horaria local del ordenador se aplicaría durante el análisis.

Cambio de época

var d = new Date();
d.setTime(d.getTime() + someOffset * 60000);

El enfoque anterior intenta manipular la zona horaria del objeto Date desplazando la marca de tiempo Unix por algún otro desplazamiento de zona horaria. Sin embargo, dado que el objeto Date solo rastrea el tiempo en UTC, en realidad solo hace que el objeto Date represente un punto en el tiempo diferente.

El mismo enfoque a veces se utiliza directamente en el constructor, y también es inválido.

El cambio de época a veces se utiliza internamente en las bibliotecas de fechas como un atajo para evitar escribir aritmética de calendario. Al hacerlo, se debe evitar el acceso a propiedades que no sean UTC. Por ejemplo, una vez desplazado, una llamada a getUTCHours sería aceptable, pero una llamada a getHours sería inválida porque utiliza la zona horaria local.

Se llama "desplazamiento de época", porque cuando se utiliza correctamente, la Época Unix (1970-01-01T00:00:00.000Z) ya no está correlacionada con una marca de tiempo de 0, sino que se ha desplazado a una marca de tiempo diferente por la cantidad del desplazamiento.

Si no estás creando una biblioteca de fechas, no deberías estar cambiando la época.

Para más detalles sobre el cambio de época, mira este videoclip de Greg Miller en CppCon 2015. El video trata sobre time_t en C++, pero la explicación y los problemas son idénticos. (Para la gente de JavaScript, cada vez que escuches a Greg mencionar time_t, simplemente piensa en el "objeto Date").

Intentando hacer una "Fecha UTC"

var d = new Date();
var utcDate = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()));

En este ejemplo, tanto d como utcDate son idénticos. El trabajo para construir utcDate fue redundante, porque d ya está en términos de UTC. Examinar la salida de las funciones toISOString, getTime o valueOf mostrará valores idénticos para ambas variables.

Un enfoque similar observado es:

var d = new Date();
var utcDate = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());

Este enfoque pasa valores UTC al constructor Date donde se esperan valores de hora local. El objeto Date resultante ahora representa un punto en el tiempo completamente diferente. Es esencialmente el mismo resultado que el desplazamiento de época descrito anteriormente y, por lo tanto, debe evitarse.

La forma correcta de obtener un objeto Date basado en UTC es simplemente new Date(). Si necesitas una representación en cadena que esté en UTC, entonces usa new Date().toISOString().

Respuesta #2

Como dijo Matt Johnson

Si puedes limitar tu uso a navegadores web modernos, ahora puedes hacer lo siguiente sin ninguna biblioteca especial:

new Date().toLocaleString("en-US", {timeZone: "America/New_York"})

Esta no es una solución integral, pero funciona para muchos escenarios que requieren solo la conversión de salida (de UTC o hora local a una zona horaria específica, pero no en la otra dirección).

Entonces, aunque el navegador no puede leer las zonas horarias IANA al crear una fecha, o no tiene ningún método para cambiar las zonas horarias de un objeto Date existente, parece que hay un truco para solucionarlo.

Considera la siguiente función

function changeTimezone(date, ianatz) {

  // suppose the date is 12:00 UTC
  var invdate = new Date(date.toLocaleString('en-US', {
    timeZone: ianatz
  }));

  // then invdate will be 07:00 in Toronto
  // and the diff is 5 hours
  var diff = date.getTime() - invdate.getTime();

  // so 12:00 in Toronto is 17:00 UTC
  return new Date(date.getTime() - diff); // needs to substract

}

Sin embargo, al observar de cerca el valor de retorno, esto se puede simplificar a:

function changeTimezone(date, ianatz) {
  return new Date(date.toLocaleString('en-US', {
    timeZone: ianatz
  }));
}

// E.g.
var here = new Date();
var there = changeTimezone(here, "America/Toronto");

console.log(`Here: ${here.toString()}\nToronto: ${there.toString()}`);

Respuesta #3

Esto debería resolver su problema, por favor siéntase libre de ofrecer soluciones. Este método también tendrá en cuenta el horario de verano para la fecha dada.

dateWithTimeZone = (timeZone, year, month, day, hour, minute, second) => {
  let date = new Date(Date.UTC(year, month, day, hour, minute, second));

  let utcDate = new Date(date.toLocaleString('en-US', { timeZone: "UTC" }));
  let tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
  let offset = utcDate.getTime() - tzDate.getTime();

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

  return date;
};

Cómo usar con zona horaria y hora local:

dateWithTimeZone("America/Los_Angeles",2019,8,8,0,0,0)

Respuesta #4

Puedes especificar un desfase de zona horaria en new Date(), por ejemplo:

new Date('Feb 28 2013 19:00:00 EST')

o

new Date('Feb 28 2013 19:00:00 GMT-0500')

Dado que Date almacena la hora en UTC (es decir, getTime devuelve en UTC), JavaScript luego convierte la hora a UTC, y cuando llamas a cosas como toString, JavaScript convierte la hora UTC a la zona horaria local del navegador y devuelve la cadena en la zona horaria local, es decir, si estoy usando UTC+8:

> new Date('Feb 28 2013 19:00:00 GMT-0500').toString()
< "Fri Mar 01 2013 08:00:00 GMT+0800 (CST)"

También puedes usar el método normal getHours/Minute/Second:

> new Date('Feb 28 2013 19:00:00 GMT-0500').getHours()
< 8

( Esto 8 significa que después de que la hora se convierte en mi hora local - UTC+8, el número de horas es 8. )

Respuesta #5

Ahora es 2023, esta pregunta tiene 10 años, con 1,1 millones de vistas, pero no estaba satisfecho con todas las soluciones anteriores.

La pregunta original era:

[si] tengo "28 de feb de 2013 7:00 PM ET", entonces [...] hasta donde sé, puedo establecer la hora en UTC o la hora local. Pero, ¿cómo establezco la hora en otra zona horaria?

  • La respuesta de Matt Johnson-Pint fue excelente, y casi fue suficiente, pero se quedó corta de responder directamente a la pregunta. La respuesta demuestra cómo establecer una fecha en otra zona horaria, pero lo hace utilizando el desfase UTC (+08:00) en lugar del nombre de la zona horaria según la pregunta original.

    • Esto nos señala la solución "correcta": especificar el desplazamiento UTC al crear la fecha; solo necesitamos convertir de la zona horaria IANA al desplazamiento UTC :
  • La respuesta de commonpike y la respuesta de chicken vuelven a analizar el formato específico de la localidad, lo cual no es Javascript estándar y Mozilla desaconseja.

    • Ambos también crean un objeto Date que no realmente representa la fecha y hora UTC deseadas
  • La respuesta de maowtm también estaba casi correcta, pero de nuevo no utiliza el único formato compatible con la especificación de Javascript.
  • Las otras respuestas todas sufren de los errores/fallos que Matt Johnson-Pint señaló, y/o de volver a analizar formatos no estándar.

La manera "correcta" en 2023

Los navegadores modernos y node.js>=18 proporcionan una lista de ~400 zonas horarias con la siguiente consulta:

const tzs = Intl.supportedValuesOf("timeZone"); // [ "Africa/Abidjan", "Africa/Accra" ...]
console.log(tzs);

Para cada zona horaria listada, podemos obtener un desplazamiento GMT/UTC construyendo una instancia de DateTimeFormat, formateando una nueva fecha con el formateador longOffset y analizando el resultado:

// gives '2/28/2013, GMT-05:00'
const tzname = "America/Detroit";
const longOffsetFormatter = new Intl.DateTimeFormat("en-US", {timeZone: tzname ,timeZoneName: "longOffset"});
const longOffsetString = longOffsetFormatter.format(new Date("2013-02-28T19:00:00.000")); // '2/28/2013, GMT-05:00'

// longOffsetString.split('GMT')[1] will give us '-05:00'
const gmtOffset = longOffsetString.split('GMT')[1];

console.log(longOffsetString);
console.log(gmtOffset);

Ahora podemos construir una nueva Fecha, correctamente (es decir, internamente tendrá la fecha/hora UTC correcta), usando la cadena de desplazamiento de zona horaria.

// Feb 28 2013 7:00 PM EST
const gmtOffset = '-05:00'; // see above to figure out where this came from
const d = new Date("2013-02-28T19:00:00.000" + gmtOffset);

console.log(d.toISOString()); // '2013-03-01T00:00:00.000Z' (UTC)
console.log(d.toLocaleString("en-US", {timeZone: "America/Detroit"})); // '2/28/2013, 7:00:00 PM'

Respuesta #6

Encontré que la manera más respaldada de hacer esto, sin preocuparse por una biblioteca de terceros, era usar getTimezoneOffset para calcular la marca de tiempo adecuada, o actualizar la hora y luego usar los métodos normales para obtener la fecha y hora necesarias.

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);

// ET timezone offset in hours.
var timezone = -5;
// Approximate check for daylight savings if applicable (northern hemisphere)
var startDate = new Date(mydate.getFullYear(), 02, 31);
var endDate = new Date(mydate.getFullYear(), 09, 31);
var is_daylight_savings = mydate.getTime() >= startDate.getTime() && mydate.getTime() <= endDate.getTime();
if (is_daylight_savings) {
    timezone += 1;
}
// Timezone offset in minutes + the desired offset in minutes, converted to ms.
// This offset should be the same for ALL date calculations, so you should only need to calculate it once.
var offset = (mydate.getTimezoneOffset() + (timezone * 60)) * 60 * 1000;

// Use the timestamp and offset as necessary to calculate min/sec etc, i.e. for countdowns.
var timestamp = mydate.getTime() + offset,
    seconds = Math.floor(timestamp / 1000) % 60,
    minutes = Math.floor(timestamp / 1000 / 60) % 60,
    hours   = Math.floor(timestamp / 1000 / 60 / 60);

// Or update the timestamp to reflect the timezone offset.
mydate.setTime(mydate.getTime() + offset);
// Then Output dates and times using the normal methods.
var date = mydate.getDate(),
    hour = mydate.getHours();

EDITAR

Anteriormente estaba usando métodos UTC al realizar las transformaciones de fecha, lo cual era incorrecto. Al agregar el desplazamiento al tiempo, usar las funciones locales get devolverá los resultados deseados.

Artículos relacionados