はじめに:「時刻バリデーション」は“バグの温床”を最初に潰す作業
「09:00 と入力してほしいのに、9時 とか 9:0 とか 25:00 とかが来る」
「開始時刻より終了時刻が前になっている」
時刻入力まわりは、放っておくとバグとクレームの宝庫になります。
だからこそ、“画面から受け取った時刻を、まずちゃんとチェックする”ための
「時刻バリデーションユーティリティ」を持っておくと、実務ではかなり効きます。
ここでは、
文字列として入力された時刻を「形式」と「値」の両面からチェックする
業務ルール(開始<終了、業務時間内かどうか)まで含めて判定する
という流れで、初心者向けにかみ砕いて説明していきます。
基本の考え方:「形式」と「意味」の2段階で見る
まずは「文字列として正しいか」を見る
ユーザー入力は、まず文字列として飛んできます。
ここで見るべきポイントは「書式が正しいか」です。
例えば「HH:mm 形式(ゼロ埋め2桁の時:分)」だけを許したいなら、DateTime.TryParseExact や TimeSpan.TryParseExact を使うのが王道です。
using System;
using System.Globalization;
public static class TimeValidator
{
public static bool TryParseTime(string input, out TimeSpan time)
{
return TimeSpan.TryParseExact(
input,
"hh\\:mm", // 09:30 のような形式
CultureInfo.InvariantCulture,
out time);
}
}
C#使い方の例です。
if (TimeValidator.TryParseTime("09:30", out var t1))
{
Console.WriteLine(t1); // 09:30:00
}
if (!TimeValidator.TryParseTime("9:30", out _))
{
Console.WriteLine("書式エラー"); // 9:30 は NG(ゼロ埋め必須)
}
C#ここでの重要ポイントは、「パースに成功したかどうか」を bool で返していることです。
例外に頼らず、「成功したら値が取れる/失敗したら false」という形にしておくと、
画面バリデーションのコードがとても書きやすくなります。
次に「値として妥当か」を見る
TryParseExact が通った時点で、
「00:00〜23:59 の範囲で、形式も正しい」ことは保証されます。
ただし、業務によっては「9:00〜18:00 の間だけ許可したい」など、
さらに一段階上の“意味的なバリデーション”が必要になります。
ここから先は、「TimeSpan(あるいは DateTime)になった値に対して、業務ルールを当てる」イメージです。
業務時間内かどうかをチェックする
「9:00〜18:00 の間だけ有効」の例
例えば、「勤務開始時刻は 9:00〜18:00 の間だけ許可」というルールを考えます。
public static bool IsWithinBusinessHours(TimeSpan time, TimeSpan start, TimeSpan end)
{
return time >= start && time <= end;
}
C#使い方の例です。
TimeSpan businessStart = new TimeSpan(9, 0, 0);
TimeSpan businessEnd = new TimeSpan(18, 0, 0);
if (TimeValidator.TryParseTime("08:59", out var t1) &&
!IsWithinBusinessHours(t1, businessStart, businessEnd))
{
Console.WriteLine("業務時間外"); // 8:59 は NG
}
if (TimeValidator.TryParseTime("09:00", out var t2) &&
IsWithinBusinessHours(t2, businessStart, businessEnd))
{
Console.WriteLine("OK"); // 9:00 は OK
}
C#ここでの重要ポイントは、「書式チェック」と「業務時間チェック」を分けていることです。
一つのメソッドに全部詰め込むのではなく、
文字列 → TimeSpan に変換できるか
変換できた TimeSpan が業務時間内か
という二段階に分けると、テストもしやすく、再利用もしやすくなります。
開始時刻と終了時刻の関係をチェックする
「開始<終了」を保証する
勤務時間や予約時間などでは、「開始時刻より終了時刻が後であること」が必須です。
これもユーティリティとして切り出しておくと便利です。
public static bool IsValidTimeRange(TimeSpan start, TimeSpan end, bool allowEqual = false)
{
if (allowEqual)
{
return start <= end;
}
else
{
return start < end;
}
}
C#使い方の例です。
if (TimeValidator.TryParseTime("09:00", out var start) &&
TimeValidator.TryParseTime("18:00", out var end))
{
if (!IsValidTimeRange(start, end))
{
Console.WriteLine("開始時刻は終了時刻より前である必要があります。");
}
}
C#ここでの重要ポイントは、「等しいのを許すかどうか」を引数で切り替えられるようにしていることです。
「開始=終了は NG(0分はダメ)」というルールもあれば、
「開始=終了は OK(“空き”を表すなど)」というケースもあり得ます。
ユーティリティ側で柔軟にしておくと、いろいろな画面で使い回せます。
文字列入力から一気に業務ルールまでチェックする
まとめて判定するメソッドを用意する
ここまでの要素を組み合わせて、
「文字列2つ(開始・終了)を受け取り、書式と業務ルールをまとめてチェックする」
ユーティリティを作ってみます。
public sealed class TimeRangeValidationResult
{
public bool IsValid { get; }
public string? ErrorMessage { get; }
public TimeSpan? Start { get; }
public TimeSpan? End { get; }
private TimeRangeValidationResult(
bool isValid,
string? errorMessage,
TimeSpan? start,
TimeSpan? end)
{
IsValid = isValid;
ErrorMessage = errorMessage;
Start = start;
End = end;
}
public static TimeRangeValidationResult Success(TimeSpan start, TimeSpan end)
=> new TimeRangeValidationResult(true, null, start, end);
public static TimeRangeValidationResult Fail(string message)
=> new TimeRangeValidationResult(false, message, null, null);
}
public static class TimeRangeValidator
{
public static TimeRangeValidationResult Validate(
string startText,
string endText,
TimeSpan businessStart,
TimeSpan businessEnd)
{
if (!TimeValidator.TryParseTime(startText, out var start))
{
return TimeRangeValidationResult.Fail("開始時刻の形式が正しくありません。(例: 09:00)");
}
if (!TimeValidator.TryParseTime(endText, out var end))
{
return TimeRangeValidationResult.Fail("終了時刻の形式が正しくありません。(例: 18:00)");
}
if (!IsWithinBusinessHours(start, businessStart, businessEnd))
{
return TimeRangeValidationResult.Fail("開始時刻が業務時間外です。");
}
if (!IsWithinBusinessHours(end, businessStart, businessEnd))
{
return TimeRangeValidationResult.Fail("終了時刻が業務時間外です。");
}
if (!IsValidTimeRange(start, end))
{
return TimeRangeValidationResult.Fail("開始時刻は終了時刻より前である必要があります。");
}
return TimeRangeValidationResult.Success(start, end);
}
}
C#使い方の例です。
TimeSpan businessStart = new TimeSpan(9, 0, 0);
TimeSpan businessEnd = new TimeSpan(18, 0, 0);
var result = TimeRangeValidator.Validate("09:00", "18:00", businessStart, businessEnd);
if (!result.IsValid)
{
Console.WriteLine(result.ErrorMessage);
}
else
{
Console.WriteLine($"OK: {result.Start}〜{result.End}");
}
C#ここでの重要ポイントは、「バリデーション結果をオブジェクトとして返している」ことです。
単に bool だけ返すのではなく、
「何がダメだったのか」「パースに成功した値は何か」まで一緒に返すことで、
画面側でのエラーメッセージ表示や、その後の処理がとても書きやすくなります。
実務で意識してほしいポイント
どこでバリデーションするかを決める
時刻バリデーションは、
画面側(フロントエンド)
サーバー側(バックエンド)
両方で行うのが理想です。
ただし、C# のユーティリティとしては「サーバー側の最終防衛ライン」を担うことになります。
「画面でチェックしているから大丈夫」と思わず、
必ずサーバー側でも TryParse と業務ルールチェックを行う、という前提で設計しておくと安全です。
文化依存のパースに頼りすぎない
DateTime.Parse や TimeSpan.Parse は、
OS やカルチャ設定によって解釈が変わることがあります。
業務システムでは、基本的に
TryParseExact を使って、許可する書式を明示する
カルチャは CultureInfo.InvariantCulture か、アプリで決めたカルチャに固定する
という方針にしておくと、「環境によって動きが違う」事故を避けられます。
まとめ:「時刻バリデーションユーティリティ」は“入力のゆらぎを受け止めるクッション”
時刻バリデーションの本質は、
「ユーザーから来るバラバラな入力」を、
「システムが安心して扱える TimeSpan / DateTime」に変換するクッションを用意することです。
押さえておきたいポイントを言葉でまとめると、こうなります。
文字列としての書式チェック(TryParseExact)と、値としての意味チェック(業務時間内か、開始<終了か)を分けて考える。
TimeSpan や DateTime に変換してから、業務ルールを当てるようにする。
開始・終了のペアは、専用のバリデーション結果クラスで「何がダメか」まで返すと扱いやすい。
カルチャ依存のパースではなく、書式を固定した TryParseExact を使う。
ここまで押さえれば、
「とりあえず文字列のまま比較している」状態から抜け出して、
“業務・実務で安心して使える時刻バリデーションユーティリティ”を、
自分の C# プロジェクトにしっかり組み込めるようになります。

