Speichert struct tm Zeitzoneninformationen als Datenelement

8

Betrachten Sie den folgenden C ++ - Code

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

So gespeichert nowist ein eindeutiger Integralwert, während localund gmsind , struct tmdass Speicher für Menschen lesbaren Datum / Uhrzeit. Dann drucke ich die formatierten Informationen (Zeitzone) nur basierend auf den struct tmObjekten aus.

Gemäß der cplusplus Referenz der Datenelemente struct tmsind ,

tm_sec  
tm_min  
tm_hour 
tm_mday 
tm_mon  
tm_year 
tm_wday 
tm_yday 
tm_isdst

Wenn das alles ist, was a struct tmenthält, woher weiß das Programm, dass die Zeitzoneninformationen daraus stammen? Das heißt, woher weiß es, dass die Zeitzone HKTfür localund die Zeitzone UTCfür ist gm?

Wenn das nicht alles ist, was ein struct tmenthält, erklären Sie bitte, wie es Zeitzoneninformationen speichert.

Übrigens, obwohl der Demo-Code in C ++ ist, denke ich, dass diese Frage im Wesentlichen auch eine legitime C-Frage ist.

aafulei
quelle
2
tmenthält keine Zeitzoneninformationen. strftimeErhält die Zeitzone durch Voodoo hinter den Kulissen. Wenn Sie die Zeitzone im Allgemeinen erhalten möchten, ist das ein bisschen chaotisch. Es gibt ( derzeit ) keine Standardmethode, um eine Zeitzone zu erhalten. Zum Glück Howard Hinnant ist auf diesem Job ... .
user4581301
Danke @ user4581301 Dies beantwortet teilweise meine Frage. Aber ich habe noch weitere Fragen: tmWoher strftimeweiß man angesichts aller darin gespeicherten Informationen , wie man auf zwei struct tmObjekte unterschiedlich reagiert ? Es sei denn , tmenthält einige Informationen wie diese tmerstellt wirdlocaltime , die tmerstellt wirdgmtime .
aafulei
Die tmStruktur speichert keine Zeitzonen-Infos. Warum glauben Sie, dass dies der Fall ist? Der Unterschied liegt eher in den Anrufen zu gmtime()und localtime().
Ulrich Eckhardt
In der Manpage wird beschrieben, wie Zeitzoneninformationen auf einem POSIX-System erfasst werden. Immer noch auf der Suche, wie strftimedie Saugnäpfe auseinanderhalten. Sollte hinzufügen, dass POSIX das, was passiert, undefiniert lässt.
user4581301
1
Schlägt mit gcc 9.2.0 von MSYS2 unter Windows sofort fehl. Als ich das sah, erinnerte ich mich daran, dass ich nicht standardmäßige tms mit zusätzlichen Informationen gesehen hatte. Hier ist einer . Beachten Sie das const char *tm_zoneMitglied. Für welche Plattform kompilieren Sie? Schauen Sie sich die tmImplementierung an, um festzustellen, ob sie die Struktur erweitert hat.
user4581301

Antworten:

5

Der C-Standard sagt in 7.27.1 Komponenten der Zeit:

Die tmStruktur muss in beliebiger Reihenfolge mindestens die folgenden Mitglieder enthalten . Die Semantik der Mitglieder und ihre normalen Bereiche werden in den Kommentaren ausgedrückt. 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

(Schwerpunkt liegt bei mir)

Das heißt, Implementierungen können zusätzliche Mitglieder hinzufügen tm, wie Sie bei gefunden haben glibc/time/bits/types/struct_tm.h. Die POSIX-Spezifikation hat einen nahezu identischen Wortlaut.

Das Ergebnis ist, dass %Z(oder sogar %z) nicht als tragbar in betrachtet werden kann strftime. Die Spezifikation für %Zspiegelt dies wider:

%Zwird durch den Zeitzonennamen oder die Abkürzung des Gebietsschemas oder durch keine Zeichen ersetzt, wenn keine Zeitzone bestimmbar ist. [tm_isdst]

Das heißt, Anbieter dürfen ihre Hände hochwerfen und einfach sagen: "Es war keine Zeitzone bestimmbar, daher gebe ich überhaupt keine Zeichen aus."

Meine Meinung: Die C-Timing-API ist ein Chaos.


Ich versuche, die Dinge für den kommenden C ++ 20-Standard in der <chrono>Bibliothek zu verbessern .

Die C ++ 20-Spezifikation ändert dies von "keine Zeichen" in eine Ausnahme, die ausgelöst wird, wenn die time_zoneAbkürzung nicht verfügbar ist:

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

Sofern nicht ausdrücklich angefordert, enthält das Ergebnis der Formatierung eines Chronotyps keine Informationen zur Zeitzonenabkürzung und zum Zeitzonenversatz. Wenn die Informationen zur Verfügung, die Konvertierungsspezifizierer %Zund %zformatiert diese Informationen (jeweils). [ Anmerkung: Wenn die Informationen nicht zur Verfügung steht und ein %Zoder %z Konvertierungsspezifizierer erscheint in Chrono-Format-Spezifikation , eine Ausnahme vom Typ format_­errorgeworfen wird , wie oben beschrieben. - Endnote ]

Abgesehen davon, dass der obige Absatz keine Cs beschreibt strftime, sondern eine neue formatFunktion, die mit std::chronoTypen arbeitet, nicht tm. Zusätzlich gibt es einen neuen Typ: std::chrono::zoned_time( http://eel.is/c++draft/time.zone.zonedtime ), der immer die time_zoneAbkürzung (und den Offset) zur Verfügung hat und mit der oben genannten formatFunktion formatiert werden kann.

Beispielcode:

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

(Haftungsausschluss: Die endgültige Syntax der Formatierungszeichenfolge in der formatFunktion unterscheidet sich wahrscheinlich geringfügig, die Funktionalität ist jedoch vorhanden.)

Wenn Sie mit einer Vorschau dieser Bibliothek experimentieren möchten, ist diese hier kostenlos und Open Source: https://github.com/HowardHinnant/date

Einige Installationen sind erforderlich: https://howardhinnant.github.io/date/tz.html#Installation

In dieser Vorschau müssen Sie den Header verwenden "date/tz.h", und der Inhalt der Bibliothek befindet sich namespace dateanstelle von namespace std::chrono.

Die Vorschau-Bibliothek kann mit C ++ 11 oder höher verwendet werden.

zoned_timewird auf einer Vorlage erstellt, std::chrono::durationdie die Genauigkeit des Zeitpunkts angibt, und im obigen Beispielcode mithilfe der CTAD-Funktion von C ++ 17 abgeleitet . Wenn Sie diese Vorschau-Bibliothek in C ++ 11 oder C ++ 14 verwenden, sieht die Syntax eher so aus:

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

Oder es gibt eine nicht für die Standardisierung vorgeschlagene Hilfsfabrikfunktion, die den Abzug für Sie übernimmt:

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

(#CTAD_eliminates_factory_functions)

Howard Hinnant
quelle
2

Vielen Dank für alle Kommentare zu der Frage, die helfen, in die richtige Richtung zu weisen. Ich poste einige meiner eigenen Forschungen unten. Ich spreche basierend auf einem archivierten Repo der GNU C Library, das ich auf dem GitHub gefunden habe. Seine Version ist 2.28.9000.

Da glibc/time/bits/types/struct_tm.hdrin ist

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

Es scheint, dass struct tmzumindest in dieser Implementierung Zeitzoneninformationen gespeichert werden.

aafulei
quelle
1

Einer der Gründe, warum Datums- und Zeitprogrammierung so schwierig ist, ist, dass es grundsätzlich zumindest ein etwas schwieriges Problem ist: "Dreißig Tage hat September", sexagesimale Arithmetik , Zeitzonen, Sommerzeit und Schaltjahre, und lassen Sie uns nicht einmal Sprechen Sie über Schaltsekunden.

Aber der andere Grund, warum es schwierig ist, ist, dass allzu viele Bibliotheken und Sprachen ein perfektes Durcheinander daraus machen, und C ist leider keine Ausnahme. (C ++ versucht es besser zu machen, wie Howard in seiner Antwort erwähnt.)

Obwohl jeder weiß, dass globale Variablen schlecht sind, verwenden die Datums- / Zeitfunktionen von C im Grunde genommen einige davon. Tatsächlich ist das Konzept der "aktuellen Zeitzone dieses Systems" eine globale Variable, und die globalen Daten, die diese Zeitzone beschreiben, werden wohl localtimeund übel zwischen und strftimeund einer Reihe anderer Funktionen geteilt.

So strftimefüllen kann %zund %Zauf diesen globalen Daten basieren, auch wenn es nicht in als Teil eines übergeben wird struct tmWertes.

Das ist offensichtlich eine suboptimale Anordnung, und es würde echte Probleme verursachen, wenn ein Programm die Zeitzone, für die es verwendet werden möchte, und den Rest dynamisch ändern könntelocaltime . (Und diese Anordnung bleibt teilweise bestehen, weil es für ein Programm keine gute, tragbare Standardmethode gibt, um die lokale Zeitzone zu ändern, die es verwendet.)

Im Laufe der Jahre gab es verschiedene halbherzige Versuche, einen Teil des Chaos zu beseitigen (wobei natürlich die Abwärtskompatibilität erhalten blieb). Einer dieser Versuche betrifft die erweiterten Felder tm_gmtoffund tm_zoneFelder, die Sie in einigen Systemversionen von entdeckt haben struct tm. Diese Ergänzungen sind eine enorme Verbesserung - ich kann mir nicht vorstellen, ernsthafte Datums- / Zeitprogramme auf einem System ohne sie durchzuführen -, aber sie sind immer noch nicht Standard, und es gibt immer noch viele Systeme, die sie nicht haben (nicht einmal) mit den "versteckten" Schreibweisen __tm_gmtoffund __tm_zone).

In diesem Artikel können Sie viel mehr über die schmutzige Geschichte der Datums- / Zeitunterstützung in C lesen: Zeit-, Uhr- und Kalenderprogrammierung in C von Eric Raymond.

Steve Summit
quelle