Kapitel 17: Betriebssystem-Anweisungen

Inhaltsverzeichnis

Anhang A: Antworten zu den Fragen

18. Datum und Zeit

Dieses Kapitel teilt sich in drei Abschnitte: Zeitmessung innerhalb des Programms, einfache Befehle zum Abrufen und Setzen der Systemzeit und — als umfangreichster Abschnitt — der Umgang mit dem FreeBASIC-internen Datumsformat.

18.1 Zeitmessung

18.1.1 SLEEP

Einen sehr einfachen Befehl zur zeitlichen Kontrolle haben wir bereits kennen gelernt: SLEEP erlaubt es, das Programm für eine festgelegte Anzahl an Millisekunden anzuhalten.

SLEEP           ' wartet auf Tastendruck
SLEEP 3000      ' wartet 3 Sekunden; durch Tastendruck abbrechbar
SLEEP 3000, 1   ' wartet 3 Sekunden; nicht durch Tastendruck abbrechbar

Nachteil von SLEEP ist, dass die Programmausführung angehalten wird, also nicht anderweitig auf Benutzereingaben reagiert werden kann. Oft ist es dagegen das Ziel, das Programm weiterlaufen zu lassen und trotzdem nach einer festgelegter Zeit mit einer bestimmten Aktion zu reagieren. Hierzu ist SLEEP nicht geeignet.

Eine weitere Sache, über die man sich im Klaren sein sollte: Auch wenn SLEEP eine auf Millisekunden genaue Angabe erlaubt, arbeiten die Betriebssysteme gar nicht mit einer so hohen zeitlichen Auflösung. Wie genau der Zeitraum gemessen wird, hängt vom System ab. Als grobe Richtlinie können Sie unter Linux mit 10 ms und unter Windows mit 15 ms rechnen (unter älteren Windowssystemen sogar 50 ms und unter DOS 55 ms). Das bedeutet, dass ein SLEEP 1 unter Linux etwas kürzer wartet als unter Windows. Für Präzessionsmessungen ist der Befehl nicht gedacht.

Um eben einmal schnell und unkompliziert eine Pause einzufügen, eignet sich SLEEP dagegen schon. Der wichtigste Einsatzbereich ist allerdings die Unterbrechung innerhalb einer dauerhaft ausgeführten Schleife (vgl. Kapitel 11.6).

18.1.2 TIMER()

Eine deutlich zielgenauere Zeitkontrolle kann mit TIMER() erreicht werden. Die Funktion gibt ein DOUBLE zurück, welches die Anzahl der seit einem bestimmten Referenzpunkt vergangenen Sekunden angibt. Welcher Zeitpunkt den Start der Messung festlegt, hängt vom Betriebssystem ab. Unter Linux beginnt die Messung mit dem 01.01.1970, 00:00 Uhr (Unix-Epoche), unter Windows mit dem Systemstart — wobei ein „reguläres Herunterfahren“ nicht notwendigerweise zu einem Neustart führt. Allerdings ist der genaue Startzeitpunkt gar nicht entscheidend. Interessant ist TIMER() deshalb, weil damit die Zeitdauer zwischen zwei Code-Abschnitten gemessen werden kann.

Quelltext 18.1: Beispiele für den Einsatz von TIMER()
DIM AS DOUBLE start

' LOCATE und PRINT sind recht zeitaufwaendige Befehle - Messung der Dauer:
PRINT "Messung der Dauer von LOCATE und PRINT (10 000 Durchlaeufe)"
start = TIMER                     ' aktuellen TIMER-Stand merken
FOR i AS INTEGER = 1 TO 10000
  LOCATE 2, 1
  PRINT "Durchlauf"; i
NEXT
PRINT "Vergangene Zeit: ";
PRINT TIMER - start; " Sekunden"  ' Abstand zwischen "Jetzt" und "Vorhin"

PRINT "Das Programm endet in drei Sekunden."
start = TIMER
DO
  SLEEP 1
LOOP UNTIL TIMER > start + 3      ' Schleifenende, wenn 3 Sekunden vergangen

Im zweiten Programmteil soll die Ausführung nicht drei Sekunden lang den Prozess blockieren. Daher wird das SLEEP 1 eingesetzt. Die Ausführungsgeschwindigkeit des zweiten Teils wird dadurch nicht beeinträchtigt, denn die Schleife läuft ja sowieso drei Sekunden lang — ob da noch ein paar Millisekunden dazu kommen oder nicht, spielt nun wirklich keine Rolle.

Etwas anders sieht die Situation im ersten Programmteil aus. Auch hier nimmt die Ausführung einige Zeit in Anspruch (in meinem Test waren es etwa 1,5 Sekunden, was für einen Prozessor eine sehr lange Zeit ist). Deshalb sollte auch in dieser Schleife ein SLEEP 1 eingefügt werden. Probieren Sie es aus! Sie werden allerdings wahrscheinlich feststellen, dass die Programmausführung nun wesentlich länger dauert. Das ist auch kein Wunder: Immerhin wird die Ausführung des ersten Teils nun zehntausend Mal unterbrochen.

In dieser Situation würde sich ein Mittelweg anbieten, etwa dass SLEEP 1 nur bei jedem hundertsten Durchlauf eingesetzt wird. Das ist mittels Modulo-Rechnung tatsächlich möglich. Dazu benötigt die Schleife nur eine zusätzliche bedingte Anweisung, die ausgeführt wird, wenn sich die Laufvariable ohne Rest durch 100 teilen lässt. Natürlich verbraucht auch diese bedingte Anweisung etwas Rechenzeit, allerdings ist diese im Vergleich zu den (wirklich langsamen) Anweisungen LOCATE und PRINT vernachlässigbar.

Quelltext 18.2: SLEEP 1 nur jeden hundertsten Durchlauf
DIM AS DOUBLE start

' LOCATE und PRINT sind recht zeitaufwaendige Befehle - Messung der Dauer:
PRINT "Messung der Dauer von LOCATE und PRINT (10 000 Durchlaeufe)"
start = TIMER                     ' aktuellen TIMER-Stand merken
FOR i AS INTEGER = 1 TO 10000
  LOCATE 2, 1
  PRINT "Durchlauf"; i
  IF (i MOD 100) = 0 THEN SLEEP 1 ' jeden hundertsten Durchlauf
NEXT
PRINT "Vergangene Zeit: ";
PRINT TIMER - start; " Sekunden"  ' Abstand zwischen "Jetzt" und "Vorhin"
SLEEP

Experimentieren Sie mit den Werten etwas herum und verändern Sie ggf. auch die Anzahl der Schleifendurchläufe — die gemessenen Zeitintervalle hängen auch entscheidend von der Leistung Ihres Computers ab. Außerdem dürfen Sie die sonstigen laufenden Prozesse in Ihrem System nicht vergessen! Die hier behandelte Messung kann lediglich als Orientierung dienen; die Ausführungsgeschwindigkeit hängt auch sehr stark von der sonstigen Auslastung Ihres Systems ab.

Tip Unterschiede zwischen den Betriebssystemen:
Auf manchen, insbesondere älteren, Plattformen wird TIMER() um Mitternacht auf 0 zurückgesetzt. Wenn diese Rücksetzung zwischen Start- und Endpunkt der Zeitmessung stattfindet, ist die berechnete Differenz zwischen beiden Zeitpunkten negativ. Das Programm kann dadurch ein unerwartetes Verhalten aufweisen. Als Lösung dieses Problems kann bei negativer Differenz 86400 addiert werden — dies entspricht der Anzahl der Sekunden eines Tages.

18.2 Abruf und Änderung der Systemzeit

Mit den Funktionen TIME() und DATE() erhalten Sie einen String mit der aktuellen Systemzeit bzw. dem aktuellen Systemdatum. Beide haben ein fest vorgegebenes Format: Die Systemzeit wird im Format "hh:mm:ss" zurückgegeben, das Systemdatum im Format "mm-dd-yyyy". Falls Ihnen diese Schreibweise unbekannt sein sollte: Die Buchstaben bedeuten

  • im Zeitformat: h = Stunde (hour), m = Minute, s = Sekunde
    "hh" steht damit für eine Angabe der Stunde mit zwei Ziffern, also ggf. mit führender Null. Das Format "h" wäre dagegen eine Stundenanzeige ohne führende Null (bei Werten größer als 9 werden aber selbstverständlich zwei Stellen angezeigt).

  • im Datumsformat: d = Tag (day), m = Monat, y = Jahr
    Beachten Sie hier die amerikanische Formatierung, in der die Monatsangabe vor der Angabe des Tages steht.

Die Bedeutung des Buchstaben m muss sich hier leider aus dem Kontext erschließen. Der Kontext mag Ihnen hier vollkommen klar sein — für den Computer ist das nicht unbedingt so einfach. Wie FreeBASIC mit dem Problem umgeht, beleuchten wir in Kapitel 18.3.1. Dort werden wir uns eine deutlich flexiblere Möglichkeit ansehen, Datum und Zeit anzuzeigen.

PRINT "Heute ist der "; DATE; ". Es ist " & TIME & " Uhr."
SLEEP

Um Systemzeit und -datum neu zu setzen, verwenden Sie SETTIME und SETDATE. Die Zeitangabe muss in einem der Formate "hh:mm:ss" oder "hh:mm" oder "hh" sein, die Datumsangabe in einem der Formate "mm/dd/yyyy", "mm-dd-yyyy", "mm/dd/yy" oder "mm-dd-yy". Beachten Sie aber dabei, dass das Betriebssystem möglicherweise keine Änderung zulässt. Ich empfehle, auf den Einsatz dieser beiden Befehle zu verzichten.

18.3 Serial Numbers

TIME() und DATE() sind zum einen in ihrem Format sehr unflexibel, und um bestimmte Informationen wie etwa den aktuellen Tag des Monats zu extrahieren, muss zusätzlich mit Teilstrings gearbeitet werden. Zum anderen lässt sich damit nur der aktuelle Zeitpunkt bestimmen. Für weiter reichende Datumsberechnungen sind die beiden Funktionen nicht geeignet. Stattdessen werden Zeitangaben in FreeBASIC über Serial Numbers realisiert.

Eine Serial Number ist, kurz gesagt, ein DOUBLE, dessen ganzzahliger Anteil die Anzahl der Tage angibt, die seit dem 30.12.1899 (00:00 Uhr) vergangen sind. Der Nachkommaanteil gibt den entsprechenden Bruchteil des Tages an — so bedeutet der Nachkommateil .5 einen halben Tag bzw. 12 Stunden und der Nachkommateil .25 einen viertel Tag bzw. 6 Stunden. Negative Zahlen geben dann natürlich Zeitpunkte an, die vor dem 30.12.1899 liegen.

Das Format der Serial Numbers bringt einige Vorteile mit sich. Interessiert man sich nur für das Datum, muss lediglich der ganzzahlige Anteil der Zahl betrachtet werden. Die (ganzzahlige) Differenz zweier Serial Numbers ist zugleich die Differenz beider Daten in Tagen. Umgekehrt interessiert für die repräsentierte Uhrzeit lediglich der Nachkommaanteil. Ein nicht zu ignorierender Nachteil ist dagegen die eingeschränkte Genauigkeit von Gleitkommazahlen: Bei sehr großen Datumswerten leidet die Genauigkeit der Nachkommastellen. Allerdings werden Sie bei einem Datum innerhalb des „normalen“ Anwendungsbereichs nicht an diese Grenzen stoßen. Um etwa eine Genauigkeit im Sekundenbereich zu verlieren, müssten Sie mehrere Millionen Jahre in die Zukunft oder in die Vergangenheit rechnen.

FreeBASIC stellt eine Reihe an Funktionen zur Verfügung, um bequem mit Serial Numbers umzugehen. Um diese Funktionen nutzen zu können, müssen Sie mittels #INCLUDE die Datei datetime.bi einbinden, oder aber wieder vbcompat.bi, welche unter anderem auch datetime.bi einbindet.

18.3.1 Aktuelles Datum mit NOW() und FORMAT

Die einfachste Funktion in Bezug auf Serial Numbers ist NOW(). Die Funktion gibt eine Serial Number zurück, die den aktuellen Zeitpunkt repräsentieren.

#INCLUDE "vbcompat.bi"
PRINT "Aktueller Zeitpunkt: "; NOW
SLEEP
Ausgabe:
Aktueller Zeitpunkt:  44056.55517361111

Das ist jetzt noch nicht besonders hilfreich. Glücklicherweise lässt sich der Wert auf recht einfache Weise in ein für Menschen lesbares Format übertragen. Wir greifen dazu auf die Funktion FORMAT() zurück, die auf vielfältige Weise zur Formatierung von Zahlenwerten eingesetzt werden kann. Bevor wir ins Detail gehen, hier ein paar Beispiele:

Quelltext 18.3: Aktueller Zeitpunkt mit NOW() und FORMAT()
#INCLUDE "vbcompat.bi"
DIM AS DOUBLE zeitpunkt = NOW
PRINT "Heute ist der " & FORMAT(zeitpunkt, "d.m.yyyy")
PRINT "Es ist " & FORMAT(zeitpunkt, "hh:nn:ss") & " Uhr ";
PRINT "(" & FORMAT(zeitpunkt, "hh:nn:ss AM/PM)")v
PRINT "Morgen ist schon wieder " & FORMAT(zeitpunkt+1, "dddd")
SLEEP
Ausgabe:
Heute ist der 13.8.2020
Es ist 14:19:27 Uhr (02:19:27 PM)
Morgen ist schon wieder Freitag

Wie Sie sehen, ist eine Aussage über „morgen“ sehr einfach zu treffen, indem Sie zum aktuellen Zeitpunkt lediglich 1 addieren.

Aus der kurzen Demonstration können Sie schon ein paar Details über den Formatierungsstring herauslesen. d wird offenbar als Tag interpretiert, yyyy als vierstellige Jahreszahl usw. Da m als Monatsnummer interpretiert wird, wurde n (bzw. hier nn) zur Anzeige der Minuten verwendet. Tatsächlich wird ein hinter einem h stehendes m ebenfalls als Minutenangabe interpretiert anstatt als Monatsnummer. Ich würde trotzdem zur Verwendung von n raten, damit Sie nicht in Schwierigkeiten geraten, wenn Sie einmal nur die Minuten und Sekunden anzeigen lassen wollen, ohne Stundenangabe.

Neben Datums- und Zeitformatierungen dient FORMAT() auch zu allgemeinen Zahlenformatierungen. Der Formatierungsstring wird dazu analysiert und bei Auftreten bestimmter Zeichen oder Zeichenkombinationen entsprechend interpretiert. Die Möglichkeiten sind, wie gesagt, sehr vielfältig, und sind bei Weitem nicht auf die Ausgabe von Zeit und Datum beschränkt. Eine Zusammenstellung der möglichen Formatierungsangaben finden Sie in Anhang E.2. Auf den folgenden Seiten werden aber noch ein paar weitere Beispiele zum Einsatz von FORMAT() auftauchen.

18.3.2 Setzen einer Serial Number

Sie können natürlich nicht nur die Serial Number des aktuellen Zeitpunkts ermitteln, sondern die zu jedem beliebigen Zeitpunkt. Dazu gibt es zwei Funktionen zur Bearbeitung einer Zeitangabe und zwei Funktionen für Datumsangaben. Eine Zeit-Serial enthält nur die Zeitangabe ohne Datum, d. h. ihr Wert ist immer kleiner als 1. Eine Datums-Serial dagegen enthält keine Zeitangabe, ist also immer ganzzahlig. Selbstverständlich können Sie eine Zeit-Serial und eine Datums-Serial durch einfache Addition zu einer kompletten Serial Number kombinieren.

Zum Ermitteln einer Zeit-Serial gibt es folgende zwei Funktionen:

  • TIMESERIAL() erlaubt die Übergabe dreier Parameter, welche die Stunde (0-23), Minute (0-59) und Sekunde (0-59) der gewünschten Zeit repäsentieren. Beispielsweise gibt TIMESERIAL(1,30,0) die Serial Number zur Uhrzeit 1:30:00 zurück (da es sich hierbei um das Sechzehntel eines Tages handelt, ist der Rückgabewert exakt 1/16=0.0625).
    Die Funktion überprüft nicht den Gültigkeitsbereich der einzelnen Parameter — Sie können also durchaus versuchen, welche Serial Number zur Uhrzeit „0 Uhr 89 und 60 Sekunden“ gehört. In einem solchen Fall rechnet TIMESERIAL() mit: aus den 89 Minuten wird 1 Stunde und 29 Minuten, und aus den 60 Sekunden werden 1 Minute und 0 Sekunden.1

  • TIMEVALUE() erlaubt dagegen die Übergabe eines Strings, der ein Zeitformat in der Form "h:m:s" enthalten muss. Hier muss es sich nun um eine korrekte Zeitangabe handeln — die Eingabe von 89 Minuten o. ä. wird nicht akzeptiert, der Rückgabewert wäre in diesem Fall 0. Allerdings darf jedes der drei Datensegmente (Stunden, Minuten, Sekunden) vorn beliebig mit Nullen aufgefüllt werden, und vor und hinter den Datensegmenten dürfen jeweils beliebig viele Leerzeichen stehen (die Ziffern eines Datensegments müssen aber jeweils direkt beisammen stehen).

Analog dazu gibt es folgende zwei Funktionen zur Bestimmung einer Datums-Serial:

  • DATESERIAL() erlaubt die Übergabe dreier Parameter, welche das Jahr, den Monat und den Tag des gewünschten Datums repäsentieren. Beispielsweise gibt DATESERIAL(2020,2,29) die Serial Number des Datums 29.02.2020 zurück. Auch hier wird notfalls passend umgerechnet: Wäre das Jahr 2020 kein Schaltjahr gewesen, wäre der 29.02. korrekt als 01.03. interpretiert worden.

  • DATEVALUE() erlaubt die Übergabe eines Strings, der ein Zeitformat in der Form "d.m.y" enthalten muss. Als Trennzeichen zwischen den Datensegmenten (Tag, Monat, Jahr) kann anstelle des Punktes . auch der Bindestrich - oder der Slash / verwendet werden. Ansonsten gelten die gleichen Regeln wie für TIMEVALUE(): Jedes der drei Datensegmente darf vorn beliebig mit Nullen aufgefüllt werden, vor und hinter den Datensegmenten dürfen jeweils beliebig viele Leerzeichen stehen, und es muss sich um eine korrekte Datumsangabe handeln (der 29.02.2021 wäre nicht erlaubt).

Wenn Sie prüfen wollen, ob ein String eine gültige Datumsangabe enthält, können Sie ISDATE() verwenden. Die Funktion gibt -1 zurück, wenn ein wie bei DATEVALUE() angegebenes Datumsformat voliegt, und 0, wenn dies nicht der Fall ist.

Quelltext 18.4: TIMESERIAL() und Co.
#INCLUDE "vbcompat.bi"
DIM AS INTEGER stunden, minuten, sekunden
DIM AS STRING datum
DIM AS DOUBLE serial

PRINT "Geben Sie die Uhrzeit ein:"
INPUT "Stunden:  ", stunden
INPUT "Minuten:  ", minuten
INPUT "Sekunden: ", sekunden
INPUT "Geben Sie das Datum ein (dd.mm.yyyy): ", datum

IF ISDATE(datum) THEN
  serial = TIMESERIAL(stunden, minuten, sekunden) + DATEVALUE(datum)
  PRINT "Die zu den Daten gehoerige Serial Number lautet"; serial
  PRINT "Der "; FORMAT(serial, "d. mmmm yyyy, hh:mm AM/PM"); " ist ein ";
  PRINT FORMAT(serial, "dddd.")
ELSE
  PRINT "Es handelt sich nicht um eine gueltige Datumsangabe!"
END IF
Ausgabe:
Geben Sie die Uhrzeit ein:
Stunden:  37
Minuten:  37
Sekunden: 11
Geben Sie das Datum ein (dd.mm.yyyy): 28  .  02  .  2020
Die zu den Daten gehörige Serial Number lautet 43890.56748842593
Der 29. Februar 2020, 01:37 PM ist ein Samstag.

Beachten Sie in diesem Beispiel die automatische Umwandlung der zu hohen Uhrzeitangabe. Außerdem hängt die Ausgabe der Wochen- und Monatsnamen von den Systemeinstellungen ab.

18.3.3 Teilinformationen einer Serial Number

Um aus einer Serial Number bestimmte Informationen zu extrahieren, ist FORMAT nicht immer die einfachste Methode. Sie können dafür auf eine große Anzahl an Funktionen mit leicht zu merkenden Namen zurückgreifen:

  • SECOND() liefert die Sekunde,

  • MINUTE() liefert die Minute,

  • HOUR() liefert die Stunde,

  • DAY() liefert den Tag des Jahres,

  • WEEKDAY() liefert den Tag der Woche,

  • MONTH() liefert den Monat,

  • YEAR() liefert das Jahr

die in der Serial Number gespeichert sind. Der Rückgabewert ist jeweils ein INTEGER.

Außerdem gibt es noch DATEPART(), um eine beliebige Teilinformation zu extrahieren. Mit der Funktion können die zuvor genannten Funktionen ersetzt werden, aber auch weitere Informationen wie die Rückgabe der Kalenderwoche und des Quartals sind möglich.

Quelltext 18.5: Informationen aus einer Serial Number ermitteln
#INCLUDE "vbcompat.bi"
DIM AS DOUBLE serial = NOW
' Gesamtinformation ausgeben
PRINT "Heute ist der "; FORMAT(serial, "dd.mm.yyyy, hh:nn:ss"); " Uhr."
' Teilinformationen ausgeben
PRINT "Tag:            "; DAY(serial)      ' = DATEPART("d",    serial)
PRINT "Monat:          "; MONTH(serial)    ' = DATEPART("m",    serial)
PRINT "Jahr:           "; YEAR(serial)     ' = DATEPART("yyyy", serial)
PRINT "Stunden:        "; HOUR(serial)     ' = DATEPART("h",    serial)
PRINT "Minuten:        "; MINUTE(serial)   ' = DATEPART("n",    serial)
PRINT "Sekunden:       "; SECOND(serial)   ' = DATEPART("s",    serial)
PRINT "Tag der Woche:  "; WEEKDAY(serial)  ' = DATEPART("w",    serial)
' Weitere Teilinformationen
PRINT "Quartal:        "; DATEPART("q",  serial)
PRINT "Kalenderwoche:  "; DATEPART("ww", serial)
PRINT "Tag des Jahres: "; DATEPART("y",  serial)
SLEEP
Ausgabe:
Heute ist der 13.08.2020, 14:19:27 Uhr.
Tag:             13
Monat:           8
Jahr:            2020
Stunden:         14
Minuten:         19
Sekunden:        27
Tag der Woche:   5
Quartal:         3
Kalenderwoche:   33
Tag des Jahres:  226

Die durch WEEKDAY() und DATEPART() ermittelten Werte über den Tag der Woche und über die Kalenderwoche hängen davon ab, mit welchem Tag die Woche beginnt (üblich ist Sonntag oder Montag) bzw. mit welcher Woche die Zählung der Kalenderwoche beginnt. Dazu können beim Aufruf der Funktion weitere Angaben gemacht werden:

DATEPART (Intervall, Serial [, erster_Tag_der_Woche [, erste_Woche_des_Jahres]])

Intervall ist eine der in Quelltext 18.5 verwendeten Intervallangaben, Serial ist die Serial Number. Für erster_Tag_der_Woche kann eine der in der Datei datetime.bi definierten Konstanten verwendet werden:
fbUseSystem, fbSunday, fbMonday, fbTuesday, fbWednesday, fbThursday, fbFriday, fbSaturday
fbUseSystem verwendet das in den Systemeinstellungen festgelegte Verhalten, fbSunday legt den Sonntag als den ersten Tag der Woche fest, usw. Wird der Parameter ausgelassen, verwendet FreeBASIC die Systemeinstellungen.

Für erste_Woche_des_Jahres sind ebenfalls passende Konstanten definiert:

  • fbUseSystem: Verwende die Systemeinstellung.

  • fbFirstJan1: Beginne mit der Woche des ersten Januar.

  • fbFirstFourDays: Beginne mit der ersten Woche, die mindestens vier Tage hat.

  • fbFirstFullWeek: Beginne mit der ersten ganzen Woche des Jahres.

Auch hier wird standardmäßig das durch die Systemeinstellungen festgelegte Verhalten gewählt.

18.3.4 Namen des Wochentage und Monate

Die letzten beiden Funktionen im Zusammenhang mit dem Datum sind WEEKDAYNAME() und MONTHNAME(). Sie geben die Namen der Wochentage bzw. der Monate zurück. WEEKDAYNAME(1) liefert den Namen des ersten Wochentages, WEEKDAYNAME(7) entsprechend den Namen des letzten Wochentages. Für MONTHNAME() sind naürlich Parameterwerte von 1 bis 12 zulässig. Für Werte außerhalb des zugelassenen Bereichs wird ganz einfach ein Leerstring zurückgegeben.

Normalerweise liefern diese Funktionen den vollen Namen des Wochentages bzw. des Monats, also z. B. "Donnerstag" oder "August". Beide Funktionen erlauben aber auch die Ausgabe einer Kurzform wie "Do" oder "Aug". Dazu muss ein zweiter Parameter angegeben werden — ist dieser nicht 0, erfolgt die Ausgabe in Kurzform. Anders gesagt: Wenn Sie die Langform erhalten wollen, müssen Sie den zweiten Parameter 0 setzen oder gleich ganz weglassen.

WEEKDAYNAME() erlaubt außerdem einen dritten Parameter, um festzulegen, mit welchem Tag die Woche beginnt. Das funktioniert genauso wie bei DATEPART(). Wenn also etwa als dritter Parameter der Wert fbFriday angegeben wird, ist Freitag der erste Tag der Woche, der zweite Tag ist Samstag und die Woche endet am Donnerstag.

Beachten Sie auch bei diesen Funktionen, dass die Ausgabe (also insbesondere die Sprache, in der die Wochentage und Monate ausgegeben werden) von den Systemeinstellungen abhängt.

Quelltext 18.6: Namen der Wochentage und Monate
#INCLUDE "vbcompat.bi"
' aktuellen Wochentag und Monat ermitteln
DIM AS INTEGER wochentag = WEEKDAY(NOW), monat = MONTH(NOW)
' Ausgabe der Namen
PRINT "Heute ist der"; wochentag; ". Tag der Woche, also ";
PRINT WEEKDAYNAME(wochentag); "."
PRINT "In Kurzform: "; WEEKDAYNAME(wochentag, -1)
PRINT "Es ist ein Tag im "; MONTHNAME(monat); " ("; MONTHNAME(monat, -1); ")"
' Die Woche beginnt jetzt mit Freitag
wochentag = WEEKDAY(NOW, fbFriday)
PRINT "Wuerde die Woche am Freitag beginnen, waere heute der";
PRINT wochentag; ". Wochentag."
PRINT "Es waere trotzdem "; WEEKDAYNAME(wochentag,, fbFriday); "."
SLEEP
Ausgabe:
Heute ist der 5. Tag der Woche, also Donnerstag.
In Kurzform: Do
Es ist ein Tag im August (Aug)
Wuerde die Woche am Freitag beginnen, wäre heute der 7. Wochentag.
Es waere trotzdem Donnerstag.

Für den zweiten Parameter von WEEKDAYNAME bzw. MONTHNAME hätte statt -1 auch jeder andere Wert ungleich 0 verwendet werden können. Ich verwende in solchen Situationen aber gern -1, weil es am deutlichsten den Wert wahr repräsentiert.

Da im unteren Codeabschnitt sowohl die Ermittlung der Wochentagsnummer als auch des Wochentagsnamens von Freitag an zu zählen beginnen, gleicht sich das im Endeffekt wieder aus, und Sie erhalten auch hier den korrekten Wochentagsnamen. In der vorletzten Zeile hätte statt des ausgelassenen mittleren Parameters natürlich auch 0 stehen können.

18.3.5 Rechnen mit Datum und Zeit

Serial Numbers können sehr einfach zum Berechnen von Zeitunterschieden und ähnlichem eingesetzt werden, allerdings halten die Zeitintervalle noch einige Schwierigkeiten bereit. So sind z. B. die Monate unterschiedlich lang, und auch bei Berechnungen mit Jahren müssen die Schaltjahre berücksichtigt werden. Glücklicherweise gibt es zwei Funktionen, welche die Rechenarbeit deutlich vereinfachen.

DATEADD() berechnet aus einer Serial Number ein neues Datum (wobei hier mit Datum immer die Kombination von Datum und Zeit gemeint ist), das aus der Addition eines bestimmten Zeitintervalls entsteht. Sie müssen dazu die gewünschte Intervall-Einheit sowie die Anzahl dieser Einheiten angeben.

neues_Datum = DATEADD(Intervall, Anzahl, altes_Datum)

Sie können also z. B. zum alten Datum zwei Wochen addieren, oder mit einer negativen Anzahl auch subtrahieren.

Um den Abstand zwischen zwei Datumsangaben zu bestimmen — also z. B. um herauszufinden, wie viele Wochen zwischen den beiden Angaben liegen — dient die Funktion DATEDIFF():

differenz = DATEADIFF(Intervall, erstes_Datum, zweites_Datum _
                      [, erster_Tag_der_Woche [, erste_Woche_des_Jahres]])

erster_Tag_der_Woche und erste_Woche_des_Jahres haben dieselbe Bedeutung wie bei DATEPART in Kapitel 18.3.3. Die Angabe des Intervalls in DATEADD() und DATEDIFF() ist der dort angegebenen Intervallangabe sehr ähnlich, weist jedoch kleine Unterschiede auf, die aus Tabelle 18.1 ersichtlich sind. Die Unterschiede erklären sich dadurch, dass in den drei Funktionen nicht immer alle Intervallangaben sinnvoll sind.

Intervall DATEPART() DATEADD() DATEDIFF()

"yyyy"

Jahre

Jahre

Jahre

"q"

Quartale

Quartale

Quartale

"m"

Monate

Monate

Monate

"ww"

Wochen im Jahr

Wochen

Kalenderwochen

"w"

Tage in der Woche

Tage

Wochen

"d"

Tage im Monat

Tage

Tage

"y"

Tage im Jahr

Tage

Tage

"h"

Stunden

Stunden

Stunden

"n"

Minuten

Minuten

Minuten

"s"

Sekunden

Sekunden

Sekunden

Tabelle 18.1: Befehle für binäre Speicherkopien

Bei Quartale handelt es sich, wie oben bereits erwähnt, um Einheiten von drei Monaten. Eine Woche ist eine Einheit von sieben Tagen, wogegen eine Kalenderwoche von der Einstellung des ersten Wochentags abhängig ist.

Ein paar Beispiele zu den beiden Funktionen:

Quelltext 18.7: DATEDIFF() und DATEADD()
#INCLUDE "vbcompat.bi"
DIM AS DOUBLE Feb1_19 = DATESERIAL(1900, 2, 1), Mar1_19 = DATESERIAL(1900, 3, 1)
DIM AS DOUBLE Feb1_20 = DATESERIAL(2000, 2, 1), Mar1_20 = DATESERIAL(2000, 3, 1)
PRINT "Der Februar 1900 hatte " & DATEDIFF("d", Feb1_19, Mar1_19) & " Tage (";
PRINT DATEDIFF("w", Feb1_19, Mar1_19) & " Wochen)"
PRINT "Zwischen 01.03.2020 und 01.02.2020 liegen ";
PRINT DATEDIFF("d", Mar1_20, Feb1_20) & " Tagen (";
PRINT DATEDIFF("w", Mar1_20, Feb1_20) & " Wochen)"

DIM AS DOUBLE neuesDatum19 = DATEADD("h", 100000, Feb1_19)
DIM AS DOUBLE neuesDatum20 = DATEADD("h", 100000, Feb1_20)
PRINT "100.000 Std. nach 01.02.1900 0:00 Uhr liegt ";
PRINT FORMAT(neuesDatum19, "dd.mm.yyyy, h:nn ""Uhr"".")
PRINT "100.000 Std. nach 01.02.2000 0:00 Uhr liegt ";
PRINT FORMAT(neuesDatum20, "dd.mm.yyyy, h:nn ""Uhr"".")
SLEEP
Ausgabe:
Der Februar 1900 hatte 28 Tage (4 Wochen)
Zwischen 01.03.2020 und 01.02.2020 liegen -29 Tagen (-4 Wochen)
100.000 Std. nach 01.02.1900 0:00 Uhr liegt 30.06.1911 16:00 Uhr.
100.000 Std. nach 01.02.2000 0:00 Uhr liegt 29.06.2011 16:00 Uhr.

Wie Sie sehen, gibt DATEDIFF() einen positiven Wert zurück, wenn das zweite Datum zeitlich nach dem ersten liegt, und ansonsten einen negativen Wert. Außerdem wurden die Schaltjahre bei beiden Funktionen korrekt berücksichtigt: Die Jahre 1904, 1908, 2000, 2004 und 2008 waren Schaltjahre, nicht jedoch das Jahr 1900 (da 1900 ohne Rest durch 100, aber nicht durch 400 teilbar ist). Schaltsekunden können von FreeBASIC allerdings nicht berücksichtigt werden, da diese aufgrund der Unregelmäßigkeiten in der Rotationsgeschwindigkeit der Erde nicht vorhersagbar sind.

18.4 Fragen zum Kapitel

  1. Quelltext 11.9 erzeugt einen Countdown, der durch Tastendruck vorzeitig beendet werden kann. Schreiben Sie nun einen Countdown, der nur durch die ESC-Taste abgebrochen werden kann. Ein Tastendruck soll natürlich die Ablaufgeschwindigkeit nicht verändern. Nutzen Sie daher eine TIMER-gesteuerte Warteschleife, innerhalb derer Sie eine Tastenabfrage mit Überprüfung der ESC-Taste einbauen.

  2. Schreiben Sie ein Programm, das ausgibt, wie viele Tage, Wochen bzw. Minuten es noch bis Weihnachten dauert (25.12., 00:00 Uhr). Außerdem soll der Wochentag des 25.12. ausgegeben werden.


Fußnoten:
1) Durch entsprechend große bzw. durch negative Zeitangaben können Sie auf diesem Weg auch Serial Numbers erzeugen, die größer als 1 bzw. kleiner als 0 sind. Falls das ein Problem darstellt, müssen Sie die Parameter vor der Eingabe selbständig prüfen.


Kapitel 17: Betriebssystem-Anweisungen

Inhaltsverzeichnis

Anhang A: Antworten zu den Fragen