Kalender und Zeitzonen in C++20: Tageszeit

Modernes C++ Rainer Grimm  –  10 Kommentare

Mit C++20 erhält die chrono-Bibliothek mächtige Erweiterungen. Die wichtigsten sind sicherlich die Unterstützung eines Kalenders und von Zeitzonen. Das ist aber bei weitem noch nicht alles. C++20 erhält neue Uhren, mächtige Formatierungsmöglichkeiten für Zeitdauern und einen Datentyp für die Tageszeit.

Bevor ich in die Tiefe der erweiterten chrono-Bibliothek und insbesondere in den neuen Datentyp std::chrono::time_of_day abtauche, möchte ich erst ein paar Bemerkungen loswerden. Zuerst einmal nenne ich alle Datums- und Zeitfunktionalitäten der chrono-Bibliothek vereinfachend die Zeitbibliothek.

Die Zeitbibliothek in C++11

Um die wesentliche Information aus meinem Artikel zur Zeitbibliothek zu erhalten, ist ein Grundverständnis der chrono-Bibliothek in C++11 erforderlich. C++11 hat drei Komponenten zum Umgang mit Zeit definiert:

  • Ein Zeitpunkt (time point) wird durch einen Startpunkt – die sogenannte Epoche – und die Zeitdauer definiert.
  • Eine Zeitdauer (time duration) definiert die Differenz zwischen zwei Zeitpunkten. Sie wird durch die Anzahl der Ticks eines Zeitgebers bestimmt.
  • Der Zeitgeber (clock) besteht aus einem Startpunkt (Epoche) und seinen Ticks. Damit lässt sich der aktuelle Zeitpunkt berechnen.

Ehrlich gesagt, finde ich Zeit ein Mysterium. Einerseits besitzt jeder ein intuitives Verständnis von Zeit, andererseits ist die formale Definition von Zeit sehr anspruchsvoll. So hängen zum Beispiel die drei Komponenten Zeitpunkt, Zeitdauer und Zeitgeber voneinander ab. Wenn du mehr zur Zeitfunktionalität in C++11 lesen möchtest, hier sind meine Artikel dazu: time.

Die chrono-Erweiterung in C++20

C++20 erweitert die chrono-Bibliothek um neue Komponenten:

  • Die Tageszeit (time of day) ist die Zeitdauer seit Mitternacht, die in Stunden, Minuten, Sekunden und Sekundenbruchteile gesplittet ist.
  • Der Kalender steht für verschieden Kalenderdaten wie Jahr, Monat, einen Wochentag oder den n-ten Tag einer Woche.
  • Eine Zeitzone (time-zone) repräsentiert die Zeit bezogen auf ein geografisches Gebiet.

Im Wesentlichen basiert die Zeitzonenfunktionalität (C++20) auf der Kalenderfunktionalität (C++20) und die Kalenderfunktionalität (C++20) auf der ursprünglichen chrono-Funktionalität (C++11).

Das ist jedoch bei Weitem nicht alles. Die Erweiterung beinhaltet neue Zeitgeber. Mit der neuen Formatierungsbibliothek in C++20 lassen sich Zeitdauern ein- und auslesen.

Die Prototyp-Bibliothek date

Zum jetzigen Zeitpunkt unterstützt kein C++ Compiler die chrono-Erweiterungen mit C++20. Dank der Prototyp-Bibliothek date von Howard Hinnant, die die neuen chrono-Erweiterungen umfasst, lässt sich die neue Funktionalität bereits verwenden. Die Bibliothek ist iauf GitHub gehostet. Es gibt eine paar Möglichkeiten diese date-Bibliothek zu verwenden:

  1. Du kannst die Bibliothek auf wandbox einsetzen. Howard hat die Headerdatei date.h, die für die Verwendung von std::time_of_day und die Kalenderdatentypen ausreichend ist, hochgeladen. Hier ist sein Link: Try it out on wandbox!
  2. Kopiere die Header-Datei date.h in den Suchpfad deines C++-Compilers.
  3. Lade das Projekt herunter und baue es. Die bereits zitierte GibHub-Seite date enthält die notwendige Information dazu.

Ich habe meine ersten Gehversuche mit dem Online-Compiler unternommen und bin relativ schnell zur Strategie 2 gewechselt.

Notwendiger C++-Standard

Im Allgemeinen ist ein C++14-Compiler ausreichend, um die date-Bibliothek zu verwenden. Es gibt eine Ausnahme dieser Regel, die ich mit dem folgenden Aufruf erfahren habe:

auto timeOfDay = date::time_of_day(10.h + 98min + 2020s + 0.5s); // C++20

auto timeOfDay = date::hh_mm_ss(10.h + 98min + 2020s + 0.5s); // C++17

Ich verwendete in meinen ersten Gehversuchen die erste Zeile. Die erste Zeile setzt Bestimmung der Argumente eines Klassen-Templates für Alias-Templates vor. time_of_day ist ein Alias für hh_mm_ss: using time_of_day_day = hh_mm_ss<Duration>. Wird nun anstelle des Alias das Klassen-Template wie in der zweiten Zeile direkt verwendet, ist ein C++17-Compiler ausreichend. Er kann automatisch die Template-Argumente eines Klassen-Templates ermitteln.

Hier gibt es mehr Details zur automatischen Bestimmung der Template-Argumente eines Klassen-Templates in C++17: C++17: Was gibt's Neues in der Kernsprache?

Problem gelöst

Mit C++20 wurde time_of_day in hh_mm_ss umbenannt. Howard Hinnant gab mir den entscheidenden Hinweis:

"Prefer to use hh_mm_ss in place of time_of_day. time_of_day got renamed to hh_mm_ss during the standardization process for C++20, and so time_of_day remains strictly as a backwards compatible shim for current and past users of this lib."

Dies war wieder eine für einen Early Adopter typische Odyssee.

Portierung auf C++20

Wenn nur die Bestandteile der date-Bibliothek zum Einsatz kommen, die Bestandteil des C++20-Standards sind, ist die Portierung der Beispiele auf C++20 keine große Herausforderung. Ersetze zum einen die Header-Dateien der Prototyp-Biblitothek date mit der <chrono>-Header-Datei und ersetze zum anderen den Namensraum date mit dem Namensraum std::chrono:

#include "date.h"    
//#include <chrono>

int main() {

using namespace std::chrono_literals;

auto am = date::is_am(10h);
// auto am = std::chrono::is_am(10h);

}

Jetzt beginne ich über die chrono-Erweiterungen in C++20 zu schreiben.

Tageszeit

std::chrono::hh_mm_ss ist die Zeitdauer seit Mitternacht, die in Stunden, Minuten, Sekunden und Sekundenbruchteile gesplittet ist. Dieser Datentyp wird gerne zum Formatieren verwendet. Zuerst einmal gibt die folgende Tabelle einen kompakten Überblick des neuen Datentyps std::chrono::hh_mm_ss. tOfDay ist eine Instanz dieses Typs.


Das folgende Programm wendet die Funktionen an:

// timeOfDay.cpp

#include "date.h"
#include <iostream>

int main() {
using namespace date; // (3)
using namespace std::chrono_literals;

std::cout << std::boolalpha << std::endl;

auto timeOfDay = date::hh_mm_ss(10.5h + 98min + 2020s + 0.5s); // (1)

std::cout<< "timeOfDay: " << timeOfDay << std::endl; // (2)

std::cout << std::endl;

std::cout << "timeOfDay.hours(): " << timeOfDay.hours() << '\n'; // (4)
std::cout << "timeOfDay.minutes(): " << timeOfDay.minutes() << '\n'; // (4)
std::cout << "timeOfDay.seconds(): " << timeOfDay.seconds() << '\n'; // (4)
std::cout << "timeOfDay.subseconds(): " << timeOfDay.subseconds() << '\n'; // (4)
std::cout << "timeOfDay.to_duration(): " << timeOfDay.to_duration() << '\n'; // (5)

std::cout << std::endl;

std::cout << "date::hh_mm_ss(45700.5s): " << date::hh_mm_ss(45700.5s) << '\n'; // (6)

std::cout << std::endl;

std::cout << "date::is_am(5h): " << date::is_am(5h) << std::endl; // (7)
std::cout << "date::is_am(15h): " << date::is_am(15h) << std::endl; // (7)

std::cout << std::endl;

std::cout << "date::make12(5h): " << date::make12(5h) << std::endl; // (7)
std::cout << "date::make12(15h): " << date::make12(15h) << std::endl; // (7)

std::cout << std::endl;

}

In Zeile 1 erzeuge ich eine neue Instanz von std::chrono::hh_mm_ss: timeOfDay. Dank der chrono-Literale aus C++14 lassen sich ein paar Zeitdauern zu dem Tageszeitobjekt hinzuaddieren und es damit zu initialisieren. Mit C++20 lässt sich timeOfDay (Zeile 2) direkt ausgeben. Genau aus diesem Grund muss ich in Zeile 3 den Namensraum date bekannt machen. Der Rest des Programms sollte einfach zu lesen sein. Die Zeilen (4) gibt die Komponenten der Zeitdauer seit Mitternacht in Stunden, Minuten, Sekunden und Sekundenbruchteilen zurück. Zeile (5) gibt die Zeitdauer seit Mitternacht in Sekunden zurück. Die Zeile (6) ist interessanter: Die übergebenen Sekunden entsprechen der Zeit, die in Zeile (2) dargestellt wird. Zeile (7) beantwortet die Frage, ob die gegebene Zeit a.m. ist. Zeile (8) gibt schließlich das 12-Stunden-Äquivalent der gegebenen Stunde zurück.

Dank der date-Bibliothek lässt sich das Programm ausführen.

Wie geht's weiter?

In meinen nächsten Artikel stelle ich die nächste Komponente der chrono-Erweiterung vor: Kalender.