C#定石 - 日付書式利用のおまじない

## はじめに

C# ソフト開発時に、決まり事として実施していた内容を記載します。

### テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8

::: note
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
:::

## 日付書式

C# の DataTime 書式は、標準動作では、システムのカレンダー設定（西暦/和暦）に依存した動作となります。  
対象日付が 2024-12-20 の場合、DateTime.Now.ToString("yyyy") の結果は、下記のようになります。 


| カレンダー設定  | 書式 yyyy の結果  |  補足                           |
| ------------- | ---------------- | ------------------------------ |
| 西暦           | 2024             | 西暦2024年。この4桁表示。        |
| 和暦           | 06               | 和暦、令和6年。この年数で2桁表示。 |

システムのカレンダー設定に依存した表記を行うケースはありますが、yyyy と 4桁指定しているのに、この動作はイマイチ納得できない仕様と思えてしまっています。
また、パッケージとしてシステムのカレンダー設定に依存せず、パッケージとしての設定で動作させたいケースもあります。

DateTime.ToString(String, IFormatProvider) と都度 IFormatProvider で、カルチャ固有の書式情報を提供するオブジェクトを指定するのは手間がかかるため、スタートアップルーチンで、西暦/和暦の設定を決まり事のように実施していました。

https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.thread.currentculture

https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.thread.currentuiculture

Windows Forms / WPF、.NET Framework / .NET ともに同一記述ですが、記述箇所とその周辺に差異があるので、それぞれについて以降で記載します。

### Windows Forms - .NET Framework

```CSharp:Program.cs
internal static class Program
{
  [STAThread]
  static void Main()
  {
    // システム設定カルチャではなく、日本語の既定カルチャ（西暦）に変更
    var ci = new System.Globalization.CultureInfo("ja-JP", false);
    // 和暦とする場合は、下記をコメントアウト
    // ci.DateTimeFormat.Calendar = 
    //     new System.Globalization.JapaneseCalendar();
    Thread.CurrentThread.CurrentCulture = ci;
    Thread.CurrentThread.CurrentUICulture = ci; 

    // メインフォーム
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
  }
}
```

### Windows Forms - .NET

```CSharp:Program.cs
internal static class Program
{
  [STAThread]
  static void Main()
  {
    // システム設定カルチャではなく、日本語の既定カルチャ（西暦）に変更
    var ci = new System.Globalization.CultureInfo("ja-JP", false);
    // 和暦とする場合は、下記をコメントアウト
    // ci.DateTimeFormat.Calendar = 
    //     new System.Globalization.JapaneseCalendar();
    Thread.CurrentThread.CurrentCulture = ci;
    Thread.CurrentThread.CurrentUICulture = ci; 

    // メインフォーム
    ApplicationConfiguration.Initialize();
    Application.Run(new Form1());
  }
}
```

### WPF - .NET Framework / .NET

.NET Framework / .NET ともに同一ソースコードです。
[WPF定石 - スタートアップルーチン](https://qiita.com/chai0917/items/1d1ca54ca82ab2145094) 記載内容を実施済みのコードをベースとします。

```CSharp:App.xaml.cs
public partial class App : Application
{
  private void App_Startup(object sender, StartupEventArgs e)
  {
    // システム設定カルチャではなく、日本語の既定カルチャ（西暦）に変更
    var ci = new System.Globalization.CultureInfo("ja-JP", false);
    // 和暦とする場合は、下記をコメントアウト
    // ci.DateTimeFormat.Calendar = 
    //     new System.Globalization.JapaneseCalendar();
    Thread.CurrentThread.CurrentCulture = ci;
    Thread.CurrentThread.CurrentUICulture = ci; 

    // メインウィンドウ
    var mainWindow = new MainWindow();
    mainWindow.Show();
  }
}
```

## 関係情報
### システムのカレンダー設定確認

システムのカレンダー設定を確認したい場合、下記コードで確認できます。

```CSharp
private string GetCalendarType()
{
  var calendar =
      System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.Calendar;
  if (calendar is System.Globalization.JapaneseCalendar)
  {
    return "和暦";
  }
  else
  {
    return "西暦";
  }
}
```

### 日時書式

https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/custom-date-and-time-format-strings

| 書式      | 説明                |
| -------- | ------------------- |
| "yyyy"   | 年（4桁）            |
| "yy"     | 年（01-99）          |
| "y"      | 年（1-99）           |
| "g"、"gg" | 西暦/元号（令和, ...）|
| "MM"     | 月（01-12）          |
| "M"      | 月（1-12）           |
| "dd"     | 日（01-31）          |
| "d"      | 日（1-31）           |
| "HH"     | 24時間形式の時間（00-23） |
| "H"      | 24時間形式の時間（0-23）  |
| "mm"     | 分（00-59）          |
| "m"      | 分（0-59）           |
| "ss"     | 秒（00-59）          |
| "s"      | 秒（0-59）           |