Estou implementando serialização json e desserialização de carimbos de data/hora com nlohmann::json
. Depois de transformar std::chrono::time_point
para mm/dd/yy hh:mm:ss
o estilo data-hora (de acordo com nossos requisitos, deve ser legível por humanos), naturalmente estou perdendo algumas informações, portanto, meu critério para a igualdade dos pontos no tempo após a desserialização é "diferente em não mais que um segundo".
Tudo estava funcionando bem na minha máquina local com o gcc 11.4.0, mas falhou no servidor de produção com o gcc 8.5.0.
Código mínimo para reproduzir o exemplo ao vivo do problema em godbolt :
#include <iostream>
#include <iomanip>
#include <chrono>
#include <string>
int main() {
// serialization
auto ts = std::chrono::system_clock::now();
auto timeS_t = std::chrono::system_clock::to_time_t(ts);
std::ostringstream outStream;
outStream << std::put_time(std::localtime(&timeS_t), "%x %X");
std::string tsStr = outStream.str();
// deserialization
std::istringstream ss(tsStr);
std::tm t = {};
ss >> std::get_time(&t, "%x %X");
auto parsedTs = std::chrono::system_clock::from_time_t(std::mktime(&t));
// difference in seconds
int secDiff = std::chrono::duration_cast<std::chrono::seconds>(ts - parsedTs).count();
// just debug prints
std::cout << secDiff << std::endl;
std::time_t ttp1 = std::chrono::system_clock::to_time_t(ts);
std::time_t ttp2 = std::chrono::system_clock::to_time_t(parsedTs);
std::cout << "ts1: " << std::ctime(&ttp1);
std::cout << "ts2: " << std::ctime(&ttp2);
// my equality criterion
if (std::abs(secDiff) > 1) {
std::cout << "FAIL\n";
}
}
Com o compilador mais recente, obtenho resultados iguais, por exemplo
ts1: Thu Feb 22 16:06:25 2024
ts2: Thu Feb 22 16:06:25 2024
mas com o antigo vejo a diferença nos pontos no tempo em exatamente 100 anos:
ts1: Thu Feb 22 16:06:17 2024
ts2: Fri Feb 22 16:06:17 1924
Acho que pode ter algo a ver com std::localtime(&timeS_t)
a rotina de serialização, mas não encontrei a função "reversa" para o desserializador. O que posso mudar para ter um comportamento consistente?
Eu diria que o principal problema é usar "%X %x" como formato. De acordo com cppreference , isso depende da localidade. Isso significa que você obterá representações diferentes dependendo de onde for executado ou até mesmo, como você pode ver, diferentes versões do compilador. Para o formato, eu recomendaria
"%Y-%m-%d %H:%M:%S"
evitar o comportamento dependente da localidade.Eu também recomendaria o horário local, que depende do fuso horário.
std::gmtime
seria melhor.Veja os resultados em https://godbolt.org/z/osT9741vz
Você não deseja serializar uma data/hora para um formato dependente de localidade. Em vez disso, serialize algo que seja independente da localidade, como milissegundos desde a época:
Demonstração ao vivo
O problema é como interpretar o ano de dois dígitos. Quando o ser humano vê,
'80
ele assume o ano de 1980. E quando vê'22
, ele assume o ano de 2022.Agora, esse preconceito humano não foi refletido na versão mais antiga da biblioteca, então
std::get_time
apenas assume que o ano de dois dígitos se refere a 1900. A nova versão da biblioteca tenta equilibrar isso e, dependendo do valor de dois dígitos, ela assume 1900 ou 2000.Aqui está uma demonstração do problema, incluindo uma possível solução alternativa para o gcc 8.5: https://godbolt.org/z/MThhac943
Seria bom encontrar uma explicação desse comportamento na documentação. Eu não ficaria surpreso se a fronteira entre a mudança de séculos se movesse da mesma forma que se move com o tempo atual, portanto o futuro 2100 é abordado.
Editar:
Com a ajuda de perplexity.ai consegui encontrar alguma documentação que explica esse comportamento (google e bing falharam nesta pesquisa):
data
Portanto, esse comportamento é padronizado
IEEE Std 1003.1-2001
e espera-se que seja atualizado no futuro. Até agora não consegui encontrar documentos disponíveis publicamente com este padrão.Howard Hinnant forneceu um ótimo link para o padrão C++ 20 como comentário:
[tempo.parse]