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::include_unchanged and the same solve also reports the tasks that didn't move. You don't need a separate call.

Steps#

  1. Build the solve options with include_unchanged set to true:

    use plen_core::SolveOptions;
    
    let options = SolveOptions {
        include_unchanged: true,
        ..Default::default()
    };
    
  2. Pass the options to the solve that loads the data. Use Project::solve with Some(data) for a long-lived project, or schedule for a one-shot job.

  3. Read every entry of result.updates. Each entry has start_date, end_date and duration set.

The full example below schedules three tasks. review doesn't move, but it's still reported:

use plen_core::{DateTime, LinkData, LinkType, Project, ProjectData, SolveOptions, TaskData};

fn task(id: &str, start: DateTime, duration: f64) -> TaskData {
    let mut t = TaskData::new(id);
    t.start_date = Some(start);
    t.duration = Some(duration);
    t
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let jan1 = DateTime::from_ymd_hm(2026, 1, 1, 0, 0).ok_or("invalid date")?;
    let jan5 = DateTime::from_ymd_hm(2026, 1, 5, 0, 0).ok_or("invalid date")?;

    let mut data = ProjectData {
        tasks: vec![
            task("design", jan1, 2.0),
            task("build", jan1, 1.0),
            task("review", jan5, 1.0),
        ],
        links: vec![LinkData {
            id: "design-build".into(),
            source: "design".into(),
            target: "build".into(),
            link_type: LinkType::Fs,
            lag: 0.0,
            lag_unit: None,
        }],
        ..Default::default()
    };
    // 24/7 time keeps the example dates easy to check.
    data.config.use_calendars = false;

    let options = SolveOptions {
        include_unchanged: true,
        ..Default::default()
    };

    let mut project = Project::default();
    let result = project.solve(Some(data), &options)?;

    for m in &result.updates {
        if let (Some(start), Some(end), Some(duration)) = (m.start_date, m.end_date, m.duration) {
            println!("{} {}..{} {}", m.id, start, end, duration);
        }
    }
    Ok(())
}

Get the full list from an already solved project#

To reload every date later, call solve(None, ...) with the same options. On a solved project with no pending edits, the call doesn't replan or change the accepted state, and baseline_id stays the same:

let all_dates = project.solve(None, &options)?;

Switch back to sparse results for the edits that follow. SolveOptions::default() returns only what changed:

let next = project.solve(None, &SolveOptions::default())?;

The result cache uses the whole SolveOptions as its key. An include_unchanged call never gets a cached sparse result, and a sparse call never gets a cached full one.

What is and isn't reported#

  • The result includes every scheduled task: leaf tasks, summaries and milestones. Check Mutation::kind to tell them apart.
  • Tasks with unscheduled: true are never reported.
  • leveling_delay and pauses never appear, because a solve doesn't change them.
  • baseline_id follows 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. subtree is ignored, and no entry has a reason, even with reasons: true.
  • Project::preview and Project::level always 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 include_unchanged, the same solve reports only build.

See Reading results for how to write the dates back into your model.