YOUSEFUL / DELPHI / COMPONENTS
TAtlasSchedule
In-memory appointment model with stable IDs, per-resource conflict checking, interval queries, change notification and transactional JSON.
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
| Member | Kind | What it does |
|---|---|---|
Create | constructor | Creates an empty schedule. |
Destroy | destructor | Frees the appointment and listener lists. |
Add | method | Adds an appointment and returns its new ID. |
Update | method | Replaces every field of appointment ID at once. |
Delete | method | Removes appointment ID. |
Clear | method | Removes every appointment and notifies listeners and OnChange. |
Appointment | method | Returns a copy of the appointment record with the given ID; changing the copy does not change the schedule (use Update). |
Query | method | Returns every appointment whose interval overlaps the half-open interval [StartsAt, EndsAt), sorted by start time and then by ID. |
CanPlace | method | True when no appointment of Resource, other than IgnoreID, overlaps [StartsAt, EndsAt). |
ToJSON | method | 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}]. |
LoadJSON | method | Replaces the appointments with the array produced by ToJSON. |
AddListener | method | Registers a TNotifyEvent that is called with the schedule as Sender after every change, before OnChange. |
RemoveListener | method | Removes a listener registered with AddListener (matched by method and object). |
Count | property | Number of appointments in the schedule. |
AllowOverlap | property | When False (the default), Add, Update and LoadJSON reject an appointment that overlaps another one of the same resource. |
OnChange | event | Fires 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.
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 fineUpdate 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.
Item := Schedule.Appointment(ID);
Schedule.Update(ID, Item.Caption, Item.Resource, Item.StartsAt + 1/24, Item.EndsAt + 1/24, Item.Color); // one hour laterDelete 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.
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.
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.
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).
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;Related
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