Anwendungen in dBMAN: Datumsfunktionen

Seit gut einem Jahr ist nun die Version 5.01 des Datenbanksystems dBMAN auf dem Markt. Mit der dazugehörigen Programmiersprache und dem Greased Lightning-Compiler steht für den ATARI ST ein Entwicklungspacket zur Verfügung, das hinsichtlich der Geschwindigkeit verblüffende Ergebnisse zeigt, aber auch bei ganz speziellen Aufgabenstellungen kaum noch Wünsche offen läßt. Mit einigen Kniffen kommt man aber auch in diesen Fällen oft recht schnell und einfach zu brauchbaren Ergebnissen.

Dabei ist interessant, daß dBASE III plus-Anwendungen auf dem dBMAN-Interpreter relativ problemlos laufen. Umgekehrt ist das nicht ganz so einfach, da dBMAN sowohl in der Behandlung von Variablen mehr Möglichkeiten zur Verfügung stellt als auch eine wesentlich größere Funktionsvielfalt als dBASE III plus besitzt. Dies gilt insbesondere für den Bereich der Menügestaltung. Aber auch die Funktionen zur String-Selektion und für die Behandlung von Zeit- und Datumsangaben erleichtern die Programmierung erheblich.

Datenbanken...

... mehr als nur Adressen- und Artikelverwaltung. Diese Kursreihe wird auf diese und andere, etwas “exotischere" Funktionen in diesem Datenbanksystem eingehen, und versuchen zu zeigen, was über die eigentliche Behandlung von Datenbanken hinaus möglich ist.

Man kann zwar von grundsätzlichen Regeln für das Programmieren ausgehen, letztendlich aber entwickelt jeder mit der Zeit seinen eigenen individuellen Programmierstil. Die Programme und Programm-Module, die in dieser Reihe vorgestellt und erläutert werden, dienen als Beispiele, an denen prinzipielle Vorgehensweisen deutlich gemacht werden sollen. Dabei orientiere ich mich an folgenden Leitlinien:

a. ) Die Programm-Module sollen möglichst kompakt sein.
b. ) Die Programm-Module sollen flexibel einsetzbar sein.
c. ) Die Programm-Module sollen möglichst kurz sein.
d. ) Das Programm als Ganzes soll möglichst schnell sein.
e. ) und daher häufige Disketten-/Plattenzugriffe vermeiden.

Die Realisierung insbesondere der ersten beiden Leitlinen führt aber doch leicht zur Unübersichtlichkeit, so daß ich es für sinnvoller gehalten habe, die Programme nur mit Minimalkommentaren zu versehen. Bei der Beschreibung der Anwendungsmöglichkeiten, des Aufbaus und der Funktionsweise des Programms sowie der Erklärung und Kommentierung wichtiger Befehle, Funktionen und Ausdrücke werde ich auf die jeweiligen Zeilennummern im entsprechenden Listing verweisen.

In diesem ersten Teil wollen wir im wesentlichen auf den Umgang mit Datumsfunktionen eingehen. Ein kleines, aber sehr nützliches Programm soll als Anschauungsbeispiel dienen. Zunächst mal Grundsätzliches zu den Datumsfunktionen.

Sowohl Memory- als auch Feldvariablen können Daten im Datumsformat aufnehmen. Das Systemdatum DATE() ist das einzige Datum, das bereits im richtigen Format vorliegt (ich gehe im folgenden davon aus, daß dies immer das aktuelle Datum ist.).

Datumsanzeige

Mit den Befehlen SET DATE TO GERMAN und SET CENTURY ON oder ASSIGN LONGYEAR(T) erfolgt die Anzeige von Variablen, die als DATUM definiert sind, im Format tt.mm.jjjj.

Rechnen im Datumsformat

Die Rechenoperationen mit Datumsvariablen beschränken sich logischerweise auf Addition und Subtraktion. Das Ergebnis ist entweder wieder ein Datum oder ein numerischer Wert.

DATUM +/- num. Wert = DATUM
DATUM +/- DATUM = num. Wert

Datumsumformung

Es gibt nun eine ganze Reihe von Umformungsmöglichkeiten, die ebenfalls für die Anzeige und Berechnung notwendig sind. Um überhaupt ein Datum, das noch nicht existiert, in eine Variable schreiben zu können, muß es als Character-String vorliegen und kann dann beliebig umgeformt werden.

CTOD('01.05.1989') C(haracter) TO D(ate)
DTOC(Y.DATUM) D(ate) TO C(haracter)

Datumseinheiten extrahieren

Als numerischer Wert können der Tag, der Monat, das Jahr und die laufende Nummer des Wochentags ermittelt werden.

DAY(DATE())
MONTH(DATE())
YEAR(DATE())
DOW(DATE()) —> D(ay) O(f) W(eek)

Kalender bis 2039

Es gibt eine ganze Reihe von Anwendungen. bei denen es ganz sinnvoll wäre, wenn das Programm selbständig erkennen könnte, ob ein bestimmter Tag ein (arbeitsfreier) Feiertag ist. Man denke zum Beispiel an die Datenbank einer Autovermietung, die natürlich den Fahrzeugpark mit Kundendienstterminen. Fälligkeitsdaten der Versicherungsprämien sowie die Kundendatei verwaltet und Rechnungen schreibt.

Ein anwenderfreundliches Programm-Modul RECHNUNG SCHREIBEN müßte Sonn- und Feiertage erkennen können, um dann die entsprechend höheren Tarife für die Berechnung der Ausleihgebühr zugrundezulegen. Aber auch Anwenderprogramme für Schulen/Internate etc. können, wenn die Feiertage und das Bundesland definiert sind. Ferien berechnen oder dafür sorgen, daß Prüfungstermine o.ä. nicht versehentlich auf einen schulfreien Tag gelegt werden. Ich selbst bin durch die Verwaltung meiner Kurse auf die Notwendigkeit einer Feiertagserkennung innerhalb einer Datenbankanwendung gekommen.

Das Programm

Es geht also darum, ein bestimmtes gegebenes Datum daraufhin zu überprüfen, ob es auf ein Wochenende oder auf einen gesetzlichen Feiertag fällt. Das Programm ISTFEI.CMD verlangt die Eingabe eines Datums. Dieses Datum wird nur aufgrund der Jahreszahl daraufhin überprüft. ob der Rechenbereich von dB MAN nicht überschritten wird. Es folgt die Ausgabe des Wochentags und des Monats in Worten. Anschließend werden die einzelnen Feiertage berechnet, in numerische Werte umgewandelt und ein Vergleich mit dem eingegebenen Datum durchgeführt. Das Ergebnis des Vergleiches ist 0. wenn kein Feiertag vorliegt, oder nimmt einen Wert zwischen 1 und 15 an. Dieser Wert ist identisch mit der Position des gefundenen Feiertags in einer Liste der 15 möglichen Feiertage. Der entsprechende Feiertag wird als C-String einer Variablen übergeben. Die DO WHILE-Schleife wird wiederholt, solange die Eingabe nicht mit der Taste >ESC< beendet wird.

Tage und Monate selbst gemacht

Da dBMAN nur in englischer Version vorliegt, erfolgt die Darstellung von Wochentagen und Monaten in Worten ebenfalls in englischer Sprache also CDOW ('11/09/89') ergibt Monday, und CMONTH('15/05/89') ergibt May.

Ein kleiner Trick hilft weiter. Am besten definieren wir am Anfang jeder Anwendung zwei Variablen, die alle Wochentage und Monate enthalten sowie die Variable X.DAT für das jeweilige Datum, dessen Monat oder Wochentag angezeigt werden soll (Listing Zeile 6-8).

Die Funktion EXTRACT() sucht nun den durch DOW(datum) errechneten numerischen Wochentag aus der durch Kommas getrennten Wochentagsliste heraus. Das "Trennzeichen” (delimiter) kann frei gewählt werden. Dabei geht es nicht nur darum, welches Zeichen schöner oder übersichtlicher ist, sondern vielmehr erleichtert es die Behandlung von Ausdrücken mit fest vorgegebenen Trennzeichen wie zum Beispiel Dateisuchpfade (Backslash "\“).

EXTRACT('delimiter'.LISTE.n'ter)

Die "Schwester” dieser Funktion erlaubt die Wahl von zwei Trennzeichen.

EXTRACT2(delimiter1.delimited.C-String.n)

Allerdings darf "n” hierbei nur Werte zwischen 1 und 3 annehmen, entsprechend wird dann links, rechts oder zwischen beiden Trennzeichen extrahiert. Das vorangestellte Makrosymbol "&”. (Listing Zeile 24.25) bewirkt. daß bei der Ausführung der Variablenname X.WOT und das Symbol selbst durch den Inhalt der Variablen ersetzt und die EXTRACT-Funktion ausgeführt werden. Wir werden im zweiten Teil dieser Reihe noch ausführlich auf die Verwendung von Makros eingehen.

Feiertage Das ganze Jahr

Da Feiertage nicht jedes Jahr neu und beliebig festgesetzt werden, sondern ganz bestimmten Regeln folgen, können sie berechnet werden. Grundsätzlich gibt es drei unterschiedliche Arten von Feiertagen:

Bei den sogenannten "fixen Feiertagen” wie dem 1.Mai. dem 17.Juni oder dem 24. Dezember ändert sich ja nur der Wochentag. Das Datum selbst ist konstant und stellt somit kein Problem dar. Man braucht nur das entsprechende Jahr als C-String anzuhängen (Listing Zeile 49-56). Schwieriger wird es bei den beweglichen Feiertagen.

Wann ist eigentlich Ostern 2002?

Die christlichen Feste und damit die meisten gesetzlichen Feiertage richten sich nach dem Ostersonntag. Der wiederum ist aber bezüglich seines "Termins”, "heidnischen” Ursprungs und richtet sich deshalb nach dem Mondzyklus. So hat das Konzil von Nicäa im Jahr 325 n.Chr. beschlossen, daß Ostern immer auf den ersten Sonntag nach dem ersten Frühlingsvollmond fällt.

Den Algorithmus, mit dem der Ostersonntag letztendlich berechnet wird, ausführlich zu erklären, würde den Rahmen dieses Kurses sprengen. Grundsätzliches über die Kalenderberechnung ist aber sehr interessant und soll deshalb auch kurz erwähnt werden.

Wie jeder weiß, ist das sogenannte "bürgerliche Jahr” in 365 Tage bzw. 12 Monate unterteilt. Die Monate hängen ursprünglich. w ie der Name schon vermuten läßt, mit dem Lichtwechsel des Mondes zusammen. Da die Zeit zwischen zw ei Neumonden nur etwa 29.53 mittlere Sonnentage umfaßt, müßte in einem reinen Mondkalender von Zeit zu Zeit ein "Schaltmonat” eingefügt werden, um zu verhindern, daß der (Mond-)Kalender zu sehr von den Jahreszeiten abweicht.

29.53 Tage * 12 Monate = 354.36 Tage Jahr 365 Tage Jahr - 354 Tage Jahr ~ 9 Tage (bürgerl.Jahr) - (Mondjahr) Abweichung pro Jahr.

Das entspricht schon innerhalb von 10 Jahren einer Verschiebung von drei Monaten, das hieße zum Beispiel, daß im März schon Sommer wäre.

Nach mehreren Kalenderreformen, bei denen teilweise tatsächlich mehrere Monate einfach übersprungen wurden, führten dann endlich die Römer eine Monatsdauer von 30, 31 bzw. 28 Tagen ein. Dummerweise führt aber auch diese Einteilung alle vier Jahre zu einer Abweichung von einem Tag, die durch den berühmten 29. Februar des Schaltjahres korrigiert wird. Dadurch haben wir nur noch alle 100 Jahre ungefähr ein Tag zuviel. Deshalb haben diejenigen, die an diesem denkwürdigen Tag geboren sind, alle 1000 Jahre doppelt Pech, weil in den Jahren, die ohne Rest durch 100 teilbar sind, das Schaltjahr, der 29. Februar und die Geburtstagsfete ausfallen.

Doch um Schaltjahre und andere Unregelmäßigkeiten brauchen wir uns nicht zu kümmern, das erledigt dBMAN automatisch für uns. Allerdings nur bis zum 31.12.2039. Daher die obere Jahresbegrenzung (Listing Zeile 14/15). Zurück zu unserer Ostersonntag-Berechnung. Frühlingsanfang ist gewöhnlich am 21.März, in Schaltjahren entsprechend einen Tag früher. Das ist genau an dem Tag des Jahres, an dem die Sonne den Himmelsäquator von Süden nach Norden überschreitet. Auf diesem Hintergrund hat C.F. Gauss (1777-1855) einen Algorithmus entwickelt, mit dem der Ostersonntag berechnet werden kann (Listing Zeile 18-32). Dabei bedeuten

Y.JAHR die Jahreszahl Y.D und Y.E Divisionsreste (sie berechnen Schaltjahre und Wochentage)

Die Ermittlung der Divisionsreste erfolgt mit der Funktion MOD(wert1,wert2). Sie liefert den Rest der Division von Wert1 und Wert2. Bei dBASE II hätte man noch schreiben müssen:

MOD(wert1,wert2) = wert1 - INT(wert1/wert2)*wert2

Je nachdem, ob das Jahr zwischen 1900 und 2099 oder 2200 und 2199 liegt, muß bei der Berechnung von Y.E der Wert 5 oder 6 eingesetzt werden (Listing Zeile 31).

Nützlich ist hier die Funktion IIFN(), die manchem vielleicht aus Tabellenkalkulationen bekannt vorkommt. [Meist lautet sie dort WENN().] Sie hilft, so manche Programmzeile einzusparen, zumal sie in fünf Variationen existiert.

IIF(): IIFC(); IIFD(); IIFN(); IIFL()

Die Syntax ist für alle Formen identisch:

IIF(Bedingung,Ausgabe wenn t, Ausgabe wenn f)

Die einfache Form von IIF() kann zwei unterschiedliche Ausgabetypen besitzen. Die Erweiterungen IIFC,D.N,L stehen jeweils für den bestimmten Ausgabetyp (Character, Datum, numerisch, logisch). Da dBMAN bei der Eingabe eines Datums, das größer als der 31.12.2039 ist, ohnehin unerbittlich eine Fehlermeldung auswirft, kann hier auch 5 als fester Wert eingesetzt werden.

* ISTFEI.CMD

***** PARAMETER / GLOBALE VARIABLE 
SET TALK OFF 
SET DB3 ON 
X.DAT = DATE()
X.MO = 'EXTRACT(",","Januar,Februar,März,April,Mai,Juni,Juli,August,September, Oktober,November,Dezember",MONTH(X.DAT))' 
X.WOT ='EXTRACT(",","Sonntag,Montag,Dienstag, Mittwoch,Donnerstag,Freitag,Samstag",DOW(X.DAT))'

DO WHILE .T.
ERASE
***** EINGABE 
X.SDAT = DATE()
@ 2,5 SAY 'GESUCHTER TAG: ' GET X.SDAT PICT '##.##.####' VALID NRANGE(YEAR(X.SDAT), 1989,2039) ;
ERRMSG 'DATUM AUSSERHALB DES DERZEITIGEN RECHENBEREICHES (1989-2035)'
@21,1 SAY'ENDE MIT ESC'
@ 3,0 TO 3,80 
READ
***** EXIT BEDINGUNG 
    IF LASTKEY() = 27 
        RETURN 
    ENDIF
X. DAT = X.SDAT
@ ROW()+1,5 SAY &X.MO 
@ ROW()+1,5 SAY &X.WOT

***** BERECHNUNG DES JEWEILIGEN OSTERDATUMS
Y.JAHR =YEAR(X.SDAT)
Y.VAR = IIFN(Y.JAHR-1989=0,1,((Y.JAHR-1989)*5)+1) 
Y.D = MOD(19*MOD(Y.JAHR,19)+24,30)
Y.E = MOD(2*MOD(Y.JAHR,4)+4*MOD(Y.JAHR,7)+6*Y.D+IIFN(NRANGE(Y.JAHR,1900,2035),5,6),7)
X.DOST = CTOD(STR(22+Y.D+Y.E,2,0)+'.03.'+STR(Y.JAHR,4,0))

***** ASCHERMITTWOCH/LISTE/REFERENZDATUM
Y.DAMI=X.DOST-46
Y.FLISTE='ROSENMONTAG/KARFREITAG/OSTERMONTAG/CHR.HIMMELF./PFINGSTMONTAG/; 
          FRONLEICHNAM/3 KÖNIG/TAG DER ARBEIT/17.JUNI/MARIA HIMMELF./;
          ALLERHEILIGEN/HL.ABEND/1.FEIERT./2.FEIERT./BUP+BETTAG'
Y.J1=CTOD('01.01.'+STR(Y.JAHR,4,0))

***** BERECHNUNG DER FEIERTAGE FÜR DAS GANZE JAHR 
Y.ND0=X.SDAT-Y.J1 
Y.ND1=Y.DAMI-2-Y.J1 
Y.ND2=Y.DAMI+44-Y.J1 
Y.ND3=Y.DAMI+47-Y.J1 
Y.ND4=Y.DAMI+85-Y.J1 
Y.ND5=Y.DAMI+96-Y.J1 
Y.ND6=Y.DAMI+106-Y.J1
Y.ND7=CTOD('07.01.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND8=CTOD('01.05.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND9=CTOD('17.06.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND10=CTOD('15.08.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND11=CTOD('01.11.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND12=CTOD('24.12.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND13=CTOD('25.12.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND14=CTOD('26.12.'+STR(Y.JAHR,4,0))-Y.J1 
Y.ND15=(Y.J1+46*7-DOW(Y.J1)+4)-Y.J1

Y.NLISTE=NLIST(Y.ND0,Y.ND1,Y.ND2,Y.ND3,Y.ND4,Y.ND5,Y.ND6,Y.ND7,Y.ND8,Y.ND9,Y.ND10, Y.ND11,Y.ND12,Y.ND13,Y.ND14,Y.ND15)
X.FEIERT=EXTRACT('/',Y.FLISTE,Y.NLISTE)
@ ROW()+2,5 SAY IIFC (Y.NLISTE<>0,X.FEIERT,'')
WAIT
ENDDO
RETURN

Der Ostersonntag ist dann der (Y.D+Y.E+22)'te März des entsprechenden Jahres. Bekanntlich ist aber Ostern viel öfter im April als im März, so daß der Ausdruck (Y.D+Y.E+22) zwangsläufig einen Wert annehmen muß, der größer als 31 ist. Normalerweise beschert uns dBMAN in so einem Fall den Fehler ERMSG->Invalid date, bzw. wir können das “GET-Feld" nicht verlassen, solange wir kein gültiges Datum eingegeben haben. Dabei ist als einzige Ausnahme zu beachten, daß ein “leeres" Datum CTOD(‘..‘) akzeptiert wird. Durch den Befehl SET DB3 ON (Listing Zeile 5) wird dies verhindert und ein falsches Datum in ein gültiges Datum umgerechnet. So wird aus dem 33. März automatisch der 2. April. Bei Daten, die aus einem gültigen Datum berechnet worden sind, ist das äußerst praktisch, bei Benutzereingaben allerdings werden auch Tippfehler klaglos akzeptiert.

Dieser Befehl hat allerdings noch andere, u.U. nicht erwünschte Effekte. DBF- und NDX-Files werden dann im dBASE III plus-Format angelegt, und der Befehl CLEAR ALL selektiert den Arbeitsbereich A(FJ) anstatt G(FP)[rimary]. Also, entweder gleich wieder ausschalten oder selbst umformen, z.B.

IF= 22+Y.E+Y.D >31
X.DOST=(Y.E+Y.D-9)'ter April
ELSE ....

und die Eingabe mit dem GET-Parameter VALID wie z.B im Listing, Zeile 17 überprüfen. dBMAN besitzt noch eine ganze Reihe weiterer Möglichkeiten, Benutzereingaben auf ihre “Sinnhaftigkeit" hin zu überprüfen. Wir werden in den nächsten Folgen ausführlich darauf eingehen.

Nun könnten die restlichen Feste, die sich auf Ostern beziehen, berechnet werden. Da aber der Vergleich nicht einzeln, d.h. mit einer IF- oder CASE-Folge durchgeführt werden soll (das wäre viel zu umständlich), muß das Datum als numerischer Wert vorliegen. Das ist eigentlich nur mit dem sogenannten Julianischen Datum möglich, bei dem die Tage, ohne Berücksichtigung der Jahre oder Monate, einfach vom 01.01.4713 v.Chr. durchgezählt werden.

dBMAN rechnet zwar intern mit dieser Datums-Seriennummer, stellt sie aber nicht direkt als numerischen Wert zur Verfügung, so daß wir uns ein eigenes Referenzdatum erstellen müssen. Wir wählen für Y.J1 den 01.Jan. des jeweiligen Jahres, damit die Zahlen nicht zu groß werden, und berechnen den n-ten Tag dieses Jahres (Listing Zeile 42-56). Wenn man das Programm benutzen will, um eine Reihe von Terminen nacheinander zu berechnen, die u.U. einen Jahreswechsel beinhalten, ist es günstiger, ein konstantes Referenzdatum zu wählen und den Eingabebereich entsprechend nach unten zu begrenzen, um keine negativen Referenzen zu erhalten.

Da auch der Rosenmontag in unserer Kennung berücksichtigt werden soll, ist es übersichtlicher, zur Berechnung der weiteren Feiertage nicht Ostern zugrundezulegen, sondern den Aschermittwoch. An diesem Tag beginnt die sogenannte fünfundvierzigtägige Fastenzeit, so daß gilt:

Aschermittwoch = Ostern-46 Rosenmontag = Aschermittwoch-2 Pfingsten = Aschermittwoch+85 usw.

Zunächst aber wird noch die Variable FLISTE definiert (Listing Zeile 36-38). Sie beinhaltet die Namen der Feiertage in derselben Reihenfolge, wie sie den Variablen Y.ND1-Y.ND15 zugeordnet werden. Die Variable Y.ND0 enthält das zu suchende Datum. Dabei sind Y.ND1-Y.ND6 von Ostern abhängige Feste und Y.ND7-Y.NDI4 fixe Feiertage. Y.ND15 berechnet den Buß- und Bettag (Listung Zeile 57), der zu der dritten Gruppe der Feiertage gehört. Er ist immer am 3. Mittwoch im November (46.Woche). Falls der 1. Nov. selbst ein Mittwoch ist, zählt er nicht mit.

Termine, am n-ten Montag, Dienstag... eines bestimmten Monats können mit der DOW(datum) berechnet werden. DOW(DATUM) liefert den numerischen Wert des Wochentages:

Sonntag = 1 Montag = 2... Samstag = 7

Der Ausdruck -(DOW(datum))+4) ergibt also immer einen Wert zwischen

+3= -1+4 (für Datum = Sonntag) -3= -7+4 (für Datum = Samstag)

und wird zu 0, wenn das Datum auf den entsprechenden Wochentag fällt (Listing Zeile 57). Die bayrischen Sommerferien z.B. beginnen in der Regel am letzten Donnerstag im Juli. Wenn wir das Datum Y.FANF des 1. Ferientages im Jahr XY suchen, so gilt Donnerstag ist der 5. Tag der Woche.

DOW(donnerstag)= 5 Y.JAHR= XY
Y.VAR1 = CTOD('31.07.'+STR(Y.JAHR, 4,0)) 
Y.FANF = IIFD(DOW(Y.VAR1)<5,Y.VAR1-
             (DOW(Y.VAR1)+2),Y.VAR1+
             (-DOW(Y.VAR1)+5))

Wenn also der 31.07. nicht größer als Donnerstag ist, wird der Ausdruck

-(DOW(Y.VAR1)+2)

zu 0, sonst wird

-(DOW(Y.VAR1)+5)) 

zu 0 und die Bedingung

Ferienanfang = <= 31.07.XY 
Ferienanfang = >= 31.07.XY -7

ist immer erfüllt.

Im Prinzip kann so jedes Datum, das für eine bestimmte Anwendung relevant ist. berechnet und in die Liste eingefügt werden. Eine Grenze stellt lediglich die maximal erlaubte Zeilenlänge dar, aber wer will, kann ja eine zweite Liste erstellen.

Wenn so alle Tage des Jahres, die in Frage kommen, bereitstehen, kann die NLIST(suchwert,wert1,wert2...wertn) nun die Position des Wertes in der Liste, der dem Suchwert entspricht, in die Variable Y.NLISTE schreiben (Listing Zeile 59). Ist kein identischer Wert vorhanden, wird Y.NLISTE zu 0.

Die Funktion EXTRACT() extrahiert mit Hilfe des Wertes von Y.NLISTE den entsprechenden String aus Y.FLISTE (Listing Zeile 60). Der Name des Feiertags steht nun in der Variablen X.FEIERT. Die Funktion IIFC() benötigen wir, weil EXTRACT() (leider) keinen Null-String liefert, wenn Y.NLISTE (Position) = 0 ist (Listing Zeile 62).

Das abgedruckte Listing von ISTFEI.CMD ist hier (zum Austesten), was die Eingabemöglichkeit und die Verwendung von Memory-Variablen anbelangt, als eigenständig lauffähiges Programm geschrieben. Zur Anzeige liegen daher globale X.-Variablen vor.

X.SUFEI das überprüfte Datum im Datumsformat
&X.WOT der Wochentag in Worten
&X.MO der Monat in Worten
X.FEIERT der Name des Feiertags

Kommen wir aber noch einmal zu dem möglichen Anwendungsbereich als Programm-Modul beim Programmteil RECHNUNG SCHREIBEN in der Datenbank einer Autovermietung zurück. Ein vereinfachter Ablaufplan könnte folgendermaßen aussehen:

Der Programmteil RECHNUNG SCHREIBEN übernimmt aus dem KUNDEN-Datensatz das Datum und aus dem Fahrzeugpark-Datensatz den Tarif. Nun muß festgestellt werden, ob das fragliche Datum ein Wochenende oder Feiertag ist.

Wenn DOW(Ausleihtag) 1 oder 7, also ein Wochenende ist, kann man sich ja den “Ausflug” nach ISTFEI.CMD sparen. Ansonsten muß das Datum nach ISTFEI.CMD (Teil 5-7) übermittelt und der Name des Feiertages als Variable wieder nach RECHNUNG SCHREIBEN übernommen werden. Wenn nun ein Feiertag erkannt worden ist,

FEIERT <>

wird der Tarif z.B. um 25% erhöht, und es gilt TARIF = TARIF*I .25, wenn der Programmteil _RECHNUNG SCHREIBEN_ fortgesetzt wird.

In solch einem Fall bietet sich ein anderer Umgang mit den Variablen an. Wie schon gesagt, stellt dBMAN wesentlich mehr Möglichkeiten als dBASE III plus zur Behandlung von Memory-Variablen zur Verfügung.

Memory-Variablen-Handling

In der Standardkonfiguration von dBMAN können gleichzeitig 256 Variablen ohne Präfix und 128 X.-Variablen als globale Variablen und 64 Y.-Variablen bzw. Z.-Variablen pro Programm als lokale Variablen aktiv sein. Dabei entsprechen die 256 Variablen ohne Präfix den PRIVAT/PUBLIC-Variablen von dBASE III plus, d.h sie gelten als PRIVAT und werden automatisch nach Beendigung des Programms, das sie definiert hat, gelöscht, wenn sie nicht ausdrücklich vor der Belegung mit Werten als PUBLIC definiert wurden. Das bedeutet:

PRIVAT-Variablen können aber auch als PARAMETER mit dem Befehl DO Programm WITH PARAMETER var1, var2,... an andere Programme übergeben werden. Wir werden diese Möglichkeit im zweiten Teil noch näher kennenlernen. Auf X.-Variablen haben alle Programme uneingeschränkt Zugriff. Sie können wie PUBLIC-Variablen nur mit dem Befehl RELEASE [ FX ] ALL [ EXEPT/LIKE maske ] gelöscht werden.

Y.- und Z.-Variablen werden wie PRIVATE gelöscht, nur mit dem Unterschied, daß sie in einem mit DO program.cmd aufgerufenen Programm als Z.- und Y.-Variablen weiterhin verwendbar und veränderbar sind und nach der Rückkehr in das Programm, in dem sie ursprünglich definiert worden sind, mit neuem Inhalt als Y.- bzw. Z.-Variablen weiterverarbeitet werden können. Erst nach Verlassen des Ursprungsprogramms werden sie endgültig gelöscht. Anschaulich - aber vereinfacht - könnte man sagen, für jede Y.-Variable existiert im folgenden Programm-Modul eine Z.-Variable. Beim Rücksprung verhält es sich genau umgekehrt. Konkret würde sich also für die Einbindung des Programm-Moduls in die Rechnungsschreibung einer Autovermietung dann folgende Variablenbenennung anbieten.

WOT, MO und DAT werden im Hauptprogramm vor der ersten DO WHILE-Schleife als PUBLIC definiert und dann erst einmal mit DATE() belegt.

In der Datei mit dem ALIAS-Namen “KUNDEN” stehen der oder die Ausleihtage im 3. Feld des jeweiligen Datensatzes der Form tt.mm.jjjjnnn zur Verfügung. nun steht für die Anzahl der Ausleihtage. Im Programm-Modul RECHNUNG SCHREIBEN werden die Variable für den ersten Ausleihtag als

Y.SUFEI = CTOD($(EXTRACT(',',FIELD(%KUNDEN,3),1),1,10))

die Anzahl der Tage als

Y.ANZ = VAL($(EXTRACT(',',FIELD(%KUNDEN,3),1),11,3))

definiert, so daß im Modul ISTFEI.CMD die Variable Y.SUFEI als Z.SUFEI (das zu suchende Datum) weiterverarbeitet werden kann. Y.ANZ, nunmehr anprechbar als Z.ANZ, bestimmt, wie oft das Datum um 1 erhöht und der Vergleich weiter durchgeführt werden muß. Die Variable FEIERT wird im erst im Modul ISTFEI.CMD und deshalb gleich als Z.FEIERT definiert. Nach der Rückkehr zum Programm-Modul RECHNUNG SCHREIBEN stehen dann Y.SUFEI und Y.ANZ wieder und Y.FEIERT erstmals als Y.-Variablen zur Verfügung und werden nach Beendigung des Moduls RECHNUNG SCHREIBEN automatisch gelöscht.

Diese Möglichkeit gilt also grundsätzlich für verschachtelte Programm-Module. Eine mehrfache Verschachtelung ist natürlich auch möglich. dBMAN erstellt dann beim Aufruf des 3. Programm-Moduls automatisch die Datei DBMEM.MEM, so daß bei der Rückkehr immer die entsprechenden Y.- und Z.-Variablen wiedereingelesen werden.

Zur Wahrung der Übersichtlichkeit und in Anbetracht der Diskettenzugriffe, die das Schreiben und Wiedereinlesen von DBMEM.MEM erfordert, sollte man im doppelten Sinne des Worte,s aber nicht ohne Not, zu “tief stapeln”.

Im nächsten Teil werden wir uns ein komplettes Programm aus mehreren Modulen ansehen, das mit einer Datenbank zusammenarbeitet, in der Tage, Zeiten und die Art der Tätigkeiten, die während des angegebenen Zeitraums ausgeführt wurden, gespeichert sind. Das Beispielprogramm ermöglicht und kontrolliert die Eingabe von neuen Datensätzen, berechnet Stunden, Minuten und erstellt Listen für beliebige Zeiträume, die auf beliebige Ausgabeeinheiten geleitet werden können. Mit einigen Ergänzungen könnte dieses Programm in eine komfortable Arbeitszeit- bzw. Arbeitsstundenverwaltung für Handwerker oder Freischaffende eingebaut werden.


Peter Neuchel
Links

Copyright-Bestimmungen: siehe Über diese Seite