c#の非同期処理時のDateTimeFormat

C#で日付のフォーマットを変更していた際に、思い通りの挙動にならず
仕様について調べたのでそれの備忘録です


# 発生した問題
``` C#:test.cs
using System.Globalization;
using System.Threading;
// 2025/01/06
CultureInfo originalCulture = new CultureInfo("ja-JP");
originalCulture.DateTimeFormat.Calendar = new JapaneseCalendar();
originalCulture.DateTimeFormat.ShortDatePattern = "ggy年M月d日";
Thread.CurrentThread.CurrentCulture = originalCulture;
DateTime.Today.ToString();
// 令和7年1月6日 0:00:00
```
`Thread.CurrentThread.CurrentCulture`で和暦で日付を表示させるようにしていましたが...
↓

``` C#:test.cs
using System.Globalization;
using System.Threading;
// 2025/01/06
CultureInfo originalCulture = new CultureInfo("ja-JP");
originalCulture.DateTimeFormat.Calendar = new JapaneseCalendar();
originalCulture.DateTimeFormat.ShortDatePattern = "ggy年M月d日";
Thread.CurrentThread.CurrentCulture = originalCulture;
// 非同期で実行
Task.Run(() => {
    DateTime.Today.ToString();
});  
// 2025/01/06 0:00:00
```
非同期処理だと日付のフォーマットがデフォルトのままでした

# 問題の原因
`Thread.CurrentThread.CurrentCulture`は設定したスレッドでのみ有効で、非同期処理は別のスレッドで実行されるため日付のフォーマットがデフォルトになっていました。

# 問題の解決策

**解決策**　(2025/01/08追記)
#### 1.非同期処理の中でDateTimeFormatを設定
``` C#:test.cs
using System.Globalization;
using System.Threading;
// 2025/01/06
CultureInfo originalCulture = new CultureInfo("ja-JP");
originalCulture.DateTimeFormat.Calendar = new JapaneseCalendar();
originalCulture.DateTimeFormat.ShortDatePattern = "ggy年M月d日";
// 非同期で実行
Task.Run(() => {
    // 非同期処理の中でDateTimeFormatを設定
    Thread.CurrentThread.CurrentCulture = originalCulture;
    DateTime.Today.ToString();
});  
// 令和7年1月6日 0:00:00
```
- 良い点：どこで日付のフォーマットが変更されたか見やすい
- 悪い点：非同期処理をするたび(スレッドが変わるたび)に設定しないといけない
#### 2.CultureInfo.DefaultThreadCurrentCultureで全体のフォーマットを設定
``` C#:test.cs
CultureInfo originalCulture = new CultureInfo("ja-JP");
originalCulture.DateTimeFormat.Calendar = new JapaneseCalendar();
originalCulture.DateTimeFormat.ShortDatePattern = "ggy年M月d日";
CultureInfo.DefaultThreadCurrentCulture = originalCulture;
// 非同期で実行
Task.Run(() => {
    DateTime.Today.ToString();
});  
// 令和7年1月6日 0:00:00
```
- 良い点：設定されていない全ての非同期処理のフォーマットが切り替わる
- 悪い点：全てのフォーマットが切り替わるので、個別でフォーマットを変えたいときに不便

#### 2025/01/08追記
コメントでアドバイスをいただいたので、追記させていただきます（ありがとうございます！）
1.2の合わせ技や`String`の書式指定で個別のFormatを綺麗に設定できそうです

**日付フォーマットの適用順**
`CultureInfo.DefaultThreadCurrentCulture`
↓
`Thread.CurrentThread.CurrentCulture`
↓
`String.Format`や`ToString`

上に行くほど範囲が広くなり、下に行くほど優先度が高くなります

``` C#:test.cs
using System.Globalization;
using System.Threading;
// 2025/01/06
CultureInfo originalCulture = new CultureInfo("ja-JP");
originalCulture.DateTimeFormat.Calendar = new JapaneseCalendar();
originalCulture.DateTimeFormat.ShortDatePattern = "ggy年M月d日";
// 全体のデフォルトFormatを設定
CultureInfo.DefaultThreadCurrentCulture = originalCulture;
// 非同期で実行
Task.Run(() => {
    DateTime.Today.ToString();
    DateTime.Today.ToString();
    DateTime.Today.ToString();
});  
// 令和7年1月6日 0:00:00
// 令和7年1月6日 0:00:00
// 令和7年1月6日 0:00:00

// 非同期で実行(非同期処理内全部に設定 和暦→西暦)
Task.Run(() => {
    // 西暦のカレンダーでフォーマットを設定
    CultureInfo asyncCulture = new CultureInfo("ja-JP");
    asyncCulture.DateTimeFormat.Calendar = new GregorianCalendar();
    asyncCulture.DateTimeFormat.ShortDatePattern = "yyyy年MM月dd日";
    Thread.CurrentThread.CurrentCulture = asyncCulture;
    
    DateTime.Today.ToString();
    DateTime.Today.ToString();
    DateTime.Today.ToString();
});  
// 2025/01/06 0:00:00
// 2025/01/06 0:00:00
// 2025/01/06 0:00:00

// 非同期で実行(個別に設定 和暦→西暦)
Task.Run(() => {
    // 西暦のカレンダーを設定
    CultureInfo gregorianCulture = new CultureInfo("ja-JP");
    gregorianCulture.DateTimeFormat.Calendar = new GregorianCalendar();
    
    // デフォルト
    DateTime.Today.ToString();
    // ToStringのカスタム日時書式指定子で変換
    DateTime.Today.ToString("yyyy年M月d日", gregorianCulture);
    // String.Formatで変換
    string.Format(gregorianCulture, "{0:yyyy年MM月dd日}", DateTime.Today); 
});  
// 令和7年1月6日 0:00:00
// 2025年1月6日
// 2025年01月06日
```

# 実行環境　(2025/01/10追記)
.NET Framework 4.5

`.NET Framework`のバージョンによって非同期処理時のCurrentCultureの挙動が異なるようです
↓こちらの記事で、詳しく解説されています。


https://qiita.com/yamagutiesneo/items/5f81513cd87e1a30f7bd#_reference-a03681cdc9f97e24af98

# おわりに
今回の問題は、同期処理を非同期処理に変更する時(逆もしかり)などに、よく起こりそうなので、そういった修正をする際には日付のフォーマットに注意しておこうと思いました。

# 参考にさせていただいた記事、サイト

https://learn.microsoft.com/ja-jp/dotnet/fundamentals/runtime-libraries/system-globalization-cultureinfo-currentculture

https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.cultureinfo.defaultthreadcurrentculture?view=net-8.0

https://qiita.com/yamagutiesneo/items/5f81513cd87e1a30f7bd#_reference-a03681cdc9f97e24af98