pascalx.utils.pas

Переключить прокрутку окна
Загрузить этот исходный код

{
    pascalx.utils — модуль, содержащий классы таблиц «ключ=значение» и работы с датой и временем.

    Copyright © 2021 Малик Разработчик

    Это свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она может быть полезна,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit pascalx.utils;

{$MODE DELPHI}

interface

uses
    windows,
    pascalx.lang,
    osindepended.fileformats;

{$ASMMODE INTEL,CALLING REGISTER,TYPEINFO ON}

{%region public }
const { значения для Calendar }
    FLD_YEAR = int(1);
    FLD_MONTH = int(2);
    FLD_DAY = int(5);
    FLD_WEEKDAY = int(7);
    FLD_AMPM = int(9);
    FLD_HOUR12 = int(10);
    FLD_HOUR = int(11);
    FLD_MINUTE = int(12);
    FLD_SECOND = int(13);
    FLD_MILLISECOND = int(14);
	MONDAY = int(1);
	TUESDAY = int(2);
	WEDNESDAY = int(3);
	THURSDAY = int(4);
	FRIDAY = int(5);
	SATURDAY = int(6);
    SUNDAY = int(0);
	JANUARY = int(1);
	FEBRUARY = int(2);
	MARCH = int(3);
	APRIL = int(4);
	MAY = int(5);
	JUNE = int(6);
	JULY = int(7);
	AUGUST = int(8);
	SEPTEMBER = int(9);
	OCTOBER = int(10);
	NOVEMBER = int(11);
	DECEMBER = int(12);
    AM = int(0);
    PM = int(1);

type
    Calendar = interface;
    Enumeration = interface;
    MapEnumeration = interface;
    EmptyEnumerator = class;
    Hashtable = class;
    CustomCalendar = class;
    GregorianCalendar = class;
    OverflowException = class;
    NoSuchElementException = class;

    Calendar = interface(_Interface) ['{A3919068-04E5-47B0-A10D-591E2A664129}']
        function after(anot: Calendar): boolean;
        function before(anot: Calendar): boolean;
        function getDaysCount(): int;
        function getField(field: int): int;
        function getOffset(): int;
        function getTime(): long;
        function getEpochStart(): long;
        procedure setField(field, value: int);
        procedure setOffset(offset: int);
        procedure setTime(time: long);
        property year: int index FLD_YEAR read getField write setField;
        property month: int index FLD_MONTH read getField write setField;
        property day: int index FLD_DAY read getField write setField;
        property weekday: int index FLD_WEEKDAY read getField;
        property ampm: int index FLD_AMPM read getField write setField;
        property hour12: int index FLD_HOUR12 read getField write setField;
        property hour: int index FLD_HOUR read getField write setField;
        property minute: int index FLD_MINUTE read getField write setField;
        property second: int index FLD_SECOND read getField write setField;
        property millisecond: int index FLD_MILLISECOND read getField write setField;
        property offset: int read getOffset write setOffset;
        property time: long read getTime write setTime;
        property epochStart: long read getEpochStart;
    end;

    Enumeration = interface(_Interface) ['{A3919068-04E5-47B0-A10D-591E2A66412A}']
        function hasMoreElements(): boolean;
        function nextElement(): Value;
    end;

    MapEnumeration = interface(Enumeration) ['{A3919068-04E5-47B0-A10D-591E2A66412B}']
        function value(): pascalx.lang.Value;
    end;

    EmptyEnumerator = class(DynamicalyAllocatedObject, Enumeration)
    public
        function hasMoreElements(): boolean;
        function nextElement(): Value;
    end;

    Hashtable = class(_Object, DataHolder)
    private type
        Entry = class;
        ElementEnumerator = class;
        KeyEnumerator = class;
        Entry_Array1d = packed array of Entry;
        Entry = class(_Object)
        public
            hash: long;
            key: pascalx.lang.Value;
            value: pascalx.lang.Value;
            next: Entry;
            constructor create(hash: long; key, value: pascalx.lang.Value; next: Entry);
        end;
        ElementEnumerator = class(RefCountInterfacedObject, Enumeration)
        private
            keys: boolean;
            index: int;
            table: Entry_Array1d;
            next: Entry;
            current: Entry;
        public
            constructor create(keys: boolean; const table: Entry_Array1d);
            function hasMoreElements(): boolean;
            function nextElement(): Value;
        end;
        KeyEnumerator = class(ElementEnumerator, MapEnumeration)
        public
            constructor create(const table: Entry_Array1d);
            function value(): pascalx.lang.Value;
        end;
    private
        factor: int;
        length: int;
        table: Entry_Array1d;
    protected
        procedure rehash(); virtual;
    public
        constructor create(); overload;
        constructor create(initialCapacity: int); overload;
        destructor destroy; override;
        function toString(): AnsiString; override;
        function isEmpty(): boolean; virtual;
        function contains(value: pascalx.lang.Value): boolean; virtual;
        function containsKey(key: pascalx.lang.Value): boolean; virtual;
        function size(): int; virtual;
        function put(key, value: pascalx.lang.Value): pascalx.lang.Value; virtual;
        function remove(key: pascalx.lang.Value): pascalx.lang.Value; virtual;
        function get(key: pascalx.lang.Value): pascalx.lang.Value; virtual;
        function elements(): Enumeration; virtual;
        function keys(): Enumeration; virtual;
        procedure clear(); virtual;
    end;

    Vector = class(_Object, DataHolder)
    private type
        ElementEnumerator = class(RefCountInterfacedObject, Enumeration)
        private
            index: int;
            owner: Vector;
        public
            constructor create(owner: Vector);
            function hasMoreElements(): boolean;
            function nextElement(): Value;
        end;
    private
        length: int;
        data: Value_Array1d;
    public
        constructor create(); overload;
        constructor create(initialCapacity: int); overload;
        function toString(): AnsiString; override;
        function isEmpty(): boolean; virtual;
        function contains(element: Value): boolean; virtual;
        function indexOf(element: Value): int; virtual;
        function size(): int; virtual;
        function elements(): Enumeration; virtual;
        function lastElement(): Value; virtual;
        function firstElement(): Value; virtual;
        function elementAt(index: int): Value; virtual;
        function setElementAt(index: int; element: Value): Value; virtual;
        function delete(index: int): Value; virtual;
        function remove(element: Value): boolean; virtual;
        procedure insert(index: int; element: Value); virtual;
        procedure append(element: Value); virtual;
        procedure clear(); virtual;
    end;

    CustomCalendar = class(DynamicalyAllocatedObject, Calendar)
    private
        procedure updateHour12();
        procedure updateHour24();
    protected
        offsetInMillis: int;
        timeInMillis: long;
        fields: int_Array1d;
        procedure computeFields(); virtual; abstract;
        procedure computeTime(); virtual; abstract;
    public
        constructor create(); override;
        function equals(anot: TObject): boolean; override;
        function getHashCode(): long; override;
        function after(anot: Calendar): boolean;
        function before(anot: Calendar): boolean;
        function getDaysCount(): int; virtual; abstract;
        function getField(field: int): int;
        function getOffset(): int;
        function getTime(): long;
        function getEpochStart(): long; virtual; abstract;
        procedure setField(field, value: int);
        procedure setOffset(offset: int);
        procedure setTime(time: long);
    published
        property year: int index FLD_YEAR read getField write setField stored false;
        property month: int index FLD_MONTH read getField write setField stored false;
        property day: int index FLD_DAY read getField write setField stored false;
        property weekday: int index FLD_WEEKDAY read getField stored false;
        property ampm: int index FLD_AMPM read getField write setField stored false;
        property hour12: int index FLD_HOUR12 read getField write setField stored false;
        property hour: int index FLD_HOUR read getField write setField stored false;
        property minute: int index FLD_MINUTE read getField write setField stored false;
        property second: int index FLD_SECOND read getField write setField stored false;
        property millisecond: int index FLD_MILLISECOND read getField write setField stored false;
        property offset: int read offsetInMillis write setOffset stored true;
        property time: long read timeInMillis write setTime stored true;
        property epochStart: long read getEpochStart stored false;
    end;

    GregorianCalendar = class(CustomCalendar)
    private
        class function getDaysCount(year, month: int): int; static; overload;
    protected
        procedure computeFields(); override;
        procedure computeTime(); override;
    public
        function getDaysCount(): int; override; overload;
        function getEpochStart(): long; override;
    end;

    OverflowException = class(RuntimeException);

    NoSuchElementException = class(RuntimeException);

resourcestring
    msgIllegalYearMonth = 'Недопустимые значения года и месяца';
    msgHashtableOverflow = 'Таблица значений переполнена';
    msgNoSuchElement = 'В перечислении больше не осталось элементов';
{%endregion}

implementation

{%region EmptyEnumerator }
    function EmptyEnumerator.hasMoreElements(): boolean;
    begin
        result := false;
    end;

    function EmptyEnumerator.nextElement(): Value;
    begin
        raise NoSuchElementException.create('Enumeration.nextElement: ' + msgNoSuchElement);
    end;
{%endregion}

{%region Hashtable.Entry }
    constructor Hashtable.Entry.create(hash: long; key, value: pascalx.lang.Value; next: Entry);
    begin
        inherited create();
        self.hash := hash;
        self.key := key;
        self.value := value;
        self.next := next;
    end;
{%endregion}

{%region Hashtable.ElementEnumerator }
    constructor Hashtable.ElementEnumerator.create(keys: boolean; const table: Entry_Array1d);
    begin
        inherited create();
        self.keys := keys;
        self.index := system.length(table);
        self.table := table;
    end;

    function Hashtable.ElementEnumerator.hasMoreElements(): boolean;
    var
        i: int;
        e: Entry;
        t: Entry_Array1d;
    begin
        if next <> nil then begin
            result := true;
            exit;
        end;
        t := table;
        for i := index - 1 downto 0 do begin
            e := t[i];
            next := e;
            if e <> nil then begin
                index := i;
                result := true;
                exit;
            end;
        end;
        index := -1;
        result := false;
    end;

    function Hashtable.ElementEnumerator.nextElement(): Value;
    var
        i: int;
        e: Entry;
        t: Entry_Array1d;
    begin
        e := next;
        if e = nil then begin
            t := table;
            i := index - 1;
            while i >= 0 do begin
                e := t[i];
                if e <> nil then break;
                dec(i);
            end;
            next := e;
            if i < 0 then begin
                index := -1;
            end else begin
                index := i;
            end;
        end;
        if e = nil then begin
            current := nil;
            raise NoSuchElementException.create('Enumeration.nextElement: ' + msgNoSuchElement);
        end;
        current := e;
        next := e.next;
        if keys then begin
            result := e.key;
            exit;
        end;
        result := e.value;
    end;
{%endregion}

{%region Hashtable.KeyEnumerator }
    constructor Hashtable.KeyEnumerator.create(const table: Entry_Array1d);
    begin
        inherited create(true, table);
    end;

    function Hashtable.KeyEnumerator.value(): pascalx.lang.Value;
    var
        e: Entry;
    begin
        e := current;
        if e = nil then begin
            raise NoSuchElementException.create('MapEnumeration.value: ' + msgNoSuchElement);
        end;
        result := e.value;
    end;
{%endregion}

{%region Hashtable }
    procedure Hashtable.rehash();
    var
        i: int;
        j: int;
        nc: int;
        oc: int;
        e: Entry;
        f: Entry;
        nt: Entry_Array1d;
        ot: Entry_Array1d;
    begin
        ot := table;
        oc := system.length(ot);
        if oc >= $40000000 then exit;
        nc := (oc shl 1) + 1;
        nt := Entry_Array1d(TObject_Array1d_create(nc));
        table := nt;
        factor := nc - intSar(nc, 2);
        for i := oc - 1 downto 0 do begin
            f := ot[i];
            while f <> nil do begin
                e := f;
                j := int((e.hash and LONG_MAX_VALUE) mod nc);
                f := f.next;
                e.next := nt[j];
                nt[j] := e;
            end;
        end;
    end;

    constructor Hashtable.create();
    begin
        create($1f);
    end;

    constructor Hashtable.create(initialCapacity: int);
    var
        i: int;
    begin
        inherited create();
        if initialCapacity <= $1f then begin
            initialCapacity := $1f;
        end else begin
            i := $20;
            while (i > 0) and (i <= initialCapacity) do i := i shl 1;
            initialCapacity := i - 1;
        end;
        self.factor := initialCapacity - intSar(initialCapacity, 2);
        self.table := Entry_Array1d(TObject_Array1d_create(initialCapacity));
    end;

    destructor Hashtable.destroy;
    var
        i: int;
        e: Entry;
        f: Entry;
        t: Entry_Array1d;
    begin
        t := table;
        for i := system.length(t) - 1 downto 0 do begin
            e := t[i];
            while e <> nil do begin
                f := e.next;
                e.free();
                e := f;
            end;
        end;
        inherited destroy;
    end;

    function Hashtable.toString(): AnsiString;
    var
        i: int;
        s: AnsiString;
        e: MapEnumeration;
    begin
        s := '{';
        e := KeyEnumerator.create(table);
        for i := length - 1 downto 0 do begin
            s := s + e.nextElement().toString() + '=' + e.value().toString();
            if i > 0 then s := s + ', ';
        end;
        result := s + '}';
    end;

    function Hashtable.isEmpty(): boolean;
    begin
        result := length <= 0;
    end;

    function Hashtable.contains(value: pascalx.lang.Value): boolean;
    var
        i: int;
        e: Entry;
        t: Entry_Array1d;
    begin
        if value = nil then begin
            raise NullPointerException.create('Hashtable.contains: ' + msgNullPointerArgument + 'value');
        end;
        t := table;
        for i := system.length(table) - 1 downto 0 do begin
            e := t[i];
            while e <> nil do begin
                if value.equals(e.value as TObject) then begin
                    result := true;
                    exit;
                end;
                e := e.next;
            end;
        end;
        result := false;
    end;

    function Hashtable.containsKey(key: pascalx.lang.Value): boolean;
    var
        h: long;
        e: Entry;
        t: Entry_Array1d;
    begin
        if key = nil then begin
            raise NullPointerException.create('Hashtable.containsKey: ' + msgNullPointerArgument + 'key');
        end;
        t := table;
        h := key.getHashCode();
        e := t[int((h and LONG_MAX_VALUE) mod system.length(t))];
        while e <> nil do begin
            if (h = e.hash) and key.equals(e.key as TObject) then begin
                result := true;
                exit;
            end;
            e := e.next;
        end;
        result := false;
    end;

    function Hashtable.size(): int;
    begin
        result := length;
    end;

    function Hashtable.put(key, value: pascalx.lang.Value): pascalx.lang.Value;
    var
        i: int;
        len: int;
        h: long;
        e: Entry;
        t: Entry_Array1d;
    begin
        if key = nil then begin
            raise NullPointerException.create('Hashtable.put: ' + msgNullPointerArgument + 'key');
        end;
        if value = nil then begin
            raise NullPointerException.create('Hashtable.put: ' + msgNullPointerArgument + 'value');
        end;
        if not(key is TObject) then begin
            raise IllegalArgumentException.create('Hashtable.put: ' + msgIllegalArgument + 'key');
        end;
        if not(value is TObject) then begin
            raise IllegalArgumentException.create('Hashtable.put: ' + msgIllegalArgument + 'value');
        end;
        t := table;
        h := key.getHashCode();
        i := int((h and LONG_MAX_VALUE) mod system.length(t));
        e := t[i];
        while e <> nil do begin
            if (h = e.hash) and key.equals(e.key as TObject) then begin
                result := e.value;
                e.value := value;
                exit;
            end;
            e := e.next;
        end;
        len := length;
        if len = INT_MAX_VALUE then begin
            raise OverflowException.create('Hashtable.put: ' + msgHashtableOverflow);
        end;
        if len >= factor then begin
            rehash();
            t := table;
            i := int((h and LONG_MAX_VALUE) mod system.length(t));
        end;
        t[i] := Entry.create(h, key, value, t[i]);
        length := len + 1;
        result := nil;
    end;

    function Hashtable.remove(key: pascalx.lang.Value): pascalx.lang.Value;
    var
        i: int;
        h: long;
        e: Entry;
        f: Entry;
        t: Entry_Array1d;
    begin
        if key = nil then begin
            result := nil;
            exit;
        end;
        f := nil;
        t := table;
        h := key.getHashCode();
        i := int((h and LONG_MAX_VALUE) mod system.length(t));
        e := t[i];
        while e <> nil do begin
            if (h <> e.hash) or not key.equals(e.key as TObject) then begin
                f := e;
                e := e.next;
                continue;
            end;
            if f <> nil then begin
                f.next := e.next;
            end else begin
                t[i] := e.next;
            end;
            dec(length);
            result := e.value;
            e.free();
            exit;
        end;
        result := nil;
    end;

    function Hashtable.get(key: pascalx.lang.Value): pascalx.lang.Value;
    var
        h: long;
        e: Entry;
        t: Entry_Array1d;
    begin
        if key = nil then begin
            result := nil;
            exit;
        end;
        t := table;
        h := key.getHashCode();
        e := t[int((h and LONG_MAX_VALUE) mod system.length(t))];
        while e <> nil do begin
            if (h = e.hash) and key.equals(e.key as TObject) then begin
                result := e.value;
                exit;
            end;
            e := e.next;
        end;
        result := nil;
    end;

    function Hashtable.elements(): Enumeration;
    begin
        result := ElementEnumerator.create(false, table);
    end;

    function Hashtable.keys(): Enumeration;
    begin
        result := KeyEnumerator.create(table);
    end;

    procedure Hashtable.clear();
    var
        i: int;
        e: Entry;
        f: Entry;
        t: Entry_Array1d;
    begin
        t := table;
        length := 0;
        for i := system.length(t) - 1 downto 0 do begin
            e := t[i];
            t[i] := nil;
            while e <> nil do begin
                f := e.next;
                e.free();
                e := f;
            end;
        end;
    end;
{%endregion}

{%region Vector.ElementEnumerator }
    constructor Vector.ElementEnumerator.create(owner: Vector);
    begin
        inherited create();
        self.owner := owner;
    end;

    function Vector.ElementEnumerator.hasMoreElements(): boolean;
    var
        index: int;
    begin
        index := self.index;
        result := (index >= 0) and (index < owner.length);
    end;

    function Vector.ElementEnumerator.nextElement(): Value;
    var
        index: int;
        owner: Vector;
        current: Value;
    begin
        index := self.index;
        owner := self.owner;
        if (index < 0) or (index >= owner.length) then begin
            raise NoSuchElementException.create('Enumeration.nextElement: ' + msgNoSuchElement);
        end;
        current := owner.data[index];
        self.index := index + 1;
        result := current;
    end;
{%endregion}

{%region Vector }
    constructor Vector.create();
    begin
        create($1f);
    end;

    constructor Vector.create(initialCapacity: int);
    var
        i: int;
    begin
        inherited create();
        if initialCapacity <= $1f then begin
            initialCapacity := $1f;
        end else begin
            i := $20;
            while (i > 0) and (i <= initialCapacity) do i := i shl 1;
            initialCapacity := i - 1;
        end;
        self.data := Value_Array1d(IUnknown_Array1d_create(initialCapacity));
    end;

    function Vector.toString(): AnsiString;
    var
        i: int;
        lim: int;
        s: AnsiString;
        e: Enumeration;
    begin
        s := '(';
        e := ElementEnumerator.create(self);
        lim := length - 1;
        for i := 0 to lim do begin
            s := s + e.nextElement().toString();
            if i < lim then s := s + ', ';
        end;
        result := s + ')';
    end;

    function Vector.isEmpty(): boolean;
    begin
        result := length <= 0;
    end;

    function Vector.contains(element: Value): boolean;
    var
        i: int;
        len: int;
        elements: Value_Array1d;
    begin
        if element = nil then begin
            result := arrayfindeqfInterface(data, 0, length, nil) >= 0;
            exit;
        end;
        len := length;
        elements := data;
        for i := 0 to len - 1 do begin
            if element.equals(elements[i] as TObject) then begin
                result := true;
                exit;
            end;
        end;
        result := false;
    end;

    function Vector.indexOf(element: Value): int;
    var
        i: int;
        len: int;
        elements: Value_Array1d;
    begin
        if element = nil then begin
            result := arrayfindeqfInterface(data, 0, length, nil);
            exit;
        end;
        len := length;
        elements := data;
        for i := 0 to len - 1 do begin
            if element.equals(elements[i] as TObject) then begin
                result := i;
                exit;
            end;
        end;
        result := -1;
    end;

    function Vector.size(): int;
    begin
        result := length;
    end;

    function Vector.elements(): Enumeration;
    begin
        result := ElementEnumerator.create(self);
    end;

    function Vector.lastElement(): Value;
    var
        len: int;
    begin
        len := length;
        if len <= 0 then begin
            raise IndexOutOfBoundsException.create('Vector.lastElement: ' + msgIndexOutOfBounds);
        end;
        result := data[len - 1];
    end;

    function Vector.firstElement(): Value;
    begin
        if length <= 0 then begin
            raise IndexOutOfBoundsException.create('Vector.firstElement: ' + msgIndexOutOfBounds);
        end;
        result := data[0];
    end;

    function Vector.elementAt(index: int): Value;
    begin
        if (index < 0) or (index >= length) then begin
            raise IndexOutOfBoundsException.create('Vector.elementAt: ' + msgIndexOutOfBounds);
        end;
        result := data[index];
    end;

    function Vector.setElementAt(index: int; element: Value): Value;
    var
        prev: Value;
        elements: Value_Array1d;
    begin
        if (index < 0) or (index >= length) then begin
            raise IndexOutOfBoundsException.create('Vector.setElementAt: ' + msgIndexOutOfBounds);
        end;
        if (element <> nil) and not(element is TObject) then begin
            raise IllegalArgumentException.create('Vector.setElementAt: ' + msgIllegalArgument + 'element');
        end;
        elements := data;
        prev := elements[index];
        elements[index] := element;
        result := prev;
    end;

    function Vector.delete(index: int): Value;
    var
        len: int;
        prev: Value;
        elements: Value_Array1d;
    begin
        len := length - 1;
        if (index < 0) or (index > len) then begin
            raise IndexOutOfBoundsException.create('Vector.delete: ' + msgIndexOutOfBounds);
        end;
        elements := data;
        prev := elements[index];
        arraycopyInterfaces(elements, index + 1, elements, index, len - index);
        elements[len] := nil;
        length := len;
        result := prev;
    end;

    function Vector.remove(element: Value): boolean;
    var
        i: int;
        len: int;
        index: int;
        elements: Value_Array1d;
    begin
        index := -1;
        len := length - 1;
        elements := data;
        if element = nil then begin
            for i := 0 to len do begin
                if elements[i] = nil then begin
                    index := i;
                    break;
                end;
            end;
        end else begin
            for i := 0 to len do begin
                if element.equals(elements[i] as TObject) then begin
                    index := i;
                    break;
                end;
            end;
        end;
        if index < 0 then begin
            result := false;
            exit;
        end;
        arraycopyInterfaces(elements, index + 1, elements, index, len - index);
        elements[len] := nil;
        length := len;
        result := true;
    end;

    procedure Vector.insert(index: int; element: Value);
    var
        len: int;
        capa: int;
        elements: Value_Array1d;
        newelements: Value_Array1d;
    begin
        len := length;
        if (index < 0) or (index > len) or (len = INT_MAX_VALUE) then begin
            raise IndexOutOfBoundsException.create('Vector.insert: ' + msgIndexOutOfBounds);
        end;
        if (element <> nil) and not(element is TObject) then begin
            raise IllegalArgumentException.create('Vector.insert: ' + msgIllegalArgument + 'element');
        end;
        elements := data;
        capa := system.length(elements);
        if len = capa then begin
            newelements := Value_Array1d(IUnknown_Array1d_create((len shl 1) + 1));
            arraycopyInterfaces(elements, 0, newelements, 0, len);
            elements := newelements;
            data := newelements;
        end;
        arraycopyInterfaces(elements, index, elements, index + 1, len - index);
        elements[index] := element;
        length := len + 1;
    end;

    procedure Vector.append(element: Value);
    var
        len: int;
        capa: int;
        elements: Value_Array1d;
        newelements: Value_Array1d;
    begin
        len := length;
        if len = INT_MAX_VALUE then begin
            raise IndexOutOfBoundsException.create('Vector.append: ' + msgIndexOutOfBounds);
        end;
        if (element <> nil) and not(element is TObject) then begin
            raise IllegalArgumentException.create('Vector.append: ' + msgIllegalArgument + 'element');
        end;
        elements := data;
        capa := system.length(elements);
        if len = capa then begin
            newelements := Value_Array1d(IUnknown_Array1d_create((len shl 1) + 1));
            arraycopyInterfaces(elements, 0, newelements, 0, len);
            elements := newelements;
            data := newelements;
        end;
        elements[len] := element;
        length := len + 1;
    end;

    procedure Vector.clear();
    begin
        arrayfillInterfaces(data, 0, length, nil);
        length := 0;
    end;
{%endregion}

{%region CustomCalendar }
    procedure CustomCalendar.updateHour12();
    var
        hour: int;
        fields: int_Array1d;
    begin
        fields := self.fields;
        hour := fields[FLD_HOUR];
        if hour >= 12 then begin
            dec(hour, 12);
            fields[FLD_AMPM] := PM;
        end else begin
            fields[FLD_AMPM] := AM;
        end;
        if hour = 0 then hour := 12;
        fields[FLD_HOUR12] := hour;
    end;

    procedure CustomCalendar.updateHour24();
    var
        hour: int;
        fields: int_Array1d;
    begin
        fields := self.fields;
        hour := fields[FLD_HOUR12];
        if hour = 12 then hour := 0;
        fields[FLD_HOUR] := fields[FLD_AMPM] * 12 + hour;
    end;

    constructor CustomCalendar.create();
    var
        offset: int;
        fields: int_Array1d;
        zone: TTimeZoneInformation;
        time: TSystemTime;
    begin
        inherited create();
        initialize(zone);
        initialize(time);
        getTimeZoneInformation(zone);
        getSystemTime(time);
        offset := -60000 * zone.bias;
        fields := int_Array1d_create(16);
        fields[FLD_YEAR] := time.year;
        fields[FLD_MONTH] := time.month;
        fields[FLD_DAY] := time.day;
        fields[FLD_HOUR] := time.hour;
        fields[FLD_MINUTE] := time.minute;
        fields[FLD_SECOND] := time.second;
        fields[FLD_MILLISECOND] := time.millisecond;
        self.offsetInMillis := offset;
        self.fields := fields;
        updateHour12();
        computeTime();
    end;

    function CustomCalendar.equals(anot: TObject): boolean;
    begin
        result := (anot = self) or (anot is CustomCalendar) and (CustomCalendar(anot).timeInMillis = timeInMillis);
    end;

    function CustomCalendar.getHashCode(): long;
    begin
        result := timeInMillis;
    end;

    function CustomCalendar.after(anot: Calendar): boolean;
    begin
        if anot = nil then begin
            raise NullPointerException.create('CustomCalendar.after: ' + msgNullPointerArgument + 'anot');
        end;
        result := timeInMillis > anot.time;
    end;

    function CustomCalendar.before(anot: Calendar): boolean;
    begin
        if anot = nil then begin
            raise NullPointerException.create('CustomCalendar.before: ' + msgNullPointerArgument + 'anot');
        end;
        result := timeInMillis < anot.time;
    end;

    function CustomCalendar.getField(field: int): int;
    begin
        if (field < 0) or (field >= 16) then begin
            raise IllegalArgumentException.create('CustomCalendar.getField: ' + msgIllegalArgument + 'field');
        end;
        result := fields[field];
    end;

    function CustomCalendar.getOffset(): int;
    begin
        result := offsetInMillis;
    end;

    function CustomCalendar.getTime(): long;
    begin
        result := timeInMillis;
    end;

    procedure CustomCalendar.setField(field, value: int);
    var
        fields: int_Array1d;
    begin
        if (field < 0) or (field >= 16) or (field = FLD_WEEKDAY) then begin
            raise IllegalArgumentException.create('CustomCalendar.setField: ' + msgIllegalArgument + 'field');
        end;
        fields := self.fields;
        if fields[field] = value then exit;
        fields[field] := value;
        case field of
        FLD_AMPM,
        FLD_HOUR12:
            updateHour24();
        FLD_HOUR:
            updateHour12();
        end;
        computeTime();
    end;

    procedure CustomCalendar.setOffset(offset: int);
    begin
        if self.offsetInMillis = offset then exit;
        self.offsetInMillis := offset;
        computeFields();
        updateHour12();
    end;

    procedure CustomCalendar.setTime(time: long);
    begin
        if self.timeInMillis = time then exit;
        self.timeInMillis := time;
        computeFields();
        updateHour12();
    end;
{%endregion}

{%region GregorianCalendar }
    class function GregorianCalendar.getDaysCount(year, month: int): int;
    const
        DAYS: array[boolean, 1..12] of byte = (
            ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ),
            ( 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 )
        );
    var
        period: int;
    begin
        if year <= 0 then begin
            raise IllegalStateException.create('GregorianCalendar.getDaysCount: ' + msgIllegalYearMonth);
        end;
        if (month < 1) or (month > 12) then begin
            raise IllegalStateException.create('GregorianCalendar.getDaysCount: ' + msgIllegalYearMonth);
        end;
        if year mod 100 = 0 then begin
            period := 400;
        end else begin
            period := 4;
        end;
        result := DAYS[year mod period = 0, month];
    end;

    procedure GregorianCalendar.computeFields();
    var
        year: long;
        month: long;
        day: long;
        daysPerMonth: long;
        weekday: long;
        hour: long;
        minute: long;
        second: long;
        millisecond: long;
        millis: long;
        days: long;
        time: long;
        fields: int_Array1d;
    begin
        time := timeInMillis + offsetInMillis;
        days := time div 86400000;
        millis := time mod 86400000;
        weekday := (days + 1) mod 7;
        year := 400 * (days div 146097);
        days := days mod 146097;
        if days >= 146096 then begin
            inc(year, 399);
            days := 365;
        end else begin
            inc(year, 100 * (days div 36524));
            days := days mod 36524;
            inc(year, 4 * (days div 1461));
            days := days mod 1461;
            if days >= 1460 then begin
                inc(year, 3);
                days := 365;
            end else begin
                inc(year, days div 365);
                days := days mod 365;
            end;
        end;
        inc(year);
        month := 1;
        repeat
            daysPerMonth := getDaysCount(int(year), int(month));
            if days < daysPerMonth then break;
            dec(days, daysPerMonth);
            inc(month);
        until false;
        day := days + 1;
        hour := millis div 3600000;
        millis := millis mod 3600000;
        minute := millis div 60000;
        millis := millis mod 60000;
        second := millis div 1000;
        millisecond := millis mod 1000;
        fields := self.fields;
        fields[FLD_YEAR] := int(year);
        fields[FLD_MONTH] := int(month);
        fields[FLD_DAY] := int(day);
        fields[FLD_WEEKDAY] := int(weekday);
        fields[FLD_HOUR] := int(hour);
        fields[FLD_MINUTE] := int(minute);
        fields[FLD_SECOND] := int(second);
        fields[FLD_MILLISECOND] := int(millisecond);
    end;

    procedure GregorianCalendar.computeTime();
    var
        i: int;
        srcyear: long;
        year: long;
        month: long;
        day: long;
        hour: long;
        minute: long;
        second: long;
        millisecond: long;
        days: long;
        fields: int_Array1d;
    begin
        fields := self.fields;
        year := fields[FLD_YEAR];
        month := fields[FLD_MONTH];
        if (year <= 0) or (month < 1) or (month > 12) then begin
            raise IllegalStateException.create('GregorianCalendar.setField: ' + msgIllegalYearMonth);
        end;
        day := fields[FLD_DAY];
        hour := fields[FLD_HOUR];
        minute := fields[FLD_MINUTE];
        second := fields[FLD_SECOND];
        millisecond := fields[FLD_MILLISECOND];
        srcyear := year;
        dec(year);
        days := 146097 * (year div 400);
        year := year mod 400;
        inc(days, 36524 * (year div 100));
        year := year mod 100;
        inc(days, 1461 * (year div 4) + 365 * (year mod 4) + (day - 1));
        for i := 1 to int(month - 1) do begin
            inc(days, getDaysCount(int(srcyear), i));
        end;
        fields[FLD_WEEKDAY] := int((days + 1) mod 7);
        timeInMillis := 86400000 * days + 3600000 * hour + 60000 * minute + 1000 * second + millisecond - offsetInMillis;
    end;

    function GregorianCalendar.getDaysCount(): int;
    begin
        result := getDaysCount(year, month);
    end;

    function GregorianCalendar.getEpochStart(): long;
    begin
        result := -long(offsetInMillis);
    end;
{%endregion}

initialization
    classInfoAdd([
        typeInfo(EmptyEnumerator),
        typeInfo(Calendar),
        typeInfo(CustomCalendar),
        typeInfo(GregorianCalendar)
    ]);

end.