Resources Numerical accuracy Calculation policy

Construction schedule dates and critical path

Reads and writes a project schedule, and works out each task's earliest and latest dates, its float and the critical path.

How the precision is kept

Dates are whole day numbers since 1970-01-01 UTC and every duration, lag and float is a whole number of days, so the critical path arithmetic is integer addition, subtraction, minimum and maximum with no rounding. The one conversion is on import, where a lag given in tenths of a minute is rounded to whole days.

Calculations under this policy

5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

schedule-io.js BimCraft 0.8.1 64-bit 5 calculations Reviewed

Tests tools/bimcraft-test/schedule-test.mjs

Task duration

Exact

A task's duration in days, counting its start and finish day.

duration = finish − start + 1

Whole-number arithmetic on day numbers.

  • schedule-io.js
  • line 29
  • BimCraft 0.8.1
const dur = (t) => t.finish - t.start + 1;

Date read from a project file

Exact

Turns a date and time from a project XML file into a day number.

day = date part of the text

Only the first 10 characters are read, so the time of day is dropped: a task that finishes at 08:00 counts that whole day.

  • schedule-io.js
  • lines 59 to 60
  • BimCraft 0.8.1
/** "2026-11-02T08:00:00" → day number (the date part only; the file's clock times do not matter to a day plan). */
const dayOf = (s) => parseDay(String(s).slice(0, 10));

Link lag read from a project file

64-bit

Converts a dependency's lag from tenths of a minute to whole days.

lag (days) = Math.round(LinkLag ÷ 10 ÷ minutes per day)

The lag is rounded to the nearest whole day, so a lag of less than half a working day becomes 0. The file's LinkLagFormat is read and not used, so an elapsed-time lag is converted at the working-day length too.

  • schedule-io.js
  • lines 84 to 87
  • BimCraft 0.8.1
const lagMin = (+val(p, 'LinkLag') || 0) / 10;
void fmt;
if (!(ty >= 0 && ty <= 3) || !val(p, 'PredecessorUID')) { errors.push({ line: 0, message: 'link', task: name }); continue; }
preds.push({ uid: val(p, 'PredecessorUID'), type: TYPES[ty], lag: Math.round(lagMin / perDay) });

Early and late dates, float and the critical path

Exact

Each task's earliest start and finish, latest start and finish, float, and whether it is critical.

ES = max(own start, ES_pred + d); EF = ES + dur − 1; LS = min(finish − dur + 1, LS_succ − d); float = LS − ES

Only whole-number addition, subtraction, minimum and maximum, so a float of exactly 0 is a reliable test for a critical task. A task never starts before its own given start.

  • schedule-io.js
  • lines 212 to 224
  • BimCraft 0.8.1
for (const t of order) {
  t.es = t.start;                                                    // a task may not start before its own Start (it is the constraint)
  for (const e of inn.get(t)) t.es = Math.max(t.es, e.from.es + e.d);
  t.ef = t.es + dur(t) - 1;
}
const finish = list.length ? Math.max(...list.map((t) => t.ef)) : null;
for (const t of [...order].reverse()) {
  t.ls = finish - dur(t) + 1;
  for (const e of out.get(t)) t.ls = Math.min(t.ls, e.to.ls - e.d);
  t.lf = t.ls + dur(t) - 1;
  t.float = t.ls - t.es;
  t.critical = t.float === 0;
}

In detail

Method
Critical path method on calendar days. Each link is one inequality start(successor) ≥ start(predecessor) + d, with d from the link type and lag. Forward pass in topological order, then a backward pass from the latest finish.
Number types
Whole numbers held in 64-bit numbers. Durations count both the start and the finish day.
Limits
Only the date part of a start or finish is read; clock times are dropped. A lag is rounded to whole days at 480 minutes a day unless the file says otherwise, and the file's lag format field is not used. Every calendar day counts as a working day.

Approach: Exact values

Constants and factors are held as exact fractions from their definitions. The arithmetic is ordered so that no rounded factor is ever used.

Every policy that takes this approach