Kalender und Zeitzonen in C++20: Kalendertag

Modernes C++ Rainer Grimm  –  13 Kommentare

Ein neuer Datentyp der Kalender- und Zeitzonenerweiterung in C++20 ist der Kalendertag. C++20 bietet viele Möglichkeiten an, Kalendertage zu erzeugen und mit ihnen zu interagieren.

Ich habe einige Zeit benötigt, die nahezu zwanzig neuen Datentypen rund um die Kalenderdaten zu verstehen. Genau aus dem Grund beginne ich den Artikel mit dem Kalendertag. Entsprechend meinem letzten Artikel "Kalender und Zeitzonen in C++20: Tageszeit" verwende ich die date-Bibliothek von Howard Hinnant als Prototyp der neuen chrono-Erweiterung.

Kalendertag

Ein Kalendertag ist ein Tag, der aus einem Jahr, einem Monat und einem Tag besteht. Dementsprechend besitzt C++20 den Datentyp std::chrono_year_month_day. C++20 bietet aber deutlich mehr an. Die beiden Tabelle geben den ersten Überblick:

Ich möchte mit einem einfachen Programm beginnen. createCalender.cpp stellt verschiedene Möglichkeiten vor, Kalenderdaten zu erzeugen:

// createCalendar.cpp

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

int main() {

std::cout << std::endl;

using namespace date;

constexpr auto yearMonthDay{year(1940)/month(6)/day(26)}; // (1)
std::cout << yearMonthDay << " ";
std::cout << date::year_month_day(1940_y, June, 26_d) << std::endl; // (2)

std::cout << std::endl;

constexpr auto yearMonthDayLast{year(2010)/March/last}; // (3)
std::cout << yearMonthDayLast << " ";
std::cout << date::year_month_day_last(2010_y, month_day_last(month(3))) << std::endl;

constexpr auto yearMonthWeekday{year(2020)/March/Thursday[2]}; // (4)
std::cout << yearMonthWeekday << " ";
std::cout << date::year_month_weekday(2020_y, month(March), Thursday[2]) << std::endl;

constexpr auto yearMonthWeekdayLast{year(2010)/March/Monday[last]}; // (5)
std::cout << yearMonthWeekdayLast << " ";
std::cout << date::year_month_weekday_last(2010_y, month(March), weekday_last(Monday)) << std::endl;

std::cout << std::endl;

constexpr auto day_{day(19)}; // (5)
std::cout << day_ << " ";
std::cout << date::day(19) << std::endl;

constexpr auto month_{month(1)}; // (6)
std::cout << month_ << " ";
std::cout << date::month(1) << std::endl;

constexpr auto year_{year(1988)}; // (7)
std::cout << year_ << " ";
std::cout << date::year(1988) << std::endl;

constexpr auto weekday_{weekday(5)};
std::cout << weekday_ << " ";
std::cout << date::weekday(5) << std::endl;

constexpr auto yearMonth{year(1988)/1};
std::cout << yearMonth << " ";
std::cout << date::year_month(year(1988), January) << std::endl;

constexpr auto monthDay{10/day(22)};
std::cout << monthDay << " ";
std::cout << date::month_day(October, day(22)) << std::endl;

constexpr auto monthDayLast{June/last};
std::cout << monthDayLast << " ";
std::cout << date::month_day_last(month(6)) << std::endl;

constexpr auto monthWeekday{2/Monday[3]};
std::cout << monthWeekday << " ";
std::cout << date::month_weekday(February, Monday[3]) << std::endl;

constexpr auto monthWeekDayLast{June/Sunday[last]};
std::cout << monthWeekDayLast << " ";
std::cout << date::month_weekday_last(June, weekday_last(Sunday)) << std::endl;

std::cout << std::endl;

}

C++20 bietet im Wesentlichen zwei Wege an, ein Kalenderdatum zu erzeugen. Einerseits gibt es die sogenannte Cute-Syntax yearMonthDay{year(1940)/month(6)/day(26)} (Zeile 1), andererseits lässt sich ein expliziter Datentyp verwenden: date::year_month_day(1940_y, June, 26_d) (Zeile 2). Ich verschiebe die Erklärung der Cute-Syntax auf den nächsten Abschnitt dieses Artikels und konzentriere mich zuerst auf die expliziten Datentypen. Sie sind sehr praktisch, denn sie können Zeitliterale (1940_y, 26_d) und vordefinierte Konstante (June) verwenden. Mit C++20 werden die Zeitliterale wie 1940_y und 26_d ohne Unterstrich zur Verfügung stehen: 1940y und 26d. Dies war wohl der eingängige Teil meiner Erklärung.

Die Zeilen (3), (4) und (5) bieten weitere angenehme Möglichkeiten an, Kalenderdaten zu definieren.

  • Line (3): der letzte Tag des März 2010: 2010: {year(2010)/March/last} oder year_month_day_last(2010_y, month_day_last(month(3))
  • Line (4): der zweite Donnerstag des März 2020: {year(2020)/March/Thursday[2]} oder year_month_weekday(2020_y, month(March), Thursday[2])
  • Line (5): der letzte Montag des März 2010: {year(2010)/March/Monday[last]} oder year_month_weekday_last(2010_y, month(March), weekday_last(Monday))

Die verbleibenden Kalenderdaten stehen für einen Tag (Zeile 6), einen Monat (Zeile 7) oder ein Jahr (Zeile 8). Du kannst diese Kalenderdaten als elementare Bausteine verwenden, um Kalendertage wie in den Zeilen (3) oder (4) zu erzeugen.

Bevor ich noch weiter in den Kalenderdaten abtauche, ist hier die Ausgabe des Programms:

Wie angekündigt, schaue ich mir jetzt die Cute-Syntax genauer an.

Cute-Syntax

Die Cute-Syntax besteht aus einem überladenen Teile-Operator, mit dem sich ein Kalendertag spezifizieren lässt. Der überladene Operator unterstützt die Zeitliterale (zum Beispiel 2020_y und 31_d) und die folgenden Konstanten: January, February, March, April, May, June, July, August, September, October, November, December.

Die folgenden drei Kombinationen von Jahr, Monat und Tag sind beim Einsatz der Cute-Syntax zulässig.

  1. Jahr/Monat/Tag
  2. Tag/Monat/Jahr
  3. Monat/Tag/Jahr

Diese drei Kombinationen sind aus dem naheliegenden Grund die einzigen, denn sie werden weltweit verwendet. Jede andere Kombination ist nicht zulässig.

Durch den ersten Datentyp der Cute-Syntax werden die zwei verbleibenden Datentypen vorgegeben. Daher ist es für die zwei verbleibenden Datentypen ausreichend, ein Integral zu verwenden:

// cuteSyntax.cpp

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

int main() {

std::cout << std::endl;

using namespace date;

constexpr auto yearMonthDay{year(1966)/6/26};
std::cout << yearMonthDay << std::endl;

constexpr auto dayMonthYear{day(26)/6/1966};
std::cout << dayMonthYear << std::endl;

constexpr auto monthDayYear{month(6)/26/1966};
std::cout << monthDayYear << std::endl;

constexpr auto yearDayMonth{year(1966)/month(26)/6}; //(1)
std::cout << yearDayMonth << std::endl;

std::cout << std::endl;

}

Die Kombination year/day/month in Zeile (1) ist nicht zulässig und führt zu einer Laufzeitnachricht.

Ich denke, du willst ein Kalenderdatum wie {year(2010)/March/last} in einem lesbaren Format wie 2020-03-31 darstellen? Dies ist der Job des local_days- oder sys_days-Operators.

Kalenderdatum darstellen

Dank std::chrono::local_days oder std::chrono::sys_days lassen sich Kalenderdaten zu einem std::chrono::time_point konvertieren. Dieser repräsentiert dann dasselbe Datum wie year_month_day. Ich verwende in meinem Beispiel std::chrono::sys_days. std::chrono::sys_days basiert auf der std::chrono::system_clock. Im folgenden Programm sysDays.cpp konvertiere ich die Kalenderdaten (Zeile (3) bis Zeile (5)) auf dem vorherigen Programm createCalendar.cpp:

// sysDays.cpp

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

int main() {

std::cout << std::endl;

using namespace date;

constexpr auto yearMonthDayLast{year(2010)/March/last};
std::cout << "sys_days(yearMonthDayLast): " << sys_days(yearMonthDayLast) << std::endl;

constexpr auto yearMonthWeekday{year(2020)/March/Thursday[2]};
std::cout << "sys_days(yearMonthWeekday): " << sys_days(yearMonthWeekday) << std::endl;

constexpr auto yearMonthWeekdayLast{year(2010)/March/Monday[last]};
std::cout << "sys_days(yearMonthWeekdayLast): " << sys_days(yearMonthWeekdayLast) << std::endl;

std::cout << std::endl;

constexpr auto leapDate{year(2012)/February/last}; // (1)
std::cout << "sys_days(leapDate): " << sys_days(leapDate) << std::endl;

constexpr auto noLeapDate{year(2013)/February/last}; // (2)
std::cout << "sys_day(noLeapDate): " << sys_days(noLeapDate) << std::endl;

std::cout << std::endl;

}

Dank std::chrono::last kann ich sehr einfach herausfinden, wie viele Tage ein Monat besitzt. Die Ausgabe zeigt, dass das Jahr 2012 im Gegensatz zum Jahr 2013 ein Schaltjahr war:

Wie geht's weiter?

Das Arbeiten mit Kalenderdaten wird sehr mächtig, wenn sich diese auf ihre Gültigkeit prüfen lassen oder mit Zeitdauern modifiziert werden können.

Neue Online-Seminare