Durante uma experiência recente ao escrever um intérprete de JS, lutei bastante com o funcionamento interno das datas da ECMA / JS. Então, acho que vou jogar meus 2 centavos aqui. Esperamos que o compartilhamento dessas coisas ajude outras pessoas com qualquer dúvida sobre as diferenças entre os navegadores na maneira como lidam com as datas.
O lado da entrada
Todas as implementações armazenam seus valores de data internamente como números de 64 bits que representam o número de milissegundos (ms) desde 01-01-2009 UTC (GMT é a mesma coisa que UTC). Essa data é a época do ECMAScript que também é usada por outros idiomas, como sistemas Java e POSIX, como o UNIX. As datas que ocorrem após a época são números positivos e as datas anteriores são negativas.
O código a seguir é interpretado como a mesma data em todos os navegadores atuais, mas com o deslocamento do fuso horário local:
Date.parse('1/1/1970'); // 1 January, 1970
No meu fuso horário (EST, que é -05: 00), o resultado é 18000000 porque é quantos ms há em 5 horas (são apenas 4 horas durante os meses de verão). O valor será diferente em diferentes fusos horários. Esse comportamento é especificado no ECMA-262 para que todos os navegadores façam o mesmo.
Embora exista alguma variação nos formatos de sequência de entrada que os principais navegadores analisarão como datas, eles essencialmente os interpretam da mesma forma no que diz respeito a fusos horários e horário de verão, embora a análise seja amplamente dependente da implementação.
No entanto, o formato ISO 8601 é diferente. É um dos dois únicos formatos descritos no ECMAScript 2015 (ed 6) especificamente que deve ser analisado da mesma maneira por todas as implementações (o outro é o formato especificado para Date.prototype.toString ).
Mas, mesmo para as cadeias de formato ISO 8601, algumas implementações estão erradas. Aqui está uma saída de comparação do Chrome e Firefox quando esta resposta foi originalmente escrita para 1/1/1970 (a época) na minha máquina usando cadeias de formato ISO 8601 que devem ser analisadas exatamente para o mesmo valor em todas as implementações:
Date.parse('1970-01-01T00:00:00Z'); // Chrome: 0 FF: 0
Date.parse('1970-01-01T00:00:00-0500'); // Chrome: 18000000 FF: 18000000
Date.parse('1970-01-01T00:00:00'); // Chrome: 0 FF: 18000000
- No primeiro caso, o especificador "Z" indica que a entrada está no horário UTC, portanto não é deslocada da época e o resultado é 0
- No segundo caso, o especificador "-0500" indica que a entrada está no GMT-05: 00 e os dois navegadores interpretam a entrada como estando no fuso horário -05: 00. Isso significa que o valor UTC é deslocado da época, o que significa adicionar 18000000ms ao valor da hora interna da data.
- O terceiro caso, onde não há especificador, deve ser tratado como local para o sistema host. O FF trata corretamente a entrada como hora local, enquanto o Chrome a trata como UTC, produzindo valores de hora diferentes. Para mim, isso cria uma diferença de 5 horas no valor armazenado, o que é problemático. Outros sistemas com diferentes compensações obterão resultados diferentes.
Essa diferença foi corrigida a partir de 2020, mas existem outras peculiaridades entre os navegadores ao analisar as cadeias de formato ISO 8601.
Mas piora. Uma peculiaridade do ECMA-262 é que o formato somente data ISO 8601 (AAAA-MM-DD) deve ser analisado como UTC, enquanto o ISO 8601 exige que ele seja analisado como local. Aqui está a saída do FF com os formatos de data ISO longo e curto sem especificador de fuso horário.
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
Portanto, o primeiro é analisado como local porque é a data e hora da ISO 8601 sem fuso horário e o segundo é analisado como UTC porque é apenas a data da ISO 8601.
Portanto, para responder diretamente à pergunta original, o "YYYY-MM-DD"ECMA-262 exige que seja interpretado como UTC, enquanto o outro é interpretado como local. É por isso:
Isso não produz resultados equivalentes:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
Isto faz:
console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());
A linha inferior é esta para analisar seqüências de datas. A ÚNICA string ISO 8601 que você pode analisar com segurança nos navegadores é o formato longo com um deslocamento (± HH: mm ou "Z"). Se você fizer isso, poderá ir e voltar com segurança entre o horário local e o UTC.
Isso funciona nos navegadores (após o IE9):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
A maioria dos navegadores atuais trata os outros formatos de entrada igualmente, incluindo os usados com frequência '1/1/1970' (M / D / AAAA) e '1/1/1970 00:00:00 AM' (M / D / AAAA hh : mm: ss ap) formatos. Todos os seguintes formatos (exceto o último) são tratados como entrada da hora local em todos os navegadores. A saída desse código é a mesma em todos os navegadores no meu fuso horário. O último é tratado como -05: 00, independentemente do fuso horário do host, porque o deslocamento é definido no registro de data e hora:
console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));
No entanto, como a análise até dos formatos especificados no ECMA-262 não é consistente, é recomendável nunca confiar no analisador interno e sempre analisar manualmente as seqüências de caracteres, digamos, usando uma biblioteca e fornecendo o formato ao analisador.
Por exemplo, em moment.js você pode escrever:
let m = moment('1/1/1970', 'M/D/YYYY');
O lado da saída
No lado da saída, todos os navegadores convertem os fusos horários da mesma maneira, mas manipulam os formatos de sequência de maneira diferente. Aqui estão as toStringfunções e o que elas produzem. Observe que as funções toUTCStringe toISOStringsaem às 5:00 da manhã na minha máquina. Além disso, o nome do fuso horário pode ser uma abreviação e pode ser diferente em diferentes implementações.
Converte do UTC para o horário local antes de imprimir
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
Imprime diretamente a hora UTC armazenada
- toUTCString
- toISOString
No Chrome
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString 1/1/1970 12:00:00 AM
toLocaleDateString 1/1/1970
toLocaleTimeString 00:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
No Firefox
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString Thursday, January 01, 1970
toLocaleTimeString 12:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
Normalmente não uso o formato ISO para entrada de string. A única vez que usar esse formato é benéfico para mim é quando as datas precisam ser classificadas como cadeias. O formato ISO pode ser classificado como está, enquanto os outros não. Se você precisar ter compatibilidade entre navegadores, especifique o fuso horário ou use um formato de sequência compatível.
O código new Date('12/4/2013').toString()passa pela seguinte pseudo-transformação interna:
"12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
Eu espero que a resposta tenha ajudado.