Skip to main content
Back to examples
Popular features/Work calendars and holidays
Loading live demo…from dhtmlxcode.com
Popular features

Work calendars and holidays

Give each crew its own working week, shade every row by its own calendar, and stop all of them with one holiday.

gantt.plugins({ tooltip: true, marker: true });

gantt.message({
	text: "Task calendars define working days. Company holidays stop every crew.",
	expire: -1
});

gantt.config.date_format         = "%Y-%m-%d";
gantt.config.auto_types          = true;
gantt.config.open_tree_initially = true;
gantt.config.grid_resize         = true;
gantt.config.work_time           = true;
gantt.config.row_height          = 40;
gantt.config.bar_height          = 20;

let gridFmt = gantt.date.date_to_str("%d %M");

/* ---------------------------------------------------------------------------
 *  CALENDARS  (worktime.days index: 0=Sun .. 6=Sat; 1 = working)
 * ------------------------------------------------------------------------ */
let CALS = {
	"":        { label: "Office (Mon-Fri)", color: "#0ea5e9", nwClass: "nw"      },
	"six_day": { label: "Site (Mon-Sat)",   color: "#f59e0b", nwClass: "nw_six"  },
	"four_day":{ label: "Crew (Mon-Thu)",   color: "#7e57c2", nwClass: "nw_four" },
	"weekend": { label: "Weekend crew",     color: "#16a34a", nwClass: ""        }
};

/* default calendar (Mon-Fri): tune the built-in one */
gantt.setWorkTime({ day: 0, hours: false });   // Sunday off
gantt.setWorkTime({ day: 6, hours: false });   // Saturday off

gantt.addCalendar({ id: "six_day",  worktime: { hours: [8, 18], days: [0, 1, 1, 1, 1, 1, 1] } }); // Sun off
gantt.addCalendar({ id: "four_day", worktime: { hours: [7, 17], days: [0, 1, 1, 1, 1, 0, 0] } }); // Fri/Sat/Sun off
gantt.addCalendar({ id: "weekend",  worktime: { hours: [8, 18], days: [1, 1, 1, 1, 1, 1, 1] } });

/* company-wide holidays on every calendar */
let HOLIDAYS = [
	{ date: "2026-07-14", label: "Founders Day" },
	{ date: "2026-07-20", label: "Maintenance shutdown" }
];   // shown + treated as non-working when enabled
function applyHolidays(on) {
	HOLIDAYS.forEach(function (h) {
		let d = gantt.date.str_to_date("%Y-%m-%d")(h.date);
		[gantt, gantt.getCalendar("six_day"), gantt.getCalendar("four_day"), gantt.getCalendar("weekend")].forEach(function (cal) {
			cal.setWorkTime({ date: d, hours: on ? false : true });
		});
	});
}

/* ---------------------------------------------------------------------------
 *  DATASET
 * ------------------------------------------------------------------------ */
let demoData = {
	tasks: [
		{ id: 1, text: "AI edge cloud launch", type: "project", open: true },

		{ id: 10, text: "Platform foundation", type: "project", parent: 1, open: true },
		{ id: 11, text: "Architecture lock",          parent: 10, start_date: "2026-07-06", duration: 4,  calendar_id: "",         progress: 1    },
		{ id: 12, text: "Private cloud landing zone", parent: 10, start_date: "2026-07-10", duration: 7,  calendar_id: "six_day",  progress: 0.75 },
		{ id: 13, text: "Kubernetes GPU pool",        parent: 10, start_date: "2026-07-18", duration: 8,  calendar_id: "six_day",  progress: 0.55 },
		{ id: 14, text: "Service mesh rollout",       parent: 10, start_date: "2026-07-24", duration: 6,  calendar_id: "four_day", progress: 0.25 },
		{ id: 15, text: "Observability backbone",     parent: 10, start_date: "2026-07-29", duration: 7,  calendar_id: "",         progress: 0.15 },
		{ id: 16, text: "Foundation ready", type: "milestone", parent: 10, start_date: "2026-08-07", progress: 0 },

		{ id: 20, text: "Model and data pipeline", type: "project", parent: 1, open: true },
		{ id: 21, text: "Telemetry contract",         parent: 20, start_date: "2026-07-08", duration: 5,  calendar_id: "",         progress: 0.95 },
		{ id: 22, text: "Synthetic data generator",   parent: 20, start_date: "2026-07-15", duration: 9,  calendar_id: "four_day", progress: 0.5  },
		{ id: 23, text: "Feature store migration",    parent: 20, start_date: "2026-07-22", duration: 10, calendar_id: "six_day",  progress: 0.35 },
		{ id: 24, text: "Model training run A17",     parent: 20, start_date: "2026-08-01", duration: 6,  calendar_id: "weekend",  progress: 0.2  },
		{ id: 25, text: "Safety eval harness",        parent: 20, start_date: "2026-08-05", duration: 8,  calendar_id: "",         progress: 0    },
		{ id: 26, text: "Model candidate freeze", type: "milestone", parent: 20, start_date: "2026-08-17", progress: 0 },

		{ id: 30, text: "Edge device fleet", type: "project", parent: 1, open: true },
		{ id: 31, text: "Reference board bring-up",   parent: 30, start_date: "2026-07-07", duration: 8,  calendar_id: "six_day",  progress: 0.8  },
		{ id: 32, text: "Firmware OTA channel",       parent: 30, start_date: "2026-07-17", duration: 7,  calendar_id: "four_day", progress: 0.45 },
		{ id: 33, text: "Secure boot validation",     parent: 30, start_date: "2026-07-28", duration: 6,  calendar_id: "",         progress: 0.25 },
		{ id: 34, text: "Factory provisioning line",  parent: 30, start_date: "2026-08-03", duration: 9,  calendar_id: "six_day",  progress: 0.1  },
		{ id: 35, text: "Field pilot batch",          parent: 30, start_date: "2026-08-12", duration: 7,  calendar_id: "weekend",  progress: 0    },
		{ id: 36, text: "Pilot devices shipped", type: "milestone", parent: 30, start_date: "2026-08-22", progress: 0 },

		{ id: 40, text: "Customer control plane", type: "project", parent: 1, open: true },
		{ id: 41, text: "Tenant admin console",       parent: 40, start_date: "2026-07-13", duration: 8,  calendar_id: "",         progress: 0.7  },
		{ id: 42, text: "Realtime fleet map",         parent: 40, start_date: "2026-07-22", duration: 7,  calendar_id: "four_day", progress: 0.35 },
		{ id: 43, text: "Policy engine UI",           parent: 40, start_date: "2026-07-30", duration: 8,  calendar_id: "",         progress: 0.15 },
		{ id: 44, text: "Usage analytics workspace",  parent: 40, start_date: "2026-08-06", duration: 9,  calendar_id: "four_day", progress: 0    },
		{ id: 45, text: "Customer preview build", type: "milestone", parent: 40, start_date: "2026-08-21", progress: 0 },

		{ id: 50, text: "Security and compliance", type: "project", parent: 1, open: true },
		{ id: 51, text: "Threat model workshop",      parent: 50, start_date: "2026-07-09", duration: 3,  calendar_id: "",         progress: 1    },
		{ id: 52, text: "Secrets rotation drill",     parent: 50, start_date: "2026-07-18", duration: 5,  calendar_id: "weekend",  progress: 0.6  },
		{ id: 53, text: "SOC2 evidence automation",   parent: 50, start_date: "2026-07-27", duration: 8,  calendar_id: "four_day", progress: 0.25 },
		{ id: 54, text: "Pen test remediation",       parent: 50, start_date: "2026-08-07", duration: 9,  calendar_id: "six_day",  progress: 0.05 },
		{ id: 55, text: "Launch approval gate", type: "milestone", parent: 50, start_date: "2026-08-20", progress: 0 },

		{ id: 60, text: "Launch operations", type: "project", parent: 1, open: true },
		{ id: 61, text: "Runbook and on-call drills", parent: 60, start_date: "2026-08-03", duration: 6,  calendar_id: "",         progress: 0.15 },
		{ id: 62, text: "Blue/green traffic rehearsal", parent: 60, start_date: "2026-08-10", duration: 5, calendar_id: "weekend",  progress: 0    },
		{ id: 63, text: "Partner sandbox enablement", parent: 60, start_date: "2026-08-13", duration: 8,  calendar_id: "four_day", progress: 0    },
		{ id: 64, text: "Launch room staffing",       parent: 60, start_date: "2026-08-19", duration: 6,  calendar_id: "six_day",  progress: 0    },
		{ id: 65, text: "Public launch", type: "milestone", parent: 60, start_date: "2026-08-28", progress: 0 }
	],
	links: [
		{ id: 1,  source: 11, target: 12, type: "0" }, { id: 2,  source: 12, target: 13, type: "0" },
		{ id: 3,  source: 13, target: 14, type: "0" }, { id: 4,  source: 14, target: 15, type: "0" },
		{ id: 5,  source: 15, target: 16, type: "0" },

		{ id: 6,  source: 21, target: 22, type: "0" }, { id: 7,  source: 22, target: 23, type: "0" },
		{ id: 8,  source: 23, target: 24, type: "0" }, { id: 9,  source: 24, target: 25, type: "0" },
		{ id: 10, source: 25, target: 26, type: "0" },

		{ id: 11, source: 31, target: 32, type: "0" }, { id: 12, source: 32, target: 33, type: "0" },
		{ id: 13, source: 33, target: 34, type: "0" }, { id: 14, source: 34, target: 35, type: "0" },
		{ id: 15, source: 35, target: 36, type: "0" },

		{ id: 16, source: 41, target: 42, type: "0" }, { id: 17, source: 42, target: 43, type: "0" },
		{ id: 18, source: 43, target: 44, type: "0" }, { id: 19, source: 44, target: 45, type: "0" },

		{ id: 20, source: 51, target: 52, type: "0" }, { id: 21, source: 52, target: 53, type: "0" },
		{ id: 22, source: 53, target: 54, type: "0" }, { id: 23, source: 54, target: 55, type: "0" },

		{ id: 24, source: 61, target: 62, type: "0" }, { id: 25, source: 62, target: 63, type: "0" },
		{ id: 26, source: 63, target: 64, type: "0" }, { id: 27, source: 64, target: 65, type: "0" },

		{ id: 28, source: 16, target: 24, type: "0" }, { id: 29, source: 16, target: 35, type: "0" },
		{ id: 30, source: 26, target: 45, type: "0" }, { id: 31, source: 36, target: 63, type: "0" },
		{ id: 32, source: 45, target: 62, type: "0" }, { id: 33, source: 55, target: 65, type: "0" }
	]
};

/* ---------------------------------------------------------------------------
 *  GRID
 * ------------------------------------------------------------------------ */
function calCell(task) {
	if (!isLeafTask(task)) return "";
	let c = CALS[task.calendar_id || ""];
	return c ? "<span class='cal_chip'><i style='background:" + c.color + "'></i>" + c.label + "</span>" : "";
}
gantt.config.columns = [
	{ name: "text",       label: "Task", tree: true, width: 190, resize: true },
	{ name: "calendar",   label: "Work calendar", width: 160, resize: true, template: calCell },
	{ name: "start_date", label: "Start", align: "center", width: 100, resize: true, template: function (task) {
		return task.type === gantt.config.types.project ? "" : gridFmt(task.start_date);
	}},
	{ name: "duration",   label: "Days", align: "center", width: 56, resize: true, template: function (task) {
		return task.type === gantt.config.types.milestone ? "" : task.duration;
	}},
	{ name: "add", width: 40, resize: true }
];
gantt.config.grid_width = 520;

/* per-row non-working shading honoring each task's own calendar */
let highlightOn = true, holidaysOn = true;
let holidayMarkerIds = [];

function isLeafTask(task) {
	return !gantt.isSummaryTask(task) &&
		task.type !== gantt.config.types.project &&
		task.type !== gantt.config.types.milestone;
}

gantt.templates.timeline_cell_class = function (task, date) {
	let scales = gantt.config.scales;
	if (scales[scales.length - 1].unit !== "day") return "";
	if (holidaysOn && isHoliday(date)) return "nw_holiday";
	if (!highlightOn || !task) return "";
	let cls = CALS[task.calendar_id || ""] ? CALS[task.calendar_id || ""].nwClass : "nw";
	if (!cls) return "";   // Weekend crew has no regular days off
	return gantt.isWorkTime({ date: date, task: task }) ? "" : cls;
};
function isHoliday(date) {
	let key = gantt.date.date_to_str("%Y-%m-%d")(date);
	return HOLIDAYS.some(function (h) { return h.date === key; });
}

gantt.templates.tooltip_text = function (start, end, task) {
	if (task.type === gantt.config.types.project) return "<b>" + task.text + "</b>";
	let c = CALS[task.calendar_id || ""];
	return "<b>" + task.text + "</b><br>" + (c ? "Calendar: " + c.label + "<br>" : "") +
		gridFmt(start) + " &ndash; " + gridFmt(new Date(end.getTime() - 1));
};

/* lightbox: a calendar picker that refits the edited task on save */
gantt.locale.labels.section_calendar = "Work calendar";
gantt.config.lightbox.sections = [
	{ name: "description", height: 38, map_to: "text", type: "textarea", focus: true },
	{ name: "calendar", height: 22, map_to: "calendar_id", type: "select", options: Object.keys(CALS).map(function (k) { return { key: k, label: CALS[k].label }; }) },
	{ name: "time", type: "duration", map_to: "auto" }
];

/* fixed day scale: per-calendar shading only renders on a day scale */
gantt.config.scale_height = 50;
gantt.config.scales = [
	{ unit: "week", step: 1, format: function (d) { return gridFmt(d); } },
	{ unit: "day",  step: 1, format: "%D, %d" }
];

gantt.attachEvent("onLightboxSave", function (taskId, updatedTask, isNewTask) {
	if (!isLeafTask(updatedTask)) {
		return true;
	}

	updatedTask.start_date = gantt.getClosestWorkTime({
		date: updatedTask.start_date,
		dir: "future",
		unit: gantt.config.duration_unit,
		task: updatedTask
	});

	updatedTask.end_date = gantt.calculateEndDate({
		start_date: updatedTask.start_date,
		duration: updatedTask.duration,
		task: updatedTask
	});

	return true;
});

/* ---------------------------------------------------------------------------
 *  TOGGLES
 * ------------------------------------------------------------------------ */
function toggleHighlight() {
	highlightOn = !highlightOn;
	DHX.toggle.setById("hl_switch", highlightOn);
	gantt.render();
}
function refitTasksToCalendars() {
	gantt.batchUpdate(function () {
		gantt.eachTask(function (task) {
			if (!isLeafTask(task)) return;
			task.end_date = gantt.calculateEndDate({ start_date: task.start_date, duration: task.duration, task: task });
			gantt.updateTask(task.id);
		});
	});
}
function refreshHolidayMarkers() {
	holidayMarkerIds.forEach(function (id) {
		gantt.deleteMarker(id);
	});
	holidayMarkerIds = [];
	if (!holidaysOn) return;

	HOLIDAYS.forEach(function (h) {
		holidayMarkerIds.push(gantt.addMarker({
			start_date: gantt.date.str_to_date("%Y-%m-%d")(h.date),
			css: "company_holiday",
			text: "Company day off",
			title: h.label + ": all crews off"
		}));
	});
}
function toggleHolidays() {
	holidaysOn = !holidaysOn;
	DHX.toggle.setById("hol_switch", holidaysOn);
	applyHolidays(holidaysOn);
	refitTasksToCalendars();
	refreshHolidayMarkers();
	gantt.render();
}

/* ---------------------------------------------------------------------------
 *  INIT
 * ------------------------------------------------------------------------ */
applyHolidays(true);
gantt.init("gantt_here");
gantt.parse(demoData);
refreshHolidayMarkers();
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Work calendars &amp; company holidays: dhtmlxGantt</title>
	<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
	<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
	<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
	<script src="../common/demo-controls/dhx_controls.js"></script>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
	<style>
		body { display: flex; flex-direction: column; }
		#gantt_here { width: 100%; flex: 1 1 auto; min-height: 0; }

		.nw          { background: rgba(2,6,23,.05); }
		.nw_six      { background: rgba(245,158,11,.10); }
		.nw_four     { background: rgba(124,58,237,.10); }
		.nw_holiday  { background: repeating-linear-gradient(45deg, rgba(220,38,38,.18) 0 5px, rgba(220,38,38,.05) 5px 10px); }

		:root[data-gantt-theme="dark"] .nw         { background: rgba(255,255,255,.07); }
		:root[data-gantt-theme="dark"] .nw_six     { background: rgba(245,158,11,.20); }
		:root[data-gantt-theme="dark"] .nw_four    { background: rgba(167,139,250,.20); }
		:root[data-gantt-theme="dark"] .nw_holiday { background: repeating-linear-gradient(45deg, rgba(248,113,113,.26) 0 5px, rgba(248,113,113,.08) 5px 10px); }
		.gantt_marker.company_holiday { width: 2px; background: #dc2626; }
		.gantt_marker.company_holiday .gantt_marker_content {
			display: inline-flex;
			align-items: center;
			justify-content: center;
			height: 24px;
			padding: 0 9px;
			border-radius: 4px;
			background: #dc2626;
			color: #fff;
			font: 600 12px var(--dhx-font);
			line-height: 1;
			box-sizing: border-box;
			white-space: nowrap;
		}

		.cal_chip {
			display: inline-flex; align-items: center; gap: 6px; font: 600 12px var(--dhx-font); color: var(--dhx-ink-2);
		}
		.cal_chip i { width: 9px; height: 9px; border-radius: 3px; flex: none; }
		.footer_legend { display: inline-flex; gap: 16px; flex-wrap: wrap; }
		.footer_legend .lg { display: inline-flex; align-items: center; gap: 6px; }
		.footer_legend .sw { width: 14px; height: 10px; border-radius: 2px; display: inline-block; }
		.sw_nw      { background: rgba(2,6,23,.12); }
		.sw_six     { background: rgba(245,158,11,.35); }
		.sw_four    { background: rgba(124,58,237,.35); }
		.sw_holiday { background: rgba(220,38,38,.5); }
		.sw_work    { background: rgba(22,163,74,.30); }

		:root[data-gantt-theme="dark"] .sw_nw      { background: rgba(255,255,255,.16); }
		:root[data-gantt-theme="dark"] .sw_six     { background: rgba(245,158,11,.45); }
		:root[data-gantt-theme="dark"] .sw_four    { background: rgba(167,139,250,.50); }
		:root[data-gantt-theme="dark"] .sw_holiday { background: rgba(248,113,113,.60); }
		:root[data-gantt-theme="dark"] .sw_work    { background: rgba(74,222,128,.45); }
	</style>
</head>
<body>

<!-- Toolbar -->
<div class="dhx_header dhx_header--centered">
	<label class="dhx_toggle active" onclick="toggleHighlight()">
		<span>Highlight non-working time</span><span class="dhx_toggle__track on" id="hl_switch"></span>
	</label>
	<label class="dhx_toggle active" onclick="toggleHolidays()">
		<span>Company holidays</span><span class="dhx_toggle__track on" id="hol_switch"></span>
	</label>
</div>

<div id="gantt_here"></div>

<!-- Footer -->
<div class="dhx_footer">
	<div class="footer_legend">
		<span class="lg"><i class="sw sw_nw"></i> Weekend (Mon-Fri crews)</span>
		<span class="lg"><i class="sw sw_six"></i> Sunday off (6-day crew)</span>
		<span class="lg"><i class="sw sw_four"></i> Fri-Sun off (4-day crew)</span>
		<span class="lg"><i class="sw sw_holiday"></i> Company holiday: all crews off</span>
		<span class="lg"><i class="sw sw_work"></i> Weekend crew works Sat-Sun</span>
	</div>
</div>


</body>
</html>