Skip to content

Working calendar

The calendar says which days are working days; the rest of the chart reads it. Days off are shaded, a duration can count working days only, and a drag can land on a working day instead of a day off. Here the week is Monday to Thursday in June (Saturday, Sunday and, by a rule of its own, Friday are off), Wednesday 10 June is a holiday and Saturday 13 June is worked anyway.

Terminal window
pnpm add @ganttlane/dom # vanilla
pnpm add @ganttlane/react react react-dom # React
View on GitHub
data.ts
import type { Link, Task } from '@ganttlane/core';
// The calendar (see `options.ts`): Saturday and Sunday are off, so are Fridays in June (a custom
// rule), Wednesday 10 June is a holiday, and Saturday 13 June is worked. 2026-06-08 is a Monday.
//
// A task with a `duration` and no `end` lasts that many days, counted as `durationMode` says:
// 'working' working days only: days off inside the task do not count, so it is longer;
// 'calendar' every day counts, a day off or not.
// A task with no `durationMode` of its own takes `scheduling.durationMode`, which is 'working'.
export const tasks: Task[] = [
// 3 working days: Mon 8, Tue 9 and Thu 11; the holiday on Wed 10 is skipped. Ends Thu 11.
{ id: 'design', name: 'Design', start: '2026-06-08', duration: 3, progress: 50 },
// 5 working days: Mon 8, Tue 9, Thu 11, Sat 13 (a worked Saturday) and Mon 15. The holiday, the
// Friday and the Sunday are skipped. Ends Mon 15, 8 calendar days after it starts.
{ id: 'build', name: 'Build', start: '2026-06-08', duration: 5, progress: 40 },
// 3 working days: Tue 16, Wed 17, Thu 18.
{ id: 'review', name: 'Review', start: '2026-06-16', duration: 3 },
// 5 working days from Thu 18: Thu 18, Mon 22, Tue 23, Wed 24 and Thu 25. It spans a weekend.
{ id: 'handover', name: 'Handover', start: '2026-06-18', duration: 5 },
// The same 5 days, counted in calendar days: Thu 18 to Mon 22. This task says so itself, so it
// ignores `scheduling.durationMode`.
{
id: 'on-call',
name: 'On-call',
start: '2026-06-18',
duration: 5,
durationMode: 'calendar',
progress: 20,
},
// One working day, and it is the worked Saturday: a task may sit on it, and is not in conflict.
{ id: 'hotfix', name: 'Hotfix', start: '2026-06-13', duration: 1 },
];
export const links: Link[] = [];

Files

For a calendar at one-hour granularity, with hours of the day, see Working days at one-hour granularity.

Options

Option Value What it does
calendar.weekends [0, 6] Days of the week that are off, 0 is Sunday (the default)
calendar.holidays [{ date, name }] A named day off; the name shows when you hover the day
calendar.workingDays ['2026-06-13'] A day that is worked whatever the other rules say (here a Saturday)
calendar.isWorkingDay a function Your own rule: true, false, or undefined for “ask the next rule”
scheduling.durationMode 'working' How a duration is counted, for the tasks that say nothing (the select)
scheduling.skipNonWorkingDays { move, resize, progress } Whether an edited edge moves off a day off (the three boxes)
task.durationMode 'calendar' on On-call A task counts its own duration its own way
viewModes { day: { pxPerDay: 32 } } A narrower day column than the default 40 px, so that the plan fits
onTasksChange a function (in the example) One ChangeSet per edit; the example reads the edited task’s record

What to try

  • Look at the lengths. Build, Handover and On-call all last five days, but Build and Handover count working days (the holiday, the Fridays and the weekends do not count, the worked Saturday does) and On-call counts calendar days.
  • Switch Durations count to calendar. Design, Build and Handover become shorter, and Design and Build now end on a day off: they are drawn with a red outline, and record.conflicts has a non-working-end for each. On-call does not change.
  • Drag Review three days to the right, so that it would start on a Friday. With Skip days off: move ticked, the start moves on to Monday, and the bar keeps its three working days. Untick the box and drag again: the bar stays on the Friday, and the status line reports a non-working-start conflict.
  • Drag the right edge of Build one day to the left, onto Sunday. With resize ticked the end goes back to Saturday, the last working day; with it unticked the bar ends on Sunday, with a non-working-end conflict.
  • Drag the progress handle of On-call (the little handle at the foot of the done part) into the weekend. With progress ticked the boundary jumps to the edge of the days off; without it, it rests on one. On-call counts calendar days: in a task that counts working days the boundary never rests on a day off, whatever the box says.
  • Hover a shaded day for the name of the holiday.

Notes

  • Which rule decides a day. workingDays first, then holidays, then isWorkingDay, then weekends. A rule that returns undefined passes the day on. It must be pure (the answer is cached for the life of the calendar), and it is given a CalendarDay: weekday, month, day, ….
  • durationMode. A working duration skips days off and keeps counting until it has its days; duration.workingDays and duration.calendarDays of the record say both. A calendar duration counts every day, so a task can start or end on a day off: that is a non-working-start or non-working-end conflict, a warning, not an error.
  • skipNonWorkingDays. move is on by default, resize is not, and progress is on. It works on a drag: updateTask and propagation through links put the dates exactly where you say.
  • A conflict is a warning. It is drawn (data-conflict), listed in record.conflicts and changeSet.conflicts, and passed to onBeforeTaskChange, as in Propagation and conflicts.
  • React. scheduling is passed to <Gantt> after the spread of options, from state: in React the props are the truth, so an option changed with setOptions on the ref would be overwritten by the next render.