O struct tm armazena informações de fuso horário como seu membro de dados

8

Considere o seguinte código C ++

#include <ctime>
#include <iostream>

int main()
{
    std::time_t now = std::time(nullptr);
    struct tm local = *std::localtime(&now);
    struct tm gm = *std::gmtime(&now);
    char str[20];
    std::strftime(str, 20, "%Z", &local);
    std::cout << str << std::endl;          // HKT
    std::strftime(str, 20, "%Z", &gm);
    std::cout << str << std::endl;          // UTC

    return 0;
}

Então armazenados em nowé um valor integral inequívoca, enquanto locale gmsão struct tmque armazenam informações de data / hora legível. Em seguida, imprimo as informações formatadas (fuso horário) com base apenas nos struct tmobjetos.

De acordo com a referência cplusplus , os membros dos dados struct tmsão

tm_sec  
tm_min  
tm_hour 
tm_mday 
tm_mon  
tm_year 
tm_wday 
tm_yday 
tm_isdst

Se isso é tudo o que a struct tmcontém, como o programa sabe as informações de fuso horário? Isto é, como ele sabe que o fuso horário é HKTpara local, e que o fuso horário é UTCpara gm?

Se isso não é tudo o que a struct tmcontém, explique como ele armazena informações de fuso horário.

A propósito, embora o código de demonstração esteja em C ++, acho que essa questão também é essencialmente uma questão C legítima.

aafulei
fonte
2
tmnão contém informações de fuso horário. strftimeobtém o fuso horário pelo vodu nos bastidores. Se você deseja obter o fuso horário em geral, isso é uma bagunça. Existe ( atualmente ) nenhuma maneira padrão para obter um fuso horário. Felizmente Howard Hinnant está em que trabalho ... .
user4581301
Obrigado @ user4581301 Isso responde parcialmente à minha pergunta. Mas ainda tenho perguntas de acompanhamento: dadas todas as informações armazenadas tm, como strftimesaber responder de maneiras diferentes a dois struct tmobjetos? A menos que tmcontém algumas informações como este tmé criado porlocaltime , que tmé criado porgmtime .
aafulei 18/10/19
O tmstruct não armazena informações de fuso horário, o que faz você pensar que ele faz? A diferença está nas chamadas para gmtime()e localtime().
Ulrich Eckhardt
A página de manual cobre como as informações do fuso horário são adquiridas em um sistema POSIX. Ainda estou procurando como strftimedistinguir os otários. Deve-se acrescentar que o POSIX deixa indefinido o que acontece.
user4581301
1
Falha total com o gcc 9.2.0 do MSYS2 no Windows. Ver isso me lembrou que eu tinha visto tms fora do padrão com informações extras. Aqui está um . Observe o const char *tm_zonemembro. Para qual plataforma você está compilando? Dê uma olhada na tmimplementação para ver se eles estenderam a estrutura.
user4581301

Respostas:

5

O padrão C diz em 7.27.1 Componentes do tempo:

A tmestrutura deve conter pelo menos os seguintes membros, em qualquer ordem. A semântica dos membros e seus intervalos normais são expressos nos comentários. 318)

int tm_sec;    // seconds after the minute — [0, 60]
int tm_min;    // minutes after the hour — [0, 59]
int tm_hour;   // hours since midnight — [0, 23]
int tm_mday;   // day of the month — [1, 31]
int tm_mon;    // months since January — [0, 11]
int tm_year;   // years since 1900
int tm_wday;   // days since Sunday — [0, 6]
int tm_yday;   // days since January 1 — [0, 365]
int tm_isdst;  // Daylight Saving Time flag

(ênfase é minha)

Ou seja, as implementações têm permissão para adicionar membros adicionais tm, como você encontrou glibc/time/bits/types/struct_tm.h. A especificação POSIX possui uma redação quase idêntica.

O resultado é que %Z(ou mesmo %z) não pode ser considerado portátil strftime. A especificação para %Zreflete isso:

%Zé substituído pelo nome ou abreviação do fuso horário da localidade ou por nenhum caractere se nenhum fuso horário for determinável. [tm_isdst]

Ou seja, os fornecedores podem levantar as mãos e simplesmente dizer: "nenhum fuso horário era determinável; portanto, não estou produzindo nenhum caractere".

Minha opinião: a API de tempo C é uma bagunça.


Estou tentando melhorar as coisas para o próximo padrão C ++ 20 na <chrono>biblioteca.

A especificação do C ++ 20 altera isso de "sem caracteres" para uma exceção sendo lançada se a time_zoneabreviação não estiver disponível:

http://eel.is/c++draft/time.format#3

A menos que seja solicitado explicitamente, o resultado da formatação de um tipo de cronômetro não contém abreviação de fuso horário e informações de deslocamento de fuso horário. Se as informações estiverem disponíveis, os especificadores de conversão %Ze %zformatarão essas informações (respectivamente). [ Nota: Se as informações não estiverem disponíveis e um especificador de conversão %Zou %zaparecer na especificação de formato de cronômetro , uma exceção do tipo format_­errorserá lançada, conforme descrito acima. - nota final ]

Exceto que o parágrafo acima não está descrevendo Cs strftime, mas uma nova formatfunção que opera em std::chronotipos, não tm. Além disso, existe um novo tipo: std::chrono::zoned_time( http://eel.is/c++draft/time.zone.zonedtime ) que sempre tem a time_zoneabreviação (e deslocamento) disponível e pode ser formatada com a formatfunção mencionada anteriormente .

Código de exemplo:

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std;
    using namespace std::chrono;
    auto now = system_clock::now();
    std::cout << format("%Z\n", zoned_time{current_zone(), now});   // HKT (or whatever)
    std::cout << format("%Z\n", zoned_time{"Asia/Hong_Kong", now}); // HKT or HKST
    std::cout << format("%Z\n", zoned_time{"Etc/UTC", now});        // UTC
    std::cout << format("%Z\n", now);                               // UTC
}

(Isenção de responsabilidade: a sintaxe final da string de formatação na formatfunção provavelmente será um pouco diferente, mas a funcionalidade estará lá.)

Se você deseja experimentar uma prévia desta biblioteca, ela é gratuita e de código aberto aqui: https://github.com/HowardHinnant/date

Alguma instalação é necessária: https://howardhinnant.github.io/date/tz.html#Installation

Nesta visualização, você precisará usar o cabeçalho "date/tz.h"e o conteúdo da biblioteca está em namespace datevez de namespace std::chrono.

A biblioteca de visualização pode ser usada com C ++ 11 ou posterior.

zoned_timeé modelado em um std::chrono::durationque especifica a precisão do ponto no tempo e é deduzido no código de exemplo acima, usando o recurso CTAD do C ++ 17 . Se você estiver usando esta biblioteca de visualização no C ++ 11 ou C ++ 14, a sintaxe seria mais parecida com:

cout << format("%Z\n", zoned_time<system_clock::duration>{current_zone(), now});

Ou existe uma função auxiliar de fábrica não proposta para padronização que fará a dedução para você:

cout << format("%Z\n", make_zoned(current_zone(), now));

(#CTAD_eliminates_factory_functions)

Howard Hinnant
fonte
2

Obrigado por todos os comentários à pergunta que ajudam a apontar para a direção certa. Eu posto algumas das minhas próprias pesquisas abaixo. Falo com base em um repositório arquivado da GNU C Library que encontrei no GitHub. A sua versão é 2.28.9000.

Em glibc/time/bits/types/struct_tm.h

struct tm
{
  int tm_sec;           /* Seconds. [0-60] (1 leap second) */
  int tm_min;           /* Minutes. [0-59] */
  int tm_hour;          /* Hours.   [0-23] */
  int tm_mday;          /* Day.     [1-31] */
  int tm_mon;           /* Month.   [0-11] */
  int tm_year;          /* Year - 1900.  */
  int tm_wday;          /* Day of week. [0-6] */
  int tm_yday;          /* Days in year.[0-365] */
  int tm_isdst;         /* DST.     [-1/0/1]*/

# ifdef __USE_MISC
  long int tm_gmtoff;       /* Seconds east of UTC.  */
  const char *tm_zone;      /* Timezone abbreviation.  */
# else
  long int __tm_gmtoff;     /* Seconds east of UTC.  */
  const char *__tm_zone;    /* Timezone abbreviation.  */
# endif
};

Parece que struct tmarmazena informações de fuso horário, pelo menos nesta implementação.

aafulei
fonte
1

Uma das razões pelas quais a programação de data e hora é tão difícil é que é fundamentalmente pelo menos um problema um pouco difícil: "Trinta dias em setembro" e aritmética sexagesimal e fusos horários, horário de verão e anos bissextos, e nem vamos falar sobre segundos bissextos.

Mas a outra razão pela qual é difícil é que muitas bibliotecas e linguagens fazem uma bagunça perfeita, e C infelizmente não é exceção. (C ++ está tentando fazer melhor, como Howard menciona em sua resposta.)

Embora todo mundo saiba que variáveis ​​globais são Ruim, as funções de data / hora de C usam basicamente algumas delas. Com efeito, o conceito de "fuso horário atual deste sistema" é uma variável global, e os dados globais que descreve esse fuso horário é compartilhada querendo ou não entre localtimee strftimee uma série de outras funções.

Assim, é strftimepossível preencher %ze com %Zbase nesses dados globais, mesmo que não sejam passados ​​como parte de um struct tmvalor.

Obviamente, esse é um arranjo abaixo do ideal e começaria a causar problemas reais se houvesse uma maneira de um programa alterar dinamicamente o fuso horário que deseja usar localtimee o resto. (E esse arranjo persiste em parte porque na verdade não existe uma maneira padrão portátil e boa para um programa alterar o fuso horário local.)

Ao longo dos anos, houve várias tentativas táticas de limpar parte da bagunça (preservando a compatibilidade com versões anteriores, é claro). Uma dessas tentativas envolve os campos estendidos tm_gmtoffe tm_zonedescobertos nas versões de alguns sistemas struct tm. Essas adições são uma grande melhoria - não consigo imaginar fazer programação de data / hora em um sistema sem elas - mas ainda não são padrão e ainda existem muitos sistemas que não os possuem (nem mesmo com as grafias "ocultas" __tm_gmtoffe __tm_zone).

Você pode ler muito mais sobre a história sórdida do suporte a data / hora em C neste artigo: Horário, Relógio e Programação de Calendário Em C , de Eric Raymond.

Steve Summit
fonte