AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 78042581
Accepted
pptaszni
pptaszni
Asked: 2024-02-23 00:25:59 +0800 CST2024-02-23 00:25:59 +0800 CST 2024-02-23 00:25:59 +0800 CST

100 anos de diferença no time_point após a serialização usando std::put_time e std::get_time

  • 772

Estou implementando serialização json e desserialização de carimbos de data/hora com nlohmann::json. Depois de transformar std::chrono::time_pointpara mm/dd/yy hh:mm:sso 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?

c++
  • 3 3 respostas
  • 115 Views

3 respostas

  • Voted
  1. Dave S
    2024-02-23T00:56:26+08:002024-02-23T00:56:26+08:00

    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::gmtimeseria melhor.

    Veja os resultados em https://godbolt.org/z/osT9741vz

    • 2
  2. AndyG
    2024-02-23T00:59:55+08:002024-02-23T00:59:55+08:00

    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:

    // Serialize a time_point to a string
    std::string serialize(const std::chrono::system_clock::time_point& time_point) {
        auto duration_since_epoch = time_point.time_since_epoch();
        auto milliseconds_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(duration_since_epoch).count();
        return std::to_string(milliseconds_since_epoch);
    }
    
    // Deserialize a string to a time_point
    std::chrono::system_clock::time_point deserialize(const std::string& str) {
        std::istringstream iss(str);
        long long milliseconds_since_epoch;
        iss >> milliseconds_since_epoch;
        auto duration_since_epoch = std::chrono::milliseconds(milliseconds_since_epoch);
        return std::chrono::system_clock::time_point(duration_since_epoch);
    }
    
    int main() {
        // Get the current time
        auto now = std::chrono::system_clock::now();
    
        // Serialize the time to a string
        std::string serialized_time = serialize(now);
        std::cout << "Serialized time: " << serialized_time << std::endl;
    
        // Deserialize the string back to a time_point
        auto deserialized_time = deserialize(serialized_time);
        std::cout << "Deserialized time: " << std::chrono::duration_cast<std::chrono::milliseconds>(deserialized_time.time_since_epoch()).count() << " milliseconds since epoch" << std::endl;
    
        return 0;
    }
    

    Demonstração ao vivo

    • 2
  3. Best Answer
    Marek R
    2024-02-23T01:44:14+08:002024-02-23T01:44:14+08:00

    O problema é como interpretar o ano de dois dígitos. Quando o ser humano vê, '80ele 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_timeapenas 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

    struct Param
    {
        std::string fullTime;
        std::string shortTime;
    };
    
    class MagicTimeStringTest : public testing::TestWithParam<Param>
    {
    public:
        using clock = std::chrono::system_clock;
        
        static clock::time_point parse(std::string in, std::string format)
        {
            std::istringstream stream(in);
            std::tm t = {};
            EXPECT_TRUE(stream >> std::get_time(&t, format.c_str()));
    #ifdef WORAROUND
            if (t.tm_year < 69) {
                t.tm_year += 100;
            }
    #endif
            return clock::from_time_t(std::mktime(&t));
        }
    
        static std::string toString(clock::time_point p)
        {
            std::ostringstream stream;
            auto t = clock::to_time_t(p);
            stream << std::put_time(std::localtime(&t), "%a %b %d %T %Y");
            return stream.str();
        }
    };
    
    TEST_P(MagicTimeStringTest, BarReturnsExpectedValue)
    {
        auto fromShort = parse(GetParam().shortTime, "%x %X");
        auto fromFull = parse(GetParam().fullTime, "%b %d %T %Y");
    
        EXPECT_EQ(fromShort, fromFull) << toString(fromShort) << '\n' << toString(fromFull);
    
        auto diffHours = std::chrono::duration_cast<std::chrono::hours>(fromFull - fromShort).count();
        EXPECT_EQ(diffHours, 0);
    }
    
    const Param data[] {
        {"Feb 22 16:51:30 1994", "02/22/94 16:51:30"},
        {"Feb 22 16:51:30 1974", "02/22/74 16:51:30"},
        {"Feb 22 16:51:30 1970", "02/22/70 16:51:30"},
        {"Feb 22 16:51:30 1969", "02/22/69 16:51:30"},
        {"Feb 22 16:51:30 2068", "02/22/68 16:51:30"}, // note here change in test data it is 2068 not 1968
        {"Feb 22 16:51:30 2024", "02/22/24 16:51:30"},
    };
    
    INSTANTIATE_TEST_SUITE_P(StackOverflow, MagicTimeStringTest, testing::ValuesIn(data));
    

    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

    Se o século não for especificado, então os valores no intervalo [69,99] deverão referir-se aos anos de 1969 a 1999 inclusive, e os valores no intervalo [00,68] deverão referir-se aos anos de 2000 a 2068 inclusive. O ano atual será o padrão se yy for omitido.[Opção Fim]

    Observação:

    Espera-se que em uma versão futura do IEEE Std 1003.1-2001 o século padrão inferido a partir de um ano de 2 dígitos mude. (Isso se aplicaria a todos os comandos que aceitam um ano de 2 dígitos como entrada.)

    Portanto, esse comportamento é padronizado IEEE Std 1003.1-2001e 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]

    %y

    Os dois últimos dígitos decimais do ano .

    Se o século não for especificado de outra forma (por exemplo, com %C), presume-se que os valores no intervalo [69, 99] se referem aos anos de 1969 a 1999, e os valores no intervalo [00, 68] se referem aos anos de 1969 a 1999. os anos de 2000 a 2068 .

    O comando modificado % N y especifica o número máximo de caracteres a serem lidos .

    Se N não for especificado, o padrão será 2 .

    Zeros à esquerda são permitidos, mas não obrigatórios .

    Os comandos modificados %Ey e %Oy interpretam a representação alternativa do código do idioma .

    • 2

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve