Pleasanterで申請フォームを作成するとき、「開始日は過去日でも入力できるようにしたいが、終了日は開始日から1年以内に制限したい」という要件が発生することがあります。
たとえば、利用期間や契約期間を申請するフォームでは、申請が遅れたことにより、開始日が申請日より前になるケースがあります。このとき、開始日を申請日以降に限定すると、業務上は正しい申請であっても登録できません。
一方で、終了日を無制限に入力できるようにすると、本来認めていない長期間の申請が登録される可能性があります。そのため、開始日は過去日を許可しつつ、終了日は開始日以降、かつ開始日から1年後までに制限する設計が必要です。
この日付制御では、画面上のJavaScriptだけに頼らず、申請処理の直前にサーバー側でも同じ条件を検証することが重要です。画面側では利用者が入力中に間違いへ気付けるようにし、サーバー側では不正な日付の登録を確実に防ぎます。
本記事では、開始日をDateA、終了日をDateBとした例を使い、要件整理、判定ロジック、エラーメッセージ、カレンダーの入力範囲、うるう年への対応、テストパターンまで解説します。実際に使用する際は、項目名や申請処理の設定を利用中のPleasanter環境に合わせて置き換えてください。
今回の基本条件:開始日は過去日でも許可します。終了日は開始日以降とし、開始日から数えて1年後の日付を上限にします。
今回実装したい日付チェック
最初に、実装したい日付チェックの条件を明確にします。日付制御では、「1年以内」という言葉だけで実装を始めると、基準日や境界日の認識に違いが生じやすいためです。
今回の要件では、開始日は申請日を基準に制限しません。申請日より前の日付であっても、業務上必要であれば入力できるようにします。そのうえで、終了日は開始日と同じ日、または開始日より後の日付に限定します。
さらに、終了日の上限は開始日の1年後です。たとえば、開始日が2026年7月24日の場合、終了日は2027年7月24日まで入力できます。2027年7月25日は、開始日から1年後を超えるためエラーにします。
日付チェックの条件
- 開始日は過去日でも入力できます。
- 終了日は開始日と同じ日、または開始日より後にします。
- 終了日は開始日から1年後まで入力できます。
- 申請日は期間の上限計算に使用しません。
- 条件に違反した場合は申請処理を中止します。
たとえば、申請日が2026年7月31日、開始日が2026年7月23日、終了日が2027年7月23日という申請は許可します。開始日は申請日より前ですが、終了日が開始日から1年以内に収まっているためです。
一方、開始日が2026年7月24日、終了日が2027年7月25日の場合は、終了日が上限を1日超えているためエラーにします。このように、申請日ではなく、入力された開始日を基準に終了日の妥当性を判断します。
当初の要件で発生した問題
当初は、開始日と終了日の両方を「申請日から1年以内」に制限する考え方がありました。しかし、この条件では、申請日より前の開始日を入力できなくなります。
「申請日から1年以内」という条件
申請日を基準とする場合、次のような条件になります。
当初の条件:開始日と終了日は、申請日以降かつ申請日から1年以内の日付とする。
この条件は、未来の利用期間だけを申請する業務では分かりやすい方法です。しかし、利用開始後に申請するケースや、申請処理が遅れたケースには対応できません。
過去の開始日を入力できなくなる
たとえば、実際の利用開始日は2026年7月23日でしたが、申請を行った日が2026年7月24日だったとします。
申請日:2026年7月24日
開始日:2026年7月23日
終了日:2027年7月23日
この申請は、開始日から終了日までが1年以内に収まっています。しかし、「開始日は申請日以降」という条件があると、開始日が1日前という理由だけでエラーになります。
業務上、開始日が過去になることを認めるのであれば、申請日を基準とした制限は要件と一致しません。そこで、開始日に対する申請日基準のチェックを外し、終了日の上限だけを開始日基準で判定する形に変更します。
要件を開始日基準へ変更する
最終的な条件は、次のように整理できます。
最終的な条件:終了日は、開始日と同じ日から開始日の1年後まで入力できます。開始日には、申請日を基準とした過去日制限を設けません。
つまり、申請日がいつであるかにかかわらず、開始日と終了日の関係だけを確認します。これにより、過去日から始まる申請を許可しながら、必要以上に長い期間が登録されることを防げます。
日付チェックの条件を整理する
実装前には、正常になるケースとエラーになるケースを具体的に整理します。特に、開始日と終了日が同じ場合、上限日と同じ場合、上限を1日超える場合など、境界値を明確にすることが重要です。
開始日または終了日が未入力の場合
開始日と終了日を必須項目として扱う場合は、どちらか一方でも未入力であればエラーにします。日付の前後関係を確認する前に未入力チェックを行うことで、利用者に分かりやすいエラーを返せます。
開始日:未入力
終了日:2027年7月24日
結果 :エラー
開始日がなければ終了日の上限を計算できません。また、終了日がなければ申請期間が確定しないため、期間を必須とする申請では処理を中止します。
終了日が開始日より前の場合
終了日は、開始日と同じ日か、それより後の日付である必要があります。
開始日:2026年7月24日
終了日:2026年7月23日
結果 :エラー
この場合、終了日が開始日の前日になっているため、期間として成立しません。比較処理では、終了日が開始日より小さい場合にエラーを返します。
終了日が開始日の1年後を超える場合
開始日が2026年7月24日であれば、終了日の上限は2027年7月24日です。
開始日:2026年7月24日
終了日:2027年7月25日
結果 :エラー
上限日を1日でも超えた場合はエラーにします。一方、終了日が2027年7月24日であれば、開始日の1年後と同じ日なので許可します。
開始日が過去日でも許可する
今回の要件では、開始日が申請日より前であること自体はエラーにしません。
申請日:2026年7月31日
開始日:2026年7月23日
終了日:2027年7月23日
結果 :許可
終了日が開始日以降であり、開始日から1年以内であれば、申請日は判定に影響しません。この条件を明確にしておくことで、画面の説明文とサーバー側の判定が一致します。
申請ボタン押下時にチェックする理由
入力画面のJavaScriptを使えば、開始日や終了日を変更した時点で警告を表示できます。しかし、画面側のチェックだけでは、不正な日付の登録を完全に防げません。
入力中の警告だけでは不十分
JavaScriptは、利用者が通常の画面操作を行う場合には便利です。終了日が開始日より前になったときや、1年後を超えたときに、その場でメッセージを表示できます。
一方で、JavaScriptが読み込まれない状態、ブラウザーの開発者ツールによる書き換え、APIからの登録、インポート処理などでは、画面上の制御を通らない可能性があります。
また、終了日のカレンダーで選択できない日付をグレーアウトしても、日付を直接入力したり、コピー&ペーストしたりする操作まで完全に防げるとは限りません。
最終的にはサーバースクリプトで検証する
不正な申請を確実に防ぐには、申請処理を実行する直前にサーバー側で開始日と終了日を確認します。条件に違反している場合は、エラーメッセージを返し、申請処理を中止します。
サーバー側では、少なくとも次の順番で確認します。
- 開始日と終了日が入力されているか確認します。
- 日付として正しく解釈できるか確認します。
- 終了日が開始日より前でないか確認します。
- 開始日の1年後を計算します。
- 終了日が上限日を超えていないか確認します。
画面側とサーバー側を併用する
| 実装場所 | 主な役割 | 特徴 |
|---|---|---|
| 画面側JavaScript | 入力中に警告し、利用者の修正を促す | 分かりやすい一方、回避される可能性があります。 |
| サーバースクリプト | 申請前に最終検証し、不正登録を防ぐ | 画面以外の登録経路にも備えられます。 |
画面側は利用者の操作性を高めるため、サーバー側はデータの正しさを守るために使用します。どちらか一方ではなく、同じ条件を両方へ実装することが安全です。
サーバースクリプトで使用する日付判定ロジック
ここでは、開始日をDateA、終了日をDateBとした日付判定の考え方を示します。Pleasanterで実際に項目値を取得する記述やエラーを返す記述は、利用しているバージョン、スクリプトの実行条件、項目構成に合わせて置き換えてください。
開始日と終了日を取得する
開始日:DateA
終了日:DateB
取得した値は、文字列のまま比較せず、日付として解釈してから比較します。文字列の形式が2026-07-24でも2026/07/24でも扱えるようにする場合は、区切り文字を統一して年・月・日に分解します。
日付を安全に変換する関数
function parseDateValue(value) {
if (!value) return null;
const parts = String(value).trim().replace(///g, '-').split('-');
if (parts.length !== 3) return null;
const year = Number(parts[0]);
const month = Number(parts[1]);
const day = Number(parts[2]);
if (!Number.isInteger(year) ||
!Number.isInteger(month) ||
!Number.isInteger(day)) {
return null;
}
const date = new Date(year, month - 1, day);
if (date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day) {
return null;
}
return date;
}
JavaScriptのDateは、存在しない日付を自動的に翌月へ繰り越すことがあります。たとえば、存在しない月日を渡してもエラーにならない場合があるため、生成後の年・月・日が入力値と一致するかを確認しています。
1年後の日付を計算する関数
開始日の1年後を計算するときは、単純に365日分のミリ秒を加算しません。うるう年をまたぐ場合、業務上の「1年後」と365日後が一致しないことがあるためです。
function isLeapYear(year) {
return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
}
function addOneYear(date, leapDayPolicy = 'feb28') {
const nextYear = date.getFullYear() + 1;
const month = date.getMonth();
const day = date.getDate();
if (month === 1 && day === 29 && !isLeapYear(nextYear)) {
return leapDayPolicy === 'mar1'
? new Date(nextYear, 2, 1)
: new Date(nextYear, 1, 28);
}
return new Date(nextYear, month, day);
}
この例では、開始日が2月29日で翌年に2月29日が存在しない場合、feb28を指定すると2月28日、mar1を指定すると3月1日を返します。どちらを採用するかは、実装前に業務ルールとして決めます。
期間を検証する関数
function validatePeriod(startValue, endValue) {
if (!startValue || !endValue) {
return '期間(開始日)と期間(終了日)を入力してください。';
}
const startDate = parseDateValue(startValue);
const endDate = parseDateValue(endValue);
if (!startDate || !endDate) {
return '期間には正しい日付を入力してください。';
}
if (endDate < startDate) {
return '期間(終了日)は、期間(開始日)以降の日付を入力してください。';
}
const maximumDate = addOneYear(startDate, 'feb28');
if (endDate > maximumDate) {
return '期間(終了日)は、期間(開始日)の1年後までの日付を入力してください。';
}
return '';
}
戻り値が空文字であれば正常、メッセージが返ればエラーとして扱います。Pleasanterのサーバースクリプトでは、この関数へDateAとDateBの値を渡し、エラーが返った場合に申請を中止する処理へ接続します。
const startValue = /* DateAの値を取得 */;
const endValue = /* DateBの値を取得 */;
const errorMessage = validatePeriod(startValue, endValue);
if (errorMessage) {
/* Pleasanterのエラー表示処理を実行 */
/* 申請処理を中止 */
}
注意:上記は日付判定部分を分かりやすく示した例です。項目値の取得方法、エラーの返却方法、申請処理を止める方法は、実際のPleasanter環境のスクリプト仕様に合わせて実装してください。
エラーメッセージを分かりやすくする
入力チェックでは、単に「日付が不正です」と表示するのではなく、どの条件に違反しているかを具体的に伝えます。利用者が修正方法を理解できるメッセージにすると、問い合わせや再申請を減らせます。
終了日が開始日より前の場合
「開始日より後」とすると、開始日と終了日が同じ1日だけの期間を許可できないように読めます。開始日と同じ日を許可する場合は、「開始日以降」と表現します。
開始日の1年後を超える場合
ここでは、「申請日の1年後」ではなく「開始日の1年後」と明記します。基準日を具体的に書くことで、利用者がどの日付を修正すべきか判断しやすくなります。
画面上へ入力ガイドを表示する
※期間(終了日)は、期間(開始日)の1年後までの日付を入力してください。開始日は過去日でも入力できます。
エラーが発生してから説明するだけでなく、終了日の項目付近に入力ガイドを表示しておくと、利用者が申請前に条件を理解できます。特に、開始日が過去日でもよいことを明記すると、不要な問い合わせを減らせます。
JavaScriptで入力時に警告する
サーバー側の検証に加えて、画面側でも開始日と終了日の関係を確認します。利用者が終了日を入力した直後にエラーへ気付ければ、申請ボタンを押してから修正する手間を減らせます。
終了日の最小値と最大値を設定する
HTMLの日付入力欄では、minとmaxを設定することで、カレンダー上の選択範囲を制御できます。開始日を終了日の最小値にし、開始日の1年後を最大値にします。
function formatDateValue(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}`;
}
function updateEndDateLimit(startInput, endInput) {
const startDate = parseDateValue(startInput.value);
if (!startDate) {
endInput.removeAttribute('min');
endInput.removeAttribute('max');
return;
}
const maximumDate = addOneYear(startDate, 'feb28');
endInput.min = formatDateValue(startDate);
endInput.max = formatDateValue(maximumDate);
}
実際には、Pleasanterの画面上でDateAとDateBに対応する入力要素を取得し、この関数へ渡します。画面の要素名やセレクターは環境によって異なるため、実際のHTML構造を確認して設定してください。
開始日を変更した場合
開始日が変更されたら、終了日の最小値と最大値を再計算します。すでに入力されている終了日が新しい範囲から外れた場合は、警告を表示するか、利用者へ再入力を促します。
function checkCurrentPeriod(startInput, endInput) {
const message = validatePeriod(startInput.value, endInput.value);
if (message) {
return message;
}
return '';
}
開始日を後の日付へ変更すると、これまで正常だった終了日が開始日より前になることがあります。また、開始日を変更した結果、終了日が新しい上限を超える場合もあります。そのため、開始日の変更時にも両方の日付を再検証します。
終了日を変更した場合
終了日を変更したときは、開始日より前でないか、開始日の1年後を超えていないかを確認します。エラーがある場合は、終了日の近くへメッセージを表示すると分かりやすくなります。
ただし、画面上で警告したからといって、サーバー側の検証を省略してはいけません。画面側は入力支援、サーバー側は登録防止という役割を分けて実装します。
選択できない日付をグレーアウトする
終了日のカレンダーで、開始日より前の日付と開始日の1年後を超える日付を選択できないようにすると、利用者は入力可能な範囲を視覚的に理解できます。
開始日を基準に選択範囲を更新する
開始日が2026年7月24日の場合、終了日の選択範囲を2026年7月24日から2027年7月24日までに設定します。
| 設定項目 | 設定値 |
|---|---|
| 開始日 | 2026年7月24日 |
| 終了日の最小値 | 2026年7月24日 |
| 終了日の最大値 | 2027年7月24日 |
開始日を変更した場合は、終了日の範囲も即座に更新します。終了日の入力欄へ古い値が残っている場合は、新しい範囲に収まっているか再確認します。
グレーアウトだけに頼らない
カレンダーの選択範囲を制御しても、直接入力、コピー&ペースト、ブラウザー側の書き換え、API、インポートなど、別の経路から範囲外の日付が渡される可能性があります。
さらに、利用しているブラウザーや入力部品によっては、minやmaxの見え方が異なる場合があります。そのため、グレーアウトは利用者向けの補助機能と考え、申請時にはサーバー側で必ず同じ条件を検証します。
1年後の日付を判定するときの注意点
「開始日から1年以内」という条件では、1年をどのように計算するかを決める必要があります。365日を加算するだけでは、うるう年をまたぐ場合に業務上の想定とずれる可能性があります。
365日後と1年後は同じとは限らない
たとえば、開始日が2027年3月1日の場合、2028年はうるう年です。単純に365日を加える方法と、年だけを1つ進める方法では、結果が異なる場合があります。
業務要件が「同じ月日の翌年まで」であるなら、年を1つ加算して上限日を計算する方が自然です。2026年7月24日の1年後は、2027年7月24日として扱います。
2月29日の扱いを決める
開始日が2月29日で、翌年がうるう年ではない場合、翌年には同じ月日が存在しません。そのため、上限を2月28日とするか、3月1日とするかを業務要件として決める必要があります。
| 開始日 | 考え方 | 上限日 |
|---|---|---|
| 2024年2月29日 | 翌年2月の最終日まで | 2025年2月28日 |
| 2024年2月29日 | 翌年の同等期間を3月1日までとする | 2025年3月1日 |
どちらが正しいかは、技術だけでは決められません。利用期間、契約期間、資格の有効期限など、対象業務の規程に合わせて決定します。そして、画面側とサーバー側で同じルールを使用します。
時刻を含めず日付単位で比較する
今回の条件は日付単位の比較です。時刻やタイムゾーンが混在すると、同じ日付でも内部的には前日や翌日として扱われる場合があります。
そのため、年・月・日を取り出してローカル日付として生成し、時刻を含まない形で比較します。ISO形式の文字列をそのままnew Date()へ渡す方法は、実行環境によってタイムゾーンの影響を受ける可能性があるため、日付項目では年・月・日を明示的に分解する方が安全です。
テストしておきたいパターン
日付制御を実装したら、通常ケースだけでなく、境界値と異常値を確認します。特に、開始日と終了日が同じ場合、上限日と同じ場合、上限を1日超える場合は必ずテストします。
| 開始日 | 終了日 | 想定結果 | 確認内容 |
|---|---|---|---|
| 2026/7/24 | 2026/7/24 | OK | 開始日と終了日が同じ |
| 2026/7/24 | 2027/7/24 | OK | 上限日と同じ |
| 2026/7/24 | 2027/7/25 | エラー | 上限を1日超える |
| 2026/7/24 | 2026/7/23 | エラー | 終了日が開始日より前 |
| 2026/7/23 | 2027/7/23 | OK | 開始日が申請日より前でも許可 |
| 未入力 | 2027/7/24 | エラー | 開始日が未入力 |
| 2026/7/24 | 未入力 | エラー | 終了日が未入力 |
| 2024/2/29 | 2025/2/28 | 要件による | 2月29日の上限ルールを確認 |
また、画面上の手入力だけでなく、日付ピッカーからの選択、コピー&ペースト、レコードの編集、再申請など、実際の運用で使用する操作も確認します。
可能であれば、申請ボタン以外の更新経路やインポート処理についても、同じ制限が必要かを整理します。どの登録経路でも同じデータ品質を維持する必要がある場合は、サーバー側の検証を共通化します。
実装後の確認ポイント
- 開始日が過去日でも申請できること
- 開始日と終了日が同じでも申請できること
- 開始日の1年後当日まで申請できること
- 上限を1日超えた場合に申請が止まること
- 終了日が開始日より前の場合に申請が止まること
- 画面側とサーバー側のメッセージが一致すること
- 2月29日の判定が業務ルールどおりであること
まとめ
Pleasanterで終了日を開始日から1年以内に制限する場合は、申請日ではなく、入力された開始日を基準に上限を計算します。これにより、開始日が申請日より前でも、業務上必要な申請を許可できます。
基本条件は、開始日は過去日でも許可し、終了日は開始日以降、かつ開始日の1年後までとすることです。開始日と終了日が同じ場合は許可し、開始日の1年後と同じ日も許可します。終了日が開始日より前の場合や、1年後を1日でも超える場合はエラーにします。
実装では、画面側のJavaScriptと申請時のサーバースクリプトを併用します。画面側では、終了日の最小値と最大値を設定し、入力中に分かりやすく警告します。一方、サーバー側では、未入力、日付形式、前後関係、1年後の上限を再確認し、不正な申請を確実に止めます。
また、「1年後」を365日後として計算すると、うるう年をまたぐ場合に結果がずれることがあります。年を1つ加算する考え方を基本とし、開始日が2月29日の場合は、翌年の上限を2月28日とするか3月1日とするかを業務要件として決めてください。
エラーメッセージでは、「申請日の1年後」ではなく、「開始日の1年後まで」と明記します。画面上の入力ガイドにも同じ表現を使用し、利用者が基準日を誤解しないようにします。
最後に、開始日と終了日が同じケース、上限日と同じケース、上限を1日超えるケース、開始日が過去になるケース、未入力、2月29日などをテストします。日付制御は境界値で不具合が発生しやすいため、画面の操作性とサーバー側の登録防止を分けて確認することが重要です。



コメント