予約日、受取日、配送希望日などを<select>で選ばせるフォームでは、休業日や過去日を選択肢に表示しつつ、選択だけできないようにしたいことがあります。HTMLのdisabled属性とJavaScriptを組み合わせれば、条件に合う<option>を動的に無効化できます。
ただし、「特定の日付」と「今日以前の日付」は別の条件です。また、JavaScriptだけの制御は開発者ツールや直接送信で回避できます。この記事では、固定日・過去日・今日・土日・期間を正しく判定し、画面と送信処理の両方で検証する方法を解説します。
- 固定日を無効化するなら、
SetにYYYY-MM-DD形式で登録します。 - 過去日だけを禁止する条件は
date < today、今日も禁止するならdate <= todayです。 - 文字列比較は、すべてゼロ埋めされた
YYYY-MM-DD形式に限って使います。 - 土日判定では、日付文字列をローカル日付として明示的に組み立てます。
- 選択済みのoptionを無効化した場合は、未選択へ戻して理由を表示します。
- 連続した最小・最大日だけを制限するなら
input type="date"のmin・maxが簡単です。 - 最終的な受付可否は、必ずサーバー側でも同じ業務ルールで検証します。
select要素の値や表示テキストを取得・変更する基本はselectタグの値とテキストを取得・設定する方法、未選択項目の作り方はselect要素にプレースホルダーを設定する方法も参考になります。
HTMLだけで特定の日付を選択不可にする
無効にする日付が固定されているなら、対象のoptionへ直接disabled属性を付ける方法が最も簡単です。
<label for="delivery-date">配送希望日</label>
<select id="delivery-date" name="delivery_date" required>
<option value="">日付を選択してください</option>
<option value="2026-06-12">2026年6月12日(金)</option>
<option value="2026-06-13">2026年6月13日(土)</option>
<option value="2026-06-14" disabled>
2026年6月14日(日)休業日
</option>
<option value="2026-06-15">2026年6月15日(月)</option>
</select>
disabledのoptionは通常の操作では選択できず、フォームの送信値にもなりません。ただし、HTMLは利用者側で変更できるため、サーバー側の受付判定は別途必要です。
JavaScriptで固定日をまとめて無効化する
休業日が複数ある場合は、無効化したい日付をSetへ登録します。配列のincludes()でも実装できますが、照合対象が増える場合はSet.has()が分かりやすい書き方です。
const blockedDates = new Set([
"2026-06-14",
"2026-06-20",
"2026-06-21"
]);
const select = document.querySelector("#delivery-date");
for (const option of select.options) {
if (blockedDates.has(option.value)) {
option.disabled = true;
}
}
optionのvalueはすべてYYYY-MM-DD形式へ統一します。表示テキストは「2026年6月14日」のような日本語表記でも構いませんが、判定にはvalueを使います。
今日の日付をローカル時間でYYYY-MM-DDにする
new Date().toISOString().slice(0, 10)はUTC基準です。日本時間の深夜から朝にかけて日付が前日になる可能性があるため、利用者のローカル日付を使う場合は年月日を個別に取得します。
function formatLocalDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
const today = formatLocalDate(new Date());
console.log(today);
予約業務の基準日が店舗所在地や日本時間で固定される場合、閲覧者の端末時刻を基準にしてはいけません。サーバー側で業務タイムゾーンの日付を生成し、HTMLのdata-business-todayなどで渡す設計が安全です。
過去日だけを選択不可にする
ゼロ埋めされたYYYY-MM-DD同士なら、辞書順と日付順が一致するため文字列で比較できます。過去日だけを禁止し、今日は許可する条件はoption.value < todayです。
const select = document.querySelector("#delivery-date");
const today = formatLocalDate(new Date());
for (const option of select.options) {
if (option.value !== "" && option.value < today) {
option.disabled = true;
}
}
今日も選択不可にする場合だけ、比較演算子を<=へ変更します。
if (option.value !== "" && option.value <= today) {
option.disabled = true;
}
<は過去日のみ、<=は過去日と今日を無効化します。「過去日を禁止」と書きながら<=を使うと、仕様と実装が一致しません。土曜日・日曜日を選択不可にする
new Date("2026-06-14")のような日付だけの文字列はUTCとして解釈されるため、タイムゾーンによって曜日判定がずれる可能性があります。年月日へ分割し、ローカル日付として生成します。
function parseLocalDate(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) {
return null;
}
const [, year, month, day] = match.map(Number);
const date = new Date(year, month - 1, day);
// 2026-02-31のような存在しない日付を除外
if (
date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day
) {
return null;
}
return date;
}
function isWeekend(value) {
const date = parseLocalDate(value);
if (!date) {
return false;
}
const dayOfWeek = date.getDay();
return dayOfWeek === 0 || dayOfWeek === 6;
}
for (const option of select.options) {
if (option.value !== "" && isWeekend(option.value)) {
option.disabled = true;
}
}
getDay()は日曜日が0、土曜日が6です。祝日は曜日だけでは判定できないため、固定休業日やAPIから取得した休日一覧と組み合わせます。
特定の期間を選択不可にする
年末年始や設備メンテナンス期間など、開始日から終了日までをまとめて無効化できます。境界日を含めるなら>=と<=を使います。
const closedFrom = "2026-08-10";
const closedTo = "2026-08-16";
function isInClosedRange(value) {
return value >= closedFrom && value <= closedTo;
}
for (const option of select.options) {
if (option.value !== "" && isInClosedRange(option.value)) {
option.disabled = true;
}
}
この文字列比較も、値が正しいYYYY-MM-DD形式であることが前提です。入力値を自由記述させる場合は、形式と実在日を先に検証してください。
固定日・過去日・土日・期間をまとめた完成版
実務では、複数条件のどれに該当したかを判定し、選択肢へ理由を表示すると利用者に伝わりやすくなります。既に選択されている日付が無効になった場合は、値を空へ戻します。
<form id="reservation-form" action="/reserve" method="post">
<label for="reservation-date">予約日</label>
<select
id="reservation-date"
name="reservation_date"
data-business-today="2026-06-10"
required
aria-describedby="date-message"
>
<option value="">日付を選択してください</option>
<option value="2026-06-10">2026年6月10日(水)</option>
<option value="2026-06-11">2026年6月11日(木)</option>
<option value="2026-06-12">2026年6月12日(金)</option>
<option value="2026-06-13">2026年6月13日(土)</option>
<option value="2026-06-14">2026年6月14日(日)</option>
<option value="2026-06-15">2026年6月15日(月)</option>
<option value="2026-06-16">2026年6月16日(火)</option>
</select>
<p id="date-message" aria-live="polite"></p>
<button type="submit">予約する</button>
</form>
<script src="/js/reservation-date.js" defer></script>
const blockedDates = new Set([
"2026-06-12"
]);
const closedRanges = [
{ from: "2026-06-15", to: "2026-06-16" }
];
const form = document.querySelector("#reservation-form");
const select = document.querySelector("#reservation-date");
const message = document.querySelector("#date-message");
const businessToday = select.dataset.businessToday;
function parseLocalDate(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) {
return null;
}
const [, year, month, day] = match.map(Number);
const date = new Date(year, month - 1, day);
if (
date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day
) {
return null;
}
return date;
}
function getDisabledReason(value) {
if (!parseLocalDate(value)) {
return "日付形式が不正です";
}
if (value < businessToday) {
return "過去日は選択できません";
}
if (blockedDates.has(value)) {
return "休業日です";
}
const date = parseLocalDate(value);
const dayOfWeek = date.getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) {
return "土日は選択できません";
}
const inClosedRange = closedRanges.some(
({ from, to }) => value >= from && value <= to
);
if (inClosedRange) {
return "受付停止期間です";
}
return "";
}
function updateDateOptions() {
for (const option of select.options) {
if (option.value === "") {
continue;
}
const originalLabel = option.dataset.label ?? option.textContent;
option.dataset.label = originalLabel;
const reason = getDisabledReason(option.value);
option.disabled = reason !== "";
option.textContent = reason
? `${originalLabel}(${reason})`
: originalLabel;
}
if (select.selectedOptions[0]?.disabled) {
select.value = "";
message.textContent =
"選択中の日付が受付対象外になったため、選択を解除しました。";
}
}
form.addEventListener("submit", (event) => {
const reason = getDisabledReason(select.value);
if (select.value === "" || reason !== "") {
event.preventDefault();
select.setCustomValidity(
reason || "予約日を選択してください"
);
select.reportValidity();
message.textContent = reason;
return;
}
select.setCustomValidity("");
});
select.addEventListener("change", () => {
select.setCustomValidity("");
message.textContent = "";
});
updateDateOptions();
data-business-todayは説明用に固定値を記載しています。実際のページでは、サーバーが業務タイムゾーンの当日を出力してください。端末の日付を信用すると、時刻設定のずれや海外利用者のタイムゾーンによって受付条件が変わります。
input type=dateを使った方がよい場合
候補日をすべてoptionとして並べる必要がなく、「今日から30日後まで」のような連続範囲だけを制限するなら、input type="date"の方が簡潔です。
<label for="visit-date">来店日</label> <input type="date" id="visit-date" name="visit_date" min="2026-06-10" max="2026-07-10" required >
minとmaxは連続した範囲の制限に向いています。一方、毎週土日、飛び飛びの休業日、複数の受付停止期間をネイティブの日付ピッカー上で個別に無効化することはできません。その場合は、選択後にJavaScriptで検証するか、選択可能日だけをselectへ表示します。
選択不可の日付を非表示にするべきか
optionへdisabledを付けると、「その日は存在するが予約できない」ことを伝えられます。選択肢自体を削除すると一覧は短くなりますが、利用者には日付がない理由が分かりません。
- disabledにする:休業日や満席日を見せたい場合
- optionを生成しない:選択可能日だけを簡潔に提示したい場合
- 理由を併記する:「休業日」「満席」などを伝えたい場合
長いselectでは、無効な日付が大量に並ぶと探しにくくなります。候補数が多い場合は、カレンダーUIや日付入力も検討してください。
サーバー側でも必ず日付を再検証する
JavaScriptはユーザー体験を改善するための機能であり、業務ルールを守る境界ではありません。利用者はdisabledを外したり、HTTPリクエストを直接送ったりできます。予約登録の直前にサーバー側で形式、実在日、過去日、休業日、受付期間、空き状況を再検証します。
<?php
$value = $_POST['reservation_date'] ?? '';
$timezone = new DateTimeZone('Asia/Tokyo');
$today = new DateTimeImmutable('today', $timezone);
$blockedDates = ['2026-06-12'];
$date = DateTimeImmutable::createFromFormat(
'!Y-m-d',
$value,
$timezone
);
$isValidFormat = $date !== false
&& $date->format('Y-m-d') === $value;
if (!$isValidFormat) {
exit('日付形式が不正です');
}
if ($date < $today) {
exit('過去日は予約できません');
}
if (in_array($value, $blockedDates, true)) {
exit('休業日は予約できません');
}
$dayOfWeek = (int) $date->format('w');
if ($dayOfWeek === 0 || $dayOfWeek === 6) {
exit('土日は予約できません');
}
// 空き状況もデータベースで再確認してから登録する
echo '予約可能です';
画面表示から送信までの間に満席になることもあります。日付だけでなく、在庫・予約枠・締切時刻など最新状態を登録直前に確認してください。
よくある失敗
今日まで無効になってしまう
条件がvalue <= todayになっています。今日を許可するならvalue < todayへ変更します。
日付の比較結果がおかしい
2026-6-2のようにゼロ埋めされていない値や、2026/06/02など形式が混在していると文字列比較は使えません。optionのvalueをYYYY-MM-DDへ統一します。
曜日が1日ずれる
new Date("YYYY-MM-DD")を使うとUTC解釈の影響を受けます。年月日へ分割し、new Date(year, month - 1, day)でローカル日付を作ります。文字列からDateへ変換する基礎はJavaScriptで文字列をDate型に変換する方法も参照してください。
disabledにしたのに不正な日付が送信される
JavaScriptやHTMLは変更できます。サーバー側で同じ受付条件を再検証し、不正な日付を拒否してください。
よくある質問
まとめ
select内の日付を選択不可にする基本は、対象optionのdisabledをtrueにすることです。固定日はSet、過去日はYYYY-MM-DDの比較、土日はローカル日付のgetDay()、期間は開始日・終了日の範囲判定を使います。
今日を含めるかどうか、基準タイムゾーン、選択済み値の解除、理由表示まで仕様として決めてください。そして、JavaScriptの判定結果を信用せず、予約や注文を登録する直前にサーバー側で同じルールと最新の空き状況を再検証することが重要です。