Task duration
ExactA task's duration in days, counting its start and finish day.
duration = finish − start + 1
Whole-number arithmetic on day numbers.
const dur = (t) => t.finish - t.start + 1;
Resources Numerical accuracy Calculation policy
Reads and writes a project schedule, and works out each task's earliest and latest dates, its float and the critical path.
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.
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.
Tests tools/bimcraft-test/schedule-test.mjs
A task's duration in days, counting its start and finish day.
duration = finish − start + 1
Whole-number arithmetic on day numbers.
const dur = (t) => t.finish - t.start + 1;
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.
/** "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));
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.
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) });
Turns each link type and lag into the least number of days between the two tasks' starts.
FS: d = dur_pred + lag; SS: lag; FF: dur_pred + lag − dur_succ; SF: lag − dur_succ
Whole-number sums of durations and lags.
const da = dur(a), ds = dur(t), lag = p.lag || 0;
const d = p.type === 'SS' ? lag : p.type === 'FF' ? da + lag - ds : p.type === 'SF' ? lag - ds : da + lag;
edges.push({ from: a, to: t, d });
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.
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;
}
Constants and factors are held as exact fractions from their definitions. The arithmetic is ordered so that no rounded factor is ever used.