Load all task dates#
Get the start, end and duration of every scheduled task from one solve, for example to fill your UI when a project first opens.
By default, Updates in a ScheduleResult is sparse: it lists only the tasks that moved. Set SolveOptions.IncludeUnchanged and the same solve also reports the tasks that didn't move. You don't need a separate call.
Steps#
-
Create the solve options with
IncludeUnchangedset totrue:var options = new SolveOptions { IncludeUnchanged = true }; -
Pass the options to the solve that loads the data. Use
Project.Solvewith yourProjectDatafor a long-lived project, orEngine.Schedulefor a one-shot job. -
Read every entry of
result.Updates. EachMutationhasStartDate,EndDateandDurationset.
The full example below schedules three tasks. review doesn't move, but it's still reported:
using Plen;
var jan1 = Moment.FromYmdHm(2026, 1, 1, 0, 0) ?? throw new InvalidOperationException("invalid date");
var jan5 = Moment.FromYmdHm(2026, 1, 5, 0, 0) ?? throw new InvalidOperationException("invalid date");
var data = new ProjectData
{
Tasks =
[
new TaskData("design") { StartDate = jan1, Duration = 2.0 },
new TaskData("build") { StartDate = jan1, Duration = 1.0 },
new TaskData("review") { StartDate = jan5, Duration = 1.0 },
],
Links = [new LinkData { Id = "design-build", Source = "design", Target = "build" }],
};
// 24/7 time keeps the example dates easy to check.
data.Config.UseCalendars = false;
var options = new SolveOptions { IncludeUnchanged = true };
var project = new Project();
var result = project.Solve(data, options);
foreach (var m in result.Updates)
{
Console.WriteLine($"{m.Id} {m.StartDate}..{m.EndDate} {m.Duration}");
}
Get the full list from an already solved project#
To reload every date later, call Solve(null, …) with the same options. On a solved project with no pending edits, the call doesn't replan or change the accepted state, and BaselineId stays the same:
var allDates = project.Solve(null, options);
Switch back to sparse results for the edits that follow. A default SolveOptions returns only what changed:
var next = project.Solve(null, new SolveOptions());
The result cache uses the whole SolveOptions as its key, compared by value. An IncludeUnchanged call never gets a cached sparse result, and a sparse call never gets a cached full one. Repeating a call with equal options returns the same ScheduleResult object, so treat it as read-only, or call DeepClone() before you change it.
What is and isn't reported#
- The result includes every scheduled task: leaf tasks, summaries and milestones. Check
Mutation.Kindto tell them apart. - Tasks with
Unscheduled = trueare never reported. LevelingDelayandPausesare alwaysnull, because a solve doesn't change them.BaselineIdfollows real changes only. Asking for unchanged tasks doesn't create a new baseline.- With
Subtree, the result holds every task in the subtree, including the subtree tasks' ancestors. Tasks outside the subtree appear only when a sparse subtree solve would report them too, because they changed or were edited. - On a project with a dependency cycle, nothing is scheduled. The result still lists every scheduled task, in model order, with its dates and duration as stored.
Subtreeis ignored, and no entry has aReason, even withReasons = true. Project.PreviewandProject.Levelalways return sparse updates.
Result#
The example prints one line per task, including review, which didn't move (the lines may come in a different order):
design 2026-01-01 00:00..2026-01-03 00:00 2
build 2026-01-03 00:00..2026-01-04 00:00 1
review 2026-01-05 00:00..2026-01-06 00:00 1
Without IncludeUnchanged, the same solve reports only build.
See Reading results for how to write the dates back into your model.