¿Cómo formatear ISO 8601 una fecha con compensación de zona horaria en JavaScript?

100

Objetivo: buscar local timey UTC time offsetluego construir la URL en el siguiente formato.

Ejemplo de URL: / Actions / Sleep? Duration = 2002-10-10T12: 00: 00−05: 00

El formato se basa en la recomendación del W3C: http://www.w3.org/TR/xmlschema11-2/#dateTime

La documentación dice:

Por ejemplo, 2002-10-10T12: 00: 00−05: 00 (mediodía del 10 de octubre de 2002, horario de verano central y horario estándar del este en los EE. UU.) Es igual a 2002-10-10T17: 00: 00Z, cinco horas después de 2002-10-10T12: 00: 00Z.

Entonces, según mi comprensión, necesito encontrar mi hora local por nueva Fecha () y luego usar la función getTimezoneOffset () para calcular la diferencia y luego adjuntarla al final de la cadena.

1.Obtener la hora local con formato

var local = new Date().format("yyyy-MM-ddThh:mm:ss"); //today (local time)

salida

2013-07-02T09:00:00

2.Obtener diferencia horaria de UTC por hora

var offset = local.getTimezoneOffset() / 60;

salida

7

3.Construir URL (solo parte del tiempo)

var duration = local + "-" + offset + ":00";

salida:

2013-07-02T09:00:00-7:00

El resultado anterior significa que mi hora local es 2013/07/02 9 a.m. y la diferencia con respecto a UTC es de 7 horas (UTC es de 7 horas por delante de la hora local)

Hasta ahora parece funcionar, pero ¿qué pasa si getTimezoneOffset () devuelve un valor negativo como -120?

Me pregunto cómo debería verse el formato en tal caso porque no puedo averiguarlo a partir del documento W3C. Gracias por adelantado.

maullar
fuente

Respuestas:

174

Lo siguiente debería funcionar correctamente y para todos los navegadores (gracias a @MattJohnson por el consejo)

Date.prototype.toIsoString = function() {
    var tzo = -this.getTimezoneOffset(),
        dif = tzo >= 0 ? '+' : '-',
        pad = function(num) {
            var norm = Math.floor(Math.abs(num));
            return (norm < 10 ? '0' : '') + norm;
        };
    return this.getFullYear() +
        '-' + pad(this.getMonth() + 1) +
        '-' + pad(this.getDate()) +
        'T' + pad(this.getHours()) +
        ':' + pad(this.getMinutes()) +
        ':' + pad(this.getSeconds()) +
        dif + pad(tzo / 60) +
        ':' + pad(tzo % 60);
}

var dt = new Date();
console.log(dt.toIsoString());

Steven Moseley
fuente
2
El letrero indica el desplazamiento de la hora local de GMT
Steven Moseley
1
@ masato-san Tienes que invertir el letrero, ver la definición en developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bfavaretto
2
Aquí la función pad devuelve la cadena adecuada para cada sección de la fecha, excepto milisegundos. Por ejemplo, para 5ms como entrada devolverá 05, que se supone que es 005. Aquí hay un enlace con la versión mínima modificada de la misma función jsbin
Safique Ahmed Faruque
9
Tenga en cuenta: hay un error sutil en esta respuesta. La compensación de la zona horaria no se calculará correctamente si la zona tenía minutos de compensación y era negativa (por ejemplo, -09: 30), como ciertas ubicaciones en Francia y Canadá. Mod devuelve un número negativo, por lo que hacer floor () antes de abs () inadvertidamente hizo que el desplazamiento sea MÁS negativo. Para corregir este error, entonces abs () y ENTONCES piso (). Intenté editar la respuesta, pero aparentemente "Esta edición estaba destinada a dirigirse al autor de la publicación y no tiene sentido como edición".
tytk
5
@tytk - ¡Gran captura! Eso es, de hecho, sutil. Agregué tu solución a la respuesta. ¡Gracias por comentar!
Steven Moseley
67

getTimezoneOffset() devuelve el signo opuesto del formato requerido por la especificación a la que hizo referencia.

Este formato también se conoce como ISO8601 , o más precisamente como RFC3339 .

En este formato, UTC se representa con un Zmientras que todos los demás formatos se representan con un desplazamiento de UTC. El significado es el mismo que el de JavaScript, pero el orden de resta está invertido, por lo que el resultado lleva el signo opuesto.

Además, no hay ningún método en el Dateobjeto nativo llamado format, por lo que su función en el n. ° 1 fallará a menos que esté utilizando una biblioteca para lograrlo. Consulte esta documentación .

Si está buscando una biblioteca que pueda trabajar con este formato directamente, le recomiendo probar moment.js . De hecho, este es el formato predeterminado, por lo que simplemente puede hacer esto:

var m = moment();    // get "now" as a moment
var s = m.format();  // the ISO format is the default so no parameters are needed

// sample output:   2013-07-01T17:55:13-07:00

Esta es una solución de varios navegadores bien probada y tiene muchas otras características útiles.

Matt Johnson-Pinta
fuente
Usar toISOString () no funcionará. El formato +01: 00 requiere que la parte de la hora sea la hora local. toISOString () daría una cadena de tiempo UTC.
Austin Francia
1
@AustinFrance - ¡Tienes razón! Me sorprende haber cometido ese error en ese momento, ya que corrijo a otros en este punto a menudo. ¡Lo siento, no vi tu comentario hace dos años! Respuesta editada.
Matt Johnson-Pint
9

Creo que vale la pena considerar que puede obtener la información solicitada con solo una llamada API a la biblioteca estándar ...

new Date().toLocaleString( 'sv', { timeZoneName: 'short' } );

// produces "2019-10-30 15:33:47 GMT−4"

Tendría que hacer un intercambio de texto si desea agregar el delimitador 'T', eliminar el 'GMT-' o agregar el ': 00' al final.

Pero luego puede jugar fácilmente con las otras opciones si lo desea, por ejemplo. utilice el tiempo de 12 horas u omita los segundos, etc.

Tenga en cuenta que estoy usando Suecia como configuración regional porque es uno de los países que usa el formato ISO 8601. Creo que la mayoría de los países ISO usan este formato 'GMT-4' para el desplazamiento de zona horaria diferente a Canadá, que usa la abreviatura de zona horaria, por ejemplo. "EDT" para el horario de verano del este.

Puede obtener lo mismo de la función estándar más nueva de i18n "Intl.DateTimeFormat ()", pero debe indicarle que incluya la hora a través de las opciones o simplemente dará la fecha.

Tom
fuente
1
Hm, para sv, en realidad obtengo "CET" y para una fecha de horario de verano, "CEST" ... Pero gracias por la entrada, la API estándar es suficiente para mí (necesito prefijar los mensajes de registro con una fecha). Si quisiera
tomarme
4

Esta es mi función para la zona horaria de los clientes, es liviana y simple

  function getCurrentDateTimeMySql() {        
      var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds
      var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, 19).replace('T', ' ');
      var mySqlDT = localISOTime;
      return mySqlDT;
  }
Bb
fuente
@tsh, ¿estás seguro? Obtiene la diferencia de hora actual, que luego se usa para simular la hora local. Quizás en ese momento la compensación era diferente, pero no importa porque solo te importa ahora.
Andrew
2

Mira esto:

function dateToLocalISO(date) {
    const off    = date.getTimezoneOffset()
    const absoff = Math.abs(off)
    return (new Date(date.getTime() - off*60*1000).toISOString().substr(0,23) +
            (off > 0 ? '-' : '+') + 
            (absoff / 60).toFixed(0).padStart(2,'0') + ':' + 
            (absoff % 60).toString().padStart(2,'0'))
}

// Test it:
d = new Date()

dateToLocalISO(d)
// ==> '2019-06-21T16:07:22.181-03:00'

// Is similar to:

moment = require('moment')
moment(d).format('YYYY-MM-DDTHH:mm:ss.SSSZ') 
// ==> '2019-06-21T16:07:22.181-03:00'
Nahuel Greco
fuente
1

No se necesita moment.js: aquí hay una respuesta completa de ida y vuelta, desde un tipo de entrada de "datetime-local" que genera una cadena ISOLocal a UTCseconds en GMT y viceversa:

<input type="datetime-local" value="2020-02-16T19:30">

isoLocal="2020-02-16T19:30"
utcSeconds=new Date(isoLocal).getTime()/1000

//here you have 1581899400 for utcSeconds

let isoLocal=new Date(utcSeconds*1000-new Date().getTimezoneOffset()*60000).toISOString().substring(0,16)
2020-02-16T19:30
Capitán Fantástico
fuente
0

Solo mis dos envios aqui

Estaba enfrentando este problema con las fechas y horas, así que lo que hice es esto:

const moment = require('moment-timezone')

const date = moment.tz('America/Bogota').format()

Luego, guarde la fecha en la base de datos para poder compararla con alguna consulta.


Instalar moment-timezone

npm i moment-timezone
Arnold Gandarillas
fuente
0

Mi respuesta es una ligera variación para aquellos que solo quieren la fecha de hoy en la zona horaria local en el formato AAAA-MM-DD.

Déjame ser claro:

Mi objetivo: obtener la fecha de hoy en la zona horaria del usuario pero con el formato ISO8601 (AAAA-MM-DD)

Aquí está el código:

new Date().toLocaleDateString("sv") // "2020-02-23" // 

Esto funciona porque la configuración regional de Suecia usa el formato ISO 8601.

Jonathan Steele
fuente
Respuesta no válida, esto responde, pero otra pregunta ... Esta respuesta se puede encontrar fácilmente en SO.
PsychoX
0

Aquí están las funciones que usé para este fin:

function localToGMTStingTime(localTime = null) {
    var date = localTime ? new Date(localTime) : new Date();
    return new Date(date.getTime() + (date.getTimezoneOffset() * 60000)).toISOString();
};

function GMTToLocalStingTime(GMTTime = null) {
    var date = GMTTime ? new Date(GMTTime) : new Date();;
    return new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString();
};
gildniy
fuente
0

Puede lograr esto con algunos métodos de extensión simples. El siguiente método de extensión de fecha devuelve solo el componente de zona horaria en formato ISO, luego puede definir otro para la parte de fecha / hora y combinarlos para una cadena completa de compensación de fecha y hora.

Date.prototype.getISOTimezoneOffset = function () {
    const offset = this.getTimezoneOffset();
    return (offset < 0 ? "+" : "-") + Math.floor(Math.abs(offset / 60)).leftPad(2) + ":" + (Math.abs(offset % 60)).leftPad(2);
}

Date.prototype.toISOLocaleString = function () {
    return this.getFullYear() + "-" + (this.getMonth() + 1).leftPad(2) + "-" +
        this.getDate().leftPad(2) + "T" + this.getHours().leftPad(2) + ":" +
        this.getMinutes().leftPad(2) + ":" + this.getSeconds().leftPad(2) + "." +
        this.getMilliseconds().leftPad(3);
}

Number.prototype.leftPad = function (size) {
    var s = String(this);
    while (s.length < (size || 2)) {
        s = "0" + s;
    }
    return s;
}

Uso de ejemplo:

var date = new Date();
console.log(date.toISOLocaleString() + date.getISOTimezoneOffset());
// Prints "2020-08-05T16:15:46.525+10:00"

Sé que es 2020 y la mayoría de las personas probablemente ya estén usando Moment.js, pero a veces es útil tener una solución simple de copiar y pegar.

(La razón por la que dividí los métodos de fecha / hora y desplazamiento es porque estoy usando una antigua biblioteca Datejs que ya proporciona un toStringmétodo flexible con especificadores de formato personalizados, pero simplemente no incluye el desplazamiento de la zona horaria. Por lo tanto, agregué toISOLocaleStringpara cualquier persona sin dicha biblioteca.)

Extragorey
fuente