Table of Contents

Recurrency editor

TTMSFNCPlannerItemEditorRecurrency extends the built-in planner item editor with a recurrency panel. When connected to TTMSFNCPlanner.ItemEditor, the editing dialog gains two extra tabs — General (title, text, start/end time, full-day flag) and Recurrency (frequency, interval, day-of-week, range, and exception dates) — so users can define repeating events without any additional code.

Connecting the component

Drop a TTMSFNCPlannerItemEditorRecurrency on the form and assign it to the planner's ItemEditor property at design time or at runtime:

// Drop a TTMSFNCPlannerItemEditorRecurrency on the form and connect it
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Link the recurrency editor to the planner's item editor slot
  TMSFNCPlanner1.ItemEditor := TMSFNCPlannerItemEditorRecurrency1;

  // The built-in editing dialog now shows General and Recurrency tabs
  // whenever the user double-clicks an item or uses keyboard editing
end;

After the assignment, double-clicking an item (or using the keyboard shortcut when Interaction.EditingMode is emDialog) opens the extended dialog automatically.

Recurrency frequency

The component supports six frequencies, represented by TTMSFNCRecurrencyFrequency:

Value Meaning
rfHourly Repeats every N hours
rfDaily Repeats every N days
rfWeekly Repeats on selected days of the week every N weeks
rfMonthly Repeats on a day of the month every N months
rfYearly Repeats on a specific date every N years
rfNone One-time event (no recurrency)

The user selects the frequency and interval in the Recurrency tab. The range can be defined as a fixed number of occurrences or by an end date.

Storing the recurrency string

The recurrency pattern is serialised as a compact string (an iCalendar-style RRULE). When using TTMSFNCPlannerDatabaseAdapter, map this string to a database field using the adapter's Item.Recurrency property:

// Wire the database adapter and map the recurrency field
procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCPlanner1.Adapter := TMSFNCPlannerDatabaseAdapter1;
  TMSFNCPlanner1.ItemEditor := TMSFNCPlannerItemEditorRecurrency1;

  with TMSFNCPlannerDatabaseAdapter1.Item do
  begin
    DataSource  := DataSource1;
    DBKey       := 'Id';
    StartTime   := 'StartTime';
    EndTime     := 'EndTime';
    Title       := 'Title';
    Text        := 'Text';
    Resource    := 'Resource';
    Recurrency  := 'Recurrency';   // field that stores the RRULE string
  end;

  // Load initial items from the dataset
  TMSFNCPlannerDatabaseAdapter1.LoadItems;
end;

The adapter reads and writes the recurrency string automatically when items are loaded or saved. Exception dates (days excluded from the pattern) are also stored in the same field.

Working with recurrency strings programmatically

Use TTMSFNCRecurrencyHandler to parse an existing recurrency string, iterate over the generated dates, or compose a new RRULE string without the editor UI:

// Parse and inspect a recurrency string using TTMSFNCRecurrencyHandler
procedure TForm1.InspectRecurrency(const ARRule: string);
var
  Handler: TTMSFNCRecurrencyHandler;
  StartDate, EndDate: TDateTime;
begin
  Handler := TTMSFNCRecurrencyHandler.Create;
  try
    Handler.Recurrency := ARRule;
    Handler.StartTime  := EncodeDate(2025, 1, 6);  // Monday
    Handler.EndTime    := EncodeDate(2025, 1, 6);
    Handler.Parse;

    if Handler.IsRecurrent then
    begin
      Memo1.Lines.Add('Frequency : ' + IntToStr(Ord(Handler.Frequency)));
      Memo1.Lines.Add('Interval  : ' + IntToStr(Handler.Interval));
      Memo1.Lines.Add('Occurrences:');
      StartDate := 0;
      EndDate   := 0;
      while Handler.NextDate(StartDate, EndDate) do
        Memo1.Lines.Add(DateToStr(StartDate));
    end;
  finally
    Handler.Free;
  end;
end;

// Build a recurrency string programmatically
procedure TForm1.BuildWeeklyRRule;
var
  Handler: TTMSFNCRecurrencyHandler;
begin
  Handler := TTMSFNCRecurrencyHandler.Create;
  try
    Handler.Frequency    := rfWeekly;
    Handler.Interval     := 1;
    Handler.RepeatCount  := 10;     // 10 occurrences
    Handler.Days         := [2, 4]; // Tuesday = 2, Thursday = 4 (1-based)
    Handler.StartTime    := EncodeDate(2025, 1, 6);
    Handler.EndTime      := EncodeDate(2025, 1, 6);
    Handler.Generate;

    ShowMessage(Handler.Compose);
  finally
    Handler.Free;
  end;
end;

Key TTMSFNCRecurrencyHandler members:

Member Type Description
Recurrency string The RRULE string to parse or the output of Compose
Frequency TTMSFNCRecurrencyFrequency Pattern frequency
Interval Integer Number of units between repetitions
RepeatCount Integer Maximum number of occurrences (0 = use RepeatUntil)
RepeatUntil TDateTime End date for the recurrence series
Days TTMSFNCRecurrencyHandlerDaySet Set of week-day bytes (1 = Sunday … 7 = Saturday)
ExDates TTMSFNCRecurrencyDateItems Exception date ranges excluded from the pattern
Parse method Parses Recurrency into the structured fields
Generate method Populates Dates from the structured fields
Compose method Serialises the structured fields back to a string
IsRecurrent method Returns True when Recurrency encodes a repeating pattern
NextDate method Advances to the next start/end date pair; returns False when exhausted

Getting and setting the recurrency string on the editor directly

If you need to pre-populate the editor or read back the current pattern without going through a database adapter, call SetRecurrency and GetRecurrency on the component:

// Pre-populate for a weekly event every Monday
TMSFNCPlannerItemEditorRecurrency1.SetRecurrency(
  'RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO');

// Read the pattern the user configured
var
  RRule: string;
begin
  RRule := TMSFNCPlannerItemEditorRecurrency1.GetRecurrency;
end;

Combining the editor, programmatic strings, and a recurrency handler

Pre-populate the editor with a known RRULE, let the user adjust it, then iterate the resulting dates using TTMSFNCRecurrencyHandler:

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCPlannerItemEditorRecurrency1.SetRecurrency(
    'RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR');
  TMSFNCPlanner1.ItemEditor := TMSFNCPlannerItemEditorRecurrency1;
end;

procedure TForm1.ButtonPreviewClick(Sender: TObject);
var
  h: TTMSFNCRecurrencyHandler;
  startDT, endDT: TDateTime;
begin
  h := TTMSFNCRecurrencyHandler.Create;
  try
    h.Recurrency := TMSFNCPlannerItemEditorRecurrency1.GetRecurrency;
    h.Parse;
    h.Generate;
    while h.NextDate(startDT, endDT) do
      Memo1.Lines.Add(DateTimeToStr(startDT));
  finally
    h.Free;
  end;
end;