Süß. Der Streifzug durch die Festplatten vergangener Tage fängt allmählich an, richtig Spass zu machen.

Ich habe eben eine Pascal-Unit gefunden, die ich 1996 schrieb, um mir Datumsberechnungen zu erleichtern. Hierin finden sich einige recht sinnvolle Umsetzungen von Klassen und Funktionen zum Thema Datum und Zeit. Außerdem habe ich noch das hier gefunden: waschechter 16bit Assembler!

1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996 * 1996

Direkt ins Auge schossen dabei folgende Code-Segmente:

{ TDateItem }
procedure TDateItem.Assign(ADay, AMonth, AYear: word); assembler;
asm
        LES     DI,Self
        CLD
        MOV     AX,ADay
        STOSB
        MOV     AX,AMonth
        STOSB
        MOV     AX,AYear
        STOSW
end;

So wurde damals unter PASCAL der Speed erhöht. Da schlägt das alte Programmierer-Herz doch direkt wieder höher :)

Oder hier: der Assembler-Code wurde ausgeklammert, vielleicht weil er nicht funktionierte. Stattdessen reichte ein Move-Befehl aus. TDateItem ist zwar eine Klasse. Da aber ObjectPascal erst dann einen MFT anlegte, wenn virtuelle Methode zum Einsatz kamen, konnte man hier eine Klassen-Instanz wie einen Record behandeln und die dereferenzierte Objekt-Instanz einfach kopieren, um eine Kopie der Member-Variablen zu erreichen:

procedure TDateItem.CopyFrom(D: TDateItem);
{asm
        PUSH    DS
        LDS     SI,D
        LES     DI,Self
        CLD
        MOVSB
        MOVSB
        MOVSW
        POP     DS}
begin
  Move(D, self, SizeOf(TDateItem));
end;

Das Interface der 96er Datumsklasse wirkt auch heute noch richtig schnuckelig:

Klasse TDateItem:
 
{ Typen für zeitliche Daten }
  TDayType = 1..MaxDayInMonth;  {1..31}
  TMonthType = 1..12;
  TYearType = 1900..2060;
 
{ TDateItem }
{ Object für Speicherung von einem zeitl. Datum}
  PDateItem = ^TDateItem;
  TDateItem = object
    Day: TDayType;
    Month: TMonthType;
    Year: TYearType;
    procedure Assign(ADay, AMonth, AYear: word);
    { setzt die Felder auf  }
    procedure AssignToday;
    { setzt die Felder Day, Month, Year auf die Werte in D }
    procedure CopyFrom(D: TDateItem);
    { setzt Felder aus LongDate }
    procedure CopyFromLong(D: TLongDate);
    { belegt einen TLongDate-Record mit aktuellen Werten }
    procedure CopyToLong(var D: TLongDate);
    { gibt true zurück, wenn die Werte aus D diesen Werten entsprechen }
    function Equals(D: TDateItem): boolean;
    { berechnet den Wochentag. Result ist daySunday...daySaturday }
    function GetDayOfWeek: byte;
    { berechnet den Tag vor dem Tag in diesem Object }
    procedure GetLastDay(var D: TDateItem);
    { gibt den vollständigen Monat und das Jahr aus }
    function GetMYText: string;
    { berechnet den x. Tag ab dem Tag in diesem Object }
    procedure GetNextXDate(Days: integer; var D: TDateItem);
    { berechnet den nächsten Tag }
    procedure GetNextDay(var D: TDateItem);
    { berechnet den nächsten Monat }
    procedure GetNextMonth(var D: TDateItem);
    { gibt den Vormat zurück }
    procedure GetPreMonth(var D: TDateItem);
    { gibt das Datum in Form DAY.MONTH.YEAR }
    function GetText: string;
    { gibt das Datum in Form WEEKDAY(2 Chars), DAY.MONTH.YEAR }
    function GetW2Text: string;
    { gibt das Datum normal aus, jedoch Monat ausgeschrieben }
    function GetNMText: string;
    { prüft, ob der Tag in ADate der Vortag zu self ist }
    function IsPreDay(ADate: TDateItem): boolean;
    function IsToday: boolean;
    { zählt Tage vom akt. Datum rückwärts, bis er auf einen Sonntag stößt }
    { und speichert diesen in Date }
    { ist self bereits ein Sonntag, so wird zum Sonntag davor gesprungen }
    procedure LastWeekEnd(var Date: TDateItem);
    procedure Load(var S: TStream);
    { liefert den letzten Tag des Monats Month,Year zurück }
    function MaxDayMonth: byte;
    { speichert die entsprechenden Daten in einen Record TDateFormatRec }
    procedure PutFormatRec(var Rec: TDateFormatRec);
    procedure Reset(ADay, AMonth, AYear: word);
    procedure Store(var S: TStream);
    function Valid(const Inform: boolean): TDateError;
    end;

Kerngegenstand der Überlegungen hierfür bestand in der Entwicklung eines Programmes zur Dienstplanberechnung für Krankenhäuser. Wie bei jeder anderen Programmiersprache auch heute noch anzutreffen, stellte PASCAL nur recht rudimentäre Datums-Funktionen/Prozeduren bereit.

Bei solchen Kalkulationen reichen Datumsklassen nicht immer aus. Ein Pendant bezogen auf den “Monat” als solchen habe ich damals direkt im Anschluß angefügt:

Klasse TMonthItem:
 
TWeekItem = object
    Monday: TDateItem;
    Sunday: TDateItem;
    end;
 
  TWeeks = array[1..7] of TWeekItem;
 
  PMonthItem = ^TMonthItem;
  TMonthItem = object
    Month: word;
    Year: word;
    WeekCount: byte;
    Weeks: TWeeks;
    procedure Assign(AMonth, AYear: word);
    procedure CalcWeeks;
    function DayIsWhichWeek(const Day: word): byte;
    function FirstDayOfMonth: byte;
    function LastDay: byte;
    function LastDayOfMonth: byte;
  private
    procedure CalcFirstWeek;
    procedure CalcNextWeek(First: TWeekItem; var Next: TWeekItem);
    end;

Fragt mich nicht, warum ein Monat bei mir damals 7 Wochen aufnehmen konnte. Ich vermute den Grund darin, das die Woche vor dem Monat und die Woche nach dem Monat ebenfalls erfasst wurden, weil ein Monat nunmal nicht immer an einem Montag anfängt. Bleibt noch eine Woche (die 7.) übrig…. Wer weiss….

Datumsobjekte treten bei Dienstplanberechnungen vor allem in Listen auf und sollten chronologisch sortiert werden können. Deshalb findet sich im vorliegenden Code natürlich auch das:

Klasse TDatelist:
 
{ TDateList }
{ Object zur Speicherung von einer Liste mit zeitlichen Daten }
  PDateList = ^TDateList;
  TDateList = object(TSortCollection)
    constructor Init;
    function Compare(Key1, Key2: Pointer): integer; virtual;
    function CompareList(AList: PDateList; var FoundItem: PDateItem;
      var FoundIndex: word): boolean; virtual;
    procedure DeleteOldDates(AListDate: TListDate);
    { überprüft, ob das Item bereits in der Liste existiert und gibt }
    { den Index des gefunden Items wieder oder -1 nei nichtfinden}
    function EqualItem(Item: PDateItem; var Index: integer): boolean;
    { Error-Prozedur }
    procedure Error(Code, Info: integer); virtual;
    { gibt true zurück, wenn das DateItem bereits in der Liste existiert }
    function FoundSame(Item: PDateItem): boolean; virtual;
    procedure Insert(Item: Pointer); virtual;
    function KeyOf(Item: Pointer): Pointer; virtual;
    end;

TDateList erbt in zweiter Instanz von der in TurboVision bereits vorhandenen TSortedCollection.

Natürlich gab es auch eine Time-Klasse:

Klasse TTimeItem:
 { TimeItem }
 
  PTimeItem = ^TTimeItem;
  TTimeItem = object
    S, M, H: byte;
    procedure Assign(const AHour, AMin, ASec: byte);
    procedure AssignWord(const AHour, AMin, ASec: word);
    { setzt Werte auf aktuelle Zeit }
    procedure AssignNow;
    procedure CopyFrom(const Time: TTimeItem);
    function Equals(const Time: TTimeItem): boolean;
    procedure Load(var Stream: TStream);
    procedure Store(var Stream: TStream);
    end;

Noch ein wenig Lust auf Assembler bekommen?

Ich auch:

Stringoperationen waren in PASCAL sehr teuer und je nach Anwendung recht unperformant. Somit musste ich die Methode, die ein Datum als String formatiert, in Assembler schreiben. Dieser Schritt erhöhte die Performance deutlich.

Klasse TDateItem:GetText():
 
{ For GetText }
 
procedure AXtoString; assembler;
const
  Zehn: word = 10;
  Ausgabe: array[0..5] of char = '';
asm
     PUSH AX
     PUSH BX
     PUSH DX
     PUSH DI
@@1: xor dx,dx
     div zehn
     or dl,30h
     MOV DS:[BX+(OFFSET Ausgabe-1)],DL
     dec bx
     jnz @@1
     POP DI
     POP DX
     POP BX
     PUSH BX
     MOV CX,BX
     MOV BX,1
@@2: MOV DL,DS:[BX+(OFFSET Ausgabe-1)]
     MOV AL,DL
     STOSB
     INC BX
     LOOP @@2
     POP BX
     POP AX
end;
 
procedure GetDateText(Day, Month, Year: word; var Rslt: string);
begin
  asm
    LES  DI,DWORD PTR Rslt
    CLD
    INC  DI
    { Tag }
    MOV  AX,Day
    MOV  BX,2
    CALL AXToString
    { Punkt }
    MOV AL,'.'
    STOSB
    { Monat }
    MOV  AX,Month
    MOV  BX,2
    CALL AXtoString
    { Punkt }
    MOV AL,'.'
    STOSB
    JMP @@4
@@4:{ Jahr }
    MOV  AX,Year
    MOV  BX,4
    CALL AXtoString
    { Gesamtlänge bestimmen }
    MOV  AX,DI
    LES  DI,DWORD PTR Rslt
    SUB  AX,DI
    DEC  AL
    STOSB
  end;
end;
 
function GetDateTextF(Day, Month, Year: word): string;
begin
  asm
    LES  DI,@Result
    CLD
    INC  DI
    { Tag }
    MOV  AX,Day
    MOV  BX,2
    CALL AXToString
    { Punkt }
    MOV AL,'.'
    STOSB
    { Monat }
    MOV  AX,Month
    MOV  BX,2
    CALL AXtoString
    { Punkt }
    MOV AL,'.'
    STOSB
    JMP @@4
@@4:{ Jahr }
    MOV  AX,Year
    MOV  BX,4
    CALL AXtoString
    { Gesamtlänge bestimmen }
    MOV  AX,DI
    LES  DI,@Result
    SUB  AX,DI
    DEC  AL
    STOSB
  end;
end;

Anschließend der gesamte Code. Man möge beachten, das das ganze bereits 12 Jahre her ist. :)

Dies ist Programmcode von 1996. Er dient an dieser Stelle rein dokumentarischen Zwecken und gehört zu diesem Artikel. Allerdings gibt es eine gesundheitliche Einrichtung in diesem Land, die noch heute mit Programmen (ebenfalls von 1995-1997) arbeitet, die diesen Code verwenden ;) .

:released under BSD Licence:

(**************************************************************************)
(*                                                                        *)
(*   Unit Dates (urspr�nglich f�r Klinik.EXE)                             *)
(*                                                                        *)
(*   - written by H. H�bel 04/1996 -                                      *)
(*                                                                        *)
(*   (C), (P) 1996 S.O.L.Productions/ruLe_eXcess & H. H�bel               *)
(*                                                                        *)
(**************************************************************************)
(*                                                                        *)
(*  Diese Unit enth�lt Konstanten, Typen, Objekte und Methoden f�r        *)
(*  numerische Datumsberechnungen.                                        *)
(*                                                                        *)
(**************************************************************************)
(*                                                                        *)
(*  Stream registration records:  ObjType $1005 - $1006                   *)
(*                                                                        *)
(**************************************************************************)
 
unit Dates;
 
{$F+,O+,I-,X+,S-,V-,A-}
{$C Fixed Preload Permanent}
 
interface
 
{$IFNDEF Windows}
uses Dos, Drivers, Objects, Memory;
{$ELSE}
uses WinDos, Objects;
{$ENDIF}
 
{$IFDEF Windows}
 
Type
 
  TByteSet = set of byte;
 
{$ENDIF}
 
{ Konstanten }
 
const
 
{maximale Anzahl der Tage eines Monats}
  MaxDayInMonth = 31;
 
{maximile Anzahl der Tage einer Zeitspanne (TDateDelta) }
  MaxDayDelta = 365;
 
{ Sortier-Konstanten f�r TDateCollection }
  DaySort = 0;
  MonthSort = 1;
  YearSort = 2;
 
{ Maximale Anzahl der Tage eines Monats - ohne Schaltjahr }
 
  MaxDayOfMonth: array[1..12] of byte =
    (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
 
  DayStr: array[0..6] of String[10] = (
    'Sonntag',
    'Montag',
    'Dienstag',
    'Mittwoch',
    'Donnerstag',
    'Freitag',
    'Samstag');
 
 
  Day2Str: array[0..6] of string[2] = (
    'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa');
 
  MonthStr: array[1..12] of string[9] = (
    'Januar',
    'Februar',
    'M�rz',
    'April',
    'Mai',
    'Juni',
    'Juli',
    'August',
    'September',
    'Oktober',
    'November',
    'Dezember');
 
  Month3Str: array[1..12] of string[3] = (
    'Jan',
    'Feb',
    'M�r',
    'Apr',
    'Mai',
    'Jun',
    'Jul',
    'Aug',
    'Sep',
    'Okt',
    'Nov',
    'Dez');
 
  daySunday = 0;
  dayMonday = 1;
  dayTuesday = 2;
  dayWednesday = 3;
  dayThursday = 4;
  dayFriday = 5;
  daySaturday = 6;
 
  dayWeekEnd = [daySaturday, daySunday];
 
type
  TMon = (monCurrent, monJan, monFeb, monMar, monApr, monMai, monJun,
    monJul, monAug, monSep, monOct, monNov, monDec);
 
{ Typen }
 
type
 
{ Result:Valid-Methode von TDateItem, je nach dem welches Feld ung�ltig ist}
  TDateError = (dtsOK, dtsDay, dtsMonth, dtsYear);
 
{ Typen f�r zeitliche Daten }
  TDayType = 1..MaxDayInMonth;  {1..31}
  TMonthType = 1..12;
  TYearType = 1900..2060;
 
  PLongDate = ^TLongDate;
  TLongDate = object
    Day, Month, Year: LongInt;
    procedure AssignToday;
    function Equals(ADate: TLongDate): boolean;
    procedure GetNextDay(var ADate: TLongDate);
    { gibt den Vormat zur�ck }
    procedure GetPreMonth(var D: TLongDate);
    function GetText: string;
    end;
 
  PLongWeekDayDate = ^TLongWeekDayDate;
  TLongWeekDayDate = object
    WeekDay: PString;
    Day, Month, Year: LongInt;
    end;
 
{ record f�r einen Monat und ein Jahr }
  TListDate = record
    Month: byte;
    Year: word;
    end;
 
  {TDateFormatRec }
  {f�r FormatStr-Anweisungen }
  TDateFormatRec = record
    DOW: PString;
    Day, Month, Year: longint;
    end;
 
{ TDateItem }
{ Object f�r Speicherung von einem zeitl. Datum}
  PDateItem = ^TDateItem;
  TDateItem = object
    Day: TDayType;
    Month: TMonthType;
    Year: TYearType;
    procedure Assign(ADay, AMonth, AYear: word);
    { setzt die Felder auf <heute> }
    procedure AssignToday;
    { setzt die Felder Day, Month, Year auf die Werte in D }
    procedure CopyFrom(D: TDateItem);
    { setzt Felder aus LongDate }
    procedure CopyFromLong(D: TLongDate);
    { belegt einen TLongDate-Record mit aktuellen Werten }
    procedure CopyToLong(var D: TLongDate);
    { gibt true zur�ck, wenn die Werte aus D diesen Werten entsprechen }
    function Equals(D: TDateItem): boolean;
    { berechnet den Wochentag. Result ist daySunday...daySaturday }
    function GetDayOfWeek: byte;
    { berechnet den Tag vor dem Tag in diesem Object }
    procedure GetLastDay(var D: TDateItem);
    { gibt den vollst�ndigen Monat und das Jahr aus }
    function GetMYText: string;
    { berechnet den x. Tag ab dem Tag in diesem Object }
    procedure GetNextXDate(Days: integer; var D: TDateItem);
    { berechnet den n�chsten Tag }
    procedure GetNextDay(var D: TDateItem);
    { berechnet den n�chsten Monat }
    procedure GetNextMonth(var D: TDateItem);
    { gibt den Vormat zur�ck }
    procedure GetPreMonth(var D: TDateItem);
    { gibt das Datum in Form DAY.MONTH.YEAR }
    function GetText: string;
    { gibt das Datum in Form WEEKDAY(2 Chars), DAY.MONTH.YEAR }
    function GetW2Text: string;
    { gibt das Datum normal aus, jedoch Monat ausgeschrieben }
    function GetNMText: string;
    { pr�ft, ob der Tag in ADate der Vortag zu self ist }
    function IsPreDay(ADate: TDateItem): boolean;
    function IsToday: boolean;
    { z�hlt Tage vom akt. Datum r�ckw�rts, bis er auf einen Sonntag st��t }
    { und speichert diesen in Date }
    { ist self bereits ein Sonntag, so wird zum Sonntag davor gesprungen }
    procedure LastWeekEnd(var Date: TDateItem);
    procedure Load(var S: TStream);
    { liefert den letzten Tag des Monats Month,Year zur�ck }
    function MaxDayMonth: byte;
    { speichert die entsprechenden Daten in einen Record TDateFormatRec }
    procedure PutFormatRec(var Rec: TDateFormatRec);
    procedure Reset(ADay, AMonth, AYear: word);
    procedure Store(var S: TStream);
    function Valid(const Inform: boolean): TDateError;
    end;
 
type
  TWeekItem = object
    Monday: TDateItem;
    Sunday: TDateItem;
    end;
 
  TWeeks = array[1..7] of TWeekItem;
 
  PMonthItem = ^TMonthItem;
  TMonthItem = object
    Month: word;
    Year: word;
    WeekCount: byte;
    Weeks: TWeeks;
    procedure Assign(AMonth, AYear: word);
    procedure CalcWeeks;
    function DayIsWhichWeek(const Day: word): byte;
    function FirstDayOfMonth: byte;
    function LastDay: byte;
    function LastDayOfMonth: byte;
  private
    procedure CalcFirstWeek;
    procedure CalcNextWeek(First: TWeekItem; var Next: TWeekItem);
    end;
 
 
  { sortierte und zu sortierende Collection }
 
  PSortCollection = ^TSortCollection;
  TSortCollection = object(TSortedCollection)
    TypeOfSort: byte;
    constructor Init(ALimit, ADelta: integer; SortType: byte);
    constructor Load(var S: TStream);
    procedure SortFor(ATypeOfSort: byte); virtual;
    procedure Store(var S: TStream);
    end;
 
 
{ TDateList }
{ Object zur Speicherung von einer Liste mit zeitlichen Daten }
  PDateList = ^TDateList;
  TDateList = object(TSortCollection)
    constructor Init;
    function Compare(Key1, Key2: Pointer): integer; virtual;
    function CompareList(AList: PDateList; var FoundItem: PDateItem;
      var FoundIndex: word): boolean; virtual;
    procedure DeleteOldDates(AListDate: TListDate);
    { �berpr�ft, ob das Item bereits in der Liste existiert und gibt }
    { den Index des gefunden Items wieder oder -1 nei nichtfinden}
    function EqualItem(Item: PDateItem; var Index: integer): boolean;
    { Error-Prozedur }
    procedure Error(Code, Info: integer); virtual;
    { gibt true zur�ck, wenn das DateItem bereits in der Liste existiert }
    function FoundSame(Item: PDateItem): boolean; virtual;
    procedure Insert(Item: Pointer); virtual;
    function KeyOf(Item: Pointer): Pointer; virtual;
    end;
 
{ ---- TDateDelta --------------------------------------------------------- }
{ - Datenrecord f�r TDateDeltaInput-Dialog.                               - }
{ - Enth�lt ein Anfangs- und ein End-Datum eines Zeit-Bereiches.          - }
{ ------------------------------------------------------------------------- }
 
  PDateDelta = ^TDateDelta;
  TDateDelta = record
    { Anfangs- und Enddatum }
    Min, Max: TDateItem;
    { MaxUse: true, wenn eine Zeitspanne vorliegt. }
    {         false, wenn nur ein einzelnes Datum. }
    MaxUse: boolean;
    end;
 
{ ----- TDateDeltaList ----------------------------------------------------- }
{ - Collection der TDateDelta-Records                                      - }
{ - Diese List wird innerhalb des TDateConsiderDialogs verwaltet und       - }
{ - aus einer DateList heraus erstellt und anschlie�end darin zur�ckverwandelt
{ -------------------------------------------------------------------------- }
 
  PDateDeltaList = ^TDateDeltaList;
  TDateDeltaList = object(TCollection)
    { gibt true zur�ck, wenn einige der Daten in DateDelta bereits }
    { vorhanden sind. Control ist der Zeiger auf eine Routine, die }
    { eine Nachricht auf dem Bildschirm ausgibt, da� dieses Datum  }
    { bereits existiert. Diese Prozedur mu� folgendes Format haben:}
    { procedure(Info: Pointer; Delta: WordBool                     }
    { Info ist ein Pointer auf ein DateDelta-Record, insofern      }
    { Delta true ist, andernfalls ein Pointer auf ein DateItem-    }
    { Record. }
    function DateDeltaExists(DateDelta: PDateDelta; Control: Pointer): boolean;
    { gibt den Speicherplatz eines DateDelta-Records wieder frei }
    procedure FreeItem(Item: Pointer); virtual;
    end;
 
 
  { TimeItem }
 
  PTimeItem = ^TTimeItem;
  TTimeItem = object
    S, M, H: byte;
    procedure Assign(const AHour, AMin, ASec: byte);
    procedure AssignWord(const AHour, AMin, ASec: word);
    { setzt Werte auf aktuelle Zeit }
    procedure AssignNow;
    procedure CopyFrom(const Time: TTimeItem);
    function Equals(const Time: TTimeItem): boolean;
    procedure Load(var Stream: TStream);
    procedure Store(var Stream: TStream);
    end;
 
  TDateLoopProc = function(var CurDate: TDateItem; Data: Pointer): boolean;
 
  { TDateLoop }
  { Ein Object zum Durchlaufen steigender oder fallender Daten }
  { mit gezieltem Abbruch der Schleife }
  PDateLoop = ^TDateLoop;
  TDateLoop = object
    Start: TDateItem;
    { Setzt das Startdatum }
    procedure Assign(AStart: TDateItem);
    procedure AssignLong(AStart: TLongDate);
    { function Loop }
    { Durchl�uft alle Daten von Start in Richtung Step
      (-1 = ein Tag abw�rts, +2 = zwei tage vorw�rts
      Hierzu wird eine Funktion vom Typ TDateLoopProc aufgerufen.
      Dieser Funktion wird der Daten-Record in Data �bergeben und das akt.
      Datum. Gibt diese Funktion true zur�ck, wird die Loop abgebrochen.
      Ansonsten wird die Schleife solange durchlaufen, bis Ende erreicht ist.
      Ende enth�lt in jedem Fall das zuletzt gepr�fte Datum.
      Das Erbegnis von Loop_Date ist das letzte Erbegnis der Funktion Proc.
      D.h. wird die Loop vorzeitig abgebrochen, gibt Loop_Date true zur�ck.
      Ist First true, so wird das erste Datum in Start mitgez�hlt. }
    function Loop(var Ende: TDateItem; Proc: TDateLoopProc; Data: Pointer;
      Step: longint; First: boolean): boolean;
    { Loop_WE }
    { Wie Loop, jedoch wird Proc nur bei den in WeekDays befindlichen }
    { Wochentagen aufgerufen }
    function Loop_WE(var Ende: TDateItem; Proc: TDateLoopProc; Data: Pointer;
      Step: longint; First: boolean; Weekdays: TByteSet): boolean;
    end;
 
  { Geburtsdatum }
  PBirthday = ^TBirthday;
  TBirthday = object
    private
      FData: word;
    public
      procedure Assign(ADay, AMonth: word);
      procedure AssignDate(ADate: TDateItem);
      function Day: word;
      function Equals(ADay, AMonth: word): boolean;
      function EqualsDate(ADate: TDateItem): boolean;
      function EqualsLong(ADate: TLongDate): boolean;
      function Month: word;
      procedure Load(var S: TStream);
      procedure Store(var S: TStream);
      end;
 
{ berechnet Wochentag eines Datums }
function DayOfWeek(Day, Month, Year: Integer) : Integer;
 
{ liefert die maximale Anzahl von Tagen eines Monats incl. Schaltjahr }
function GetMaxDayOfMonth(Month, Year: word): byte;
 
{ ----- CompareDateItem -------------------------------------------------- }
{ - vergleicht ein Anfangs- und ein Enddatum. R�ckgabewerte:             - }
{     if Min < Max then C := -1                                          - }
{     if Min = Max then C := 0                                           - }
{     if Min > Max then C := 1                                           - }
{ ------------------------------------------------------------------------ }
function CompareDateItem(Min, Max: TDateItem): integer;
 
{ errechnet den n�chsten Tag des gegebenen Datums }
procedure GetNextDateItem(Date: TDateItem; var Next: TDateItem);
 
{ berechnet die Anzahl Tage zwischen zwei Daten }
function CalcDayDelta(Min, Max: TDateItem): LongInt;
 
{ berechnet die Anzahl ganzer Wochen zwischen zwei Daten }
function CalcWeeks(Min, Max: TDateItem): integer;
 
{$IFNDEF Windows}
{ true, wenn Date zwischen einschl. Test1 und einschl. Test2 liegt }
function DateItem_In_TwoDates(Date, Test1, Test2: PDateItem): boolean;
{$ENDIF}
 
{ Bestimmt die Anzahl der L�cken in Datums-Liste }
{ Als L�cke gilt mindestens ein fehlender Tag in der zeitl. Reihenfolge }
function CalcDeltaDateItems(List: PDateList): integer;
 
{ Initialisiert leere TDateDeltaList anhand der Anzahl der Datums-L�cken }
{ der �bergebenen DateList. }
procedure InitDateDeltaList(DateList: PDateList; var Delta: PDateDeltaList);
 
{$IFNDEF Windows}
{ f�llt die DateDeltaList anhand der Daten in DateList }
procedure FillDateDeltaList(DateList: PDateList; var Delta: PDateDeltaList);
{$ENDIF}
 
{ f�llt eine DateList von den Daten einer DateDeltaList }
procedure ConvertDeltaToDateList(Delta: PDateDeltaList; var DateList: PDateList);
 
{$IFNDEF Windows}
{ Erzeugt neues DateDeltaItem auf dem Heap und gibt Pointer zur�ck }
function NewDateDeltaItem(Anfang, Ende: TDateItem): PDateDelta;
{$ENDIF}
 
{ Gibt den Speicher eines DateDeltaItems frei }
procedure DisposeDateDeltaItem(var Item: PDateDelta);
 
{$IFNDEF Windows}
{�berpr�ft, ob �bergebenes DateItem in einer DateDelta-Zeitspanne enthalten}
function DateItemInDeltaExists(DateItem: PDateItem; Delta: PDateDelta): boolean;
{$ENDIF}
 
{ Date-Error }
 
type
  TDatesErrorFunc = function(Code: integer; Info: Pointer): word;
 
function DefDatesErrorFunc(Code: integer; Info: Pointer): word; far;
function StdDatesErrorFunc(Code: integer; Info: Pointer): word; far;
 
const
  DatesError: TDatesErrorFunc = DefDatesErrorFunc;
 
{ stream registration }
 
procedure RegisterDates;
 
{ stream registration records }
 
const
 
  RDateList: TStreamRec = (
    ObjType: $0FFF;
    VmtLink: Ofs(TypeOf(TDateList)^);
    Load: @TDateList.Load;
    Store: @TDateList.Store);
 
implementation
 
{$IFDEF WithTVTools}
uses App, Views, GmMsgBox, AppTools;
{$ENDIF}
 
function DayOfWeek(Day, Month, Year: Integer) : Integer;
var
  century, yr, dw: Integer;
begin
  if Month < 3 then
  begin
    Inc(Month, 10);
    Dec(Year);
  end
  else
     Dec(Month, 2);
  century := Year div 100;
  yr := year mod 100;
  dw := (((26 * month - 2) div 10) + day + yr + (yr shr 2) +
    (century shr 2) - (2 * century)) mod 7;
  if dw < 0 then DayOfWeek := dw + 7
  else DayOfWeek := dw;
end;
 
{ TLongDate }
 
procedure TLongDate.AssignToday;
var
  DOW: word;
begin
  Day := 0;
  Month := 0;
  Year := 0;
  GetDate(word(Year), word(Month), word(day), DOW);
end;
 
function TLongDate.Equals(ADate: TLongDate): boolean;
begin
  Equals := (ADate.Day = Day) and (ADate.Month = Month) and
    (ADate.Year = Year);
end;
 
procedure TLongDate.GetNextDay(var ADate: TLongDate);
begin
  ADate := self;
  with ADate do
  begin
    if Day = GetMaxDayOfMonth(Month, Year) then
    begin
      Day := 1;
      if Month < 12 then Inc(Month) else
      begin
        Month := 1;
        Inc(Year);
      end;
    end
     else Inc(Day);
  end;
end;
 
procedure TLongDate.GetPreMonth(var D: TLongDate);
begin
  Move(self, D, SizeOf(TLongDate));
  if D.Month > 1 then Dec(D.Month) else
  begin
    Dec(D.Year);
    D.Month := 12;
  end;
end;
 
function TLongDate.GetText: string;
const
  F = '%02d.%02d.%d';
var
  S: string;
begin
  {$IFNDEF Windows}
  FormatStr(S, F, Day);
  {$ELSE}
  S := '';
  {$ENDIF}
  GetText := S;
end;
 
{ TDateItem }
procedure TDateItem.Assign(ADay, AMonth, AYear: word); assembler;
asm
        LES     DI,Self
        CLD
        MOV     AX,ADay
        STOSB
        MOV     AX,AMonth
        STOSB
        MOV     AX,AYear
        STOSW
end;
 
procedure TDateItem.AssignToday;
var
  D, M, Y, DOW: word;
begin
  GetDate(Y, M, D, DOW);
  Assign(D, M, Y);
end;
 
procedure TDateItem.CopyFrom(D: TDateItem);
{asm
        PUSH    DS
        LDS     SI,D
        LES     DI,Self
        CLD
        MOVSB
        MOVSB
        MOVSW
        POP     DS}
begin
  Move(D, self, SizeOf(TDateItem));
end;
 
procedure TDateItem.CopyFromLong(D: TLongDate);
begin
  Assign(D.Day, D.Month, D.Year);
end;
 
procedure TDateItem.CopyToLong(var D: TLongDate);
begin
  D.Day := Day;
  D.Month := Month;
  D.Year := Year;
end;
 
function TDateItem.Equals(D: TDateItem): boolean;
begin
  Equals := ((Day = D.Day) and (Month = D.Month) and (Year = D.Year));
end;
 
function TDateItem.GetDayOfWeek: byte; assembler;
asm
        PUSH    DS
        LDS     SI,Self
        MOV     AH,0
        LODSB
        PUSH    AX
        LODSB
        PUSH    AX
        LODSW
        PUSH    AX
        CALL    DayOfWeek
        POP     DS
end;
 
procedure TDateItem.GetLastDay(var D: TDateItem);
begin
  D.Reset(Day, Month, Year);
  with D do
  begin
    if Day > 1 then Dec(Day) else
    begin
      if Month > 1 then
      begin
        Dec(Month);
        Day := GetMaxDayOfMonth(Month, Year);
      end
       else
      begin
        Dec(Year);
        Month := 12;
        Day := GetMaxDayOfMonth(Month, Year);
      end;
    end;
  end;
end;
 
{ OLD-ASM GetLastDay }
{procedure TDateItem.GetLastDay(var D: TDateItem);
begin
  D.Reset(Day, Month, Year);
  with D do
  begin
    if Day > 1 then Dec(Day) else
    begin
      if Month > 1 then
      asm
        DEC     Byte Ptr Month
        PUSH    Word Ptr Year
        MOV     AH, Month
        MOV     AL, 0
        PUSH    AX
        CALL    GetMaxDayOfMonth
        MOV     Byte Ptr Day,AL
      end
       else
      begin
        asm
          DEC   Word Ptr Year
          MOV   Byte Ptr Month,12;
          PUSH  Word Ptr Year
          MOV     AL, Month
          MOV     AH, 0
          PUSH    AX
          CALL  GetMaxDayOfMonth
          MOV   Byte Ptr Day,AL
          end;
      end;
    end;
  end;
end;}
 
function TDateItem.GetMYText: string;
var
  W: string[4];
begin
  Str(Year, W);
  GetMYText := concat(MonthStr[Month], #32, W);
end;
 
procedure TDateItem.GetNextXDate(Days: integer; var D: TDateItem);
begin
  D.CopyFrom(self);
  if Days > 0 then
  repeat
    D.GetNextDay(D);
    Dec(Days);
  until Days = 0;
  if Days < 0 then
  repeat
    D.GetLastDay(D);
    Inc(Days);
  until Days = 0;
end;
 
procedure TDateItem.GetNextDay(var D: TDateItem);
begin
  GetNextDateItem(self, D);
end;
 
procedure TDateItem.GetNextMonth(var D: TDateItem);
begin
  D.CopyFrom(self);
  if D.Month < 12 then Inc(D.Month) else
  begin
    Inc(D.Year);
    D.Month := 1;
  end;
end;
 
procedure TDateItem.GetPreMonth(var D: TDateItem);
begin
  D.CopyFrom(self);
  if D.Month > 1 then Dec(D.Month) else
  begin
    Dec(D.Year);
    D.Month := 12;
  end;
end;
 
{ For GetText }
 
procedure AXtoString; assembler;
const
  Zehn: word = 10;
  Ausgabe: array[0..5] of char = '';
asm
     PUSH AX
     PUSH BX
     PUSH DX
     PUSH DI
@@1: xor dx,dx
     div zehn
     or dl,30h
     MOV DS:[BX+(OFFSET Ausgabe-1)],DL
     dec bx
     jnz @@1
     POP DI
     POP DX
     POP BX
     PUSH BX
     MOV CX,BX
     MOV BX,1
@@2: MOV DL,DS:[BX+(OFFSET Ausgabe-1)]
     MOV AL,DL
     STOSB
     INC BX
     LOOP @@2
     POP BX
     POP AX
end;
 
procedure GetDateText(Day, Month, Year: word; var Rslt: string);
begin
  asm
    LES  DI,DWORD PTR Rslt
    CLD
    INC  DI
    { Tag }
    MOV  AX,Day
    MOV  BX,2
    CALL AXToString
    { Punkt }
    MOV AL,'.'
    STOSB
    { Monat }
    MOV  AX,Month
    MOV  BX,2
    CALL AXtoString
    { Punkt }
    MOV AL,'.'
    STOSB
    JMP @@4
@@4:{ Jahr }
    MOV  AX,Year
    MOV  BX,4
    CALL AXtoString
    { Gesamtl�nge bestimmen }
    MOV  AX,DI
    LES  DI,DWORD PTR Rslt
    SUB  AX,DI
    DEC  AL
    STOSB
  end;
end;
 
function GetDateTextF(Day, Month, Year: word): string;
begin
  asm
    LES  DI,@Result
    CLD
    INC  DI
    { Tag }
    MOV  AX,Day
    MOV  BX,2
    CALL AXToString
    { Punkt }
    MOV AL,'.'
    STOSB
    { Monat }
    MOV  AX,Month
    MOV  BX,2
    CALL AXtoString
    { Punkt }
    MOV AL,'.'
    STOSB
    JMP @@4
@@4:{ Jahr }
    MOV  AX,Year
    MOV  BX,4
    CALL AXtoString
    { Gesamtl�nge bestimmen }
    MOV  AX,DI
    LES  DI,@Result
    SUB  AX,DI
    DEC  AL
    STOSB
  end;
end;
 
function TDateItem.GetText: string;
begin
  GetText := GetDateTextF(Day, Month, Year);
end;
 
function TDateItem.GetW2Text: string;
begin
  GetW2Text := concat(Day2Str[GetDayOfWeek], ', ',
    GetDateTextF(Day, Month, Year));
end;
 
function TDateItem.GetNMText: string;
const
  F = '%02d. %s %d';
var
  Data: record
    Day: longint;
    Month: PString;
    Year: longint;
    end;
  R: string;
begin
  Data.Day := Day;
  Data.Month := @MonthStr[Month];
  Data.Year := Year;
  {$IFNDEF Windows}
  FormatStr(R, F, Data);
  {$ELSE}
  R := '';
  {$ENDIF}
  GetNMText := R;
end;
 
function TDateItem.IsPreDay(ADate: TDateItem): boolean;
var
  V: TDateItem;
begin
  GetLastDay(V);
  IsPreDay := ADate.Equals(V);
end;
 
function TDateItem.IsToday: boolean;
var
  D, M, Y, DOW: word;
begin
  GetDate(Y, M, D, DOW);
  IsToday := (D = Day) and (M = Month) and (Y = Year)
end;
 
procedure TDateItem.LastWeekEnd(var Date: TDateItem);
begin
  Date.CopyFrom(self);
  if Date.GetDayOfWeek = daySunday then Date.GetLastDay(Date);
  while not (Date.GetDayOfWeek = daySunday) do
    Date.GetLastDay(Date);
end;
 
procedure TDateItem.Load(var S: TStream);
begin
  S.Read(Day, SizeOf(TDayType)*2 + SizeOf(TYearType));
end;
 
function TDateItem.MaxDayMonth: byte;
begin
  MaxDayMonth := GetMaxDayOfMonth(Month, Year);
end;
 
procedure TDateItem.PutFormatRec(var Rec: TDateFormatRec);
begin
  Rec.DOW := @Day2Str[GetDayOfWeek];
  Rec.Day := Day;
  Rec.Month := Month;
  Rec.Year := Year;
end;
 
procedure TDateItem.Reset(ADay, AMonth, AYear: word); assembler;
asm
        LES     DI,Self
        CLD
        MOV     AX,ADay
        STOSB
        MOV     AX,AMonth
        STOSB
        MOV     AX,AYear
        STOSW
        JMP     @@1
        db  ' ***** '
        db  '    copyright and written by hagen huebel in 1995,1996,1997    '
        db  ' ***** '
        @@1:
end;
 
procedure TDateItem.Store(var S: TStream);
begin
  S.Write(Day, SizeOf(TDayType)*2 + SizeOf(TYearType));
end;
 
function TDateItem.Valid(const Inform: boolean): TDateError;
 
 procedure DoInf(const Art: TDateError; const Min, Max: word);
 const
   Msg = 'Die Eingabe f�r %s mu� sich im Bereich zwischen %d und %d befinden';
   FeldName: array[dtsDay..dtsYear] of string[6] =
      ('Tag', 'Monat', 'Jahr');
 var
   Str: PString;
   AMin, AMax: longint;
 begin
   {$IFDEF WithTVTools}
   Str := @FeldName[Art];
   AMin := Min;
   AMax := Max;
   MessageBox(Msg, @Str, mfError or mfOKButton);
   {$ENDIF}
 end;
 
var
  V: TDateError;
begin
  V := dtsOK;
  if not (Day in [1..MaxDayInMonth]) then V := dtsDay
  else
    if not (Month in [1..12]) then V := dtsMonth
    else
      if (Year < low(TYearType)) or (Year > high(TYearType)) then
         V := dtsYear;
  Valid := V;
  if Inform then
  case V of
    dtsDay: DoInf(dtsDay, 1, MaxDayInMonth);
    dtsMonth: DoInf(dtsMonth, 1, 12);
    dtsYear: DoInf(dtsYear, low(TYearType), high(TYearType))
    end;
end;
 
{ TMonthItem }
 
procedure TMonthItem.Assign(AMonth, AYear: word);
begin
  Month := AMonth;
  Year := AYear;
  CalcWeeks;
end;
 
procedure TMonthItem.CalcFirstWeek;
var
  FirstWeekDate: TDateItem;
  LastWeekDate: TDateItem;
begin
  { erster Wochentag }
  FirstWeekDate.Month := Month;
  FirstWeekDate.Year := Year;
  FirstWeekDate.Day := 1;
  if FirstDayOfMonth <> dayMonday then
  repeat
    FirstWeekDate.GetLastDay(FirstWeekDate);
  until FirstWeekDate.GetDayOfWeek = dayMonday;
  Weeks[1].Monday.CopyFrom(FirstWeekDate);
  { letzter Wochentag }
  LastWeekDate.Month := Month;
  LastWeekDate.Year := Year;
  LastWeekDate.Day := 1;
  if LastWeekDate.GetDayOfWeek <> daySunday then
  repeat
    LastWeekDate.GetNextDay(LastWeekDate);
  until LastWeekDate.GetDayOfWeek = daySunday;
  Weeks[1].Sunday.CopyFrom(LastWeekDate);
  { erste Woche }
  WeekCount := 1;
end;
 
procedure TMonthItem.CalcNextWeek(First: TWeekItem; var Next: TWeekItem);
begin
  First.Monday.GetNextXDate(7, Next.Monday);
  First.Sunday.GetNextXDate(7, Next.Sunday);
  Inc(WeekCount);
end;
 
procedure TMonthItem.CalcWeeks;
var
  W: byte;
  Last: boolean;
begin
  { Wochen l�schen }
  WeekCount := 0;
  FillChar(Weeks, SizeOf(Weeks), #0);
  { erste Woche berechnen }
  CalcFirstWeek;
  { n�chste Wochen berechnen }
  W := 2;
  repeat
    CalcNextWeek(Weeks[W-1], Weeks[W]);
    Inc(W);
    with Weeks[WeekCount] do
    Last := (Sunday.Month <> Self.Month) or
      (Sunday.Day = Sunday.MaxDayMonth);
  until Last;
end;
 
function TMonthItem.DayIsWhichWeek(const Day: word): byte;
var
  I: byte;
  Date: TDateItem;
begin
  {$IFNDEF Windows}
  Date.Assign(Day, Month, Year);
  DayIsWhichWeek := 255;
  For I := Low(Weeks) to WeekCount do
    if DateItem_In_TwoDates(@Date, @Weeks[I].Monday, @Weeks[I].Sunday) then
    begin
      DayIsWhichWeek := I;
      Break;
    end;
  {$ENDIF}
end;
 
function TMonthItem.FirstDayOfMonth: byte;
begin
  FirstDayOfMonth := DayOfWeek(1, Month, Year);
end;
 
function TMonthItem.LastDay: byte;
begin
  LastDay := GetMaxDayOfMonth(Month, Year);
end;
 
function TMonthItem.LastDayOfMonth: byte;
begin
  LastDayOfMonth := DayOfWeek(LastDay, Month, Year);
end;
 
{ - TSortCollectionn - }
 
constructor TSortCollection.Init(ALimit, ADelta: integer; SortType: byte);
begin
  Inherited Init(ALimit, ADelta);
  TypeOfSort := SortType;
end;
 
constructor TSortCollection.Load(var S: TStream);
begin
  inherited Load(S);
  S.Read(TypeOfSort, 1);
end;
 
procedure TSortCollection.SortFor(ATypeOfSort: byte);
var
  N, C, Index1, Index2: integer;
  Dump: Pointer;
begin
  TypeOfSort := ATypeOfSort;
  N := 2;
  if Count < = 1 then exit;
  For Index2 := 0 to Count - 1 do
  begin
    for Index1 := 0 to Count - N do
    begin
      C := Compare(KeyOf(Items^[Index1]), KeyOf(Items^[Index1 + 1]));
      if C = 1 then
      begin
        Dump := Items^[Index1];
        Items^[Index1] := Items^[Index1 + 1];
        Items^[Index1 + 1] := Dump;
      end;
    end;
    inc(N);
  end;
end;
 
procedure TSortCollection.Store(var S: TStream);
begin
  inherited Store(S);
  S.Write(TypeOfSort, 1);
end;
 
 
{ TDateList }
constructor TDateList.Init;
begin
  inherited Init(1, 2, YearSort);
  Duplicates := true;
end;
 
function TDateList.Compare(Key1, Key2: Pointer): integer;
 
 function WComp(const W1, W2: word): integer;
 begin
   if W1 < W2 then WComp := -1
   else
     if W1 > W2 then WComp := 1
     else
       WComp := 0;
 end;
 
var
  C: integer;
  Item1, Item2: PDateItem;
  D1, D2, M1, M2: byte;
  J1, J2: word;
begin
  Item1 := PDateItem(Key1);
  Item2 := PDateItem(Key2);
  D1 := Item1^.Day;    D2 := Item2^.Day;
  M1 := Item1^.Month;  M2 := Item2^.Month;
  J1 := Item1^.Year;   J2 := Item2^.Year;
  case TypeOfSort of
    YearSort:
      begin
        C := WComp(J1, J2);
        if C = 0 then
        begin
          C := WComp(M1, M2);
          if C = 0 then C := WComp(D1, D2);
        end;
      end;
    MonthSort:
      begin
        C := WComp(M1, M2);
        if C = 0 then
        begin
          C := WComp(J1, J2);
          if C = 0 then C := WComp(D1, D2);
        end;
      end;
   DaySort:
      begin
        C := WComp(D1, D2);
        if C = 0 then
        begin
          C := WComp(J1, J2);
          if C = 0 then C := WComp(M1, M2);
        end;
      end;
    end;
  Compare := C;
end;
 
function TDateList.CompareList(AList: PDateList; var FoundItem: PDateItem;
  var FoundIndex: word): boolean;
 
 function DoFind(Item: PDateItem): boolean; far;
 begin
   DoFind := AList^.FoundSame(Item);
 end;
 
var
  Found: boolean;
begin
  FoundIndex := $FFFF;
  FoundItem := FirstThat(@DoFind);
  Found := FoundItem <> nil;
  if Found then FoundIndex := IndexOf(FoundItem);
  CompareList := Found;
end;
 
procedure TDateList.DeleteOldDates(AListDate: TListDate);
 
 function GetLowDate: PDateItem;
 
   function IsLow(P: PDateItem): boolean; far;
   begin
     IsLow := (P^.Year < = AListDate.Year) and (P^.Month < AListDate.Month);
   end;
 
 begin
   GetLowDate := FirstThat(@IsLow);
 end;
 
var
  LowDate: PDateItem;
begin
  repeat
    LowDate := GetLowDate;
    if LowDate <> nil then Free(LowDate);
  until LowDate = nil;
end;
 
{ TDateList.EqualItem }
function TDateList.EqualItem(Item: PDateItem; var Index: integer): boolean;
 
function Find(Date: PDateItem): boolean; far;
begin
  Find := Date^.Equals(Item^);
end;
 
var
  FoundItem: PDateItem;
begin
  FoundItem := FirstThat(@Find);
  Index := IndexOf(FoundItem);
  EqualItem := Index > -1;
end;
 
{ TDateList.Error }
procedure TDateList.Error(Code, Info: integer);
begin
  DatesError(coOverFlow, nil);
end;
 
{ TDateList.FoundSame }
function TDateList.FoundSame(Item: PDateItem): boolean;
 
 function DoFind(Test: PDateItem): boolean; far;
 begin
   DoFind := (Test^.Day = Item^.Day) and (Test^.Month = Item^.Month) and
      (Test^.Year = Item^.Year);
 end;
 
begin
  FoundSame := FirstThat(@DoFind) <> nil;
end;
 
procedure TDateList.Insert(Item: Pointer);
var
  Index: integer;
begin
  if Not EqualItem(Item, Index) then inherited Insert(Item)
  else FreeItem(Item);
end;
 
function TDateList.KeyOf(Item: Pointer): Pointer;
var
  Date: PDateItem;
begin
{  Date := PDateItem(Item);
  case TypeOfSort of
    DaySort: KeyOf := @Date^.Day;
    MonthSort: KeyOf := @Date^.Month;
    YearSort: KeyOf := @Date^.Year;
    end;}
  KeyOf := PDateItem(Item);
end;
 
{ TDateDeltaList }
 
function TDateDeltaList.DateDeltaExists(DateDelta: PDateDelta;
  Control: Pointer): boolean;
type
  TInfoProc = procedure(Info: Pointer; Delta: WordBool);
var
  Current: TDateItem;
  SearchItem: TDateItem;
  Found: boolean;
begin
  Found := false;
  if DateDelta <> nil then
  begin
    Current.Assign(0, 0, 0);
    SearchItem.Assign(0, 0, 0);
 
    Current.CopyFrom(DateDelta^.Min);
 
  end;
  DateDeltaExists := Found;
end;
 
procedure TDateDeltaList.FreeItem(Item: Pointer);
begin
  DisposeDateDeltaItem(PDateDelta(Item));
end;
 
{ TTimeItem }
 
procedure TTimeItem.Assign(const AHour, AMin, ASec: byte);
begin
  H := AHour;
  M := AMin;
  S := ASec;
end;
 
procedure TTimeItem.AssignWord(const AHour, AMin, ASec: word);
begin
  H := AHour;
  M := AMin;
  S := ASec;
end;
 
procedure TTimeItem.AssignNow;
var
  _h, _m, _s, s100: word;
begin
  GetTime(_H, _M, _S, S100);
  Assign(_H, _M, _S);
end;
 
procedure TTimeItem.CopyFrom(const Time: TTimeItem);
begin
  Assign(Time.H, Time.M, Time.S);
end;
 
function TTimeItem.Equals(const Time: TTimeItem): boolean;
begin
  Equals := (H = Time.H) and (M = Time.M) and (S = Time.S);
end;
 
procedure TTimeItem.Load(var Stream: TStream);
begin
  Stream.Read(S, 3);
end;
 
procedure TTimeItem.Store(var Stream: TStream);
begin
  Stream.Write(S, 3);
end;
 
 
{ TDateLoop }
 
procedure TDateLoop.Assign(AStart: TDateItem);
begin
  Start.CopyFrom(AStart);
end;
 
procedure TDateLoop.AssignLong(AStart: TLongDate);
begin
  Start.CopyFromLong(AStart);
end;
 
    { Durchl�uft alle Daten von Start in Richtung Step
      (-1 = ein Tag abw�rts, +2 = zwei tage vorw�rts
      Hierzu wird eine Funktion vom Typ TDateLoopProc aufgerufen.
      Dieser Funktion wird der Daten-Record in Data �bergeben und das akt.
      Datum. Gibt diese Funktion true zur�ck, wird die Loop abgebrochen.
      Ende enth�lt in jedem Fall das zuletzt gepr�fte Datum.
      Das Erbegnis von Loop_Date ist das letzte Erbegnis der Funktion Proc.
      D.h. wird die Loop vorzeitig abgebrochen, gibt Loop_Date true zur�ck }
function TDateLoop.Loop(var Ende: TDateItem; Proc: TDateLoopProc; Data: Pointer;
      Step: longint; First: boolean): boolean;
var
  Cur: TDateItem;
 
procedure GetNext;
begin
  if Step = 1 then Cur.GetNextDay(Cur) else
  if Step = -1 then Cur.GetLastDay(Cur) else
    Cur.GetNextXDate(Step, Cur);
end;
 
var
  Rslt: boolean;
  Max, Count: longint;
begin
  Cur.CopyFrom(Start);
  Max := CalcDayDelta(Start, Ende) div abs(Step) + 1;
  Count := 0;
  if not First then GetNext;
  repeat
    Rslt := Proc(Cur, Data);
    GetNext;
    Inc(Count);
  until Rslt or (Count >= Max);
  Ende := Cur;
  Loop := Rslt;
end;
 
function TDateLoop.Loop_WE(var Ende: TDateItem; Proc: TDateLoopProc; Data: Pointer;
      Step: longint; First: boolean; Weekdays: TByteSet): boolean;
var
  Cur: TDateItem;
 
procedure GetNext;
begin
  if Step = 1 then Cur.GetNextDay(Cur) else
  if Step = -1 then Cur.GetLastDay(Cur) else
    Cur.GetNextXDate(Step, Cur);
end;
 
var
  Rslt: boolean;
  Max, Count: longint;
begin
  Cur.CopyFrom(Start);
  Max := CalcDayDelta(Start, Ende) div abs(Step) + 1;
  Count := 0;
  Rslt := false;
  if not First then GetNext;
  repeat
    if Cur.GetDayOfWeek in WeekDays then
       Rslt := Proc(Cur, Data);
    GetNext;
    Inc(Count);
  until Rslt or (Count >= Max);
  Ende := Cur;
  Loop_WE := Rslt;
end;
 
{ TBirthday }
 
procedure TBirthday.Assign(ADay, AMonth: word);
begin
  WordRec(FData).Lo := ADay;
  WordRec(FData).Hi := AMonth;
end;
 
procedure TBirthday.AssignDate(ADate: TDateItem);
begin
  WordRec(FData).Lo := ADate.Day;
  WordRec(FData).Hi := ADate.Month;
end;
 
function TBirthday.Day: word;
begin
  Day := WordRec(FData).Lo;
end;
 
function TBirthday.Equals(ADay, AMonth: word): boolean;
begin
  Equals := (Day = ADay) and (Month = AMonth);
end;
 
function TBirthday.EqualsDate(ADate: TDateItem): boolean;
begin
  EqualsDate := (ADate.Day = Day) and (ADate.Month = Month);
end;
 
function TBirthday.EqualsLong(ADate: TLongDate): boolean;
begin
  EqualsLong := (ADate.Day = Day) and (ADate.Month = Month);
end;
 
function TBirthday.Month: word;
begin
  Month := WordRec(FData).Hi;
end;
 
procedure TBirthday.Load(var S: TStream);
begin
  S.Read(FData, 2);
end;
 
procedure TBirthday.Store(var S: TStream);
begin
  S.Write(FData, 2);
end;
 
{ GetMaxDayOfMonth }
 
function GetMaxDayOfMonth(Month, Year: word): byte;
var
  D: byte;
begin
  D := MaxDayOfMonth[Month];
  if Month = 2 then
    if Year mod 4 = 0 then D := 29;
  GetMaxDayOfMonth := D;
end;
 
{ CompareDateItem }
 
function CompareDateItem(Min, Max: TDateItem): integer;
var
  C: integer;
begin
  C := -1;
  if (Max.Year = Min.Year) and (Max.Month = Min.Month) and (Max.Day = Min.Day)
  then C := 0 else
  begin
    if Max.Year < Min.Year then C := 1 else
    begin
      if Max.Year = Min.Year then
      begin
        if Max.Month < Min.Month then C := 1;
        if Max.Month = Min.Month then
          if Max.Day < Min.Day then C := 1;
      end;
    end;
  end;
  CompareDateItem := C;
end;
 
{ GetNextDateItem }
 
procedure GetNextDateItem(Date: TDateItem; var Next: TDateItem);
begin
  Next.Reset(Date.Day, Date.Month, Date.Year);
  with Next do
  begin
    if Day = GetMaxDayOfMonth(Month, Year) then
    begin
      Day := 1;
      if Month < 12 then Inc(Month) else
      begin
        Month := 1;
        Inc(Year);
      end;
    end
     else Inc(Day);
  end;
end;
 
{$IFNDEF Windows}
function DateItem_In_TwoDates(Date, Test1, Test2: PDateItem): boolean;
var
  Delta: PDateDelta;
begin
  DateItem_In_TwoDates := false;
  Delta := NewDateDeltaItem(Test1^, Test2^);
  if Delta <> nil then
  begin
    DateItem_In_TwoDates := DateItemInDeltaExists(Date, Delta);
    DisposeDateDeltaItem(Delta);
  end;
end;
{$ENDIF Windows}
 
{ CalcDayDelta }
 
function CalcDayDelta(Min, Max: TDateItem): LongInt;
var
  C: LongInt;
  Ready: boolean;
  Comp: integer;
begin
  Comp := CompareDateItem(Min, Max);
  case Comp of
  -1:
  begin
    C := 0;
    repeat
      GetNextDateItem(Min, Min);
      Ready := Min.Equals(Max);
      Inc(C);
    until Ready;
    CalcDayDelta := C;
  end;
  0: CalcDayDelta := 0;
  1:
  begin
    C := 0;
    repeat
      Min.GetLastDay(Min);
      Ready := Min.Equals(Max);
      Inc(C);
    until Ready;
    CalcDayDelta := C;
  end;
  end;
end;
 
function CalcWeeks(Min, Max: TDateItem): integer;
var
  Days: longint;
begin
  Days := CalcDayDelta(Min, Max);
  CalcWeeks := LongDiv(Days, 7);
end;
 
{ Bestimmt die Anzahl der Lücken in Datums-Liste }
{ Als Lücke gilt mindestens ein fehlender Tag in der zeitl. Reihenfolge }
 
function CalcDeltaDateItems(List: PDateList): integer;
var
  Rslt: integer;
  LastDate: TDateItem;
  NextDate: TDateItem;
 
procedure Proof(P: PDateItem); far;
begin
  GetNextDateItem(LastDate, NextDate);
  if not P^.Equals(NextDate) then Inc(Rslt);
  LastDate.CopyFrom(P^);
end;
 
begin
  Rslt := 0;
  LastDate.Assign(0, 0, 0);
  NextDate.Assign(0, 0, 0);
  List^.ForEach(@Proof);
  CalcDeltaDateItems := Rslt;
end;
 
{ Initialisiert leere TDateDeltaList anhand der Anzahl der Datums-L�cken }
{ der �bergebenen DateList. }
 
procedure InitDateDeltaList(DateList: PDateList; var Delta: PDateDeltaList);
var
  Max: integer;
begin
  Max := CalcDeltaDateItems(DateList);
  if Max < = 0 then Max := 1;
  New(Delta, Init(Max, 1));
end;
 
{ FillDateDeltaList }
 
{$IFNDEF Windows}
procedure FillDateDeltaList(DateList: PDateList; var Delta: PDateDeltaList);
var
  First, Last, Next: TDateItem;
 
procedure InsertDeltaItem(F, L: TDateItem);
var
  DeltaItem: PDateDelta;
begin
  if L.Year = 0 then exit;
  DeltaItem := NewDateDeltaItem(F, L);
  if DeltaItem <> nil then Delta^.Insert(DeltaItem);
end;
 
procedure Fill(DateItem: PDateItem); far;
begin
  GetNextDateItem(Last, Next);
  if not DateItem^.Equals(Next) then
  begin
    InsertDeltaItem(First, Last);
    First.CopyFrom(DateItem^);
  end;
  Last.CopyFrom(DateItem^);
end;
 
begin
  if (Delta = nil) or (DateList = nil) then exit;
  First.Assign(0, 0, 0);
  Last.Assign(0, 0, 0);
  Next.Assign(0, 0, 0);
  if DateList^.Count > 0 then First.CopyFrom(PDateItem(DateList^.At(0))^);
  DateList^.ForEach(@Fill);
  InsertDeltaItem(First, Last);
  Delta^.Pack;
end;
{$ENDIF Windows}
 
{ ConvertDeltaToDateList }
 
procedure ConvertDeltaToDateList(Delta: PDateDeltaList; var DateList: PDateList);
var
  Cancel: boolean;
  First, Last: TDateItem;
 
procedure InsertDateItem(DateItem: TDateItem);
var
  Item: PDateItem;
begin
  {$IFNDEF Windows}
  Item := MemAlloc(SizeOf(TDateItem));
  if Item <> nil then
  begin
    Item^.Assign(DateItem.Day, DateItem.Month, DateItem.Year);
    DateList^.Insert(Item)
  end
  else
  begin
    Cancel := true;
    {$IFDEF WithTVTools}
    if Application <> nil then Application^.OutOfMemory;
    {$ENDIF WithTVTools}
  end;
  {$ENDIF Windows}
end;
 
procedure Fill(Item: PDateDelta); far;
begin
  {$IFDEF WithTVTools}
  Application^.Idle;
  {$ENDIF WithTVTools}
  if not Cancel then
  with Item^ do
  begin
    InsertDateItem(Min);
    While not Min.Equals(Max) do
    begin
      GetNextDateItem(Min, Min);
      InsertDateItem(Min);
    end;
  end;
end;
 
begin
  Cancel := false;
  if (Delta = nil) or (DateList = nil) then exit;
  {$IFDEF WithTVTools}
  if StatusLine <> nil then StatusLineMessage('Bitte einen Moment warten...');
  {$ENDIF WithTVTools}
  First.Assign(0, 0, 0);
  Last.Assign(0, 0, 0);
  Delta^.ForEach(@Fill);
  DateList^.Pack;
  {$IFDEF WithTVTools}
  if StatusLine <> nil then StatusLine^.DrawView;
  {$ENDIF WithTVTools}
end;
 
 
{ NewDateDeltaItem }
{$IFNDEF Windows}
function NewDateDeltaItem(Anfang, Ende: TDateItem): PDateDelta;
var
  Item: PDateDelta;
begin
  Item := MemAlloc(SizeOf(TDateDelta));
  if Item <> nil then
  begin
    with Anfang do Item^.Min.Assign(Day, Month, Year);
    with Ende do Item^.Max.Assign(Day, Month, Year);
    Item^.MaxUse := not Ende.Equals(Anfang);
  end
  {$IFDEF WithTVTools}
    else
      if Application <> nil then Application^.OutOfMemory;
  {$ELSE WithTVTools} ;
  {$ENDIF WithTVTools}
  NewDateDeltaItem := Item;
end;
{$ENDIF Windows}
 
{ DisposeDateDeltaItem }
procedure DisposeDateDeltaItem(var Item: PDateDelta);
begin
  if Item <> nil then
  begin
    Dispose(Item);
    Item := nil;
  end;
end;
 
{ DateItemInDeltaExists }
function DateItemInDeltaExists(DateItem: PDateItem; Delta: PDateDelta): boolean;
var
  Found: boolean;
  Min, max: integer;
begin
  Min := CompareDateItem(DateItem^, Delta^.Min);
  Max := CompareDateItem(DateItem^, Delta^.Max);
  Found := (Min >= 0) and (Max < =0);
  DateItemInDeltaExists := Found;
end;
 
{ DefDateErrorFunc }
function DefDatesErrorFunc(Code: integer; Info: Pointer): word;
begin
  DefDatesErrorFunc := 11 {cmCancel};
end;
 
function StdDatesErrorFunc(Code: integer; Info: Pointer): word;
begin
  {$IFDEF WithTVTools}
  StdDatesErrorFunc := cmCancel;
  case Code of
    coOverFlow:
      MessageBox('Die Liste kann keine weiteren Einträge aufnehmen!', nil,
        mfError or mfOKButton);
    end;
  {$ENDIF}
end;
 
procedure RegisterDates;
begin
  RegisterType(RDateList);
end;
 
end.