日時

【C#】DateTimeの時間フォーマット完全理解:標準・カスタム指定と活用テクニック

C#で日時を任意の形で表示するにはDateTimeDateTimeOffsetToStringに書式指定文字列を渡します。

標準書式"G"などは地域設定に合わせ、カスタム書式"yyyy/MM/dd HH:mm:ss"なら固定形で出力。

24時間制はHH、ミリ秒はfff、UTC扱いはToUniversalTime()を併用。

フォーマット文字を組み合わせればログやファイル名に最適な表記を自在に生成できます。

DateTimeフォーマットの基礎

.NETで扱う日付と時刻の型

.NETでは日付や時刻を扱うために主にDateTimeDateTimeOffsetという2つの型が用意されています。

これらは似ているようで用途や内部の扱い方に違いがあるため、適切に使い分けることが重要です。

DateTimeとDateTimeOffsetの違い

DateTimeは日付と時刻を表す基本的な構造体で、ローカル時間やUTC時間、あるいは不特定の時間を表現できます。

DateTimeにはKindプロパティがあり、これがLocalUtcUnspecifiedのいずれかを示します。

Kindによって時間の解釈が変わるため、時間の比較や変換時に注意が必要です。

一方、DateTimeOffsetDateTimeに加えて、UTCからのオフセット(時差)を明示的に保持します。

これにより、世界中の異なるタイムゾーンの日時を一意に表現でき、タイムゾーンの違いを考慮した日時の比較や計算が容易になります。

例えば、DateTimeで「2024年6月1日 12:00:00」と表現しても、それがどのタイムゾーンの時間かが曖昧な場合がありますが、DateTimeOffsetなら「2024年6月1日 12:00:00 +09:00」のようにオフセットを含めて表現できるため、より正確です。

ローカル時間とUTCの扱い

DateTimeKindプロパティは、日時がローカル時間かUTCか、あるいは指定されていないかを示します。

  • DateTimeKind.Local:システムのローカルタイムゾーンに基づく日時です。たとえば日本の環境ならJST(UTC+9)となります
  • DateTimeKind.Utc:協定世界時(UTC)で表現された日時です。タイムゾーンの影響を受けず、世界共通の基準時間として使われます
  • DateTimeKind.Unspecified:ローカルかUTCかが指定されていない状態です。日時の解釈が曖昧になるため、変換や比較の際に注意が必要です

ローカル時間とUTCの変換はToLocalTime()ToUniversalTime()メソッドで行います。

たとえば、サーバーがUTCで日時を管理し、ユーザーにはローカル時間で表示したい場合に使います。

DateTime utcNow = DateTime.UtcNow; // UTCの現在時刻
DateTime localNow = utcNow.ToLocalTime(); // ローカル時間に変換
Console.WriteLine($"UTC: {utcNow}, Local: {localNow}");
UTC: 2025/05/07 4:38:21, Local: 2025/05/07 13:38:21

このように、DateTimeは時間の基準を意識して使う必要があります。

DateTimeOffsetはオフセットを持つため、タイムゾーンの違いを明示的に扱いたい場合に適しています。

文字列への変換のしくみ

日時を画面表示やログ、ファイル保存などで使う際は、DateTimeDateTimeOffsetを文字列に変換します。

C#ではToStringメソッドを使い、書式指定子を渡すことで自由にフォーマットできます。

この変換の裏側にはIFormatProviderという仕組みが関わっています。

IFormatProviderの役割

IFormatProviderは、日時や数値のフォーマットに使うカルチャ(文化圏)情報を提供するインターフェースです。

DateTime.ToStringのオーバーロードにはIFormatProviderを渡せるものがあり、これを使うと特定のカルチャに合わせた書式で日時を文字列化できます。

たとえば、日本のカルチャja-JPとアメリカのカルチャen-USでは日付の表記順序や区切り文字が異なります。

using System.Globalization;
DateTime now = new DateTime(2024, 6, 1, 15, 30, 0);
string jpDate = now.ToString("D", new CultureInfo("ja-JP")); // 日本語の長い日付形式
string usDate = now.ToString("D", new CultureInfo("en-US")); // 英語(米国)の長い日付形式
Console.WriteLine($"日本語: {jpDate}");
Console.WriteLine($"英語(米国): {usDate}");
日本語: 2024年6月1日
英語(米国): Saturday, June 1, 2024

このように、IFormatProviderを指定することで、同じ日時でも文化圏に応じた表現が可能です。

カルチャ依存とインバリアントカルチャ

日時のフォーマットはカルチャに依存するため、同じ書式指定子でも表示結果が異なります。

たとえば、"D"(長い日付形式)は日本語環境では「2024年6月1日土曜日」となり、英語環境では「Saturday, June 1, 2024」となります。

一方で、カルチャに依存しない固定のフォーマットが必要な場合は、CultureInfo.InvariantCultureを使います。

これは言語や地域に依存しない共通のフォーマットを提供し、ログやファイルの日時記録などでよく使われます。

using System.Globalization;

DateTime now = new DateTime(2024, 6, 1, 15, 30, 0);
string invariantDate = now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
Console.WriteLine(invariantDate);
2024-06-01 15:30:00

このように、カルチャ依存のフォーマットとインバリアントカルチャを使い分けることで、用途に応じた日時の文字列化ができます。

特に国際化対応やログの一貫性を保つ際に重要なポイントです。

標準書式指定子の一覧

日付向け書式

d・D

dは短い形式の日付パターンを表します。

一般的に数字を中心としたシンプルな日付表示で、カルチャによって異なります。

たとえば日本のカルチャでは「2024/06/01」、アメリカのカルチャでは「6/1/2024」となります。

一方、Dは長い形式の日付パターンで、曜日や月名が含まれた読みやすい表現になります。

日本語環境では「2024年6月1日土曜日」、英語環境では「Saturday, June 1, 2024」と表示されます。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        DateTime date = new DateTime(2024, 6, 1);
        Console.WriteLine(date.ToString("d", new CultureInfo("ja-JP"))); // 短い日付(日本)
        Console.WriteLine(date.ToString("D", new CultureInfo("ja-JP"))); // 長い日付(日本)
        Console.WriteLine(date.ToString("d", new CultureInfo("en-US"))); // 短い日付(米国)
        Console.WriteLine(date.ToString("D", new CultureInfo("en-US"))); // 長い日付(米国)
    }
}
2024/06/01
2024年6月1日
6/1/2024
Saturday, June 1, 2024

M・Y

M(または小文字のm)は月日パターンを表します。

月と日を「6月1日」や「June 1」のように表示します。

曜日や年は含まれません。

Y(または小文字のy)は年月パターンで、年と月を「2024年6月」や「June 2024」のように表示します。

日や曜日は含まれません。

using System.Globalization;
DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("M", new CultureInfo("ja-JP"))); // 月日(日本)
Console.WriteLine(date.ToString("Y", new CultureInfo("ja-JP"))); // 年月(日本)
Console.WriteLine(date.ToString("M", new CultureInfo("en-US"))); // 月日(米国)
Console.WriteLine(date.ToString("Y", new CultureInfo("en-US"))); // 年月(米国)
6月1日
2024年6月
June 1
June 2024

時刻向け書式

t・T

tは短い形式の時刻パターンで、時間と分を表示します。

カルチャによって12時間制か24時間制かが変わります。

日本語環境では「15:30」、英語(米国)環境では「3:30 PM」のように表示されます。

Tは長い形式の時刻パターンで、時間、分、秒を含みます。

こちらもカルチャ依存で、24時間制か12時間制かが変わります。

DateTime time = new DateTime(2024, 6, 1, 15, 30, 45);
Console.WriteLine(time.ToString("t", new CultureInfo("ja-JP"))); // 短い時刻(日本)
Console.WriteLine(time.ToString("T", new CultureInfo("ja-JP"))); // 長い時刻(日本)
Console.WriteLine(time.ToString("t", new CultureInfo("en-US"))); // 短い時刻(米国)
Console.WriteLine(time.ToString("T", new CultureInfo("en-US"))); // 長い時刻(米国)
15:30
15:30:45
3:30 PM
3:30:45 PM

O・R

O(または小文字のo)はISO 8601準拠の日時フォーマットを表します。

日時をタイムゾーン情報付きで正確に表現でき、主にデータ交換やログで使われます。

例として「2024-06-01T15:30:45.0000000+09:00」のように表示されます。

R(または小文字のr)はRFC1123形式の日時フォーマットで、主にHTTPヘッダーなどで使われます。

UTC時間で表現され、「Sat, 01 Jun 2024 06:30:45 GMT」のようになります。

DateTimeOffset dto = new DateTimeOffset(2024, 6, 1, 15, 30, 45, TimeSpan.FromHours(9));
Console.WriteLine(dto.ToString("O")); // ISO 8601形式
DateTime utcTime = new DateTime(2024, 6, 1, 6, 30, 45, DateTimeKind.Utc);
Console.WriteLine(utcTime.ToString("R")); // RFC1123形式
2024-06-01T15:30:45.0000000+09:00
Sat, 01 Jun 2024 06:30:45 GMT

汎用フォーマット

g・G

gは一般的な日付と時刻の短い形式を表します。

日付は短い形式、時刻は短い形式で表示されます。

たとえば「2024/06/01 15:30」のようになります。

Gは一般的な日付と時刻の長い形式で、日付は短い形式、時刻は長い形式(秒まで)で表示されます。

例として「2024/06/01 15:30:45」となります。

DateTime dt = new DateTime(2024, 6, 1, 15, 30, 45);
Console.WriteLine(dt.ToString("g", new CultureInfo("ja-JP"))); // 短い汎用形式(日本)
Console.WriteLine(dt.ToString("G", new CultureInfo("ja-JP"))); // 長い汎用形式(日本)
Console.WriteLine(dt.ToString("g", new CultureInfo("en-US"))); // 短い汎用形式(米国)
Console.WriteLine(dt.ToString("G", new CultureInfo("en-US"))); // 長い汎用形式(米国)
2024/06/01 15:30
2024/06/01 15:30:45
6/1/2024 3:30 PM
6/1/2024 3:30:45 PM

f・F

fは完全な日付と短い形式の時刻を表します。

長い日付形式に短い時刻を組み合わせたもので、「2024年6月1日土曜日 15:30」のように表示されます。

Fは完全な日付と長い形式の時刻で、長い日付形式に長い時刻を組み合わせます。

例として「2024年6月1日土曜日 15:30:45」となります。

DateTime dt = new DateTime(2024, 6, 1, 15, 30, 45);
Console.WriteLine(dt.ToString("f", new CultureInfo("ja-JP"))); // 完全日付+短時刻(日本)
Console.WriteLine(dt.ToString("F", new CultureInfo("ja-JP"))); // 完全日付+長時刻(日本)
Console.WriteLine(dt.ToString("f", new CultureInfo("en-US"))); // 完全日付+短時刻(米国)
Console.WriteLine(dt.ToString("F", new CultureInfo("en-US"))); // 完全日付+長時刻(米国)
2024年6月1日土曜日 15:30
2024年6月1日土曜日 15:30:45
Saturday, June 1, 2024 3:30 PM
Saturday, June 1, 2024 3:30:45 PM

カスタム書式指定子の詳細

年を表す書式

y・yy・yyyy

yは年を表すカスタム書式指定子で、1桁または2桁の年を表示します。

たとえば2024年ならyは「2024」の最後の1桁「4」、yyは下2桁の「24」となります。

yyyyは4桁の年を完全に表示します。

西暦を4桁で表現したい場合はこちらを使います。

DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("y"));    // 4
Console.WriteLine(date.ToString("yy"));   // 24
Console.WriteLine(date.ToString("yyyy")); // 2024
4
24
2024

月・日を自在に表示

MMとMMMの違い

MMは2桁の月を数字で表示します。

1月は「01」、12月は「12」となります。

MMMは月の省略名を表示します。

日本語環境では「6月」は「6月」のままですが、英語環境では「Jun」と表示されます。

DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("MM"));  // 06
Console.WriteLine(date.ToString("MMM", new System.Globalization.CultureInfo("en-US"))); // Jun
06
Jun

ddとdddの使い分け

ddは2桁の日を数字で表示します。

1日は「01」、31日は「31」となります。

dddは曜日の省略名を表示します。

日本語環境では「土」、英語環境では「Sat」となります。

DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("dd"));  // 01
Console.WriteLine(date.ToString("ddd", new System.Globalization.CultureInfo("en-US"))); // Sat
01
Sat

時・分・秒を制御

H・HHとh・hh

Hは24時間制の時間を1桁または2桁で表示します。

たとえば9時は「9」、15時は「15」となります。

HHは24時間制の時間を必ず2桁で表示します。

9時は「09」、15時は「15」となります。

hは12時間制の時間を1桁または2桁で表示します。

15時は3時として「3」となります。

hhは12時間制の時間を必ず2桁で表示します。

15時は「03」となります。

DateTime date = new DateTime(2024, 6, 1, 9, 5, 0);
DateTime date2 = new DateTime(2024, 6, 1, 15, 5, 0);
Console.WriteLine(date.ToString("H"));   // 9
Console.WriteLine(date.ToString("HH"));  // 09
Console.WriteLine(date2.ToString("h"));  // 3
Console.WriteLine(date2.ToString("hh")); // 03
9
09
3
03

m・mm

mは分を1桁または2桁で表示します。

5分は「5」、15分は「15」となります。

mmは分を必ず2桁で表示します。

5分は「05」、15分は「15」となります。

DateTime date = new DateTime(2024, 6, 1, 9, 5, 0);
Console.WriteLine(date.ToString("m"));  // 5
Console.WriteLine(date.ToString("mm")); // 05
5
05

s・ss

sは秒を1桁または2桁で表示します。

7秒は「7」、45秒は「45」となります。

ssは秒を必ず2桁で表示します。

7秒は「07」、45秒は「45」となります。

DateTime date = new DateTime(2024, 6, 1, 9, 5, 7);
Console.WriteLine(date.ToString("s"));  // 7
Console.WriteLine(date.ToString("ss")); // 07
7
07

ミリ秒・サブ秒の表現

f・ff・fff

fは秒の小数点以下の桁数を表し、ミリ秒の最上位桁を表示します。

1桁だけ表示したい場合に使います。

ffはミリ秒の上位2桁、fffは3桁すべてを表示します。

ミリ秒を細かく表示したい場合に使います。

DateTime date = new DateTime(2024, 6, 1, 9, 5, 7, 123);
Console.WriteLine(date.ToString("f"));   // 1
Console.WriteLine(date.ToString("ff"));  // 12
Console.WriteLine(date.ToString("fff")); // 123
1
12
123

F・FF・FFF

Ffと似ていますが、末尾のゼロは表示しません。

たとえばミリ秒が「120」の場合、Fは「1」、FFは「12」、FFFは「120」となります。

DateTime date1 = new DateTime(2024, 6, 1, 9, 5, 7, 120);
DateTime date2 = new DateTime(2024, 6, 1, 9, 5, 7, 100);
Console.WriteLine(date1.ToString("F"));   // 1
Console.WriteLine(date1.ToString("FF"));  // 12
Console.WriteLine(date1.ToString("FFF")); // 120
Console.WriteLine(date2.ToString("F"));   // 1
Console.WriteLine(date2.ToString("FF"));  // 10
Console.WriteLine(date2.ToString("FFF")); // 100
1
12
120
1
10
100

タイムゾーンとオフセット

z・zz・zzz

zはUTCからのオフセットの時間部分を1桁または2桁で表示します。

たとえば+9時間なら「9」、-5時間なら「-5」となります。

zzは必ず2桁で表示し、符号も含みます。

+9時間は「+09」、-5時間は「-05」となります。

zzzは時間と分のオフセットを表示します。

たとえば日本標準時(UTC+9:00)は「+09:00」、ニューヨーク時間(UTC-5:00)は「-05:00」となります。

DateTimeOffset dto = new DateTimeOffset(2024, 6, 1, 15, 0, 0, TimeSpan.FromHours(9));
Console.WriteLine(dto.ToString("z"));   // 9
Console.WriteLine(dto.ToString("zz"));  // +09
Console.WriteLine(dto.ToString("zzz")); // +09:00
9
+09
+09:00

曜日・月名のローカライズ

ddd・dddd

dddは曜日の省略名を表示します。

日本語環境では「土」、英語環境では「Sat」となります。

ddddは曜日の完全な名前を表示します。

日本語環境では「土曜日」、英語環境では「Saturday」となります。

DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("ddd", new System.Globalization.CultureInfo("ja-JP")));  // 土
Console.WriteLine(date.ToString("dddd", new System.Globalization.CultureInfo("ja-JP"))); // 土曜日
Console.WriteLine(date.ToString("ddd", new System.Globalization.CultureInfo("en-US")));  // Sat
Console.WriteLine(date.ToString("dddd", new System.Globalization.CultureInfo("en-US"))); // Saturday
土
土曜日
Sat
Saturday

MMM・MMMM

MMMは月の省略名を表示します。

日本語環境では「6月」、英語環境では「Jun」となります。

MMMMは月の完全な名前を表示します。

日本語環境では「6月」、英語環境では「June」となります。

DateTime date = new DateTime(2024, 6, 1);
Console.WriteLine(date.ToString("MMM", new System.Globalization.CultureInfo("ja-JP")));  // 6月
Console.WriteLine(date.ToString("MMMM", new System.Globalization.CultureInfo("ja-JP"))); // 6月
Console.WriteLine(date.ToString("MMM", new System.Globalization.CultureInfo("en-US")));  // Jun
Console.WriteLine(date.ToString("MMMM", new System.Globalization.CultureInfo("en-US"))); // June
6月
6月
Jun
June

エスケープとリテラル文字の挿入

カスタム書式指定子の中で、日時の値として解釈されない文字をそのまま表示したい場合は、シングルクォーテーション ' またはバックスラッシュ \ を使ってエスケープします。

  • シングルクォーテーションで囲むと、その中の文字列がそのまま出力されます
  • バックスラッシュは直後の1文字だけをリテラルとして扱います
DateTime date = new DateTime(2024, 6, 1, 15, 30, 0);
// シングルクォーテーションで囲んだ文字列をそのまま表示
Console.WriteLine(date.ToString("yyyy年MM月dd日 'at' HH:mm"));
// バックスラッシュでスペースや記号をリテラル化
Console.WriteLine(date.ToString("yyyy\\年MM\\月dd\\日 HH\\:mm"));
2024年06月01日 at 15:30
2024年06月01日 15:30

このように、エスケープを使うことで日時のフォーマットに自由に文字を挿入でき、例えば「2024年06月01日 at 15:30」のような自然な表現が可能です。

実用フォーマット例

ログ出力向けフォーマット

ログ出力では日時の一貫性と読みやすさが重要です。

特に複数のシステムやタイムゾーンが絡む場合は、UTC基準のISO 8601形式がよく使われます。

ミリ秒まで含めることで詳細なタイムスタンプを記録できます。

using System;
class Program
{
    static void Main()
    {
        DateTime utcNow = DateTime.UtcNow;
        // ISO 8601形式でミリ秒まで含めたUTC日時をログ用に出力
        string logTimestamp = utcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
        Console.WriteLine($"ログ出力日時: {logTimestamp}");
    }
}
ログ出力日時: 2025-05-07T04:39:38.074Z

この例ではZを付けてUTCであることを明示しています。

ログ解析やトラブルシューティングでタイムゾーンの混乱を避けるために有効です。

ファイル名向けタイムスタンプ

ファイル名に日時を含める場合、ファイルシステムで使えない文字(例: :)を避ける必要があります。

一般的にはyyyyMMdd_HHmmssのような数字とアンダースコアのみのフォーマットが使われます。

using System;
class Program
{
    static void Main()
    {
        DateTime now = DateTime.Now;
        // ファイル名に使いやすい形式で日時を文字列化
        string fileTimestamp = now.ToString("yyyyMMdd_HHmmss");
        string fileName = $"backup_{fileTimestamp}.zip";
        Console.WriteLine($"ファイル名: {fileName}");
    }
}
ファイル名: backup_20250507_133942.zip

この形式ならWindowsやLinuxのファイル名として安全に使え、日時の順序も自然にソート可能です。

ユーザーインターフェース向け表示

ユーザーに見せる日時は、親しみやすく読みやすいフォーマットが求められます。

カルチャに依存した長い日付や短い時刻の組み合わせがよく使われます。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        DateTime now = DateTime.Now;
        CultureInfo ci = new CultureInfo("ja-JP");
        // 長い日付と短い時刻を別々にフォーマットして結合する
        string display = now.ToString("D", ci) + " " + now.ToString("t", ci);
        Console.WriteLine($"画面表示用日時: {display}");
    }
}
画面表示用日時: 2025年5月7日 13:41

このようにカルチャを指定することで、ユーザーの言語や地域に合わせた自然な表現が可能です。

ISO 8601準拠の構築

ISO 8601は国際標準の日時表記で、データ交換やAPIで広く使われています。

DateTimeDateTimeOffsetO(大文字オー)書式指定子を使うと簡単に生成できます。

using System;
class Program
{
    static void Main()
    {
        DateTimeOffset dto = DateTimeOffset.Now;
        // ISO 8601形式で日時を取得
        string iso8601 = dto.ToString("O");
        Console.WriteLine($"ISO 8601形式: {iso8601}");
    }
}
ISO 8601形式: 2025-05-07T13:41:36.7160861+09:00

この形式はタイムゾーンのオフセットも含み、日時の正確な意味を保持します。

和暦表記の活用

日本の和暦表記を使いたい場合は、CultureInfoja-JPを指定し、DateTimeFormatInfo.Calendarを和暦に設定します。

これにより元号と年数を含む和暦表示が可能です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        DateTime date = new DateTime(2024, 6, 1);
        CultureInfo ci = new CultureInfo("ja-JP");
        ci.DateTimeFormat.Calendar = new JapaneseCalendar();
        // 和暦の長い日付形式で表示
        string wareki = date.ToString("gg y年M月d日", ci);
        Console.WriteLine($"和暦表記: {wareki}");
    }
}
和暦表記: 令和 6年6月1日

ggは元号名を表し、yは元号の年数を示します。

和暦対応が必要なアプリケーションで役立ちます。

コードでの利用パターン

ToStringと書式文字列

DateTimeDateTimeOffsetの日時を文字列に変換する基本的な方法はToStringメソッドを使うことです。

ToStringには書式文字列を渡すことで、標準書式指定子やカスタム書式指定子を使った自由なフォーマットが可能です。

using System;
class Program
{
    static void Main()
    {
        DateTime now = new DateTime(2024, 6, 1, 15, 30, 45);
        // 標準書式指定子で短い日付形式
        Console.WriteLine(now.ToString("d")); // 2024/06/01(環境による)
        // カスタム書式指定子で詳細な日時表示
        Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); // 2024-06-01 15:30:45
    }
}
2024/06/01
2024-06-01 15:30:45

ToStringは最も直接的でわかりやすい方法で、単純な日時表示から複雑なフォーマットまで対応できます。

文字列補間 $”” の活用

C#の文字列補間機能を使うと、ToStringの書式指定子を埋め込みながら簡潔に日時を文字列化できます。

{}内にコロン:を使って書式を指定します。

using System;
class Program
{
    static void Main()
    {
        DateTime now = new DateTime(2024, 6, 1, 15, 30, 45);
        // 文字列補間でカスタム書式を指定
        string formatted = $"現在日時: {now:yyyy/MM/dd HH:mm:ss}";
        Console.WriteLine(formatted);
    }
}
現在日時: 2024/06/01 15:30:45

文字列補間はコードの可読性が高く、複数の変数を組み合わせた表示にも便利です。

string.Formatによる整形

string.Formatメソッドも日時のフォーマットに使えます。

書式指定子は引数のインデックスの後にコロンで続けて指定します。

using System;
class Program
{
    static void Main()
    {
        DateTime now = new DateTime(2024, 6, 1, 15, 30, 45);
        string formatted = string.Format("日時: {0:yyyy-MM-dd HH:mm:ss}", now);
        Console.WriteLine(formatted);
    }
}
日時: 2024-06-01 15:30:45

string.Formatは複数の値を一括で整形したい場合に便利ですが、最近は文字列補間のほうがシンプルで使われることが多いです。

TryParseExactとの組み合わせ

日時の文字列を特定のフォーマットで解析したい場合は、DateTime.TryParseExactDateTimeOffset.TryParseExactを使います。

これにより、期待する書式に合致しない文字列を安全に検出できます。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        string input = "2024-06-01 15:30:45";
        DateTime parsedDate;
        bool success = DateTime.TryParseExact(
            input,
            "yyyy-MM-dd HH:mm:ss",
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out parsedDate);
        if (success)
        {
            Console.WriteLine($"解析成功: {parsedDate}");
        }
        else
        {
            Console.WriteLine("解析失敗");
        }
    }
}
解析成功: 2024/06/01 15:30:45

このように、フォーマット指定子を使って文字列と日時の相互変換を厳密に制御できます。

フォーマットの不一致による例外を防ぐため、TryParseExactは安全な選択肢です。

ハマりやすいポイント

ロケール依存の落とし穴

日時のフォーマットはロケール(カルチャ)に依存するため、同じ書式指定子でも表示結果が異なることがあります。

たとえば、"d""D"のような標準書式指定子は、環境のカルチャ設定により日付の順序や区切り文字、曜日の表記が変わります。

これが原因で、ユーザーの環境によって表示がバラバラになったり、ログやファイルの日時表記が統一されなかったりすることがあります。

特に多言語対応や国際化対応が必要なアプリケーションでは注意が必要です。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        DateTime date = new DateTime(2024, 6, 1);
        Console.WriteLine(date.ToString("D", new CultureInfo("ja-JP"))); // 日本語環境
        Console.WriteLine(date.ToString("D", new CultureInfo("en-US"))); // 英語(米国)環境
    }
}
2024年6月1日
Saturday, June 1, 2024

対策としては、日時の表示にカルチャを明示的に指定するか、CultureInfo.InvariantCultureを使って一貫したフォーマットにする方法があります。

夏時間によるズレ

夏時間(DST: Daylight Saving Time)が適用される地域では、日時の計算や変換で予期しないズレが発生しやすいです。

たとえば、夏時間の開始・終了時刻に1時間の飛びや重複が起こるため、DateTimeの変換や比較で混乱が生じます。

using System;
class Program
{
    static void Main()
    {
        // 例: アメリカ東部時間の夏時間開始日(2024年3月10日)
        DateTime beforeDST = new DateTime(2024, 3, 10, 1, 30, 0, DateTimeKind.Local);
        DateTime afterDST = beforeDST.AddHours(1);
        Console.WriteLine($"夏時間開始前: {beforeDST}");
        Console.WriteLine($"1時間後: {afterDST}");
    }
}
夏時間開始前: 2024/03/10 1:30:00
1時間後: 2024/03/10 2:30:00

この例では1時間足しても2:30ではなく3:30となり、1時間飛ばされていることがわかります。

夏時間の影響を考慮する場合は、TimeZoneInfoクラスを使って正確な変換を行うことが推奨されます。

24時間制と12時間制の混在

時間の表示で24時間制HHHと12時間制hhhを混同すると、誤った時刻表示や混乱が起こります。

特に12時間制を使う場合は、ttでAM/PMを明示的に表示しないと、午前か午後かがわからなくなります。

using System;
class Program
{
    static void Main()
    {
        DateTime time = new DateTime(2024, 6, 1, 15, 0, 0);
        Console.WriteLine(time.ToString("HH:mm"));   // 24時間制: 15:00
        Console.WriteLine(time.ToString("hh:mm"));   // 12時間制: 03:00
        Console.WriteLine(time.ToString("hh:mm tt")); // 12時間制+AM/PM: 03:00 PM
    }
}
15:00
03:00
03:00 午後

12時間制を使う場合は必ずttを付けてAM/PMを表示し、誤解を防ぎましょう。

DateTime.Kindの誤解

DateTimeKindプロパティは日時の種類を示しますが、これを誤解するとバグの原因になります。

KindLocalUtcUnspecifiedの3種類がありますが、Unspecifiedはタイムゾーン情報がない状態であり、変換時に意図しない動作を招くことがあります。

using System;
class Program
{
    static void Main()
    {
        DateTime unspecified = new DateTime(2024, 6, 1, 12, 0, 0, DateTimeKind.Unspecified);
        DateTime local = unspecified.ToLocalTime();
        DateTime utc = unspecified.ToUniversalTime();
        Console.WriteLine($"Unspecified: {unspecified} Kind: {unspecified.Kind}");
        Console.WriteLine($"ToLocalTime: {local} Kind: {local.Kind}");
        Console.WriteLine($"ToUniversalTime: {utc} Kind: {utc.Kind}");
    }
}
Unspecified: 2024/06/01 12:00:00 Kind: Unspecified
ToLocalTime: 2024/06/01 21:00:00 Kind: Local
ToUniversalTime: 2024/06/01 3:00:00 Kind: Utc

Unspecifiedの日時をToLocalTimeToUniversalTimeに変換すると、期待した変換が行われず、同じ日時が返されることがあります。

日時の基準を明確にするため、DateTimeを生成するときはKindを意識し、可能ならDateTimeOffsetの利用も検討してください。

テストとバリデーション

CultureInfo切替テスト

日時のフォーマットはCultureInfoに依存するため、多言語対応や国際化対応のアプリケーションでは、異なるカルチャでの表示をテストすることが重要です。

テスト時にCultureInfo.CurrentCultureを切り替えて、期待通りのフォーマットになるかを検証します。

以下は日本語ja-JPと英語(米国、en-US)のカルチャを切り替えて、同じ日時の表示結果を比較する例です。

using System;
using System.Globalization;
using System.Threading;
class Program
{
    static void Main()
    {
        DateTime date = new DateTime(2024, 6, 1, 15, 30, 45);
        // 日本語カルチャに切り替え
        Thread.CurrentThread.CurrentCulture = new CultureInfo("ja-JP");
        Console.WriteLine($"日本語カルチャ: {date.ToString("F")}");
        // 英語(米国)カルチャに切り替え
        Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
        Console.WriteLine($"英語(米国)カルチャ: {date.ToString("F")}");
    }
}
日本語カルチャ: 2024年6月1日 15:30:45
英語(米国)カルチャ: Saturday, June 1, 2024 3:30:45 PM

このようにテストでカルチャを切り替えることで、異なる言語環境での表示崩れや誤表示を早期に発見できます。

ユニットテストに組み込む場合は、テストメソッド内でカルチャを切り替え、元に戻す処理を忘れないようにしましょう。

仮想タイムでの検証

日時を扱うロジックのテストでは、実際の現在時刻に依存するとテスト結果が不安定になりやすいです。

そこで「仮想タイム(Virtual Time)」を使い、任意の日時を固定してテストを行う方法が有効です。

.NET標準では日時のモック機能はありませんが、DateTimeDateTimeOffsetを直接使わず、日時取得をラップしたインターフェースを作成して注入する設計が推奨されます。

using System;
public interface IDateTimeProvider
{
    DateTime Now { get; }
}
public class SystemDateTimeProvider : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
}
public class VirtualDateTimeProvider : IDateTimeProvider
{
    private DateTime _fixedNow;
    public VirtualDateTimeProvider(DateTime fixedNow)
    {
        _fixedNow = fixedNow;
    }
    public DateTime Now => _fixedNow;
}
class Program
{
    static void Main()
    {
        // 実際の現在時刻を使う場合
        IDateTimeProvider systemProvider = new SystemDateTimeProvider();
        Console.WriteLine($"実際の現在時刻: {systemProvider.Now}");
        // テスト用に固定日時を使う場合
        DateTime fixedTime = new DateTime(2024, 6, 1, 12, 0, 0);
        IDateTimeProvider virtualProvider = new VirtualDateTimeProvider(fixedTime);
        Console.WriteLine($"仮想日時: {virtualProvider.Now}");
    }
}
実際の現在時刻: 2025/05/07 13:44:11
仮想日時: 2024/06/01 12:00:00

このように日時取得を抽象化することで、テスト時に任意の日時を設定でき、日時に依存するロジックの検証が安定して行えます。

特に時間帯や夏時間の影響を受ける処理のテストに効果的です。

パフォーマンスの考慮

文字列生成コスト

日時を文字列に変換する際、DateTime.ToStringstring.Formatなどのメソッドは内部で新しい文字列オブジェクトを生成します。

大量の日時フォーマット処理を行う場合、この文字列生成がパフォーマンスのボトルネックになることがあります。

特にリアルタイム処理や高頻度のログ出力、ループ内での日時フォーマットでは、文字列の生成とガベージコレクションの負荷が無視できません。

文字列は不変(immutable)であるため、毎回新しいインスタンスが作られ、メモリ割り当てが発生します。

対策としては、以下のような方法があります。

  • フォーマット済みの文字列をキャッシュする(同じ日時を何度も使う場合)
  • 文字列生成を減らすために日時のフォーマット回数を最小限にする
  • Span<char>Utf8Formatterなどの低レベルAPIを使い、ヒープ割り当てを抑える

Span<char>とUtf8Formatterの利用

.NET Core以降では、Span<char>Utf8Formatterを使って文字列生成のコストを抑える方法が提供されています。

これらはスタック上のバッファを使い、ヒープ割り当てを減らすことで高速かつ低メモリでのフォーマットを実現します。

Utf8FormatterSystem.Buffers.Text名前空間にあり、日時や数値をUTF-8エンコードされたバイト列に直接フォーマットできます。

これにより、文字列を経由せずにバイナリデータとして扱うことが可能です。

using System;
using System.Buffers.Text;
using System.Text;
class Program
{
    static void Main()
    {
        DateTime now = new DateTime(2024, 6, 1, 15, 30, 45);
        Span<byte> buffer = stackalloc byte[64];
        // ISO 8601形式のカスタムフォーマットを作成
        string format = "yyyy-MM-ddTHH:mm:ss";
        // まずToStringで文字列を作成(通常の方法)
        string formatted = now.ToString(format);
        Console.WriteLine($"通常のToString: {formatted}");
        // Utf8Formatterは直接DateTimeをフォーマットできないため、
        // まず文字列に変換し、UTF8エンコードする例
        int bytesWritten = Encoding.UTF8.GetBytes(formatted, buffer);
        string utf8String = Encoding.UTF8.GetString(buffer.Slice(0, bytesWritten));
        Console.WriteLine($"Utf8Formatter風の処理: {utf8String}");
    }
}
通常のToString: 2024-06-01T15:30:45
Utf8Formatter風の処理: 2024-06-01T15:30:45

より高度な使い方としては、Utf8Formatterを使って数値部分を直接バイト列に書き込む方法がありますが、日時のカスタムフォーマット全体を直接サポートしていないため、部分的に利用する形になります。

また、Span<char>を使うと、DateTime.TryFormatメソッドでスタック上のバッファに直接日時をフォーマットできます。

これにより文字列のヒープ割り当てを回避できます。

using System;
class Program
{
    static void Main()
    {
        DateTime now = new DateTime(2024, 6, 1, 15, 30, 45);
        Span<char> buffer = stackalloc char[32];
        bool success = now.TryFormat(buffer, out int charsWritten, "yyyy-MM-dd HH:mm:ss");
        if (success)
        {
            string result = new string(buffer.Slice(0, charsWritten));
            Console.WriteLine($"TryFormat結果: {result}");
        }
    }
}
TryFormat結果: 2024-06-01 15:30:45

この方法は大量の日時フォーマット処理でパフォーマンスを向上させたい場合に有効です。

特にガベージコレクションの負荷を減らしたいリアルタイム処理や高負荷環境での利用が推奨されます。

現在日時を固定フォーマットで取得するには

現在の日時を特定のフォーマットで文字列として取得したい場合は、DateTime.NowDateTime.UtcNowを使い、ToStringメソッドにカスタム書式指定子を渡します。

これにより、日時を任意の形式で簡単に取得できます。

using System;
class Program
{
    static void Main()
    {
        DateTime now = DateTime.Now;
        // 例: yyyy-MM-dd HH:mm:ss形式で現在日時を取得
        string formattedNow = now.ToString("yyyy-MM-dd HH:mm:ss");
        Console.WriteLine($"現在日時(固定フォーマット): {formattedNow}");
    }
}
現在日時(固定フォーマット): 2025-05-07 13:44:24

UTC時間が必要な場合はDateTime.UtcNowを使い、同様にフォーマットできます。

ログやファイル名、画面表示など用途に応じて使い分けてください。

カスタム書式を再利用するコツ

カスタム書式指定子を何度も使う場合は、文字列として定数や変数に保持しておくと便利です。

これにより、フォーマットの一貫性を保ちつつ、変更も容易になります。

using System;
class Program
{
    // フォーマット文字列を定数として定義
    private const string DateTimeFormat = "yyyy/MM/dd HH:mm:ss";
    static void Main()
    {
        DateTime now = DateTime.Now;
        // 定義済みのフォーマットを使って日時を文字列化
        string formatted = now.ToString(DateTimeFormat);
        Console.WriteLine(formatted);
    }
}
2025/05/07 13:44:28

また、複数の場所で使う場合は静的クラスや設定ファイルにまとめると管理しやすくなります。

DateOnlyとTimeOnlyの選択肢

.NET 6以降では、DateOnlyTimeOnlyという新しい構造体が導入され、日付だけ、または時刻だけを扱う用途に特化しています。

これらはDateTimeよりもシンプルで、不要な時刻や日付の情報を持たずに扱えます。

  • DateOnlyは年月日だけを表し、時刻情報は含みません
  • TimeOnlyは時分秒だけを表し、日付情報は含みません
using System;
class Program
{
    static void Main()
    {
        DateOnly date = DateOnly.FromDateTime(DateTime.Now);
        TimeOnly time = TimeOnly.FromDateTime(DateTime.Now);
        Console.WriteLine($"日付のみ: {date.ToString("yyyy-MM-dd")}");
        Console.WriteLine($"時刻のみ: {time.ToString("HH:mm:ss")}");
    }
}
日付のみ: 2025-05-07
時刻のみ: 13:44:32

DateOnlyTimeOnlyは、例えばカレンダーの日付選択やタイムピッカーの時刻選択など、日付と時刻を分けて扱いたいシナリオで便利です。

DateTimeと異なり、時刻や日付の余計な部分を気にせずに済むため、バグの防止にもつながります。

まとめ

この記事では、C#のDateTimeにおける標準・カスタム書式指定子の使い方から、実用的なフォーマット例、コードでの活用方法、注意すべきポイントまで幅広く解説しました。

カルチャ依存や夏時間の影響、パフォーマンス面の工夫も紹介し、日時処理のトラブルを防ぐ知識を提供しています。

DateOnlyTimeOnlyの活用も含め、日時フォーマットを自在に扱うための基礎と応用が理解できます。

関連記事

Back to top button