C#を用いたアプリケーション開発において、ログの出力やデバッグ、あるいはユーザーインターフェースへの時間表示を行う際、秒単位よりも細かい「ミリ秒」の表示が必要になる場面は非常に多くあります。

特にシステム間の通信ログや高精度な計測結果を表示する場合、ミリ秒を正しくフォーマットする技術は、情報の正確性を担保するために不可欠です。

C#では、DateTime構造体やTimeSpan構造体のToStringメソッドを活用することで、非常に柔軟にミリ秒を含めた書式指定を行うことが可能です。

本記事では、ミリ秒をフォーマット表示するための基本的なカスタム書式指定子から、実務で役立つサンプルコード、さらにはパフォーマンスを意識した最新の書き方まで詳しく紹介します。

DateTime構造体でミリ秒を表示する基本

DateTime構造体でミリ秒を表示するには、カスタム日時書式指定子であるfまたはFを使用します。

最も一般的な方法は、「fff」という指定子を使用して3桁のミリ秒を表示する方法です。

以下のサンプルコードでは、現在時刻をミリ秒まで含めて表示する基本的な実装を示します。

C#
using System;

class Program
{
    static void Main()
    {
        DateTime now = DateTime.Now;
        
        // ミリ秒を3桁で表示する標準的な方法
        string formattedTime = now.ToString("yyyy/MM/dd HH:mm:ss.fff");
        
        Console.WriteLine($"フォーマット済み時刻: {formattedTime}");
    }
}
実行結果
フォーマット済み時刻: 2026/05/09 15:30:45.123

このように、書式指定文字列の中にfffを含めることで、ミリ秒部分が3桁の数値として出力されます。

指定子の数によって精度を変更することができ、1桁から最大7桁(100ナノ秒単位)まで制御することが可能です。

書式指定子「f」と「F」の違い

ミリ秒の表示には、小文字のfと大文字のFの2種類が存在し、それぞれ挙動が異なります。

f「常に指定された桁数分を表示する」のに対し、Fは「末尾のゼロを省略して表示する」という特徴を持っています。

それぞれの指定子の違いを、以下の表にまとめました。

指定子説明出力例(ミリ秒が120の場合)
fミリ秒の1桁目(1/10秒)1
fffミリ秒の3桁目まで120
Fミリ秒の1桁目(ゼロなら非表示)1
FFFミリ秒の3桁目まで(末尾ゼロを省略)12

UI表示などで、桁数を固定して見栄えを整えたい場合はfを、データ量を削減したり簡潔に表示したりしたい場合はFを選択するのが一般的です。

TimeSpan構造体でのミリ秒フォーマット

処理の経過時間や期間を表すTimeSpan構造体でも、ミリ秒を表示する機会は多々あります。

TimeSpanのフォーマットにおいても、DateTimeと同様にfffなどの指定子が利用できますが、注意点としてリテラル文字(区切り文字)の扱いに違いがあります。

以下のコードは、ある処理の実行時間をミリ秒込みで表示する例です。

C#
using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        // 1秒と234ミリ秒を保持するTimeSpan
        TimeSpan interval = new TimeSpan(0, 0, 0, 1, 234);
        
        // TimeSpanでのカスタム書式指定(エスケープが必要な場合がある)
        string formattedInterval = interval.ToString(@"hh\:mm\:ss\.fff");
        
        Console.WriteLine($"経過時間: {formattedInterval}");
    }
}
実行結果
経過時間: 00:00:01.234

TimeSpan.ToStringでは、コロン:やドット.などの記号をそのまま記述するとエラーになる場合があるため、バックスラッシュ\でエスケープするか、シングルクォートで囲む必要があります。

「@”hh\:mm\:ss\.fff”」のように逐次リテラルを使用すると、可読性を保ちつつ正しくエスケープを記述できるため推奨されます。

高精度なミリ秒表示が必要な場合(fffffff)

C#のDateTimeは内部的に「Ticks」という単位で時間を管理しており、1ティックは100ナノ秒を表します。

通常のミリ秒表示(3桁)を超える精度が必要な場合、最大で7桁の指定子fffffffまで使用することができます。

金融システムの取引記録や科学技術計算のログなど、極めて高い時間解像度が求められる場面で利用されます。

C#
DateTime preciseNow = DateTime.Now;
// 7桁の精度で表示
string ultraPrecise = preciseNow.ToString("yyyy-MM-dd HH:mm:ss.fffffff");
Console.WriteLine($"高精度表示: {ultraPrecise}");

ただし、一般的なWindows環境のシステムクロックの精度は15ミリ秒程度であるため、fffffffを指定しても値の変化が飛び飛びになる可能性がある点に留意してください。

文字列補間(String Interpolation)による効率的な記述

モダンなC#(C# 6.0以降)では、ToStringを直接呼び出す代わりに、文字列補間を使用してミリ秒をフォーマットするのが主流です。

文字列補間を使用すると、変数を直接埋め込みながら書式を指定できるため、コードが非常に簡潔になります。

C#
DateTime timestamp = DateTime.Now;
// 文字列補間の中での書式指定
string message = $"[LOG][{timestamp:HH:mm:ss.fff}] 処理を開始しました。";

Console.WriteLine(message);

この書き方は直感的であり、「変数名:書式」という形式で記述するだけでToStringと同じ結果が得られます。

パフォーマンスを追求する場合の注意点

大量のログを出力するループ内などで、ミリ秒のフォーマット処理を頻繁に行う場合、パフォーマンスへの影響が無視できなくなることがあります。

DateTime.ToStringは柔軟ですが、内部で書式文字列の解析を行うため、相応のオーバーヘッドが発生します。

最新の.NET(.NET 8/9以降)では、ISpanFormattableインターフェースやUtf8.TryWriteなどを活用することで、ヒープへのメモリ割り当てを抑えた高速なフォーマットが可能です。

C#
using System;

// Spanを利用した高速な書き込み(高度な最適化が必要な場合)
Span<char> buffer = stackalloc char[32];
if (DateTime.Now.TryFormat(buffer, out int charsWritten, "HH:mm:ss.fff"))
{
    // buffer.Slice(0, charsWritten) で結果を利用可能
}

このように、TryFormatメソッドを使用することで、文字列インスタンスの生成を回避し、ガベージコレクションへの負荷を減らすことができます。

通常のアプリケーションでは文字列補間で十分ですが、高負荷なサーバーサイドアプリケーションやゲーム開発ではこうした最適化が効果を発揮します。

カルチャ(文化圏)による影響を避ける

日時のフォーマットは、実行環境の「カルチャ」設定(国や地域の設定)に依存して変化することがあります。

例えば、ドット.ではなくカンマ,を小数点として使用する地域があるため、意図しない書式で出力されるリスクがあります。

システムログやデータ交換用の文字列を作成する場合は、常に「CultureInfo.InvariantCulture」を指定することが推奨されます。

C#
using System.Globalization;

string safeFormat = DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture);

これにより、実行環境の設定にかかわらず、常に安定したフォーマット結果を得ることができ、外部システムとの連携トラブルを未然に防ぐことが可能です。

まとめ

C#でミリ秒をフォーマット表示するには、ToStringメソッドや文字列補間においてfffという書式指定子を利用するのが最も基本かつ確実な方法です。

用途に合わせて、固定桁数のfとゼロ省略のFを使い分けることで、ユーザーにとって見やすい表示を実現できます。

また、TimeSpanで使用する際のエスケープ処理や、グローバルな環境での動作を保証するためのInvariantCultureの利用など、細かい注意点も押さえておきましょう。

最新の.NET環境ではパフォーマンス面でも進化しており、必要に応じてTryFormatなどの低レベルAPIを選択できる柔軟性も備わっています。

この記事で紹介したテクニックを活用して、精度の高い時間表示を実現する読みやすいコードを記述してください。