Die Boost C++ Bibliotheken

Kalenderdaten

Boost.DateTime unterstützt von Haus aus lediglich Kalenderdaten basierend auf dem Gregorianischen Kalender. Das ist in der Praxis insofern kein Problem, als dass der Gregorianische Kalender der heute weltweit am weitesten verbreitete Kalender ist. So können Sie davon ausgehen, dass Sie, wenn Sie sich mit jemandem zum Beispiel für den 12. Mai 2014 verabreden, ihm nicht zusätzlich mitteilen müssen, dass sich das Datum auf den Gregorianischen Kalender bezieht.

Der Gregorianische Kalender wurde von Papst Gregor XIII. im Jahr 1582 eingeführt. Das heißt, dass erst seit diesem Jahr mit dem Gregorianischen Kalender gerechnet wird. Das ist insofern wichtig, als dass Boost.DateTime wie erwähnt ausschließlich den Gregorianischen Kalender unterstützt. Genaugenommen werden Datumsangaben für die Jahre 1400 bis 9999 unterstützt. Die Unterstützung reicht demnach über das Jahr 1582 hinaus bis zurück ins Jahr 1400. Falls Sie mit Datumsangaben vor 1582 arbeiten, können Sie Boost.DateTime verwenden, wenn Sie die Datumsangaben in den Gregorianischen Kalender umrechnen und nur bis ins Jahr 1400 zurückgehen müssen. Es besteht jedoch auch die Möglichkeit, Boost.DateTime um einen neuen Kalendar zu erweitern.

Der Namensraum, in dem Boost.DateTime Klassen und Funktionen zur Verarbeitung von Kalenderdaten zur Verfügung stellt, ist boost::gregorian. Sie können alle Klassen und Funktionen aus diesem Namensraum einsetzen, wenn Sie die Headerdatei boost/date_time/gregorian/gregorian.hpp einbinden. So können Sie dann auf die Klasse boost::gregorian::date zugreifen, um ein Datum zu erstellen.

Beispiel 36.1. Ein Datum mit boost::gregorian::date erstellen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

int main()
{
  boost::gregorian::date d{2014, 1, 31};
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';
  std::cout << d.day_of_week() << '\n';
  std::cout << d.end_of_month() << '\n';
}

Die Klasse boost::gregorian::date bietet mehrere Konstruktoren an, um ein Datum zu erstellen. Dem einfachsten Konstruktor wird ein Jahr, Monat und Tag übergeben. Wird ein ungültiger Wert angegeben, wird eine Ausnahme vom Typ boost::gregorian::bad_year, boost::gregorian::bad_month oder boost::gregorian::bad_day_of_month geworfen, die alle von std::out_of_range abgeleitet sind.

Wie Sie am Beispiel 36.1 sehen, stehen zahlreiche Methoden zur Verfügung, um auf ein Datum zuzugreifen. Während über Methoden wie year(), month() und day() auf die Werte zugegriffen werden kann, mit denen das Objekt initialisiert wurde, können mit Methoden wie day_of_week() und end_of_month() Werte erhalten werden, die selbst zu errechnen aufwändig wäre.

Während Sie dem Konstruktor von boost::gregorian::date wie im Beispiel 36.1 Zahlen übergeben können, um ein Datum zu setzen, wird beim Aufruf von month() Jan und beim Aufruf von day_of_week() Fri ausgegeben. Es handelt sich dabei um Werte vom Typ boost::gregorian::date::month_type und boost::gregorian::date::day_of_week_type. Wie Sie später in diesem Kapitel noch sehen werden, bietet Boost.DateTime eine umfangreiche Unterstützung zur formatierten Ein- und Ausgabe an, so dass Sie zum Beispiel die Ausgabe so anpassen können, dass anstatt von Jan der Wert 1 ausgegeben wird.

Beachten Sie, dass der Standardkonstruktor von boost::gregorian::date ein ungültiges Datum erstellt. Sie können ein derart ungültiges Datum explizit erstellen, wenn Sie boost::date_time::not_a_date_time als einzigen Parameter an den Konstruktor von boost::gregorian::date übergeben.

Neben dem direkten Aufruf eines Konstruktors kann ein Objekt vom Typ boost::gregorian::date auch über freistehende Funktionen und Methoden anderer Objekte erstellt werden.

Beispiel 36.2. Ein Datum von einer Uhr oder einem String erhalten
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d = day_clock::universal_day();
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';

  d = date_from_iso_string("20140131");
  std::cout << d.year() << '\n';
  std::cout << d.month() << '\n';
  std::cout << d.day() << '\n';
}

Im Beispiel 36.2 wird die Klasse boost::gregorian::day_clock verwendet. Es handelt sich dabei um eine Uhr, die das aktuelle Datum zurückgibt. Die Methode universal_day() gibt ein UTC-Datum zurück, das unabhängig von Zeitzonen oder Sommerzeit ist. UTC ist die internationale Abkürzung für die Weltzeit, der Mitteleuropa eine Stunde voraus ist. Neben universal_day() bietet boost::gregorian::day_clock auch eine Methode local_day() an, die die Systemeinstellungen bezüglich Zeitzone und Sommerzeit berücksichtigt. Sie verwenden local_day(), wenn Sie ein aktuelles Datum in der Zeitzone erhalten wollen, in der Sie sich befinden.

Im Namensraum boost::gregorian stehen außerdem zahlreiche freistehende Funktionen zur Verfügung, um ein Datum in einem String in ein Objekt vom Typ boost::gregorian::date umzuwandeln. So wird im Beispiel 36.2 mit der Funktion boost::gregorian::date_from_iso_string() ein Datum im ISO 8601-Format umgewandelt. Boost.DateTime bietet weitere freistehende Funktionen an wie boost::gregorian::from_simple_string() und boost::gregorian::from_us_string().

Während Sie mit boost::gregorian::date eine Klasse kennengelernt haben, die als Datum einen Zeitpunkt markiert, bietet Boost.DateTime mit boost::gregorian::date_duration eine Klasse für einen Zeitraum an.

Beispiel 36.3. Zeiträume mit boost::gregorian::date_duration erstellen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 31};
  date d2{2014, 2, 28};
  date_duration dd = d2 - d1;
  std::cout << dd.days() << '\n';
}

Da die Klasse boost::gregorian::date den Operator operator- überlädt, können wie im Beispiel 36.3 zwei Zeitpunkte mit dem Minuszeichen verknüpft werden. Der Rückgabewert hat den Typ boost::gregorian::date_duration und entspricht dem Zeitraum zwischen den beiden Daten.

Die wichtigste Methode, die boost::gregorian::date_duration anbietet, ist days(). Sie gibt die Anzahl der Tage zurück, aus denen der Zeitraum besteht.

Beispiel 36.4. Spezialisierte Zeiträume
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date_duration dd{4};
  std::cout << dd.days() << '\n';
  weeks ws{4};
  std::cout << ws.days() << '\n';
  months ms{4};
  std::cout << ms.number_of_months() << '\n';
  years ys{4};
  std::cout << ys.number_of_years() << '\n';
}

Sie können ein Objekt vom Typ boost::gregorian::date_duration auch selbst erstellen. Sie übergeben in diesem Fall dem Konstruktor als einzigen Parameter die Anzahl der Tage. Wenn Sie einen Zeitraum erstellen wollen, der Wochen, Monate oder Jahre umfasst, können Sie wie im Beispiel 36.4 auf die Klassen boost::gregorian::weeks, boost::gregorian::months und boost::gregorian::years zugreifen.

Die Klassen boost::gregorian::months und boost::gregorian::years bieten keine Methoden an, um die Anzahl der Tage zu ermitteln – immerhin können Monate und Jahre unterschiedlich lang sein. Warum es dennoch sinnvoll sein kann, diese Klassen zu verwenden, sehen Sie am Beispiel 36.5.

Beispiel 36.5. Mit spezialisierten Zeiträumen rechnen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d{2014, 1, 31};
  months ms{1};
  date d2 = d + ms;
  std::cout << d2 << '\n';
  date d3 = d2 - ms;
  std::cout << d3 << '\n';
}

Im Beispiel 36.5 wird zum 31. Januar 2014 ein Monat hinzuaddiert. Boost.DateTime errechnet für d2 daraufhin den 28. Februar 2014. Im nächsten Schritt wird von diesem Datum ein Monat abgezogen, woraufhin für d3 wieder der 31. Januar 2014 errechnet wird. Wie Sie sehen, lassen sich mit Zeitpunkten und -räumen Berechnungen anstellen. Sie müssen jedoch auf Besonderheiten achten wie die, dass Sie ausgehend vom letzten Tag eines Monats immer zum letzten Tag eines anderen Monats gelangen, wenn Sie mit boost::gregorian::months vorwärts- oder zurückspringen. Das kann zu unerwarteten Ergebnissen führen.

Beispiel 36.6. Besonderheiten beim Rechnen mit spezialisierten Zeiträumen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d{2014, 1, 30};
  months ms{1};
  date d2 = d + ms;
  std::cout << d2 << '\n';
  date d3 = d2 - ms;
  std::cout << d3 << '\n';
}

Beispiel 36.6 ähnelt dem vorherigen. Der einzige Unterschied ist, dass die Variable d mit dem 30. Januar 2014 initialisiert wurde. Obwohl es sich hierbei nicht um den letzten Tag im Januar handelt, wird nach dem Sprung vorwärts für d2 wieder der 28. Februar 2014 errechnet – schließlich gibt es keinen 30. Februar. Wenn im letzten Schritt ein Monat zurückgesprungen wird, wird für d3 jedoch der 31. Januar 2014 errechnet. Schließlich ist der 28. Februar 2014 der letzte Tag in diesem Monat, so dass Sie bei einem Sprung zurück zum letzten Tag im Januar gelangen.

Wenn Sie das für verwirrend halten, können Sie die Definition des Makros BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES aufheben. Sie können dann die Klassen boost::gregorian::weeks, boost::gregorian::months und boost::gregorian::years nicht verwenden. Da dann lediglich die Klasse boost::gregorian::date_duration zur Verfügung steht, mit der eine bestimmte Anzahl an Tagen vorwärts oder rückwärts gezählt wird, kann es nicht mehr zu möglicherweise unerwarteten Ergebnissen kommen.

Beispiel 36.7. Zeitintervalle mit boost::gregorian::date_period erstellen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 1};
  date d2{2014, 2, 28};
  date_period dp{d1, d2};
  date_duration dd = dp.length();
  std::cout << dd.days() << '\n';
}

Während Sie mit boost::gregorian::date_duration eine Klasse kennengelernt haben, die einen beliebigen Zeitraum umfasst, können Sie mit boost::gregorian::date_period ein Zeitintervall angeben, das an einem bestimmten Datum beginnt und an einem bestimmten Datum endet.

Dem Konstruktor von boost::gregorian::date_period werden wie im Beispiel 36.7 zwei Parameter vom Typ boost::gregorian::date übergeben, die den Anfangs- und Endzeitpunkt bestimmen. Alternativ kann auch ein Anfangszeitpunkt und ein Zeitraum vom Typ boost::gregorian::date_duration angegeben werden. Beachten Sie, dass in beiden Fällen der Endzeitpunkt nicht zum Zeitintervall zählt, sondern der Tag vor dem Endzeitpunkt der letzte Tag im Zeitintervall ist. Das ist wichtig, um zu verstehen, welche Ergebnisse Beispiel 36.8 ausgibt.

Beispiel 36.8. Überprüfen, ob Daten in einem Zeitintervall liegen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int main()
{
  date d1{2014, 1, 1};
  date d2{2014, 2, 28};
  date_period dp{d1, d2};
  std::cout.setf(std::ios::boolalpha);
  std::cout << dp.contains(d1) << '\n';
  std::cout << dp.contains(d2) << '\n';
}

Im Beispiel 36.8 wird mit Hilfe der Methode contains() getestet, ob ein bestimmes Datum innerhalb des Zeitintervalls liegt. Obwohl dem Konstruktor von boost::gregorian::date_period die beiden Variablen d1 und d2 übergeben wurden, gibt contains() lediglich beim ersten Aufruf true zurück. Weil der Endzeitpunkt nicht zum Zeitintervall zählt, wird beim Aufruf von contains() mit d2 als Parameter false zurückgegeben.

Die Klasse boost::gregorian::date_period bietet weitere Methoden an, um zum Beispiel ein Zeitintervall zu verschieben oder die Schnittmenge zweier sich überschneidender Zeitintervalle zu ermitteln.

Neben Klassen für Daten, Zeiträume und Zeitintervalle bietet Boost.DateTime Iteratoren und verschiedene nützliche freistehende Funktionen an. Sehen Sie sich dazu Beispiel 36.9 an.

Beispiel 36.9. Mit Iteratoren auf Datumsangaben zugreifen
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost;

int main()
{
  gregorian::date d{2014, 5, 12};
  gregorian::day_iterator it{d};
  std::cout << *++it << '\n';
  std::cout << date_time::next_weekday(*it,
    gregorian::greg_weekday(date_time::Friday)) << '\n';
}

Der Iterator boost::gregorian::day_iterator ermöglicht es, von einem bestimmten Datum ausgehend schrittweise einen Tag vorwärts oder rückwärts zu zählen. Neben diesem Iterator werden auch boost::gregorian::week_iterator, boost::gregorian::month_iterator und boost::gregorian::year_iterator angeboten, die jeweils Wochen, Monate oder Jahre vor- oder zurückspringen.

Im Beispiel 36.9 wird außerdem die Funktion boost::date_time::next_weekday() verwendet, die das Datum des nächsten Wochentags ausgehend von einem Zeitpunkt zurückgibt. So gibt Beispiel 36.9 2014-May-16 aus, weil dies das Datum für den ersten Freitag nach dem 13. Mai 2014 ist.

Aufgaben

  1. Entwickeln Sie ein Programm, das die Wochentage für den nächsten 24. Dezember und die beiden darauf folgenden Feiertage ausgibt.

  2. Berechnen Sie Ihr Alter in Tagen. Ihr Programm soll dabei automatisch das aktuelle Datum ermitteln und zur Berechnung verwenden.