C# Tips | 日付・時間処理:時刻丸め

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

はじめに 「時刻丸め」は“時間の解像度を落とすテクニック”

「打刻は5分単位にそろえたい」「グラフは1分刻みで表示したい」「ログを10秒単位でまとめたい」
こういう“細かすぎる時刻を、キリのいい時刻にそろえる”処理が「時刻丸め」です。

C# には専用メソッドはありませんが、DateTimeTimeSpan を組み合わせれば、
「切り捨て」「切り上げ」「四捨五入」を自由な単位(1分、5分、15分、10秒…)で実装できます。

ここでは、まず「時・分・秒を直接いじる基本形」から入り、
そのあと「任意の間隔に丸める汎用ユーティリティ」、
最後に「実務でハマりやすいポイント」まで、順にかみ砕いて説明します。


基本形:時刻の一部を0にそろえる(切り捨て)

分・秒を0にして「時間単位」にそろえる

まずは一番イメージしやすい、「分・秒を0にして時間単位にそろえる」例です。

public static DateTime TruncateToHour(DateTime dt)
{
    return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind);
}
C#

使い方の例です。

DateTime dt = new DateTime(2026, 2, 18, 20, 45, 30);

DateTime truncated = TruncateToHour(dt);

Console.WriteLine(dt);        // 2026/02/18 20:45:30
Console.WriteLine(truncated); // 2026/02/18 20:00:00
C#

やっていることは、「年・月・日・時だけを残し、分・秒を0にする」です。
これは「1時間単位への切り捨て」と考えてよいです。

同じように、「秒以下を0にして“分単位”にそろえる」こともできます。

public static DateTime TruncateToMinute(DateTime dt)
{
    return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
}
C#

ここでの重要ポイントは、「new DateTime(...) で“残したい粒度”だけを指定し、残りを0にする」というパターンです。
この形が、時刻丸め(切り捨て)の基本になります。


汎用形:任意の間隔に丸める(TimeSpan を使う)

考え方のコアは「Ticks を割って、掛け直す」

「5分単位」「15分単位」「10秒単位」など、自由な間隔に丸めたいときは、
TimeSpan を使って「割る→掛ける」という考え方をします。

まずは「切り捨て(floor)」から。

public static DateTime Floor(DateTime dt, TimeSpan interval)
{
    if (interval <= TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(nameof(interval));

    long ticks = dt.Ticks / interval.Ticks * interval.Ticks;
    return new DateTime(ticks, dt.Kind);
}
C#

使い方の例(5分単位に切り捨て)。

DateTime dt = new DateTime(2026, 2, 18, 20, 07, 30); // 20:07:30

DateTime floored = Floor(dt, TimeSpan.FromMinutes(5));

Console.WriteLine(floored); // 2026/02/18 20:05:00
C#

やっていることを言葉で整理すると、
「元の時刻の Ticks を interval(ここでは5分)の Ticks で割り、
小数点以下を捨ててから、もう一度 interval.Ticks を掛けて“キリのいい時刻”を作る」
という流れです。


切り上げと四捨五入も同じパターンで書ける

切り上げ(ceiling)の実装

切り上げは、「少しでもはみ出していたら次の区切りに進める」丸め方です。

public static DateTime Ceiling(DateTime dt, TimeSpan interval)
{
    if (interval <= TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(nameof(interval));

    long ticks = (dt.Ticks + interval.Ticks - 1) / interval.Ticks * interval.Ticks;
    return new DateTime(ticks, dt.Kind);
}
C#

使い方の例(5分単位に切り上げ)。

DateTime dt = new DateTime(2026, 2, 18, 20, 07, 30); // 20:07:30

DateTime ceiled = Ceiling(dt, TimeSpan.FromMinutes(5));

Console.WriteLine(ceiled); // 2026/02/18 20:10:00
C#

+ interval.Ticks - 1 してから割ることで、
「ちょっとでもはみ出していたら1つ上の区切りに行く」という動きになります。

四捨五入(round)の実装

四捨五入は、「区切りの真ん中より前なら下、後なら上」に丸める方法です。

public static DateTime Round(DateTime dt, TimeSpan interval)
{
    if (interval <= TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(nameof(interval));

    long halfTicks = (interval.Ticks + 1) / 2;
    long ticks = (dt.Ticks + halfTicks) / interval.Ticks * interval.Ticks;
    return new DateTime(ticks, dt.Kind);
}
C#

使い方の例(5分単位に四捨五入)。

DateTime dt1 = new DateTime(2026, 2, 18, 20, 07, 29); // 20:07:29 → 20:05
DateTime dt2 = new DateTime(2026, 2, 18, 20, 07, 31); // 20:07:31 → 20:10

Console.WriteLine(Round(dt1, TimeSpan.FromMinutes(5)));
Console.WriteLine(Round(dt2, TimeSpan.FromMinutes(5)));
C#

ここでの重要ポイントは、「丸めの本質は“Ticks を interval で割って掛け直す”」ということです。
切り捨て・切り上げ・四捨五入の違いは、「割る前に Ticks に何を足すか」に現れます。


「時刻だけ」を扱いたいときの考え方

DateTime か TimeSpan か

「日付はどうでもよくて、1日の中の時刻だけを丸めたい」
というケースもよくあります(例:営業時間の管理、タイムテーブルなど)。

この場合、2つのアプローチがあります。

ひとつは、今まで通り DateTime を使い、日付は適当な同じ日(例えば DateTime.Today)にそろえてしまう方法です。
もうひとつは、「0:00 からの経過時間」として TimeSpan で扱う方法です。

TimeSpan で扱う場合も、考え方はまったく同じです。

public static TimeSpan Floor(TimeSpan time, TimeSpan interval)
{
    if (interval <= TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(nameof(interval));

    long ticks = time.Ticks / interval.Ticks * interval.Ticks;
    return new TimeSpan(ticks);
}
C#

使い方の例(1日の中の時刻を5分単位に切り捨て)。

TimeSpan t = new TimeSpan(10, 07, 30); // 10:07:30

TimeSpan floored = Floor(t, TimeSpan.FromMinutes(5));

Console.WriteLine(floored); // 10:05:00
C#

「日付をまたがない前提の“時刻だけ”」なら TimeSpan のほうが意図が明確で、
日付との混同も起きにくくなります。


実務での注意点:丸め方の違いと Kind の扱い

切り捨て・切り上げ・四捨五入を“なんとなく”選ばない

同じ「5分単位」でも、丸め方によって結果は変わります。

20:07 を切り捨て → 20:05
20:07 を切り上げ → 20:10
20:07 を四捨五入 → 20:05

20:08 を切り捨て → 20:05
20:08 を切り上げ → 20:10
20:08 を四捨五入 → 20:10

勤怠、料金計算、課金などでは、
どの丸め方を採用するかでお金が変わることもあります。
「何のために丸めるのか」を決めてから、Floor / Ceiling / Round のどれを使うかを選ぶことが重要です。

DateTimeKind を必ず引き継ぐ

DateTime には KindUnspecified / Local / Utc)があります。
丸めるときに new DateTime(ticks) のように書くと、KindUnspecified になってしまいます。

ここまでのコードでは、すべて new DateTime(ticks, dt.Kind) のように、
元の Kind を引き継ぐようにしていました。

return new DateTime(ticks, dt.Kind);
C#

これを忘れると、
「UTC のつもりだった値が Kind 不明になり、後で ToLocalTime したときにおかしくなる」
といった事故につながります。

丸めユーティリティを書くときは、
Kind を必ず引き継ぐ」というルールを自分の中で固定しておくと安全です。


まとめ 「時刻丸めユーティリティ」は“時間の粒度をコントロールする道具」

時刻丸めは、見た目は小さなテクニックですが、
ログ集計、グラフ表示、勤怠、料金計算など、
「時間をどう区切るか」が重要な場面で必ず効いてきます。

押さえておきたいポイントをまとめると、次のようなイメージです。

時・分・秒を直接指定して0にするのが、最も素直な「切り捨て」の形。
任意の間隔への丸めは、「Ticks を interval で割って掛け直す」パターンで、切り捨て・切り上げ・四捨五入を実装できる。
「日付+時刻」で扱うなら DateTime、「1日の中の時刻だけ」なら TimeSpan で扱うと意図が明確になる。
実務では「どの丸め方を使うか」と「DateTimeKind を壊さないこと」が特に重要になる。

ここまで理解できれば、
「なんとなく時刻をいじっている」段階から抜け出して、
“ビジネスロジックに合わせて時間の粒度をきちんとコントロールできる、実務で使える時刻丸めユーティリティ”を
自分の C# コードの中に自然に組み込めるようになります。

タイトルとURLをコピーしました