There might be events that span over a weekend or another time period marked as non-business. In such cases, you may want to exclude the non-business hours from the total event duration.
The following features can assist you in implementing this scenario:
You can highlight the segments of the event that fall during non-business hours.
When moving the event, the Scheduler can automatically extend its duration to skip the non-business hours.
You can also adjust the event duration during creation and resizing using custom logic.
Highlighting the Non-Business Segments

You can highlight the non-business segments using the onBeforeEventRender event handler (see also event active areas). This example highlights Saturday and Sunday, preserving any existing active areas. It uses the default eventEndSpec: "DateTime" (exclusive end) and does not determine business hours from the row or cell settings:
onBeforeEventRender: (args) => {
const weekends = app.getWeekendsInRange(args.data.start, args.data.end);
const weekendAreas = weekends.map((w) => {
return {
start: w.start,
end: w.end,
top: 4,
bottom: 1,
cssClass: "weekend"
};
});
args.data.areas = [...(args.data.areas || []), ...weekendAreas];
},The app.getWeekendsInRange() function returns the portions of weekends that overlap the supplied range. It handles events starting on Sunday, clips both boundaries to the event, and excludes a weekend that starts exactly at the event end:
const app = {
getWeekendsInRange(start, end) {
const rangeStart = new DayPilot.Date(start);
const rangeEnd = new DayPilot.Date(end);
const weekends = [];
if (rangeEnd <= rangeStart) {
return weekends;
}
let current = rangeStart.getDatePart();
if (current.getDayOfWeek() === 0) {
current = current.addDays(-1); // Include the weekend already in progress.
}
while (current < rangeEnd) {
if (current.getDayOfWeek() === 6) { // Saturday
const weekendEnd = current.addDays(2);
const clippedStart = current < rangeStart ? rangeStart : current;
const clippedEnd = weekendEnd > rangeEnd ? rangeEnd : weekendEnd;
if (clippedStart < clippedEnd) {
weekends.push({start: clippedStart, end: clippedEnd});
}
current = current.addDays(7);
} else {
current = current.addDays(1);
}
}
return weekends;
}
};And the weekend CSS class that adds semi-transparent stripes:
.weekend {
background-image: repeating-linear-gradient(45deg, #00000033, #00000033 10px, transparent 10px, transparent 20px);
}Skipping Non-Business Hours during Event Moving
The Scheduler can preserve an event's duration measured in business hours while it is moved. Enable this behavior using the eventMoveSkipNonBusiness property:
{
// ...
eventMoveSkipNonBusiness: true,
}In this case, the Scheduler will calculate the event duration based on business hours and will automatically adjust (either prolong or shorten) the event as necessary during drag and drop.
The following time segments will not be included in the new duration:
individual cells marked as non-business using the
onBeforeCellRenderevent handler (see cell customization)
Moves That Cannot Preserve Business Duration
When eventMoveSkipNonBusiness is enabled, moving an event preserves its number of business hours. The original resource’s calendar determines that duration; the target resource’s calendar determines the new end. Since Pro version 2026.4.7083, custom business hours also apply to dates outside the visible area and to frozen rows.
The Scheduler rejects a move if the source has zero business duration or the target lacks enough business time within its row and timeline bounds. It displays a forbidden shadow and prevents the drop. It does not add an explanation label automatically.
For optional feedback, inspect read-only args.invalid in onEventMoving:
onEventMoving: args => {
if (args.invalid) {
args.right.enabled = true;
args.right.html = "This move cannot be completed here.";
}
}args.invalid is read-only and is also true if any event in a multi-move has an invalid target. Use it to explain why a drop is unavailable; use args.allowed = false for your own restrictions. Setting args.allowed = true cannot permit an invalid drop.
Skipping Non-Business Hours during Event Creation
During event creation, skipping non-business hours is not enforced automatically.
There are multiple ways to handle a new time range selection that starts or ends during a non-business segment:
Shorten the selection to end at the previous business cell.
Extend the selection to end at the next business cell.
Mark the selection as invalid.
You can implement your own rule using the onTimeRangeSelecting event handler. The following example demonstrates how to extend the selection to end on the next business day:
{
// ...
onTimeRangeSelecting: (args) => {
const cells = args.row.cells.forRange(args.start, args.end);
let first = cells[0];
let last = cells.last();
while (first && !first.properties.business) {
const minusDuration = new DayPilot.Duration(first.end, first.start);
args.start = args.start.addTime(minusDuration);
first = DayPilot.list(args.row.cells.forRange(args.start, args.start.addSeconds(1))).first();
}
if (!first) {
args.start = dp.visibleStart();
args.allowed = false;
args.right.enabled = true;
args.right.html = "Out of range";
}
while (last && !last.properties.business) {
const duration = new DayPilot.Duration(last.start, last.end);
args.end = args.end.addTime(duration);
last = DayPilot.list(args.row.cells.forRange(args.end.addSeconds(-1), args.end)).first();
}
if (!last) {
args.end = dp.visibleEnd();
args.allowed = false;
args.left.enabled = true;
args.left.html = "Out of range";
}
},
}For an example project, see the JavaScript Scheduler: Skip Non-Business Cells during Drag and Drop tutorial.
Skipping Non-Business Hours during Event Resizing
The same logic can be applied during event resizing. An example onEventResizing implementation that extends the target duration automatically could look like this (see also event resizing customization):
{
// ...
onEventResizing: function(args) {
const cells = args.row.cells.forRange(args.start, args.end);
let first = cells[0];
let last = cells.last();
while (first && !first.properties.business) {
const minusDuration = new DayPilot.Duration(first.end, first.start);
args.start = args.start.addTime(minusDuration);
first = DayPilot.list(args.row.cells.forRange(args.start, args.start.addSeconds(1))).first();
}
if (!first) {
args.start = dp.visibleStart();
args.allowed = false;
args.right.enabled = true;
args.right.html = "Out of range";
}
while (last && !last.properties.business) {
const duration = new DayPilot.Duration(last.start, last.end);
args.end = args.end.addTime(duration);
last = DayPilot.list(args.row.cells.forRange(args.end.addSeconds(-1), args.end)).first();
}
if (!last) {
args.end = dp.visibleEnd();
args.allowed = false;
args.left.enabled = true;
args.left.html = "Out of range";
}
},
}For an example project, see the JavaScript Scheduler: Skip Non-Business Cells during Drag and Drop tutorial.
API: eventMoveSkipNonBusiness, businessWeekends.
The framework examples enable eventMoveSkipNonBusiness and mark weekends as non-business. Moving a task uses that calendar to preserve its business duration, subject to the invalid-move rules described above.
The framework examples use DayPilot Pro. For installation, see the Angular, React or Vue Scheduler guide.
Angular
import {Component} from "@angular/core";
import {DayPilot, DayPilotModule} from "daypilot-pro-angular";
@Component({
selector: "app-scheduler-example",
standalone: true,
imports: [DayPilotModule],
template: `
<daypilot-scheduler [config]="config"></daypilot-scheduler>
`
})
export class SchedulerExampleComponent {
config: DayPilot.SchedulerConfig = {
startDate: "2026-11-01",
days: 30,
scale: "Day",
timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "d"}],
resources: [{id: "A", name: "Room A"}, {id: "B", name: "Room B"}],
events: [{id: 1, text: "Planning", resource: "A", start: "2026-11-03", end: "2026-11-06"}, {id: 2, text: "Delivery", resource: "B", start: "2026-11-08", end: "2026-11-11"}],
eventMoveSkipNonBusiness: true,
businessWeekends: false
};
}
React
import React from "react";
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";
export default function SchedulerExample() {
return <div>
<DayPilotScheduler
startDate={"2026-11-01"}
days={30}
scale={"Day"}
timeHeaders={[{groupBy: "Month"}, {groupBy: "Day", format: "d"}]}
resources={[{id: "A", name: "Room A"}, {id: "B", name: "Room B"}]}
events={[{id: 1, text: "Planning", resource: "A", start: "2026-11-03", end: "2026-11-06"}, {id: 2, text: "Delivery", resource: "B", start: "2026-11-08", end: "2026-11-11"}]}
eventMoveSkipNonBusiness={true}
businessWeekends={false}
/>
</div>;
}
Vue
<script setup>
import {DayPilot, DayPilotScheduler} from "daypilot-pro-vue";
const startDate = "2026-11-01";
const days = 30;
const scale = "Day";
const timeHeaders = [{groupBy: "Month"}, {groupBy: "Day", format: "d"}];
const resources = [{id: "A", name: "Room A"}, {id: "B", name: "Room B"}];
const events = [{id: 1, text: "Planning", resource: "A", start: "2026-11-03", end: "2026-11-06"}, {id: 2, text: "Delivery", resource: "B", start: "2026-11-08", end: "2026-11-11"}];
const eventMoveSkipNonBusiness = true;
const businessWeekends = false;
</script>
<template>
<div>
<DayPilotScheduler
:startDate="startDate"
:days="days"
:scale="scale"
:timeHeaders="timeHeaders"
:resources="resources"
:events="events"
:eventMoveSkipNonBusiness="eventMoveSkipNonBusiness"
:businessWeekends="businessWeekends"
/>
</div>
</template>
Availability
Availability of this feature in DayPilot editions:
| Product | Lite | Pro |
|---|---|---|
| DayPilot for JavaScript | ||
| DayPilot for ASP.NET WebForms | ||
| DayPilot for ASP.NET MVC | ||
| DayPilot for Java |
DayPilot