Two linked calendar pickers are useful when a form needs a start date and end date. Once a start date is selected, the end-date picker should disable earlier dates; if an end date already exists, the start picker should disable later dates. The form should still validate the relationship before submission.
if (startDate && candidate < startDate) {
endDayButton.disabled = true;
}
if (endDate && candidate > endDate) {
startDayButton.disabled = true;
}This builds on the single date-to-input picker, the month/year navigation picker, and the two independent picker demo.
For a value such as 2026-09-10, split the components and construct a local Date explicitly. This avoids relying on ambiguous display parsing and makes validation possible.
function toLocalDate(value) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
const [year, month, day] = value.split("-").map(Number);
const date = new Date(year, month - 1, day);
return date.getFullYear() === year &&
date.getMonth() === month - 1 &&
date.getDate() === day ? date : null;
}When rendering the end calendar, compare each candidate date with the selected start date. Reverse the comparison when rendering the start calendar.
function isAllowed(candidate, role, startDate, endDate) {
if (role === "end" && startDate && candidate < startDate) return false;
if (role === "start" && endDate && candidate > endDate) return false;
return true;
}If the start date changes to a value later than the existing end date, either clear the now-invalid end date or ask the user to correct it. Do not silently keep an invalid range.
startInput.addEventListener("change", () => {
if (endInput.value && startInput.value > endInput.value) {
endInput.value = "";
}
});The linked demo opens the end-date calendar at the start-date month when a start date exists. This reduces unnecessary navigation for common booking/range workflows.
Client-side disabling improves the experience, but it is not a security boundary. A user can alter request values, so validate the date syntax and the start/end relationship again on the server when the range affects a query, booking, report, or transaction.
if (startInput.value && endInput.value < startInput.value) {
endInput.setCustomValidity(
"End date must be on or after the start date."
);
} else {
endInput.setCustomValidity("");
}The original project demonstrated a SQL BETWEEN expression generated in the browser. That is useful for understanding the intended range, but application code should never concatenate untrusted form values directly into SQL. Send the dates to the server, validate them, and use parameters/prepared statements.
// Conceptual SQL after server-side validation
SELECT *
FROM dt_tb
WHERE dt BETWEEN ? AND ?Demo: preview a date-range query pattern
Use labeled controls, real buttons for calendar actions, aria-expanded/aria-controls for popup state, and native disabled for unavailable days. Do not communicate the range restriction with color alone. Native <input type="date"> with min/max may be simpler when a custom calendar is not required.
Demo: linked start and end date calendars
Date Reference All Date Projects
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.