The following article was printed in issue 5 1986 of the magazine „CT".
CP/M Plus supports a clock for accessing date and time through a BDOS call. This article demonstrates how to process the format of date and time.

Fröhliches Rechnen

Datum und Uhrzeit unter CP/M-Plus

Rolf Weitkunat/Michael Bührer
In c't 12/85 und 2/86 ärgert sich Willi Wagemuth über die unkomfortable Übergabe des Datums an den CP/M-Plus-Benutzer. Da wird dem Aufrufer der BDOS-Funktion 105 das Datum doch tatsächlich in Form eines Zählerstandes - vom 1. Januar 1978 an gerechnet - serviert, obwohl das im Vergleich zu CP/M 2 schwergewichtigere CP/M-Plus durch diesen 'Kunstgriff' nur ein (!) Byte einspart. Auch die Uhrzeit sorgt für Ärger: Zwar stehen StunderTund Minuten jeweils in einem Byte, und die Sekunden werden im Akku übergeben, aber leider in BCD-Codierung. Wer nicht über ein sehr sonniges Gemüt verfügt, hat Grund zu Unmut und darf sich ans Werk machen - oder die im folgenden aufgelistete Z80-Routine abtippen.

Zweifellos ist CP/M-Plus dem 'alten' CP/M 2 in vielem überlegen. Besonders erfreulich ist die größere Maschinenunabhängigkeit, die dadurch erreicht wird, daß die Anzahl der möglichen BDOS-Funktionsaufrufe mehr als verdoppelt wurde. Während man unter CP/M 2 die Uhr ziemlich umständlich mit Hilfe eines implementationsspezifischen BIOS-Aufrufes abfragen mußte, ist dies beim 'neuen' CP/M durch die BIOS-Funktion 105 (get date and time) geregelt. Sie übergibt diese Informationen in einen frei wählbaren, 4 Byte langen Speicherbereich. Damit läßt sich allerdings nicht allzuviel anfangen, wenn man während eines Anwendungsprogramms die Uhrzeit benötigt. Statt des Datums erscheint dann nämlich eine mittlerweile ziemlich große Zahl, und die Uhrzeit wird in BCD-Zahlen angegeben.

Der Anwender sieht sich nun vor die schöne Aufgabe gestellt, diese skurrilen Werte in eine verständliche Information umzurechnen. Wie bei den meisten Softwareproblemen (und auch sonst im 'richtigen Leben') gibt es hier wieder mindestens zwei Lösungsstrategien: Entweder sucht man sich einen mathematischen Umrechnungsalgorithmus mit vielen DIVs und MODs, oder man läßt das Gehirnschmalz in den dafür vorgesehenen Windungen und löst das Problem 'zu Fuß'. Damit es trotzdem nicht zu lange dauert, empfiehlt sich dafür natürlich die Maschinensprache.

Bei unserer Anpassung eines wissenschaftlichen Literaturverwaltungsprogramms an CP/M-Plus war das Zeitverhalten der Routine nicht so wichtig. Hauptsache zuverlässig und schnell zu schreiben. Aus diesem Grund haben wir uns auf den hier wiedergegebenen Kompromiß zwischen kompaktem Code und schneller Ausführung geeinigt: Der Algorithmus erfordert keine komplizierten mathematischen Umrechnungen, die ja in Assembler nicht sehr angenehm sind, und er ist ebenso kurz wie einfach. Auch Programmierer, die mit der Maschinensprache nicht auf du und du stehen, können die Befehle leicht nachvollziehen und in jede beliebige höhere Programmiersprache übersetzen oder (z.B. mit dem 'Turbo-Inliner' aus c't 2/86) einbinden. (Achtung: Im Inliner war ein 'Bug', der zuerst beseitigt werden muß: auf Seite 68, linke Spalte, ist in der Zuweisung 'Upcaseline := Copy(Textline,ExternalPos, Linelength - ExternalPos + 1);' anstelle von Textline 'Upcaseline' einzusetzen. Siehe auch c't 4/86, Ergänzungen + Berichtigungen.)

Die Routine nützt die besondere Fähigkeit des Computers zur schnellen Durchführung gleichartiger Operationen. Die wichtigste Operation ist hierbei die 16-Bit-Subtraktion, die man mit einem Z80-Prozessor ohne zusätzliche Subroutinen oder Makros durchführen kann. Der Lösungsweg ist denkbar einfach: Von dem 16-Bit-Zählerstand (im CP/M-Plus-Jargon auch Datum genannt) werden Monat für Monat die entsprechenden Tageszahlen subtrahiert und ein Monatszähler mitgeführt. Bleiben Tage übrig, wird der nächste Monat abgezogen. Wurde zuviel abgezogen, wird die Subtraktion rückgängig gemacht, und der Tageszähler enthält die Tage des aktuellen Monats. Etwas problematischer sind die Schaltjahre. Dort wird im Akku Jahr für Jahr von null bis drei mitgezählt und bei Schaltjahren (Akku = 0) der Februar gesondert behandelt: In der Tabelle mit den Monatslängen ist dieser Monat durch den Wert 0 dargestellt. Die Routine setzt dafür dann entweder 28 oder 29 ein.

Bei der Uhrzeit müssen lediglich die drei BCD-Werte für Stunde, Minute und Sekunde in binäre Zahlen umgewandelt werden. Dazu trennt das Unterprogramm 'BCD' den jeweiligen Wert in zwei Nibble, multipliziert das erste mit zehn und addiert das zweite dazu.

Wem keine Stunde schlägt, der kann sich auf das Datum beschränken, indem er die mit '!' markierten Zeilen wegläßt. Umgekehrt geht's natürlich auch. In beiden Fällen muß allerdings neu assembliert werden, um die relativen Adressen anzupassen.

Wer die gelistete Einbindung in Turbo-Pascal nicht mag, muß nur auf die Parameterübergabe achten: In Pascal werden in VAR-Parametern nicht die Werte, sondern die Adressen dieser Werte übergeben (call by reference). Wenn dies nicht der Fall ist, dann reicht es, bei den mit '()' gekennzeichneten Statements einfach die Klammern wegzulassen.

Zum Schluß noch ein Hinweis: die Routine funktioniert 'nur' bis zum 28. Februar des Jahres 2000. Dieses Jahr ist nämlich - obwohl durch 4 teilbar - kein Schaltjahr, weil es zusätzlich auch ohne Rest durch 100 dividiert werden kann. Wer es bis dahin geschafft hat, sich die Anschaffung eines 16-Bitters zu verkneifen, dem kommt es wahrscheinlich auf einen Tag mehr oder weniger auch nicht mehr an. Außerdem wird er bis dahin seinen Z80 wohl so gut kennen, daß er das DEC DE problemlos an der richtigen Stelle einfügen kann.

Hier die Z80 Quelle.
Das Pascalprogramm berücksichtigt nicht das Jahr 2000, so dass ein Jahrzehnt größer 99 werden kann. In der korrigierten Quelle ist das berücksichtigt.

Scanned by Werner Cirsovius
December 2002
© Heise Verlag