西暦と和暦を変換するには?:.NET TIPS
.NET Frameworkが提供する和暦を扱う機能を用いて、西暦と和暦を変換する方法を解説する。また新元号対応および「元年」表記対応についても取り上げる。
本稿は2003/06/06に初版公開、2018/10/31に改訂した記事を再改訂し、仕様変更について追記を行ったものです。
本稿では、西暦に基づくDateTime構造体と和暦の文字列とを相互に変換する方法を解説する。なお、2018年以降、元号の扱いに関して.NET Frameworkに重大な仕様変更があるので、改訂の機会に追記した。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
また、元号を略称(「平」や「H」など)で表示する方法については、.NET TIPS「日付の年号を略称で表示するには?[C#、VB]」をご覧いただきたい。新元号への対応を確実に行うために独自の年号テーブルを持つ方法についても、「日付の年号を表示するには?[独自テーブル参照編]」で紹介している。
和暦についての情報を得る
和暦に関する情報は、JapaneseCalendarクラス(System.Globalization名前空間)が提供してくれる。このクラスはCalendarクラス(System.Globalization名前空間)を継承したもので、同じように世界中のさまざまな暦が、このクラスを継承したクラスとして存在している。
以下に、このJapaneseCalendarクラスを用いて、日付時刻を保持するDateTime構造体に含まれる年情報を、年号と年数で取得するサンプルプログラムを示す。Visual Studio 2017でVBプロジェクトを新規作成して試す場合には、Mainプロシージャ内のコードをコピー&ペーストしてほしい(以下、同様)。
// wareki1.cs
using System;
using System.Globalization;
class WarekiSample1 {
static void Main() {
DateTime 日付 = new DateTime(2003, 7, 1, 12, 34, 56);
JapaneseCalendar カレンダー = new JapaneseCalendar();
Console.WriteLine(カレンダー.GetEra(日付));
// 出力:4
string [] 元号名 = { "明治", "大正", "昭和", "平成" };
Console.WriteLine(元号名[カレンダー.GetEra(日付) - 1]);
// 出力:平成
// あるいは、元号名の配列を持たずに、以下のようにしてもよい
// CultureInfo カルチャ = new CultureInfo("ja-JP", true);
// カルチャ.DateTimeFormat.Calendar = カレンダー;
// string 日付の元号
// = カルチャ.DateTimeFormat.GetEraName(カレンダー.GetEra(日付));
// Console.WriteLine(日付の元号);
// 出力:平成
Console.WriteLine(カレンダー.GetYear(日付));
// 出力:15
}
}
// コンパイル方法:csc wareki1.cs
' wareki1.vb
Imports System
Imports System.Globalization
Module WarekiSample1
Sub Main()
Dim 日付 As DateTime = New DateTime(2003,7,1,12,34,56)
Dim カレンダー As JapaneseCalendar = New JapaneseCalendar()
Console.WriteLine(カレンダー.GetEra(日付))
' 出力:4
Dim 元号名 As String() = {"明治", "大正", "昭和", "平成"}
Console.WriteLine(元号名(カレンダー.GetEra(日付) - 1))
' 出力:平成
' あるいは、元号名の配列を持たずに、以下のようにしてもよい
' Dim カルチャ As CultureInfo = New CultureInfo("ja-JP", True)
' カルチャ.DateTimeFormat.Calendar = カレンダー
' Dim 日付の元号 As String _
' = カルチャ.DateTimeFormat.GetEraName(カレンダー.GetEra(日付))
' Console.WriteLine(日付の元号)
' 出力:平成
Console.WriteLine(カレンダー.GetYear(日付))
' 出力:15
End Sub
End Module
' コンパイル方法:vbc wareki1.vb
このサンプルプログラムでは、和暦を扱うということで変数名などにあえて日本語を使ってみた。
さて、ここでポイントになるのは、もちろんJapaneseCalendarクラスである。このインスタンスを作成し、幾つかのメソッドを呼び出している。GetEraメソッドは、指定された日付時刻が示す元号を得る。結果は整数で得られ、1が明治、2が大正、3が昭和、4が平成となる。GetYearメソッドは、その元号内での年数を整数で返す。この2つを使えば、「2003年」が「平成15年」であることを突き止められるわけである。
DateTime構造体を和暦の文字列に変換する
ただ単にDateTime構造体の値を和暦の文字列に変換するだけなら、もっと手軽な方法がある。以下はそれを示した例である。
// wareki2.cs
using System;
using System.Globalization;
class WarekiSample2 {
static void Main() {
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
DateTime target = new DateTime(2003, 7, 1);
string result = target.ToString("ggyy年M月d日", culture);
Console.WriteLine(result);
// 出力:平成15年7月1日
}
}
// コンパイル方法:csc wareki2.cs
' wareki2.vb
Imports System
Imports System.Globalization
Module WarekiSample2
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim target As DateTime = New DateTime(2003, 7, 1)
Dim result As String = target.ToString("ggyy年M月d日", culture)
Console.WriteLine(result)
' 出力:平成15年7月1日
End Sub
End Module
' コンパイル方法:vbc wareki2.vb
ポイントは、CultureInfoクラス(System.Globalization名前空間)だ。これは各地域の文化固有の情報を扱うクラスである。コンストラクタのパラメーターに"ja-JP"を付けてインスタンスを作成すると、「日本語の日本」についてのインスタンスが得られる。
しかし、標準では、「日本語の日本」も西暦(グレゴリオ暦)が選択されているので、これを和暦(JapaneseCalendarクラスで示される)に置き換える必要がある。それに該当するのは、culture.DateTimeFormat.CalendarにJapaneseCalendarクラスのインスタンスを代入している行である。これにより、このCultureInfoクラスのインスタンスは、和暦という暦を持つカルチャーを示すオブジェクトとなった。
後はこれを用いて、DateTime型の値から文字列への変換をToStringメソッドを用いて行えばよい。ToStringメソッドの2つ目のパラメーターにCultureInfoクラスのインスタンスが指定されている点に注意をしていただきたい。なお、書式指定内の「gg」は元号名を示す。もちろん「yy」は年、「M」は月、「d」は日である。
和暦の文字列をDateTime構造体に変換する
逆の変換も同様の手法で実現できる。和暦の文字列をDateTime構造体の値に変換するには、ToStringメソッドではなく、ParseExactメソッドを用いればよい。以下はそれを記述した例である。
// wareki3.cs
using System;
using System.Globalization;
class WarekiSample3 {
static void Main() {
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
string target = "平成15年7月1日";
DateTime result
= DateTime.ParseExact(target, "ggyy年M月d日", culture);
Console.WriteLine(result.ToLongDateString());
// 出力:2003年7月1日
}
}
// コンパイル方法:csc wareki3.cs
' wareki3.vb
Imports System
Imports System.Globalization
Module WarekiSample3
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim target As String = "平成15年7月1日"
Dim result As DateTime = DateTime.ParseExact(target, "ggyy年M月d日", culture)
Console.WriteLine(result.ToLongDateString())
' 出力:2003年7月1日
End Sub
End Module
' コンパイル方法:vbc wareki3.vb
このサンプルプログラムのポイントは、ParseExactメソッドの第3パラメーターに、和暦を持つ日本語の日本を示すCultureInfoオブジェクトを指定している点である。書式指定文字などは、ToStringメソッドで使用したものと同じである。
例外を出さずにDateTime構造体へ変換する
上記のParseExactメソッドを使ったコードは、文字列が日付として正しくないときには例外が発生してしまう。.NET Framework 2.0以降で使えるTryParseExactメソッドならば、例外は発生しない(次のコード)。
using System;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
string target = "平成30年10月23日";
if (DateTime.TryParseExact(target, "ggyy年M月d日", culture,
DateTimeStyles.AssumeLocal, out DateTime result))
{
Console.WriteLine(result.ToLongDateString());
// 出力:2018年10月23日
}
}
}
Imports System.Globalization
Module Module1
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim target As String = "平成30年10月23日"
Dim result As DateTime
If (DateTime.TryParseExact(target, "ggyy年M月d日", culture,
DateTimeStyles.AssumeLocal, result)) Then
Console.WriteLine(result.ToLongDateString())
' 出力:2018年10月23日
End If
End Sub
End Module
TryParseExactメソッドは、変換に成功するとtrueを、失敗したときはfalseを返す。変換結果は引数の最後に与えたDateTime型の変数に格納される。
2番目の引数であるフォーマット指定の文字列は、ToStringメソッドで使用したものと同じである。
4番目の引数であるDateTimeStyles列挙体は、日付文字列の空白を無視するかどうかや、タイムゾーンの扱いをどうするかといったオプション指定である。ここでは、日付文字列からタイムゾーンを判定できないときはローカル時刻と見なすように指定している。たいていの場合は、DateTimeStyles.Noneでよい。
なお、このC#のコードでは、C# 7の新機能である「出力変数宣言」(引数を与える場所でその変数の宣言も行う)を使っているので、このままコンパイルするにはVisual Studio 2017以降が必要である。C# 7より前のバージョンの環境では、ローカル変数resultの宣言をif文の前に置いてほしい。
元号の扱いについて重大な仕様変更
2018年6月、Microsoftから元号の扱いについて.NET Frameworkの仕様を変更すると発表があった。次の2点である(2018/12/19追記:3点目の仕様変更も追加された。後述する)。
・ 元号の終了日を過ぎた日付文字列の扱い:
例えば「平成32年1月1日」や「昭和65年1月1日」のような日付文字列は、これまでは正しい元号で書かれていない(=次の元号であるべき)ということでParseExactメソッド/TryParseExactメソッドでの変換に失敗していた。仕様変更後は、失敗せずに変換されるようになる。もしも、従来の仕様を利用して元号表記の誤りを検出しているようなコードがあったならば、この仕様変更によって正しく動作しなくなるので注意が必要だ。
・ 元号テーブルのソース(.NET Framework 3.5):
.NET Framework 3.5までは元号テーブルを.NET Framework内に持っていた。これが、.NET Framework 4.0以降と同様に、レジストリから取得するように変更される。これによって、新元号が公表された際にWindowsアップデートで対応されるようになる。
これらの仕様変更は、Windowsのバージョンと.NET Frameworkのバージョンと仕様変更の種類ごとに、個別にアップデートが提供される。Windows 7/8.xには、サポートが継続している.NET Framework用のアップデートが2018年11月に提供された。Windows 10の一部には2018年12月までに提供された。どのアップデートを提供したかという情報は公開されたが、未提供のアップデートの提供予定は公表されていない(2018年12月時点、詳細後述)。
また、前者の仕様変更は、従来通りの動作に戻す方法も提供されている。詳しくは、TechNetのブログ記事「.NET Framework の新元号対応予定について - Japan New Era Name Support Blog」をご覧いただきたい。
前者の仕様変更によってどのような違いが生じるかを次のコードに示す。
using System;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
string target = "明治150年10月23日";
try
{
DateTime result
= DateTime.ParseExact(target, "ggyy年M月d日", culture);
Console.WriteLine(result.ToLongDateString());
// 出力(アップデート済):2017年10月23日
}
catch (FormatException ex)
{
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
// 出力(未アップデート):FormatException: 文字列で表される DateTime がカレンダー
// System.Globalization.JapaneseCalendar でサポートされていません。
}
DateTime result2;
if (DateTime.TryParseExact(target, "ggyy年M月d日", culture,
DateTimeStyles.AssumeLocal, out result2))
{
Console.WriteLine(result2.ToLongDateString());
// 出力(アップデート済):2017年10月23日
}
else
{
Console.WriteLine("「{0}」はDateTimeに変換できません", target);
// 出力(未アップデート):「明治150年10月23日」はDateTimeに変換できません
}
}
}
Imports System.Globalization
Module Module1
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim target As String = "明治150年10月23日"
Try
Dim result As DateTime _
= DateTime.ParseExact(target, "ggyy年M月d日", culture)
Console.WriteLine(result.ToLongDateString())
' 出力(アップデート済):2017年10月23日
Catch ex As FormatException
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
' 出力(未アップデート):FormatException: 文字列で表される DateTime がカレンダー
' System.Globalization.JapaneseCalendar でサポートされていません。
End Try
Dim result2 As DateTime
If (DateTime.TryParseExact(target, "ggyy年M月d日", culture,
DateTimeStyles.AssumeLocal, result2)) Then
Console.WriteLine(result2.ToLongDateString())
' 出力(アップデート済):2017年10月23日
Else
Console.WriteLine("「{0}」はDateTimeに変換できません", target)
' 出力(未アップデート):「明治150年10月23日」はDateTimeに変換できません
End If
End Sub
End Module
「明治150年」などといった表記は、これまでは受け付けられなかった。それが2018年からの仕様変更で、受け付けられるように変わる。
該当するWindowsアップデートが適用されているかどうかで、コメントに示したような実行結果の違いが出る。
(2018/12/19追記)
2018年11月にサポート情報KB4477957「.NET Framework 用の日本の新元号対応更新プログラムの概要」が公開された。上で述べた仕様変更の他に、重要な情報が2つ追加されている。
- 【仕様変更の追加】「元年」表記:
和暦で出力する際に特定のフォーマットに合致すると、「1年」が「元年」になる(後述)。また、入力文字列として「元年」も受け付けるようになる。他システムに渡すデータを出力する場合は、受け取り先が「元年」に対応していないと問題を引き起こす。また、和暦の日付文字列をソート対象にしている場合は、「元年」についての特別な処理が必要になる - .NET Frameworkのアップデート状況を公開:
アップデート済みのサポート情報の一覧が公開された(次の画像)。これを見て該当のアップデートがあれば、元号処理の仕様変更が適用済みだと判断できる。なお、未アップデートのプラットフォームに対するアップデート予定は、あいかわらず未公表である
改元対応アップデート済みのサポート情報の一覧
サポート情報KB4477957のページより(2018年12月現在)。
「該当なし」には、「アップデートの必要がない」場合と、「まだアップデートを提供していない」場合があるので、注意してほしい。
「データソースの更新」は.NET Framework 3.5だけが対象なので、.NET Framework 4.xの欄の「該当なし」は「アップデート不要」の意味だ。また、Windows 10には既定で.NET Framework 4.6以降がインストールされているため、.NET Framework 4.5.2はインストールできない。すなわち、Windows 10の.NET Framework 4.5.2の欄の「該当なし」も「アップデート不要」の意味だ。
従って、2018年12月現在では、Windows 10 1507/1809およびWindows Server 2019において未提供のアップデートが残っていることになる。
なお、Windows 10 1809に「元年」表記のアップデートが提供されたことになっているが、インサイダープレビュー版の1809には適用されていない。
追加された仕様変更である「元年」表記とは、和暦表記では最初の年を「元年」と呼ぶ風習に合わせたものだ。例えば、これまで「平成1年」と出力されていたものが、書式指定文字列によっては「平成元年」と出力されるように変わる(次のコード)。
using System;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
var dat = new DateTime(1989, 1, 8);
Console.WriteLine(dat.ToString("ggy'年'M月d日", culture));
Console.WriteLine(dat.ToString("ggy年M月d日", culture));
Console.WriteLine(dat.ToString("gy/M/d", culture));
// 出力(アップデート済):
// 平成元年1月8日
// 平成1年1月8日
// 平成1/1/8
// 出力(未アップデート):
// 平成1年1月8日
// 平成1年1月8日
// 平成1/1/8
}
}
Imports System.Globalization
Module Module1
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim dat = New DateTime(1989, 1, 8)
Console.WriteLine(dat.ToString("ggy'年'M月d日", culture))
Console.WriteLine(dat.ToString("ggy年M月d日", culture))
Console.WriteLine(dat.ToString("gy/M/d", culture))
' 出力(アップデート済):
' 平成元年1月8日
' 平成1年1月8日
' 平成1/1/8
' 出力(未アップデート):
' 平成1年1月8日
' 平成1年1月8日
' 平成1/1/8
End Sub
End Module
印刷などに使う分には「元年」表記に変えても問題ないだろう。他のプログラムに渡すデータの場合は、受け取る側が「元年」表記に対応していることをしっかり確認しておく必要がある。
和暦の文字列をDateTime構造体の値に変換するときも、「元年」が解釈されるように変わる(次のコード)。
using System;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
CultureInfo culture = new CultureInfo("ja-JP", true);
culture.DateTimeFormat.Calendar = new JapaneseCalendar();
string target1 = "平成1年1月8日"; // 「1年」
DateTime result;
if (DateTime.TryParseExact(target1, "ggy年M月d日", culture,
DateTimeStyles.AssumeLocal, out result))
{
Console.WriteLine(result.ToString("yyyy/MM/dd"));
// 出力:1989/01/08
}
string target2 = "平成元年1月8日"; // 「元年」
if (DateTime.TryParseExact(target2, "ggy年M月d日", culture,
DateTimeStyles.AssumeLocal, out result))
{
Console.WriteLine(result.ToString("yyyy/MM/dd"));
// 出力(アップデート済):1989/01/08
}
else
{
Console.WriteLine("「{0}」はDateTimeに変換できません", target2);
// 出力(未アップデート):「平成元年1月8日」はDateTimeに変換できません
}
}
}
Imports System.Globalization
Module Module1
Sub Main()
Dim culture As CultureInfo = New CultureInfo("ja-JP", True)
culture.DateTimeFormat.Calendar = New JapaneseCalendar()
Dim target1 As String = "平成1年1月8日" ' 「1年」
Dim result As DateTime
If (DateTime.TryParseExact(target1, "ggy年M月d日", culture,
DateTimeStyles.AssumeLocal, result)) Then
Console.WriteLine(result.ToString("yyyy/MM/dd"))
' 出力:1989/01/08
End If
Dim target2 As String = "平成元年1月8日" ' 「元年」
If (DateTime.TryParseExact(target2, "ggy年M月d日", culture,
DateTimeStyles.AssumeLocal, result)) Then
Console.WriteLine(result.ToString("yyyy/MM/dd"))
' 出力(アップデート済):1989/01/08
Else
Console.WriteLine("「{0}」はDateTimeに変換できません", target2)
' 出力(未アップデート):「平成元年1月8日」はDateTimeに変換できません
End If
End Sub
End Module
アップデート後でも、もちろん「1年」は解釈される。
この「元年」表記の仕様変更も、従来通りの動作に戻す方法が提供されている。詳しくは、Developer Tools Blogsの「Handling a new era in the Japanese calendar in .NET」をご覧いただきたい。
カテゴリ:クラスライブラリ 処理対象:日付と時刻
使用ライブラリ:JapaneseCalendarクラス(System.Globalization名前空間)
使用ライブラリ:Calendarクラス(System.Globalization名前空間)
使用ライブラリ:CultureInfoクラス(System.Globalization名前空間)
関連TIPS:日付の年号を略称で表示するには?[C#、VB]
関連TIPS:日付の年号を表示するには?[独自テーブル参照編]
更新履歴
【2018/12/19】仕様変更について追記を行いました。
【2018/10/31】Visual Studio 2017でコードの動作検証、図版の追加、仕様変更について追記、全般的な構成の変更などを行いました。
【2003/06/06】初版公開。
Copyright© Digital Advantage Corp. All Rights Reserved.