Por que Date.parse fornece resultados incorretos?

344

Caso um:

new Date(Date.parse("Jul 8, 2005"));

Resultado:

Sexta, 08 de julho de 2005 00:00:00 GMT-0700 (PST)

Caso dois:

new Date(Date.parse("2005-07-08"));

Resultado:

Qui Jul 07 2005 17:00:00 GMT-0700 (PST)


Por que a segunda análise está incorreta?

user121196
fonte
30
A segunda análise não está incorreta por si só, mas a primeira é analisada no horário local e a segunda no UTC. Observe que "Qui Jul 07 2005 17:00:00 GMT-0700 (PST)" é o mesmo que "2005-07-08 00:00".
jches 23/07
18
ISO 8601 xkcd.
ulidtko
11
Caso alguém tenha vindo aqui para descobrir por que uma data está retornando NaNno Firefox, descobri que a maioria dos outros navegadores (e o Node.js) analisam uma data sem um dia, como "abril de 2014" como 1º de abril de 2014, mas o Firefox retorna NaN. Você deve passar uma data adequada.
Jazzy
Para adicionar ao comentário de Jason acima: Se você estiver recebendo um NaN no Firefox, outro problema pode ser que o Firefox e o Safari não gostem de datas hifenizadas. Somente o Chrome faz. Use uma barra.
Bangkokian

Respostas:

451

Até a especificação da 5ª edição, o Date.parsemétodo era completamente dependente da implementação ( new Date(string)é equivalente a, Date.parse(string)exceto que o último retorna um número em vez de a Date). Na 5ª edição, o requisito foi adicionado para oferecer suporte a uma ISO-8601 simplificada (e um pouco incorreta) (também consulte O que são seqüências de data e hora válidas em JavaScript? ). Mas, além disso, não havia nenhum requisito para o que Date.parse/ new Date(string)deveria aceitar, exceto que eles tinham que aceitar qualquer Date#toStringsaída (sem dizer o que era).

A partir do ECMAScript 2017 (edição 8), as implementações eram necessárias para analisar sua saída para Date#toStringe Date#toUTCString, mas o formato dessas cadeias não era especificado.

A partir do ECMAScript 2019 (edição 9), o formato para Date#toStringe Date#toUTCStringfoi especificado como (respectivamente):

  1. ddd MMM DD AAAA HH: mm: ss ZZ [(nome do fuso horário)]
    por exemplo, ter 10 de julho de 2018 18:39:58 GMT + 0530 (IST)
  2. ddd, DD MMM AAAA HH: mm: ss Z e
    . Terça-feira, 10 de julho de 2018 13:09:58 GMT

fornecendo mais 2 formatos que Date.parsedevem analisar de maneira confiável em novas implementações (observando que o suporte não é onipresente e as implementações não compatíveis permanecerão em uso por algum tempo).

Eu recomendaria que as seqüências de datas sejam analisadas manualmente e o construtor Date seja usado com argumentos de ano, mês e dia para evitar ambiguidade:

// parse a date in yyyy-mm-dd format
function parseDate(input) {

  let parts = input.split('-');

  // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
CMS
fonte
Excelente, eu tive que usar isso como Date.parsenão estava se comportando com formatos de data do Reino Unido, por algum motivo eu não poderia trabalhar para fora
Ben
11
As partes do tempo estão documentadas no código @CMS. Usei esse código com um formato de data "2012-01-31 12:00:00". return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);Funciona perfeitamente, obrigado!
Richard Rout
11
@CMS, o que você quer dizer com implementação dependente ?
Royi Namir
3
@RoyiNamir, significa que os resultados dependem de qual navegador da Web (ou outra implementação JavaScript) está executando seu código.
Samuel Edwin Ward
11
Eu também tive problemas com a nova data (string) em diferentes navegadores se comportando de maneira diferente. Não é mesmo uma questão de estar quebrado nas versões antigas do IE, os diferentes navegadores simplesmente não são consistentes. Não use Date.parse ou a nova data (string) de sempre.
precisa
195

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.

drankin2112
fonte
3
Primeiro, este é um artigo fantástico. Eu queria apontar uma dependência, no entanto. Com relação aos especificadores de fuso horário, você declarou: "A ausência de um especificador deve presumir a entrada da hora local". Felizmente, o padrão ECMA-262 remove qualquer necessidade de presunção. Ele afirma : "O valor de um deslocamento de fuso horário ausente é" Z "." Portanto, uma string de data / hora sem um fuso horário especificado é assumida como UTC e não como hora local. Obviamente, como em muitas coisas em JavaScript, parece haver pouco acordo entre implementações.
Daniel
2
... incluindo os formatos '1/1/1970' e '1/1/1970 00:00:00'. - usado com mais frequência onde ? Isso não está no meu país, com certeza.
ulidtko
2
@ulidtko - Desculpe, estou nos EUA. Uau ... você está aí em Kiev. Espero que você e sua família fiquem seguros e que as coisas se estabilizem por lá em breve. Cuide-se e boa sorte com tudo.
precisa saber é o seguinte
Apenas uma nota aqui. Parece que isso não funciona nos navegadores Safari (ou seja, iOS ou OSX). Isso ou eu tenho alguma outra questão acontecendo.
precisa saber é
11
@ Daniel - felizmente, os autores do ECMAScript corrigiram seu erro com relação ao fuso horário ausente para representações de data e hora. Agora, as strings de data e hora sem fuso horário usam o deslocamento do fuso horário do host (ou seja, "local"). De maneira confusa, os formulários de data ISO 8601 são tratados apenas como UTC (embora não esteja particularmente claro nas especificações), enquanto o ISO 8601 os trata como local, portanto, eles não consertaram tudo.
RobG 28/01
70

Existe algum método para a loucura. Como regra geral, se um navegador puder interpretar uma data como uma ISO-8601, será. "08-07-2005" se enquadra neste campo e, portanto, é analisado como UTC. "8 de julho de 2005" não pode e, portanto, é analisado no horário local.

Veja JavaScript e datas, que confusão! para mais.

danvk
fonte
3
" Como regra geral, se um navegador puder interpretar uma data como uma ISO-8601, será. " Não é suportado. "2020-03-20 13:30:30" é tratado como ISO 8601 e local por muitos navegadores, mas Data inválida pelo Safari. Existem muitos formatos ISO 8601 que não são suportados pela maioria dos navegadores, por exemplo, 2004-W53-7 e 2020-092.
RobG
7

Outra solução é criar uma matriz associativa com formato de data e reformatar os dados.

Este método é útil para a data formatada de maneira não convencional.

Um exemplo:

    mydate='01.02.12 10:20:43':
    myformat='dd/mm/yy HH:MM:ss';


    dtsplit=mydate.split(/[\/ .:]/);
    dfsplit=myformat.split(/[\/ .:]/);

    // creates assoc array for date
    df = new Array();
    for(dc=0;dc<6;dc++) {
            df[dfsplit[dc]]=dtsplit[dc];
            }

    // uses assc array for standard mysql format
    dstring[r] = '20'+df['yy']+'-'+df['mm']+'-'+df['dd'];
    dstring[r] += ' '+df['HH']+':'+df['MM']+':'+df['ss'];
Guido Rizzi
fonte
5

Use moment.js para analisar datas:

var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();

O terceiro argumento determina a análise estrita (disponível a partir do 2.3.0). Sem ele, o moment.js também pode fornecer resultados incorretos.

Lukasz Wiktor
fonte
4

De acordo com http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html, o formato "aaaa / mm / dd" resolve os problemas comuns. Ele diz: "Atenha-se a" AAAA / MM / DD "para suas seqüências de datas sempre que possível. É universalmente suportado e sem ambiguidade. Com esse formato, todos os horários são locais". Eu defini testes: http://jsfiddle.net/jlanus/ND2Qg/432/ Este formato: + evita a ambiguidade da ordem do dia e do mês usando a ordenação ymd e um ano com 4 dígitos + evita o problema UTC vs. local não em conformidade com o formato ISO usando slashes + danvk, o cara dos dygraphs , diz que esse formato é bom em todos os navegadores.

Juan Lanus
fonte
Você pode querer ver a resposta do autor .
22812 Brad Koch
Eu diria que a solução com o exemplo no jsFiddle funciona bastante bem se você usar o jQuery, pois ele usa o analisador de datepicker. No meu caso, o problema está no jqGrid, mas ele encontrou seu método parseDate. De qualquer forma, o exemplo me ajudou ao me dar uma idéia com +1, obrigado.
Vasil Popov
2
Esse artigo sobre digrafos está errado e o primeiro exemplo da página ilustra claramente por que usar barras em vez de hífens é um péssimo conselho. No momento em que o artigo foi escrito, o uso de "2012/03/13" fez com que o navegador o analisasse como uma data local, em vez de UTC. A especificação ECMAScript define suporte explícito ao uso de "AAAA-MM-DD" (ISO8601), portanto, sempre use hífens. Note-se que no momento em que escrevo este comentário, o Chrome foi corrigido para tratar barras como UTC.
Lachlan caça
4

Embora o CMS esteja certo de que a passagem de strings para o método de análise geralmente não é segura, a nova especificação ECMA-262 5th Edition (também conhecida como ES5) na seção 15.9.4.2 sugere que Date.parse()realmente deve lidar com datas no formato ISO. A especificação antiga não fez tal afirmação. Obviamente, navegadores antigos e alguns navegadores atuais ainda não fornecem essa funcionalidade ES5.

Seu segundo exemplo não está errado. É a data especificada no UTC, conforme implícita Date.prototype.toISOString(), mas é representada no seu fuso horário local.

peller
fonte
11
E a análise das seqüências de datas foi alterada novamente no ECMAScript 2015 para que "08-07-2005" seja local, não UTC. BTW, ES5 não era o padrão até junho de 2011 (e atualmente o ECMAScript 2015 é). ;-)
RobG 23/09/2015
11
Para confundir as coisas, o TC39 decidiu em outubro (apenas um mês após a publicação anterior) que "2005-07-08" deveria ser UTC , no entanto "" 2005-07-08T00: 00: 00 "deveria ser local. Ambos são ISO 8601 formatos compatíveis, ambos estão sem um fuso horário, mas são tratados de forma diferente ...
Figura
2

Essa biblioteca de análise de data leve deve resolver todos os problemas semelhantes. Gosto da biblioteca porque é muito fácil estender. Também é possível identificá-lo (não muito direto, mas não tão difícil).

Exemplo de análise:

var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y");
var caseTwo = Date.parseDate("2005-07-08", "Y-m-d");

E formatar de volta para string (você notará que ambos os casos dão exatamente o mesmo resultado):

console.log( caseOne.dateFormat("M d, Y") );
console.log( caseTwo.dateFormat("M d, Y") );
console.log( caseOne.dateFormat("Y-m-d") );
console.log( caseTwo.dateFormat("Y-m-d") );
Nux
fonte
2

Aqui está um trecho curto e flexível para converter uma sequência de data / hora de maneira segura para vários navegadores, conforme o nicel detalhado por @ drankin2112.

var inputTimestamp = "2014-04-29 13:00:15"; //example

var partsTimestamp = inputTimestamp.split(/[ \/:-]/g);
if(partsTimestamp.length < 6) {
    partsTimestamp = partsTimestamp.concat(['00', '00', '00'].slice(0, 6 - partsTimestamp.length));
}
//if your string-format is something like '7/02/2014'...
//use: var tstring = partsTimestamp.slice(0, 3).reverse().join('-');
var tstring = partsTimestamp.slice(0, 3).join('-');
tstring += 'T' + partsTimestamp.slice(3).join(':') + 'Z'; //configure as needed
var timestamp = Date.parse(tstring);

Seu navegador deve fornecer o mesmo resultado de carimbo de data / hora que Date.parse:

(new Date(tstring)).getTime()
Lorenz Lo Sauer
fonte
Eu sugiro para adicionar T para o regex para pegar datas já JS-formatados assim: inputTimestamp.split (/ [T \ /: -] / g)
andig
Se você dividir a sequência em partes componentes, a próxima etapa mais confiável será usar essas partes como argumentos para o construtor Date. Criar outra string para fornecer ao analisador apenas leva você de volta à etapa 1. "2014-04-29 13:00:15" deve ser analisado como local, seu código o formata novamente como UTC. :-(
RobG 10/04/19
2

Ambos estão corretos, mas estão sendo interpretados como datas com dois fusos horários diferentes. Então você comparou maçãs e laranjas:

// local dates
new Date("Jul 8, 2005").toISOString()            // "2005-07-08T07:00:00.000Z"
new Date("2005-07-08T00:00-07:00").toISOString() // "2005-07-08T07:00:00.000Z"
// UTC dates
new Date("Jul 8, 2005 UTC").toISOString()        // "2005-07-08T00:00:00.000Z"
new Date("2005-07-08").toISOString()             // "2005-07-08T00:00:00.000Z"

Eu removi a Date.parse()chamada, pois ela é usada automaticamente em um argumento de string. Também comparei as datas usando o formato ISO8601 para que você pudesse comparar visualmente as datas entre as datas locais e as datas UTC. Os horários são separados por 7 horas, que é a diferença de fuso horário e por que seus testes mostraram duas datas diferentes.

A outra maneira de criar essas mesmas datas locais / UTC seria:

new Date(2005, 7-1, 8)           // "2005-07-08T07:00:00.000Z"
new Date(Date.UTC(2005, 7-1, 8)) // "2005-07-08T00:00:00.000Z"

Mas eu ainda recomendo o Moment.js, que é tão simples quanto poderoso :

// parse string
moment("2005-07-08").format()       // "2005-07-08T00:00:00+02:00"
moment.utc("2005-07-08").format()   // "2005-07-08T00:00:00Z"
// year, month, day, etc.
moment([2005, 7-1, 8]).format()     // "2005-07-08T00:00:00+02:00"
moment.utc([2005, 7-1, 8]).format() // "2005-07-08T00:00:00Z"
DJDaveMark
fonte
0

A resposta aceita do CMS está correta, acabei de adicionar alguns recursos:

  • aparar e limpar espaços de entrada
  • analisar barras, traços, dois pontos e espaços
  • tem dia e hora padrão

// parse a date time that can contains spaces, dashes, slashes, colons
function parseDate(input) {
    // trimes and remove multiple spaces and split by expected characters
    var parts = input.trim().replace(/ +(?= )/g,'').split(/[\s-\/:]/)
    // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
    return new Date(parts[0], parts[1]-1, parts[2] || 1, parts[3] || 0, parts[4] || 0, parts[5] || 0); // Note: months are 0-based
}
Stephane L
fonte