Kalender und Zeitzonen in C++20: Umgang mit Kalendertagen

Modernes C++ Rainer Grimm  –  2 Kommentare

In meinem letzten Artikel "Kalender und Zeitzonen in C++20: Kalendertag" habe ich den Datentyp Kalendertag eingeführt. Heute möchte ich mit ihm interagieren.

Angenommen, ich habe ein Kalenderdatum wie year(2100)/2/29. Die erste Frage ist natürlich: Ist dieses Datum gültig?

Prüfen, ob ein Datum gültig ist

Die vielen Datentypen rund um den Kalender besitzen eine Funktion ok. Sie gibt dann true zurück, wenn das Datum gültig ist:

//  leapYear.cpp

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

int main() {

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

using namespace date;

std::cout << "Valid days" << std::endl; // (1)
day day31(31);
day day32 = day31 + days(1);
std::cout << " day31: " << day31 << "; ";
std::cout << " day31.ok(): "
<< day31.ok() << std::endl;
std::cout << " day32: " << day32 << "; ";
std::cout << "day32.ok(): "
<< day32.ok() << std::endl;


std::cout << std::endl;

std::cout << "Valid months" << std::endl; // (2)
month month1(1);
month month0(0);
std::cout << " month1: " << month1 << "; ";
std::cout << " month1.ok(): "
<< month1.ok() << std::endl;
std::cout << " month0: " << month0 << "; ";
std::cout << "month0.ok(): "
<< month0.ok() << std::endl;

std::cout << std::endl;

std::cout << "Valid years" << std::endl; // (3)
year year2020(2020);
year year32768(-32768);
std::cout << " year2020: " << year2020 << "; ";
std::cout << " year2020.ok(): "
<< year2020.ok() << std::endl;
std::cout << " year32768: " << year32768 << "; ";
std::cout << "year32768.ok(): "
<< year32768.ok() << std::endl;

std::cout << std::endl;

std::cout << "Leap Years" << std::endl; // (4)

constexpr auto leapYear2016{year(2016)/2/29};
constexpr auto leapYear2020{year(2020)/2/29};
constexpr auto leapYear2024{year(2024)/2/29};

std::cout << " leapYear2016.ok(): "
<< leapYear2016.ok() << std::endl;
std::cout << " leapYear2020.ok(): "
<< leapYear2020.ok() << std::endl;
std::cout << " leapYear2024.ok(): "
<< leapYear2024.ok() << std::endl;

std::cout << std::endl;

std::cout << "No Leap Years" << std::endl; // (5)

constexpr auto leapYear2100{year(2100)/2/29};
constexpr auto leapYear2200{year(2200)/2/29};
constexpr auto leapYear2300{year(2300)/2/29};

std::cout << " leapYear2100.ok(): "
<< leapYear2100.ok() << std::endl;
std::cout << " leapYear2200.ok(): "
<< leapYear2200.ok() << std::endl;
std::cout << " leapYear2300.ok(): "
<< leapYear2300.ok() << std::endl;

std::cout << std::endl;

std::cout << "Leap Years" << std::endl; // (6)

constexpr auto leapYear2000{year(2000)/2/29};
constexpr auto leapYear2400{year(2400)/2/29};
constexpr auto leapYear2800{year(2800)/2/29};

std::cout << " leapYear2000.ok(): "
<< leapYear2000.ok() << std::endl;
std::cout << " leapYear2400.ok(): "
<< leapYear2400.ok() << std::endl;
std::cout << " leapYear2800.ok(): "
<< leapYear2800.ok() << std::endl;

std::cout << std::endl;

}

Ich habe in dem Programm geprüft, ob ein bestimmter Tag (Zeile 1), ein bestimmter Monat (Zeile 2) oder ein bestimmtes Jahr (Zeile 3) gültig ist. Der Gültigkeitsbereich eines Tags ist [1, 31], der eines Monats [1, 12] und der eines Jahres [-32767, 32767]. Entsprechend gibt der ok-Aufruf auf den Werten false zurück. Zwei Punkte sind interessant, wenn ich die Ergebnisse ausgebe. Zuerst: Wenn der Wert nicht gültig ist, erscheint in der Ausgabe: "is not a valid day"; "is not a valid month"; "is not a valid year". Zweitens werden Monatswerte in einer String-Repräsentation ausgegeben.

Du kannst den ok-Aufruf auf einem Kalendertag anwenden. Damit ist es einfach zu prüfen, ob ein spezifischer Kalendertag ein Schalttag und damit das entsprechende Jahr ein Schaltjahr ist. In dem weltweit verwendeten Gregorianischen Kalender gelten die folgenden Regeln:

Jedes Jahr, das exakt durch 4 teilbar ist, ist ein Schaltjahr.

  • Das gilt mit Ausnahme der Jahre, die exakt durch 100 teilbar sind. Diese Jahre sind keine Schaltjahre.
    • Das gilt mit Ausnahme der Jahre, die exakt durch 400 teilbar sind. Diese Jahre sind Schaltjahre.

Zu kompliziert? Das Programm leapYears.cpp bringt die Regeln auf den Punkt.

Dank der erweiterten chrono-Bibliothek ist es sehr einfach, Zeitdauer zwischen Zeitpunkten abzufragen.

Kalendertage abfragen

Das folgende Programm stellt ein paar Abfragen zu Kalendertagen vor:

// queryCalendarDates.cpp

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

int main() {

using namespace date;

std::cout << std::endl;

auto now = std::chrono::system_clock::now(); // (1)
std::cout << "The current time is: " << now << " UTC\n";
std::cout << "The current date is: "
<< floor<days>(now) << std::endl;
std::cout << "The current date is: "
<< year_month_day{floor<days>(now)} << std::endl;
std::cout << "The current date is: "
<< year_month_weekday{floor<days>(now)} << std::endl;

std::cout << std::endl;


auto currentDate = year_month_day(floor<days>(now)); // (2)
auto currentYear = currentDate.year();
std::cout << "The current year is " << currentYear << '\n';
auto currentMonth = currentDate.month();
std::cout << "The current month is " << currentMonth << '\n';
auto currentDay = currentDate.day();
std::cout << "The current day is " << currentDay << '\n';

std::cout << std::endl;
// (3)
auto hAfter = floor<std::chrono::hours>(now) -
sys_days(January/1/currentYear);
std::cout << "It has been " << hAfter << " since New Year!\n";
auto nextYear = currentDate.year() + years(1); // (4)
auto nextNewYear = sys_days(January/1/nextYear);
auto hBefore = sys_days(January/1/nextYear) -
floor<std::chrono::hours>(now);
std::cout << "It is " << hBefore << " before New Year!\n";

std::cout << std::endl;
// (5)
std::cout << "It has been " << floor<days>(hAfter)
<< " since New Year!\n";
std::cout << "It is " << floor<days>(hBefore)
<< " before New Year!\n";

std::cout << std::endl;

}

Mit der C++20-Erweiterung lässt sich direkt ein Zeitpunkt wie now (Zeile 1) ausgeben. std::chrono::floor erlaubt es, diesen Zeitpunkt in einen Tag zu konvertieren: std::chrono::sys_days. Mit diesem Wert kannst du einen Kalender-Datentyp wie std::chrono::year_month_day initialisieren. Wenn ich den Wert in den Kalender-Datentyp std::chrono::year_month_weekday stecke, erhalte ich die Antwort, dass dieser spezifische Tag der dritte Dienstag im Oktober ist.

Natürlich kann ich auch einen Kalendertag nach seinen Komponenten Jahr, Monat oder Tag (Zeile 2) abfragen.

Zeile (3) finde ich am interessantesten. Wenn ich den gegenwärtigen Tag in der Stundenauflösung von dem ersten Januar des gegenwärtigen Jahres abziehen, erhalte ich die Anzahl der Stunden seit Neujahr. Es geht auch anders herum. Wenn ich vom ersten Januar des nächsten Jahres (Zeile 4) den gegenwärtigen Tag in Stundenauflösung abziehen, erhalte ich die Stunden bis zum Neujahr des nächsten Jahres. Eventuell bevorzugst du die Auflösung der Zeitdauer in Tagen. Zeile 5 setzt diese Anforderung um.

Nun interessieren mich die Wochentage meiner Geburtstage.

Wochentage abfragen

Die Wochentage eines vorgegebenen Kalendertags lassen sich einfach bestimmen:

// weekdaysOfBirthdays.cpp

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

int main() {

std::cout << std::endl;

using namespace date;

int y;
int m;
int d;

std::cout << "Year: "; // (1)
std::cin >> y;
std::cout << "Month: ";
std::cin >> m;
std::cout << "Day: ";
std::cin >> d;

std::cout << std::endl;

auto birthday = year(y)/month(m)/day(d); // (2)

if (not birthday.ok()) { // (3)
std::cout << birthday << std::endl;
std::exit(EXIT_FAILURE);
}

std::cout << "Birthday: " << birthday << std::endl;
auto birthdayWeekday = year_month_weekday(birthday); // (4)
std::cout << "Weekday of birthday: "
<< birthdayWeekday.weekday() << std::endl;

auto currentDate = year_month_day(floor<days>(std::chrono::system_clock::now()));
auto currentYear = currentDate.year();

auto age = (int)currentDate.year() - (int)birthday.year(); // (5)
std::cout << "Your age: " << age << std::endl;

std::cout << std::endl;

std::cout << "Weekdays for your next 10 birthdays" << std::endl; // (6)

for (int i = 1, newYear = (int)currentYear; i <= 10; ++i ) {
std::cout << " Age " << ++age << std::endl;
auto newBirthday = year(++newYear)/month(m)/day(d);
std::cout << " Birthday: " << newBirthday << std::endl;
std::cout << " Weekday of birthday: "
<< year_month_weekday(newBirthday).weekday() << std::endl;
}

std::cout << std::endl;

}

Zuerst frägt das Programm das Jahr, den Monat und den Tag deines Geburtstags ab (Zeile 1). Auf der Grundlage der Eingabe erzeugt es anschließend einen Kalendertag (Zeile 2) und prüft, ob dieser gültig ist (Zeile 3). Nun kann der Wochentag deines Geburtstags dargestellt werden. Ich verwende dazu den Kalender-Datentyp std::chrono::year_month_weekday (Zeile 4). Um die int-Repräsentierung des Kalender-Datentyps zu erhalten, muss ich ihn nach int konvertieren (Zeile 5). Damit ist es möglich, dein Alter darzustellen. Zum Abschluss präsentiert das Programm noch die Wochentage der nächsten zehn Geburtstage (Zeile 6) in dem folgenden Format: Alter, Kalenderdatum und Wochentag. Dazu muss ich lediglich die Variable age und newYear inkrementieren.

Hier ist die Ausgabe des Programms mit meinem Geburtstag:

Wie geht's weiter?

Eine wichtige Komponente fehlte bisher in meiner Vorstellung der erweiterten chrono Bibliothek: Zeitzonen.