C# Tips | 日付・時間処理:時刻バリデーション

C# C#
スポンサーリンク

はじめに:「時刻バリデーション」は“バグの温床”を最初に潰す作業

「09:00 と入力してほしいのに、9時 とか 9:0 とか 25:00 とかが来る」
「開始時刻より終了時刻が前になっている」

時刻入力まわりは、放っておくとバグとクレームの宝庫になります。
だからこそ、“画面から受け取った時刻を、まずちゃんとチェックする”ための
「時刻バリデーションユーティリティ」を持っておくと、実務ではかなり効きます。

ここでは、

文字列として入力された時刻を「形式」と「値」の両面からチェックする
業務ルール(開始<終了、業務時間内かどうか)まで含めて判定する

という流れで、初心者向けにかみ砕いて説明していきます。


基本の考え方:「形式」と「意味」の2段階で見る

まずは「文字列として正しいか」を見る

ユーザー入力は、まず文字列として飛んできます。
ここで見るべきポイントは「書式が正しいか」です。

例えば「HH:mm 形式(ゼロ埋め2桁の時:分)」だけを許したいなら、
DateTime.TryParseExactTimeSpan.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.ParseTimeSpan.Parse は、
OS やカルチャ設定によって解釈が変わることがあります。
業務システムでは、基本的に

TryParseExact を使って、許可する書式を明示する
カルチャは CultureInfo.InvariantCulture か、アプリで決めたカルチャに固定する

という方針にしておくと、「環境によって動きが違う」事故を避けられます。


まとめ:「時刻バリデーションユーティリティ」は“入力のゆらぎを受け止めるクッション”

時刻バリデーションの本質は、
「ユーザーから来るバラバラな入力」を、
「システムが安心して扱える TimeSpan / DateTime」に変換するクッションを用意することです。

押さえておきたいポイントを言葉でまとめると、こうなります。

文字列としての書式チェック(TryParseExact)と、値としての意味チェック(業務時間内か、開始<終了か)を分けて考える。
TimeSpan や DateTime に変換してから、業務ルールを当てるようにする。
開始・終了のペアは、専用のバリデーション結果クラスで「何がダメか」まで返すと扱いやすい。
カルチャ依存のパースではなく、書式を固定した TryParseExact を使う。

ここまで押さえれば、
「とりあえず文字列のまま比較している」状態から抜け出して、
“業務・実務で安心して使える時刻バリデーションユーティリティ”を、
自分の C# プロジェクトにしっかり組み込めるようになります。

C#C#
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました