Como ISO 8601 formatar uma data com deslocamento de fuso horário em JavaScript?

100

Objetivo: Encontre o local timee, em UTC time offsetseguida, construa o URL no seguinte formato.

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

O formato é baseado na recomendação W3C: http://www.w3.org/TR/xmlschema11-2/#dateTime

A documentação diz:

Por exemplo, 2002-10-10T12: 00: 00−05: 00 (meio-dia em 10 de outubro de 2002, Central Daylight Savings Time, bem como Eastern Standard Time nos EUA) é igual a 2002-10-10T17: 00: 00Z, cinco horas depois de 2002-10-10T12: 00: 00Z.

Portanto, com base no meu entendimento, preciso encontrar minha hora local por new Date () e usar a função getTimezoneOffset () para calcular a diferença e anexá-la ao final da string.

1. Obtenha a hora local com formato

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

resultado

2013-07-02T09:00:00

2. Obtenha a diferença horária UTC por hora

var offset = local.getTimezoneOffset() / 60;

resultado

7

3. Construir URL (apenas parte do tempo)

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

resultado:

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

O resultado acima significa que meu horário local é 02/07/2013 e a diferença do UTC é de 7 horas (o UTC está 7 horas à frente do horário local)

Até agora parece funcionar, mas e se getTimezoneOffset () retornar um valor negativo como -120?

Estou me perguntando como o formato deve ser nesse caso, porque não consigo descobrir a partir do documento W3C. Desde já, obrigado.

Miau
fonte

Respostas:

174

O abaixo deve funcionar corretamente, e para todos os navegadores (obrigado a @MattJohnson pela dica)

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
fonte
2
O sinal indica o deslocamento da hora local de GMT
Steven Moseley
1
@ masato-san Você tem que inverter o sinal, veja a definição em developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bfavaretto
2
Aqui, a função pad retorna a string apropriada para cada seção de data, exceto milissegundos. Por exemplo, por 5ms como uma entrada retornará 05, que deveria ser 005. Aqui está um link com versão mínima modificada da mesma função jsbin
Safique Ahmed Faruque
9
Observação: há um bug sutil nesta resposta. A diferença de fuso horário não será calculada corretamente se a zona tiver minutos de diferença e for negativa (por exemplo, -09: 30), como certos locais na França e no Canadá. O mod retorna um número negativo, portanto, fazer floor () antes de abs () inadvertidamente tornou o deslocamento MAIS negativo. Para corrigir esse bug, então abs () e THEN floor (). Tentei editar a resposta, mas aparentemente "Esta edição foi destinada a se dirigir ao autor da postagem e não faz sentido como uma edição".
tytk
5
@tytk - Grande captura! Isso é, de fato, sutil. Eu adicionei sua correção à resposta. Obrigado por comentar!
Steven Moseley
67

getTimezoneOffset() retorna o sinal oposto do formato exigido pela especificação que você referenciou.

Este formato também é conhecido como ISO8601 ou, mais precisamente, RFC3339 .

Nesse formato, o UTC é representado por um Zenquanto todos os outros formatos são representados por um deslocamento do UTC. O significado é o mesmo do JavaScript, mas a ordem de subtração é invertida, então o resultado carrega o sinal oposto.

Além disso, não há método no Dateobjeto nativo chamado format, portanto, sua função no # 1 falhará, a menos que você esteja usando uma biblioteca para fazer isso. Consulte esta documentação .

Se você está procurando uma biblioteca que possa trabalhar com esse formato diretamente, recomendo experimentar o moment.js . Na verdade, este é o formato padrão, então você pode simplesmente fazer isso:

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 é uma solução bem testada para vários navegadores e possui muitos outros recursos úteis.

Matt Johnson-Pint
fonte
Usar toISOString () não funcionará. O formato +01: 00 requer que a parte da hora seja a hora local. toISOString () forneceria uma string de hora UTC.
Austin França
1
@AustinFrance - Você está certo! Estou surpreso por ter cometido esse erro na época, pois corrijo outras pessoas sobre esse ponto com frequência. Desculpe, não vi seu comentário há dois anos! Resposta editada.
Matt Johnson-Pint
9

Acho que vale a pena considerar que você pode obter as informações solicitadas com apenas uma única chamada de API para a biblioteca padrão ...

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

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

Você teria que fazer a troca de texto se quiser adicionar o delimitador 'T', remover o 'GMT-' ou anexar ': 00' ao final.

Mas então você pode facilmente jogar com as outras opções se quiser, por exemplo. use 12h ou omita os segundos etc.

Observe que estou usando a Suécia como local porque é um dos países que usa o formato ISO 8601. Acho que a maioria dos países ISO usa este formato 'GMT-4' para o deslocamento de fuso horário, exceto o Canadá, que usa a abreviatura de fuso horário, por exemplo. "EDT" para horário de verão do leste.

Você pode obter a mesma coisa da função i18n padrão mais recente "Intl.DateTimeFormat ()", mas você deve dizer a ela para incluir a hora através das opções ou ela apenas fornecerá a data.

Tom
fonte
1
Hm, para sv, eu realmente obtenho "CET" e para uma data de horário de verão, "CEST" ... Mas obrigado pela entrada, a API padrão é suficiente para mim (preciso prefixar mensagens de log com uma data). Se eu quisesse levar a sério o tempo e os fusos horários, acho que iria para a biblioteca de momentos ...
mmey
4

Esta é a minha função para o fuso horário do cliente, é leve e simples

  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;
  }
Bbb
fonte
@tsh, tem certeza? Você está obtendo a diferença de hora atual, que mais tarde é usada para simular a hora local. Talvez naquele ponto o deslocamento fosse diferente, mas não importa porque você apenas se preocupa com agora.
Andrew
2

Verifique isto:

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
fonte
1

Não é necessário moment.js: aqui está uma resposta completa de ida e volta, de um tipo de entrada "datetime-local" que gera uma string ISOLocal para UTCsegundos no GMT e vice-versa:

<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ão Fantástico
fonte
0

Só os meus dois mandam aqui

Eu estava enfrentando esse problema com os datetimes, então o que fiz foi o seguinte:

const moment = require('moment-timezone')

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

Em seguida, salve a data no banco de dados para poder compará-la com alguma consulta.


Para instalar moment-timezone

npm i moment-timezone
Arnold Gandarillas
fonte
0

Minha resposta é uma pequena variação para aqueles que querem apenas a data de hoje no fuso horário local no formato AAAA-MM-DD.

Deixe-me ser claro:

Meu objetivo: obter a data de hoje no fuso horário do usuário, mas formatado como ISO8601 (AAAA-MM-DD)

Aqui está o código:

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

Isso funciona porque a localidade da Suécia usa o formato ISO 8601.

Jonathan Steele
fonte
Resposta inválida, esta responde, mas outra pergunta ... Esta resposta pode ser facilmente encontrada no SO.
PsychoX
0

Aqui estão as funções que usei para esse fim:

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
fonte
0

Você pode conseguir isso com alguns métodos de extensão simples. O método de extensão de Data a seguir retorna apenas o componente de fuso horário no formato ISO, então você pode definir outro para a parte de data / hora e combiná-los para uma string de deslocamento de data-hora completa.

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;
}

Exemplo de uso:

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

Eu sei que estamos em 2020 e a maioria das pessoas provavelmente está usando Moment.js agora, mas uma solução simples de copiar e colar às vezes ainda é útil.

(A razão pela qual divido os métodos de data / hora e deslocamento é porque estou usando uma biblioteca Datejs antiga que já fornece um toStringmétodo flexível com especificadores de formato personalizado, mas apenas não inclui o deslocamento de fuso horário. Portanto, adicionei toISOLocaleStringpara qualquer pessoa sem dita biblioteca.)

Extragorey
fonte