読者です 読者をやめる 読者になる 読者になる

【C#】C#で和暦変換

極々稀に西暦→和暦の変換をしなきゃいけないことがあるんだけど、割と面倒だってことだけは知っているのでちょっとまとめておく。
Utilなクラスを作れば十分だと思うが、ありとあらゆるところで出てくる場合は拡張メソッドでもいいかもしれない。
とりあえずusing System.Globalization;はしておくこと。

DatetimeをToStringする際に和暦にする場合

public static string ConvertWareki(DateTime seireki, string format)
{
    return seireki.ToString(format, new CultureInfo("ja-JP", true)
                                        {
                                            DateTimeFormat =
                                            {
                                                Calendar = new JapaneseCalendar()
                                            }
                                        });
}

要はDateTimeFormat.CalendarにJapaneseCalendarを持ったja-JPのCultureInfoをToStringのproviderに指定してあげればOK。
年号(平成とか昭和とか)が欲しい場合はformatにgを指定することで出力される。
ex.”ggyy年MM月dd日”

西暦のstringをパースし和暦のDatetimeに変換する場合

public static DateTime ParseWareki(string s)
{
    return DateTime.Parse(s, new CultureInfo("ja-JP", true));
}

元号の略文字(昭和→S、平成→H etc)+和暦+月日 ただし年の部分に前0はない」とかいう意味不明なデータ(実際にあった)

public static DateTime ParseFuckWareki(string s)
{
    var ci = new CultureInfo("ja-JP", true)
    {
        DateTimeFormat =
        {
            Calendar = new JapaneseCalendar()
        }
    };

    //先頭一文字目から元号を割り出す
    var era = ci.DateTimeFormat.Calendar.GetEra(DateTime.Parse(s.Substring(0, 1) + "01年01月01日", ci));
    var eraName = ci.DateTimeFormat.GetEraName(era);

    //年月日を特定する
    var year = s.Length == 7 ? s.Substring(1, 2) : s.Substring(1, 1);
    var month = s.Substring(s.Length - 4, 2);
    var day = s.Substring(s.Length - 2, 2);

    return DateTime.Parse(eraName + year + "年" + month + "月" + day + "日", ci);
}

年月日特定のところはParseExactを使ってもっとスマートにやろうとしたんだけど、「gyMMdd」や「gyyMMdd」のようなformatを指定してもFormatExceptionが発生した。
仕方ないので泥臭いSubstringで特定し、普通にくっつけてやることに。残念。

[2014/07/31追記]

割と元号がらみの検索で飛んでくる人がいるのでちょっとした補足。

SとかHのような、和暦の元号の略称が用いられていてもja-JPなCultureInfoを使えば普通にDateTime.Parseが可能です。

var ci = new CultureInfo("ja-JP", true)
{
    DateTimeFormat = { Calendar = new JapaneseCalendar() }
};

var s = "S63年1月1日";
var d = DateTime.Parse(s, ci);

Console.WriteLine(d.ToString("yyyy/MM/dd"));
// => 1988/01/01

ただしこの場合、ちゃんと「Syy年MM月dd日」だとか、「Syy/MM/dd」のように区切られていないと当然ながらパースできません。

var ci = new CultureInfo("ja-JP", true)
{
    DateTimeFormat = { Calendar = new JapaneseCalendar() }
};

var s = "S630101";
var d = DateTime.Parse(s, ci);
// => FormatException

Console.WriteLine(d.ToString("yyyy/MM/dd"));

そんなわけで上記の例では元号の部分を略称から無理矢理戻しています。

//先頭一文字目から元号を割り出す
var era = ci.DateTimeFormat.Calendar.GetEra(DateTime.Parse(s.Substring(0, 1) + "01年01月01日", ci));
var eraName = ci.DateTimeFormat.GetEraName(era);

CultureInfo.DateTimeFormat.CalendarにはSystem.Globalization.Calendarクラスが入っています。今だと実体はJapaneseCalendarクラスですね。

JapaneseCalendarのErasプロパティのドキュメントを見てみると、どの年号に対応しているとか色々書いてあります。逆に言えば、この表に入っている表現ならパースが可能、と言うことです。

で、Calendar.GetEraメソッドを使うとDateTimeからこの年号の序数を取得することができます。その年号の序数を年号のStringに変換するメソッドDateTimeFormat.GetEraNameメソッドです。

ちょっとややこしくなってきたので、コードを書いてみましょう。

var ci = new CultureInfo("ja-JP", true)
{
    DateTimeFormat = { Calendar = new JapaneseCalendar() }
};

var s = "S63年01月01日";
var d = DateTime.Parse(s, ci);

var era = ci.DateTimeFormat.Calendar.GetEra(d);
var eraName = ci.DateTimeFormat.GetEraName(era);

Console.WriteLine("era:{0} eraName:{1}", era, eraName);
// =>era:3 eraName:昭和

じゃあ逆に、DateTimeから年号の略称を取得する場合ですが、これはなぜか存在しません。リフレクションを用いることで可能らしいので、興味のある方やどうしても必要な方はそちらをどうぞ。

ただまぁ、eraNameを取得してswitchで分岐するあたりが現実的な落としどころだとは思いますが…。

[2014/07/31追記ここまで]

参考