Estruct tm almacena información de zona horaria como miembro de datos

8

Considere el siguiente 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;
}

Así almacenada en nowes un valor entero sin ambigüedades, mientras que localy gmson struct tmque la información de fecha / hora legible tienda. Luego imprimo la información formateada (zona horaria) basada solo en los struct tmobjetos.

Según la referencia de cplusplus , los miembros de datos de struct tmson

tm_sec  
tm_min  
tm_hour 
tm_mday 
tm_mon  
tm_year 
tm_wday 
tm_yday 
tm_isdst

Si eso es todo lo que struct tmcontiene, ¿cómo sabe el programa que la información de la zona horaria de él? Es decir, ¿cómo sabe que la zona horaria es HKTpara localy que la zona horaria es UTCpara gm?

Si eso no es todo lo que struct tmcontiene, explique cómo almacena la información de la zona horaria.

Por cierto, aunque el código de demostración está en C ++, supongo que esta pregunta en esencia también es una pregunta C legítima.

aafulei
fuente
2
tmno contiene información de zona horaria. strftimeobtiene la zona horaria mediante el vudú detrás de escena. Si desea obtener la zona horaria en general, eso es un poco complicado. No existe ( actualmente ) una forma estándar de obtener una zona horaria. Afortunadamente Howard Hinnant es en ese trabajo ... .
user4581301
Gracias @ user4581301 Esto responde parcialmente a mi pregunta. Pero todavía tengo preguntas de seguimiento: dada toda la información almacenada tm, ¿cómo strftimesabe responder de diferentes maneras a dos struct tmobjetos? A menos que tmcontenga alguna información como esta tmes creada porlocaltime , que tmes creada porgmtime .
aafulei
La tmestructura no almacena información de zona horaria, ¿qué te hace pensar que sí? La diferencia está más bien en las llamadas a gmtime()y localtime().
Ulrich Eckhardt
La página de manual cubre cómo se adquiere la información de zona horaria en un sistema POSIX. Todavía estoy buscando cómo strftimediferencia a los tontos. Debería agregar que POSIX deja lo que sucede indefinido.
user4581301
1
Falla directamente con gcc 9.2.0 de MSYS2 en Windows. Ver eso me recordó que había visto correos electrónicos no estándar tmcon información adicional. Aquí hay uno . Tenga en cuenta el const char *tm_zonemiembro. ¿Para qué plataforma estás compilando? Eche un vistazo a la tmimplementación para ver si han ampliado la estructura.
user4581301

Respuestas:

5

El estándar C dice en 7.27.1 Componentes del tiempo:

La tmestructura deberá contener al menos los siguientes miembros, en cualquier orden. La semántica de los miembros y sus rangos normales se expresan en los comentarios. 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

(el énfasis es mío)

Es decir, las implementaciones pueden agregar miembros adicionales a tm, como se encontró con glibc/time/bits/types/struct_tm.h. La especificación POSIX tiene una redacción casi idéntica.

El resultado es que %Z(o incluso %z) no puede considerarse portátil en strftime. La especificación para %Zrefleja esto:

%Zse reemplaza por el nombre o la abreviatura de la zona horaria de la localidad, o por ningún carácter si no se puede determinar la zona horaria. [tm_isdst]

Es decir, a los vendedores se les permite levantar las manos y simplemente decir: "ninguna zona horaria era determinable, por lo que no estoy mostrando ningún carácter".

Mi opinión: la API de sincronización C es un desastre.


Estoy intentando mejorar las cosas para el próximo estándar C ++ 20 dentro de la <chrono>biblioteca.

La especificación C ++ 20 cambia esto de "sin caracteres" a una excepción que se genera si la time_zoneabreviatura no está disponible:

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

A menos que se solicite explícitamente, el resultado de formatear un tipo crono no contiene la abreviatura de zona horaria ni la información de compensación de zona horaria. Si la información está disponible, los especificadores de conversión %Zy %zformatearán esta información (respectivamente). [ Nota: Si la información no está disponible y una %Zo %z indicador de conversión aparece en el formato de las especificaciones crono , una excepción de tipo format_­errorse tira, como se describió anteriormente. - nota final ]

Excepto que el párrafo anterior no describe C strftime, sino una nueva formatfunción que opera en std::chronotipos, no tm. Además, hay un nuevo tipo: std::chrono::zoned_time( http://eel.is/c++draft/time.zone.zonedtime ) que siempre tiene disponible la time_zoneabreviatura (y el desplazamiento) y puede formatearse con la formatfunción mencionada anteriormente .

Código de ejemplo:

#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
}

(Descargo de responsabilidad: formates probable que la sintaxis final de la cadena de formato en la función sea ligeramente diferente, pero la funcionalidad estará allí).

Si desea experimentar con una vista previa de esta biblioteca, es gratuita y de código abierto aquí: https://github.com/HowardHinnant/date

Se requiere alguna instalación: https://howardhinnant.github.io/date/tz.html#Installation

En esta vista previa, deberá usar el encabezado "date/tz.h", y los contenidos de la biblioteca están en namespace datelugar de namespace std::chrono.

La biblioteca de vista previa se puede usar con C ++ 11 o posterior.

zoned_timetiene una plantilla std::chrono::durationque especifica la precisión del punto de tiempo y se deduce en el código de ejemplo anterior utilizando la función CTAD de C ++ 17 . Si está utilizando esta biblioteca de vista previa en C ++ 11 o C ++ 14, la sintaxis se parecería más a:

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

O hay una función auxiliar de fábrica no propuesta para la estandarización que hará la deducción por usted:

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

(#CTAD_eliminates_factory_functions)

Howard Hinnant
fuente
2

Gracias por todos los comentarios a la pregunta que ayudan a señalar la dirección correcta. Publico algunas de mis propias investigaciones a continuación. Hablo basado en un repositorio archivado de la Biblioteca GNU C que encontré en GitHub. Su versión es 2.28.9000.

En glibc/time/bits/types/struct_tm.hhay

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 tmalmacena información de zona horaria, al menos en esta implementación.

aafulei
fuente
1

Una de las razones por las que la programación de fecha y hora es tan difícil es que es fundamentalmente al menos un problema un tanto difícil: "Treinta días tiene septiembre", y la aritmética sexagesimal , y las zonas horarias, y el horario de verano, y los años bisiestos, y ni siquiera hablamos de segundos bisiestos.

Pero la otra razón por la que es difícil es que demasiadas bibliotecas e idiomas hacen un desastre perfecto, y C desafortunadamente no es una excepción. (C ++ está tratando de hacerlo mejor, como Howard menciona en su respuesta).

Aunque todo el mundo sabe que las variables globales son malas, las funciones de fecha / hora de C básicamente usan un par de ellas. En efecto, el concepto de "zona horaria actual de este sistema" es una variable global, y los datos globales que describen esa zona horaria se comparten entre localtimey entre strftimeotras funciones.

Por strftimelo tanto, puede completar %zy %Zbasarse en esos datos globales, incluso si no se pasan como parte de un struct tmvalor.

Obviamente, esa es una disposición subóptima, y ​​comenzaría a causar problemas reales si un programa cambiara dinámicamente la zona horaria que desea usar localtimey el resto. (Y esta disposición persiste en parte porque en realidad no hay una buena forma estándar y portátil para que un programa cambie la zona horaria local que está usando).

A lo largo de los años ha habido varios intentos poco entusiastas para limpiar parte del desorden (conservando la compatibilidad con versiones anteriores, por supuesto). Uno de esos intentos involucra la extensión tm_gmtoffy los tm_zonecampos que has descubierto en las versiones de algunos sistemas struct tm. Esas adiciones son una gran mejora, no puedo imaginar hacer una programación seria de fecha / hora en un sistema sin ellas, pero aún no son estándar, y todavía hay muchos sistemas que no las tienen (ni siquiera con la ortografía "oculta" __tm_gmtoffy __tm_zone).

Puede leer mucho más sobre el sórdido historial de soporte de fecha / hora en C en este documento: Programación de hora, reloj y calendario en C , por Eric Raymond.

Steve Summit
fuente