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

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

はじめに 「日付丸め」は“時間をざっくり区切るための技”

「集計は1時間単位で」「グラフは5分刻みで」「日付だけにそろえたい」
こういう“きっちりした時刻”ではなく“区切りの時刻”が欲しいときに必要になるのが「日付丸め」です。

C# には「丸め専用メソッド」はありませんが、
DateTimeTimeSpan を組み合わせることで、
「切り捨て」「切り上げ」「四捨五入」を自分で自由に作れます。

ここでは、
まず「日付だけにそろえる」「時刻を0分0秒にそろえる」といった基本から、
「5分単位」「15分単位」「日単位・月単位の丸め」、
そして“切り捨て・切り上げ・四捨五入”の違いまで、
初心者向けにかみ砕いて説明していきます。


一番基本の丸め:日付だけにそろえる(時刻を消す)

.Date と DateTime.Today の意味

「時刻はいらないから“日付だけ”にしたい」という場面はとても多いです。
このときに使うのが DateTime.Date プロパティです。

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

DateTime onlyDate = dt.Date;

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

dt.Date は、「その日の 0:00:00 にそろえた DateTime」を返します。
これは「日付単位への切り捨て」と考えてOKです。

今日の日付だけが欲しいときは DateTime.Today が使えます。

DateTime today = DateTime.Today; // 今日の 0:00:00
C#

ここでの重要ポイントは、
「日付だけで比較・集計したいときは、必ず .Date で丸めてから使う」
という習慣をつけることです。
これを忘れると、「同じ日なのに違う」と判定されるバグが生まれます。


時刻の丸め:分・秒・ミリ秒を切り捨てる

「時刻はそのまま、分以下を0にしたい」

例えば、「20:45:30 を 20:00:00 にしたい(時間単位にそろえたい)」
というような“時刻の丸め”もよく出てきます。

まずは「分以下を切り捨てる」例です。

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(truncated); // 2026/02/18 20:00:00
C#

同じように、「秒以下を切り捨てる」ならこう書けます。

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にする」
というパターンを覚えることです。
これが“時刻の丸め(切り捨て)”の基本形になります。


任意の間隔への丸め:5分単位・15分単位など

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

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

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

public static DateTime Floor(DateTime dt, TimeSpan 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#

やっていることを分解すると、

  1. interval(ここでは5分)の Ticks を使って「何個分か」を計算する
  2. 小数点以下を切り捨てる(整数除算)
  3. もう一度 interval.Ticks を掛けて「丸めた時刻のTicks」を作る

という流れです。

同じ考え方で、「切り上げ(ceiling)」も作れます。

public static DateTime Ceiling(DateTime dt, TimeSpan 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#

さらに、「四捨五入(round)」も作れます。

public static DateTime Round(DateTime dt, TimeSpan 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 で割って、掛け直す”」
というところです。
このパターンさえ分かれば、5分でも15分でも30秒でも、好きな単位に丸められます。


日単位・月単位の丸め:日付の“始まり”と“終わり”

日の始まり・終わりにそろえる

「その日の始まり(0:00:00)」や「その日の終わり(23:59:59.999…)」にそろえたい
という場面もよくあります。

日の始まりは .Date でOKです。

public static DateTime StartOfDay(DateTime dt)
{
    return dt.Date;
}
C#

日の終わりは、「翌日の0時から1ティック引く」という考え方が安全です。

public static DateTime EndOfDay(DateTime dt)
{
    DateTime nextDay = dt.Date.AddDays(1);
    return nextDay.AddTicks(-1);
}
C#

使い方の例です。

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

Console.WriteLine(StartOfDay(dt)); // 2026/02/18 0:00:00
Console.WriteLine(EndOfDay(dt));   // 2026/02/18 23:59:59.9999999
C#

月の始まり・終わりにそろえる

月単位の丸めもよく使います。
「その月の1日」「その月の末日」にそろえるイメージです。

public static DateTime StartOfMonth(DateTime dt)
{
    return new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind);
}

public static DateTime EndOfMonth(DateTime dt)
{
    DateTime firstOfNextMonth = new DateTime(dt.Year, dt.Month, 1, 0, 0, 0, dt.Kind)
        .AddMonths(1);

    return firstOfNextMonth.AddTicks(-1);
}
C#

使い方の例です。

DateTime dt = new DateTime(2024, 2, 15, 10, 0, 0); // うるう年の2月

Console.WriteLine(StartOfMonth(dt)); // 2024/02/01 0:00:00
Console.WriteLine(EndOfMonth(dt));   // 2024/02/29 23:59:59.9999999
C#

ここでの重要ポイントは、
「終わりは“次の単位の始まりから1ティック引く”」
というパターンです。
これを覚えておくと、日・月・年など、いろいろな“終わり”を安全に表現できます。


「切り捨て」「切り上げ」「四捨五入」の違いを意識する

どれを使うかで結果が変わる

丸めには大きく3種類あります。

切り捨て(floor)
切り上げ(ceiling)
四捨五入(round)

例えば「5分単位」で考えると、

20:07 を切り捨て → 20:05
20:07 を切り上げ → 20:10
20:07 を四捨五入 → 20:05(2分差なので下へ)

20:08 を切り捨て → 20:05
20:08 を切り上げ → 20:10
20:08 を四捨五入 → 20:10(3分差なので上へ)

というように、同じ元の時刻でも結果が変わります。

実務では、「どの丸め方が正しいか」は要件次第です。

勤怠の打刻 → 切り捨てか切り上げかで給与が変わる
グラフの表示軸 → 四捨五入のほうが見た目が自然なことが多い
ログのバケット分け → 切り捨てで“属する区間”を決めることが多い

など、「何のために丸めるのか」を考えて選ぶ必要があります。

ユーティリティとしては、
Floor / Ceiling / Round をそれぞれ用意しておき、
呼び出し側が明示的に選べるようにしておくのが安全です。


実務での注意点:Kind とタイムゾーンを壊さない

DateTimeKind を引き継ぐことの意味

DateTime には KindUnspecified / Local / Utc)があります。
丸めるときに new DateTime(...) を使うと、
何も考えないと KindUnspecified になってしまいます。

さきほどの例では、
new DateTime(..., dt.Kind) のように、元の Kind を引き継ぐようにしていました。

return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0, dt.Kind);
C#

これを忘れると、

UTC のつもりだった日時が「Kind 不明」になり、
後で ToLocalTimeToUniversalTime を呼んだときにおかしな変換が起きる

といった事故につながります。

丸めユーティリティを書くときは、
Kind を必ず引き継ぐ」というルールを徹底しておくと安心です。


まとめ 「日付丸めユーティリティ」は“時間の解像度を落とすための道具”

日付丸めは、一見ただの「0にそろえる」処理ですが、
日付だけの比較、時間単位の集計、5分刻みのグラフ、日・月・年の境界など、
業務システムのあちこちで使われる“時間の解像度を落とす技”です。

押さえておきたいポイントを整理すると、こうなります。

日付だけにそろえたいときは .Date(=日単位への切り捨て)を使う。
時刻の丸めは「必要な部分だけ残して0にする」か、「Ticks を interval で割って掛け直す」パターンで書く。
任意の間隔(5分・15分など)への丸めは、TimeSpan を使った Floor / Ceiling / Round を用意しておくと再利用しやすい。
日・月・年の“終わり”は「次の単位の始まりから1ティック引く」という形で安全に表現できる。
DateTimeKind を引き継ぐことと、「切り捨て/切り上げ/四捨五入」の違いを意識して選ぶことが、実務ではとても重要になる。

ここまで押さえておけば、
「なんとなく時刻をいじっている」状態から一歩進んで、
“ビジネスの時間軸をきれいに区切れる、実務で使える日付丸めユーティリティ”を
自分の C# コードの中に自信を持って組み込めるようになります。

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