はじめに 「ミリ秒切り捨て」は“誤差とノイズを消すための小技”
API のレスポンス時刻、ログのタイムスタンプ、DB に保存する日時。
こういうところで「ミリ秒までバラバラだと比較しづらい」「テストが安定しない」ということ、よくありますよね。
そこで使えるのが「ミリ秒切り捨て」です。2026-02-18 20:10:30.456 を 2026-02-18 20:10:30.000 にそろえるような処理です。
C# の DateTime は内部的に「ティック(ticks)」というもっと細かい単位で時間を持っているので、
ミリ秒切り捨ては「ティックをうまく丸める」ことで実現できます。
基本形:ミリ秒を 0 にして切り捨てる
一番素直な書き方(コンストラクタでミリ秒を 0 にする)
まずは「ミリ秒だけを 0 にする」一番ストレートな書き方です。
public static DateTime TruncateMilliseconds(DateTime dt)
{
return new DateTime(
dt.Year,
dt.Month,
dt.Day,
dt.Hour,
dt.Minute,
dt.Second,
0,
dt.Kind);
}
C#使い方の例です。
DateTime dt = new DateTime(2026, 2, 18, 20, 10, 30, 456); // 20:10:30.456
DateTime truncated = TruncateMilliseconds(dt);
Console.WriteLine(dt); // 2026/02/18 20:10:30.456
Console.WriteLine(truncated); // 2026/02/18 20:10:30.000
C#ここでのポイントは 2つです。
- 年・月・日・時・分・秒はそのまま使い、ミリ秒だけ 0 にしている
dt.Kindをそのまま渡して、Utc/Local/Unspecifiedを壊さないようにしている
「とにかくミリ秒を消したい」だけなら、この書き方が一番分かりやすいです。
もう一歩踏み込む:Ticks を使って“より正確に”切り捨てる
DateTime は「ティック(100ナノ秒)」単位で時間を持っている
DateTime は内部的に「1ティック = 100ナノ秒」という単位で時間を表現しています。
1ミリ秒は 10,000 ティックです。
つまり、「ミリ秒切り捨て」は
「ティックを 10,000 の倍数に切り捨てる」と言い換えられます。
public static DateTime TruncateMillisecondsByTicks(DateTime dt)
{
const long ticksPerMillisecond = TimeSpan.TicksPerMillisecond; // 10,000
long ticks = dt.Ticks / ticksPerMillisecond * ticksPerMillisecond;
return new DateTime(ticks, dt.Kind);
}
C#使い方の例です。
DateTime dt = new DateTime(2026, 2, 18, 20, 10, 30, 456); // 20:10:30.456
DateTime truncated = TruncateMillisecondsByTicks(dt);
Console.WriteLine(truncated); // 2026/02/18 20:10:30.456 (※ここではミリ秒がちょうどのケース)
C#new DateTime(年,月,日,時,分,秒,ミリ秒) で作った値は、
もともと「ミリ秒単位でピッタリのティック数」なので、
この例だと見た目は変わりません。
しかし、外部システムや計算結果によっては、
「ミリ秒表示は .456 だけど、内部のティックは .456 の少し手前/先」ということもあり得ます。
そういう“ミリ秒未満のブレ”も含めて切り捨てたいときは、
この「Ticks で丸める」方法がより厳密です。
実務での典型パターン:DB・JSON・比較のためにミリ秒を落とす
「保存するときにミリ秒を消す」ユーティリティ
よくあるのが、「DB に保存するときはミリ秒を持たせない」という方針です。
その場合、保存前に一度ミリ秒を切り捨てるユーティリティを通すようにします。
public static DateTime NormalizeForStorage(DateTime dt)
{
return TruncateMilliseconds(dt);
}
C#これをリポジトリや ORM の前段で必ず通すようにしておくと、
「あるデータは .123、別のデータは .124 で微妙に違うせいで比較に失敗する」
といった事故を防げます。
「比較前にミリ秒を無視する」パターン
「ミリ秒の違いは無視して“同じ時刻”とみなしたい」
というときも、比較前に切り捨ててしまうのがシンプルです。
public static bool EqualsIgnoringMilliseconds(DateTime x, DateTime y)
{
return TruncateMilliseconds(x) == TruncateMilliseconds(y);
}
C#これで、2026-02-18 20:10:30.123 と 2026-02-18 20:10:30.999 を
「同じ秒」として扱うことができます。
「なぜミリ秒を切り捨てるのか」を意識する
テストの安定性・ログの見やすさ・ビジネスルール
ミリ秒切り捨ては、単なる“見た目の揃え”ではなく、
実務では次のような意味を持ちます。
- テストの安定性
- 「今の時刻」を使うテストで、ミリ秒まで一致させるのは現実的ではない
- ミリ秒を切り捨ててから比較することで、テストが安定する
- ログ・監査の見やすさ
- ミリ秒まで出ていると、人間が読むときにノイズになる
- 秒単位にそろえることで、時系列が追いやすくなる
- ビジネスルール
- 「締め処理は秒単位まで」「ミリ秒は意味を持たない」
- そういうルールなら、最初からミリ秒を捨てておいたほうが安全
「なぜミリ秒を切り捨てるのか」を自分の中で言語化しておくと、
どこでこのユーティリティを通すべきかが見えてきます。
実務での注意点:Kind を壊さないこと
Utc / Local / Unspecified をそのまま保つ
さきほどのコードで、すべて new DateTime(..., dt.Kind) や new DateTime(ticks, dt.Kind) としていたのは、DateTimeKind を壊さないためです。
もしここを省略して new DateTime(ticks) と書くと、Kind が Unspecified になってしまいます。
その結果、
- 本当は UTC のつもりだった値が「Kind 不明」になり
- 後で
ToLocalTime()やToUniversalTime()を呼んだときに、意図しない変換が起きる
といった事故につながります。
「丸めるときは必ず Kind を引き継ぐ」
これは日付・時刻ユーティリティを書くときの鉄則として、ぜひ覚えておいてください。
まとめ 「ミリ秒切り捨てユーティリティ」は“時間のノイズを消すフィルタ”
ミリ秒切り捨ては、小さなテクニックですが、
DB 保存、ログ、比較、テストなど、実務のあちこちで効いてきます。
押さえておきたいポイントは、次のイメージです。
- 一番シンプルなのは「コンストラクタでミリ秒を 0 にする」書き方
- より厳密にやるなら「Ticks を
TimeSpan.TicksPerMillisecondで割って掛け直す」 - 比較・保存・表示の前に一度ミリ秒を落としておくと、ブレやノイズを減らせる
DateTimeKindを必ず引き継ぐことが、タイムゾーン絡みのバグを防ぐうえでとても重要
ここまで理解できれば、
「なんとなくミリ秒が付いている DateTime」をそのまま使うのではなく、
“必要な精度にそろえた、実務で扱いやすい日時”としてコントロールできるようになります。
