C#

C#: DateTimeの年2桁の解釈 (0~29, 39~99 → 2000~2029, 1930~1999) を変更する

More than 1 year has passed since last update.

日付形式を年2桁(yy)で扱うことは今日日あまりないと思いますが、最近必要になったので参考に記載します。

C# (.NET Framework) では、"yy/MM/dd" (や "y/M/d") など、年2桁の日付形式文字列を DateTime.Parse(), DateTime.ParseExact(), DateTime.TryParseExact() で扱うと下記のようになります。

00/01/01 → 2000/01/01

:
29/01/01 → 2029/01/01
:
30/01/01 → 1930/01/01
:
99/01/01 → 1999/01/01

for (var yy = 0; yy <= 99; yy++)

{
// DateTime.Parse()
var d1 = DateTime.Parse($"{yy:D2}/01/01");
// DateTime.ParseExact()
var d2 = DateTime.ParseExact($"{yy:D2}/01/01", "yy/MM/dd", null);
// DateTime.TryParseExact()
DateTime d3; DateTime.TryParseExact($"{yy:D2}/01/01", "yy/MM/dd", null, System.Globalization.DateTimeStyles.None, out d3);

// 出力
Console.WriteLine($"{yy:D2}/01/01 → {d1:yyyy/MM/dd}, {d2:yyyy/MM/dd}, {d3:yyyy/MM/dd}");
}

2029年の次が1930年になってしまうので、2030年になると2000年問題のようなことが起こるおそれがあります。


何故このような動作になるのか

この動作は、Windowsの地域設定 (カレンダー設定) がもとになっています。

(Windows 10でコントロールパネル経由でたどり着くには、「コントロールパネル」> 「時計、言語、および地域」>「日付と時刻の設定」>「カレンダーの設定の変更」)

1.png

「2桁の数字で年を入力すると、次の範囲内での暦年として解釈する」の設定を「2099」(2000 から 2099) にすると、下記のように挙動が変わります。

00/01/01 → 2000/01/01

:
29/01/01 → 2029/01/01
:
30/01/01 → 2030/01/01
:
99/01/01 → 2099/01/01


プログラム上のみで動作を変更できないのか

複数のPCで動作させるプログラムの場合、PCの設定を変えるのはできるだけ避けたいです。(全台変える必要が出てくるので)

DateTime.Parse() などはカルチャ (地域情報オブジェクト) によって動作が変わるようになっていて、明示的にカルチャを指定すれば動作を変更できます。(前述のコードのようにカルチャを指定しない場合はWindowsの設定が使用される)

年2桁の設定は Calendar.TwoDigitYearMax という項目です。

// カルチャの生成 (年2桁の解釈(TwoDigitYearMax)を2099(2000~2099)にする)

var culture = new System.Globalization.CultureInfo("ja-JP");
culture.Calendar.TwoDigitYearMax = 2099;

for (var yy = 0; yy <= 99; yy++)
{
// DateTime.Parse() + カルチャ明示
var d1 = DateTime.Parse($"{yy:D2}/01/01", culture);
// DateTime.ParseExact() + カルチャ明示
var d2 = DateTime.ParseExact($"{yy:D2}/01/01", "yy/MM/dd", culture);
// DateTime.TryParseExact() + カルチャ明示
DateTime d3; DateTime.TryParseExact($"{yy:D2}/01/01", "yy/MM/dd", culture, System.Globalization.DateTimeStyles.None, out d3);

// 出力
Console.WriteLine($"{yy:D2}/01/01 → {d1:yyyy/MM/dd}, {d2:yyyy/MM/dd}, {d3:yyyy/MM/dd}");
}

System.Globalization.CultureInfousing System.Globalization; していれば CultureInfo のみでよいです。

CultureInfo.CurrentCulture に現在のカルチャが格納されていますが、このオブジェクトは読み取り専用なので設定変更ができません。

DateTime.Parse() などを MSDN で確認するとカルチャを指定するメソッドがないように見えますが、CultureInfoIFormatProvider を実装しているので、IFormatProvider が引数として必要なオーバーロードに引数として指定することができます。