Kalender und Zeitzonen in C++20: Zeitzonen

Modernes C++ Rainer Grimm  –  14 Kommentare

Mit diesem Artikel schließe ich meine Einführung in die chrono-Erweiterung in C++20 ab. Heute geht es um die Zeitzonenfunktionalität.

Zuerst möchte ich auf Howard Hinnants Online-Quelle "Examples and Recipes" hinweisen, die mehr als 40 Beispiele zur neuen chrono-Funktionalität anbietet. Zugegeben, sie ist nicht einfach zu verstehen; daher ist es umso wichtiger, viele Rezepte studieren zu können. Diese sind ein idealer Startpunkt für weitere Experimente und helfen, das Verständnis der chrono-Erweiterungen zu vertiefen. Es lassen sich auch neue Rezepte hinzufügen.

Das Ordinaldatum berechnen

Um einen kleinen Einblick in die Beispiele und Rezepte zu erhalten, möchte ich ein Programm von Roland Bock vorstellen, das das Ordinaldatum (ordinal date) berechnet.

"An ordinal date consists of a year and a day of year (1st of January being day 1, 31st of December being day 365 or day 366). The year can be obtained directly from year_month_day. And calculating the day is wonderfully easy. In the code below we make us of the fact that year_month_day can deal with invalid dates like the 0th of January:" (Roland Bock)

Ich habe die notwendigen Header-Dateien zum Programm hinzugefügt:

// ordinalDate.cpp

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

int main()
{
using namespace date;

const auto time = std::chrono::system_clock::now();
const auto daypoint = floor<days>(time); // (1)
const auto ymd = year_month_day{daypoint};

// calculating the year and the day of the year
const auto year = ymd.year();
const auto year_day = daypoint - sys_days{year/January/0}; // (2)
// (3)
std::cout << year << '-' << std::setfill('0') << std::setw(3)
<< year_day.count() << std::endl;

// inverse calculation and check
assert(ymd == year_month_day{sys_days{year/January/0} + year_day});
}

Ich möchte das Programm um ein paar Bemerkungen ergänzen. Zeile (1) reduziert den gegenwärtigen Zeitpunkt auf die Tagesauflösung. Der Wert wird in der folgenden Zeile zum Initialisieren eines Kalenderdatums verwendet. Zeile (2) berechnet die Zeitdauer zwischen den zwei Zeitpunkten. Beide besitzen eine Tagesauflösung. Zum Abschluss gibt year_day.count() in Zeile (3) die Zeitdauer in Tagen zurück.

Die Ideen für meine weiteren Beispiele gehen auf die genannten "Examples and Recipes" zurück.

Zeitzonen

Zuerst einmal ist eine Zeitzone eine Region und ihre vollständige Geschichte zu Sommerzeiten und Schaltjahren. Die Zeitzonen-Bibliothek in C++20 ist ein vollständiger Parser für die IANA-Zeitzonen-Datenbank. Die folgende Tabelle gibt eine erste Idee der neuen Funktionalität.

Ich verwende in meinen Beispielen die Funktion std::chrono::zones_time, die im Wesentlichen eine Zeitzone mit einem Zeitpunkt kombiniert darstellt.

Bevor ich zwei Beispiele vorstelle, möchte ich noch einen kleine Bemerkung loswerden. Um die Zeitzonenfunktionalität zu verwenden, muss die Datei tz.cpp übersetzt und gegen die curl-Bibliothek gelinkt werden. Die Bibliothek ist für den Zugriff auf die IANA-Zeitzonen-Datenbank notwendig. Die folgende Kommandozeile mit dem g++ fasst alle Informationen zusammen:

g++ localTime.cpp -I <Path to data/tz.h> tz.cpp -std=c++17 -lcurl -o localTime

Mein erstes Programm ist denkbar einfach und stellt sowohl die UTC- als auch die lokale Zeit dar.

UTC- und lokale Zeit

Die UTC oder koordinierte Weltzeit ist der Zeitstandard weltweit. Ein Computer verwendet die Unixzeit, die eine genaue Näherung der UTC ist. Die Unixzeit ist die Anzahl der Sekunden seit der Unix-Epoche. Die Unix-Epoche beginnt mit dem Zeitpunkt 00:00:00 UTC am 1. Januar 1970.

std::chrono::system_clock::now() in Zeile (1) gibt in dem folgenden Programm localTime.cpp die Unixzeit zurück:

// localTime.cpp

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

int main() {

std::cout << std::endl;

using namespace date;

std::cout << "UTC time" << std::endl; // (1)
auto utcTime = std::chrono::system_clock::now();
std::cout << " " << utcTime << std::endl;
std::cout << " " << date::floor<std::chrono::seconds>(utcTime) << '\n':

std::cout << std::endl;

std::cout << "Local time" << std::endl; // (2)
auto localTime = date::make_zoned(date::current_zone(), utcTime);
std::cout << " " << localTime << std::endl;
std::cout << " " << date::floor<std::chrono::seconds>(localTime.get_local_time())
<< std::endl;

auto offset = localTime.get_info().offset; // (3)
std::cout << " UTC offset: " << offset << std::endl;

std::cout << std::endl;

}

Ich habe dem Programm nicht allzu viel hinzuzufügen. Der Codebereich, beginnend mit der Zeile (1), erhält den gegenwärtige Zeitpunkt, schneidet ihn auf Sekunden ab und stellt ihn dar. date::make_zoned erzeugt std::chrono::zoned_time localTime. Der anschließende Funktionsaufruf localTime.get_local_time() gibt die gespeicherte Zeit in der lokalen Zeit zurück. Dieser Zeitpunkt besitzt auch eine Sekundenauflösung. localTime (Zeile 3) lässt sich dazu verwenden, Informationen über die Zeitzone zu erhalten. In diesem Fall interessiert mich die Differenz zur UTC-Zeit.

Mein nächstes Programm beantwortet mir die entscheidende Frage, wenn ich eine Schulung in verschiedenen Zeitzonen halte: Wann soll ich mit der Online-Schulung beginnen?

Verschiedene Zeitzonen für Online-Schulungen

Das Programm onlineClass.cpp stellt die folgenden Informationen dar: Wie spät ist es in einer gegebenen Zeitzone, wenn ich die Online-Schulung um 7h, 13h oder 17h in meiner lokalen Zeit (Deutschland) beginne?

// onlineClass.cpp

#include "date/tz.h"
#include <algorithm>
#include <iomanip>
#include <iostream>

template <typename ZonedTime>
auto getMinutes(const ZonedTime& zonedTime) { // (1)
return date::floor<std::chrono::minutes>(zonedTime.get_local_time());
}

void printStartEndTimes(const date::local_days& localDay, // (2)
const std::chrono::hours& h,
const std::chrono::hours& durationClass,
const std::initializer_list<std::string>& timeZones ){

date::zoned_time startDate{date::current_zone(), localDay + h}; // (4)
date::zoned_time endDate{date::current_zone(), localDay + h + durationClass};
std::cout << "Local time: [" << getMinutes(startDate) << ", "
<< getMinutes(endDate) << "]" << std::endl;
// (5)
longestStringSize = std::max(timeZones, [](const std::string& a, const std::string& b) { return a.size() < b.size(); }).size();
for (auto timeZone: timeZones) { // (6)
std::cout << " " << std::setw(longestStringSize + 1) << std::left
<< timeZone
<< "[" << getMinutes(date::zoned_time(timeZone, startDate))
<< ", " << getMinutes(date::zoned_time(timeZone, endDate))
<< "]" << std::endl;

}
}

int main() {

using namespace std::string_literals;
using namespace std::chrono;

std::cout << std::endl;

constexpr auto classDay{date::year(2021)/2/1};
constexpr auto durationClass = 4h;
auto timeZones = {"America/Los_Angeles"s, "America/Denver"s, "America/New_York"s,
"Europe/London"s, "Europe/Minsk"s, "Europe/Moscow"s,
"Asia/Kolkata"s, "Asia/Novosibirsk"s, "Asia/Singapore"s,
"Australia/Perth"s, "Australia/Sydney"s};

for (auto startTime: {7h, 13h, 17h}) { // (3)
printStartEndTimes(date::local_days{classDay}, startTime,
durationClass, timeZones);
std::cout << std::endl;
}

}

Bevor ich mich mit den Funktionen getMinutes (Zeile 1) und printStartEndTimes (Zeile 2) beschäftige, stelle ich die main-Funktion vor. Sie definiert den Tag, die Dauer und die Zeitzone der Online-Schulung. Anschließend iteriert die Range-basierte for-Schleife durch alle Startpunkte für Online-Schulungen. Dank der Funktion printStartEndTimes (Zeile 3) werden alle wichtigen Informationen dargestellt.

Die Zeilen beginnend mit (4) berechnen den startDate und den endDate meiner Online-Schulung, indem die Zeitdauer auf den Startzeitpunkt addiert wird. Beide Werte stellt die Funktion getMinutes (Zeile 1) dar. date::floor<std::chrono::minutes>(zonedTime.get_local_time()) erhält den gespeicherten Zeitpunkt aus std::chrono::zoned_time und reduziert ihn auf seine Minutenauflösung. Um die Werte richtig auszurichten, bestimmt Zeile (5) die Länge des längsten Namens der verschieden Zeitzonen. Zeile (6) iteriert durch alle Zeitzonen, stellt den Namen der Zeitzone sowie den Start- und Endzeitpunkt der Online-Schulung dar. Einige Kalenderdaten überschreiten die Tagesgrenze.

Es gibt sicherlich mehr zur erweiterten chrono-Bibliothek zu schreiben. So bietet C++20 zum Beispiel den neuen Zeiger std::chrono::utc_clock an. Dieser berücksichtigt Schaltsekunden. Der weitere Zeitgeber std::chrono::tai_clock steht für die internationale Atomzeit (TAI). Zusätzlich lassen sich Zeitdauern mit der neuen Formatierungsbibliothek schön formatiert ausgeben. Dieses Feature steht aber noch nicht zur Verfügung. Wenn du die Formatierungsregeln bereits studieren willst, hier sind sie: std::formatter.

Wie geht's weiter?

Werden Ganzahlen mit und ohne Vorzeichen verglichen, führt dies oft zu Überraschungen. Diese Überraschungen enden mit C++20.