YOUSEFUL / DELPHI / COMPONENTS

TAtlasSchedule

In-memory appointment model with stable IDs, per-resource conflict checking, interval queries, change notification and transactional JSON.

Non-visual component Palette: Atlas Unit Atlas.Schedule · Inherits TComponent · Product Atlas VCL

Description

TAtlasSchedule holds TAtlasAppointment records, each with an ID that is assigned once (sequentially from 1) and never reused for the life of the component. Every appointment has a caption, a resource name (a room, a person, a machine), a half-open interval [StartsAt, EndsAt) in TDateTime and a colour.

Intervals must increase and lie between the TDateTime values 1 (31 December 1899) and 2958465 (31 December 9999); the error message calls this 1900 to 9999. Two appointments conflict when they belong to the same resource and their intervals overlap; touching intervals (one ends exactly when the other starts) do not. Unless AllowOverlap is True, Add, Update and LoadJSON refuse a conflicting appointment and leave the model unchanged. CanPlace answers the same question without changing anything.

Views such as TAtlasScheduler attach through AddListener; OnChange is a single application hook that fires after the listeners. Both fire after every successful Add, Update, Delete, Clear and LoadJSON. ToJSON and LoadJSON persist the appointments including their IDs; the import is all or nothing.

Not implemented: recurrence, time zones (dates are plain TDateTime), reminders, attendee lists and a built-in editor; the application creates appointments through this model and handles the scheduler's OnEdit event with its own dialog. The component is designed for the UI thread. Errors raise EAtlasScheduleError.

Members

MemberKindWhat it does
CreateconstructorCreates an empty schedule.
DestroydestructorFrees the appointment and listener lists.
AddmethodAdds an appointment and returns its new ID.
UpdatemethodReplaces every field of appointment ID at once.
DeletemethodRemoves appointment ID.
ClearmethodRemoves every appointment and notifies listeners and OnChange.
AppointmentmethodReturns a copy of the appointment record with the given ID; changing the copy does not change the schedule (use Update).
QuerymethodReturns every appointment whose interval overlaps the half-open interval [StartsAt, EndsAt), sorted by start time and then by ID.
CanPlacemethodTrue when no appointment of Resource, other than IgnoreID, overlaps [StartsAt, EndsAt).
ToJSONmethodSerializes the appointments as a JSON array of objects with the keys id, caption, resource, start, end (both TDateTime numbers) and color, for example [{"id":1,"caption":"Stand-up","resource":"Room A","start":46286.375,"end":46286.3958333333,"color":12161078}].
LoadJSONmethodReplaces the appointments with the array produced by ToJSON.
AddListenermethodRegisters a TNotifyEvent that is called with the schedule as Sender after every change, before OnChange.
RemoveListenermethodRemoves a listener registered with AddListener (matched by method and object).
CountpropertyNumber of appointments in the schedule.
AllowOverlappropertyWhen False (the default), Add, Update and LoadJSON reject an appointment that overlaps another one of the same resource.
OnChangeeventFires after every successful Add, Update, Delete, Clear and LoadJSON, after the listeners registered with AddListener.

Constructors

Create constructor

constructor Create(AOwner: TComponent); override;

Creates an empty schedule. The next appointment ID is 1 and AllowOverlap is False.

Destructors

Destroy destructor

destructor Destroy; override;

Frees the appointment and listener lists. Attached TAtlasScheduler controls receive the usual component free notification and detach themselves.

Properties

Count property

property Count: Integer read GetCount;

Number of appointments in the schedule.

AllowOverlap property

property AllowOverlap: Boolean read FAllowOverlap write SetAllowOverlap default False;

When False (the default), Add, Update and LoadJSON reject an appointment that overlaps another one of the same resource. Setting it back to False while existing appointments overlap raises EAtlasScheduleError and leaves the property True.

Events

OnChange event

property OnChange: TNotifyEvent read FOnChange write FOnChange;

Fires after every successful Add, Update, Delete, Clear and LoadJSON, after the listeners registered with AddListener. Use it to refresh application state such as a status bar or a dirty flag.

Methods

Add method

function Add(const Caption, Resource: string; StartsAt, EndsAt: TDateTime; Color: Integer = $00B99036): Integer;

Adds an appointment and returns its new ID. Raises EAtlasScheduleError when the interval is not increasing or lies outside 1900..9999, or when AllowOverlap is False and the interval overlaps another appointment of the same Resource. Color is a TColor value in $00BBGGRR form; the default is a muted blue. Fires the listeners and OnChange.

pascal
Day := EncodeDate(2026, 9, 21);
ID := Schedule.Add('Stand-up', 'Room A', Day + 9/24, Day + 9.5/24);          // 09:00-09:30
Schedule.Add('Review', 'Room A', Day + 9.5/24, Day + 10.5/24, $00C8A060); // touching is fine

Update method

procedure Update(ID: Integer; const Caption, Resource: string; StartsAt, EndsAt: TDateTime; Color: Integer);

Replaces every field of appointment ID at once. Validates the interval and, unless AllowOverlap is True, checks the new interval against all other appointments of the new Resource (the appointment itself is ignored). Raises EAtlasScheduleError for an unknown ID or a conflict; when it raises, the appointment is unchanged. Fires the listeners and OnChange on success.

pascal
Item := Schedule.Appointment(ID);
Schedule.Update(ID, Item.Caption, Item.Resource, Item.StartsAt + 1/24, Item.EndsAt + 1/24, Item.Color); // one hour later

Delete method

procedure Delete(ID: Integer);

Removes appointment ID. Other IDs keep their values and the deleted ID is not reused. Raises EAtlasScheduleError for an unknown ID. Fires the listeners and OnChange.

Clear method

procedure Clear;

Removes every appointment and notifies listeners and OnChange. The ID counter is not reset, so appointments added afterwards continue the sequence.

Appointment method

function Appointment(ID: Integer): TAtlasAppointment;

Returns a copy of the appointment record with the given ID; changing the copy does not change the schedule (use Update). Raises EAtlasScheduleError for an unknown ID.

Query method

function Query(StartsAt, EndsAt: TDateTime; const Resource: string = ''): TArray<TAtlasAppointment>;

Returns every appointment whose interval overlaps the half-open interval [StartsAt, EndsAt), sorted by start time and then by ID. An empty Resource matches all resources; otherwise only that resource is returned. An appointment that ends exactly at StartsAt or starts exactly at EndsAt is not included. The query interval itself is validated like an appointment interval and raises EAtlasScheduleError when it is not increasing.

pascal
for Item in Schedule.Query(Day, Day + 1, 'Room A') do   // everything in Room A on that day
  Writeln(Item.Caption);

CanPlace method

function CanPlace(StartsAt, EndsAt: TDateTime; const Resource: string; IgnoreID: Integer = 0): Boolean;

True when no appointment of Resource, other than IgnoreID, overlaps [StartsAt, EndsAt). Pass the ID of the appointment you are about to move as IgnoreID. The answer does not depend on AllowOverlap, so you can still detect double bookings in a schedule that permits them. Raises EAtlasScheduleError when the interval is invalid.

pascal
if Schedule.CanPlace(Slot, Slot + 1/24, 'Room A') then
  Schedule.Add('Interview', 'Room A', Slot, Slot + 1/24);

ToJSON method

function ToJSON: string;

Serializes the appointments as a JSON array of objects with the keys id, caption, resource, start, end (both TDateTime numbers) and color, for example [{"id":1,"caption":"Stand-up","resource":"Room A","start":46286.375,"end":46286.3958333333,"color":12161078}]. This is an Atlas-specific format, not iCalendar.

LoadJSON method

procedure LoadJSON(const Text: string);

Replaces the appointments with the array produced by ToJSON. The import is staged: every object must be a JSON object with all six keys, an id between 1 and MaxInt - 1 that is unique within the text, a valid interval and, unless AllowOverlap is True, no conflict with the other imported appointments. If anything fails, EAtlasScheduleError is raised and the current appointments are untouched. On success the ID counter moves past the largest imported ID and the listeners and OnChange fire once.

pascal
try
  Schedule.LoadJSON(TFile.ReadAllText('schedule.json'));
except
  on E: EAtlasScheduleError do
    ShowMessage('Schedule file rejected: ' + E.Message);   // nothing was changed
end;

AddListener method

procedure AddListener(const Listener: TNotifyEvent);

Registers a TNotifyEvent that is called with the schedule as Sender after every change, before OnChange. Adding the same method of the same object twice keeps a single registration; a nil listener is ignored. Views use this so that several controls can share one model.

RemoveListener method

procedure RemoveListener(const Listener: TNotifyEvent);

Removes a listener registered with AddListener (matched by method and object). Call it before freeing the listening object if it may outlive the schedule's notifications.

Examples

Book a room and check a slot

Two touching appointments are accepted; a third one overlapping the second is refused for the same resource but allowed for another. The query uses the half-open interval [09:00, 10:00).

pascal
uses System.SysUtils, Atlas.Schedule;

procedure BookRoom;
var
  Schedule: TAtlasSchedule;
  Day: TDateTime;
  Item: TAtlasAppointment;
begin
  Schedule := TAtlasSchedule.Create(nil);
  try
    Day := EncodeDate(2026, 9, 21);
    Schedule.Add('Stand-up', 'Room A', Day + 9/24, Day + 9.5/24);
    Schedule.Add('Review', 'Room A', Day + 9.5/24, Day + 10.5/24);      // touching is allowed
    Writeln(Schedule.CanPlace(Day + 10/24, Day + 11/24, 'Room A'));      // FALSE: overlaps Review
    Writeln(Schedule.CanPlace(Day + 10/24, Day + 11/24, 'Room B'));      // TRUE: another resource
    for Item in Schedule.Query(Day + 9/24, Day + 10/24) do                // half-open [09:00, 10:00)
      Writeln(Item.ID, ' ', Item.Caption);                                // 1 Stand-up, 2 Review
  finally
    Schedule.Free;
  end;
end;

TAtlasAppointment · TAtlasScheduler · EAtlasScheduleError

Also in Atlas VCL

TAtlasTable · TAtlasGrid · TAtlasValueEdit · TAtlasTree · TAtlasWorkbook · TAtlasSheetGrid · TAtlasPivot · TAtlasInspector · TAtlasRichEdit · TAtlasSpellChecker · TAtlasChart · TAtlasGauge · TAtlasThemer · TAtlasWizard · TAtlasWorkspace · TAtlasCommandBar · TAtlasActionTile · TAtlasTileBoard · TAtlasNavigation · TAtlasMap · TAtlasGraph · TAtlasDiagram · TAtlasPrintDocument · TAtlasScheduler · TAtlasTaskPlan · TAtlasGantt · TAtlasPdfDocument · TAtlasPdfViewer