日付の年号を略称で表示するには?[C#、VB]:.NET TIPS
日本では日付を年号やその略称を使って表示したいことがよくある。本稿では、年号やその略称を用いて日付を表示する方法を幾つか紹介する。
■更新履歴【2018/05/28】Windows 10 1803およびVisual Studio 2017で動作を確認し、Windows 10 1803での注意事項の追加、図版の追加および差し替え、補完文字列を使用するようにコードを変更などの改訂作業を行いました。
【2015/06/10】初版公開。
本稿は2015/06/10に初版公開された記事を改訂し、Windows 10 1803(2018年春の大型アップデート)における注意事項を追加するとともに、改訂作業を行ったものです。
対象:.NET 1.0以降
業務アプリ開発で時折出くわす要件に「日付を和暦で表示する/印字する」というものがある。.NET Frameworkの場合、「平成27年6月10日」のように表示するのは書式化するときにカルチャーを与えることで可能なのだが、「平27.6.10」や「H27.6.10」のように年号(=元号)を略称で表示するには少々工夫が必要になる。本稿では、以上3パターンの文字列を作成する方法を解説する。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。また、2017年春にリリースされたWindows 10 1803では新元号への仮対応が行われているが、これについての注意事項を追記した。
なお、利用するAPI自体は.NET 1.0から存在するものであるが、本稿のコードはVisual Studio(以降、VSと略す)2017で検証しており、VS 2005以降で登場した言語機能も使用していることをお断りしておく。本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015以降が必要である。
注意:Windows 10 1803では新元号の仮対応が導入されている
Windows 10バージョン1803(2018年春の大型アップデート)では、.NET Framework 4.0以降で和暦表示をした場合に、2019年5月からの新元号が「??」という仮の形で表示されるようになっている。詳しくは次の公式ブログ記事を参照していただきたい。
- Japan New Era Name Support Blog「Windows 10 機能更新プログラム (2018 Spring Release) における元号のレジストリ更新について」
.NET Framework 4.0以降の場合、本稿の方法は自動的に新元号対応が行われる。年号の表示として「??」を許容できるのであれば、メンテナンス不要というメリットがある。一方、年号の表示として「??」は許容されないという場合は、次のTIPSで紹介しているレジストリに依存しない方法が適している。
- .NET TIPS「日付の年号を表示するには?[独自テーブル参照編]」
和暦を扱う準備
和暦を扱うには、JapaneseCalendarクラスとCultureInfoクラス(ともにSystem.Globalization名前空間)を利用する。
JapaneseCalendarクラスは、DateTime構造体(System名前空間)から年号の番号(明治=1/大正=2/昭和=3/平成=4)と和暦の年(2015年なら27年)を算出できる。年号の名称(「明治」/「大正」/「昭和」/「平成」)は、CultureInfoクラスが持っている(正確には、CultureInfoクラスが内部に保持しているSystem.Globalization名前空間のDateTimeFormatInfoクラスが持っている)。
JapaneseCalendarクラスのインスタンスを作り、年号の番号を取得すること。また、CultureInfoクラスのインスタンスを日本語用として作ること。そのコードだけをまず紹介しておこう(次のコード)。この後の解説では省略するが、共通に利用するコードである。
// 表示したい日
DateTime theDay = new DateTime(2015, 6, 10);
Console.WriteLine($"表示したい日(西暦): {theDay:yyyy/MM/dd}");
// 出力:
// 表示したい日(西暦): 2015/06/10
// JapaneseCalendarクラスのインスタンスを作る
var calendarJp = new System.Globalization.JapaneseCalendar();
// JapaneseCalendarクラスは日付に該当する年号の番号を算出できる
int era = calendarJp.GetEra(theDay);
Console.WriteLine($"Era Number: {era}");
// 出力:
// Era Number: 4
// 和暦カルチャー:
// CultureInfoクラスのインスタンスを日本語用として作成し、
// そのDateTimeFormatプロパティにJapaneseCalendarインスタンスをセットしておく
var cultureJp = new System.Globalization.CultureInfo("ja-JP", false);
cultureJp.DateTimeFormat.Calendar = calendarJp;
' 表示したい日
Dim theDay As DateTime = New DateTime(2015, 6, 10)
Console.WriteLine($"表示したい日(西暦): {theDay:yyyy/MM/dd}")
' 出力:
' 表示したい日(西暦): 2015/06/10
' JapaneseCalendarクラスのインスタンスを作る
Dim calendarJp = New System.Globalization.JapaneseCalendar()
' JapaneseCalendarクラスは日付に該当する年号の番号を算出できる
Dim era As Integer = calendarJp.GetEra(theDay)
Console.WriteLine($"Era Number: {era}")
' 出力:
' Era Number: 4
' 和暦カルチャー:
' CultureInfoクラスのインスタンスを日本語用として作成し、
' そのDateTimeFormatプロパティにJapaneseCalendarインスタンスをセットしておく
Dim cultureJp = New System.Globalization.CultureInfo("ja-JP", False)
cultureJp.DateTimeFormat.Calendar = calendarJp
和暦を扱うには、JapaneseCalendarクラスのインスタンスと、それをDateTimeFormatプロパティに設定した日本語用のCultureInfoクラスのインスタンスが必要になる。
太字にしたローカル変数は、この後に紹介するコードの中で使用する。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。また、Visual Basic 14/C# 6以降で使用可能な補完文字列も使用している(以下、同様)。
上のコードの「cultureJp」変数、すなわちJapaneseCalendarインスタンスをDateTimeFormatプロパティに設定した日本語用のCultureInfoクラスのインスタンス(以降、「和暦カルチャー」)が、和暦表示に重要な役割を果たす。
「平成」と表示するには?
年号をそのまま表示するには、DateTime構造体のToStringメソッドを呼び出すときに、上述した和暦カルチャーを渡せばよい(次のコード)。
Console.WriteLine(theDay.ToString("方法その1:ggyy.MM.dd", cultureJp));
// 出力:
// 方法その1:平成27.06.10
Console.WriteLine(theDay.ToString("方法その1(カルチャー未指定):ggyy.MM.dd"));
// 出力:
// 方法その1(カルチャー未指定):西暦15.06.10
Console.WriteLine(theDay.ToString("方法その1:ggyy.MM.dd", cultureJp))
' 出力:
' 方法その1:平成27.06.10
Console.WriteLine(theDay.ToString("方法その1(カルチャー未指定):ggyy.MM.dd"))
' 出力:
' 方法その1(カルチャー未指定):西暦15.06.10
DateTime構造体のToStringメソッドを使って書式化する際に、和暦カルチャー(=「cultureJp」変数)を渡せば和暦表示になる。比較のために、CultureInfoインスタンスを渡さない場合も併記した。
書式指定文字列「gg」は年号の表示になる。ただし、カルチャーを指定していないときは、システムの既定のカルチャーに従う。筆者の環境では、システムのカレンダーの種類として「西暦 (日本語)」をセットしてあったので、カルチャー未指定時には年号の部分が「西暦」と表示された。
書式指定文字列「yy」は2桁の年表示になる。和暦カルチャーを指定したときは、自動的に和暦の年に換算される。
上のコード例では、カルチャー未指定時に年号が「西暦」と表示された。これはコントロールパネルで設定しているシステムのカルチャーに従っているのである(次の画像)。
システムのカレンダーの種類の設定(Windows 10)
システムのカルチャーは、コントロールパネルから設定できる。Windows 10の場合は、コントロールパネルで[時計と地域]を選び、そこで[日付、時刻、または数値の形式の変更]リンクをクリックすると[地域]ダイアログ(図の左)が出てくる。そこで[追加の設定]ボタン(赤丸内)をクリックすると[形式のカスタマイズ]ダイアログ(図の右)が出てくるので、その[日付]タブ(赤丸内)を選ぶと、[カレンダーの種類]が選択できる(赤丸内)。
カルチャーを指定せずに書式指定文字列「gg」を使うと、この[カレンダーの種類]で決まる年号が使われるのである([西暦 (日本語)]では「西暦」/[和暦]では「平成」/[西暦 (英語)]では「A.D.」)。
通常は上で示したコードのように簡潔に書けばよいが、理解を助けるために年号の名称と和暦の年を個別に求めるコードも示しておこう(次のコード)。
// 年号の名称は、CultureInfoクラスのDateTimeFormatプロパティから得られる
string eraName = cultureJp.DateTimeFormat.GetEraName(era);
// 和暦の年はJapaneseCalendarクラスから得られる
int yearJp = calendarJp.GetYear(theDay);
// 次のように書いてもよい
// yearJp = cultureJp.DateTimeFormat.Calendar.GetYear(theDay);
// 和暦の月日も同様
int monthJp = calendarJp.GetMonth(theDay);
int dayJp = calendarJp.GetDayOfMonth(theDay);
// 和暦では次のように書いてもよい
// monthJp = theDay.Month;
// dayJp = theDay.Day;
Console.WriteLine($"方法その2:{eraName}{yearJp:00}.{monthJp:00}.{dayJp:00}");
// 出力:
// 方法その2:平成27.06.10
' 年号の名称は、CultureInfoクラスのDateTimeFormatプロパティから得られる
Dim eraName As String = cultureJp.DateTimeFormat.GetEraName(era)
' 和暦の年はJapaneseCalendarクラスから得られる
Dim yearJp As Integer = calendarJp.GetYear(theDay)
' 次のように書いてもよい
' yearJp = cultureJp.DateTimeFormat.Calendar.GetYear(theDay)
' 和暦の月日も同様
Dim monthJp As Integer = calendarJp.GetMonth(theDay)
Dim dayJp As Integer = calendarJp.GetDayOfMonth(theDay)
' 和暦では次のように書いてもよい
' monthJp = theDay.Month
' dayJp = theDay.Day
Console.WriteLine($"方法その2:{eraName}{yearJp:00}.{monthJp:00}.{dayJp:00}")
' 出力:
' 方法その2:平成27.06.10
通常はこのような長いコードを書く必要はない。
CultureInfoクラスのDateTimeFormatプロパティのGetEraNameメソッドで年号の名称が得られる。JapaneseCalendarクラスのGetYearメソッドで和暦に直した年が求まる。同様にして月/日も算出できる(和暦ではDateTime構造体のMonth/Dayメンバーの値と一致するのでそれを使っても構わないのだが、例えば回教暦などでは月/日も異なる)。最後にそれらを組み合わせて、表示する文字列を組み立てる。
DateTime構造体のToStringメソッドを使って書式化する際に和暦カルチャーを渡したとき、内部ではこのような処理が行われているのだと思ってほしい。
「平」と表示するには?
年号の名称を取得してその1文字目を取り出しても構わないが、CultureInfoクラスのDateTimeFormatプロパティが持っているGetAbbreviatedEraNameメソッドを使うとよい。
// 年号の漢字略称はGetAbbreviatedEraNameメソッドで得られる
string eraAbbName = cultureJp.DateTimeFormat.GetAbbreviatedEraName(era);
Console.WriteLine(eraAbbName + theDay.ToString("yy.MM.dd", cultureJp));
// 出力:
// 平27.06.10
' 年号の漢字略称はGetAbbreviatedEraNameメソッドで得られる
Dim eraAbbName As String = cultureJp.DateTimeFormat.GetAbbreviatedEraName(era)
Console.WriteLine(eraAbbName + theDay.ToString("yy.MM.dd", cultureJp))
' 出力:
' 平27.06.10
書式指定文字列に「g」を指定したら年号を漢字1文字で表示してくれてもよさそうなものだが、そうなってはいない。そこで、このコードのように年号の漢字略称を取得してきて、表示する文字列を組み立てることになる。
CultureInfoクラスのDateTimeFormatプロパティが持っているGetAbbreviatedEraNameメソッドで、年号の漢字略称が得られる。
年月日は、DateTime構造体のToStringメソッドを使って書式化する際に和暦カルチャーを渡して書式化する(そうしないと、システムのカレンダーが例えば回教暦などになっていたら年/月/日が想定とは異なる数字になってしまう)。
「H」と表示するには?
年号の略称を英字で取得する手段は、.NET Frameworkでは公開されていない*1。ならば、年号の番号と年号の英字略称のテーブルを自前で作って使えばよい。
*1 .NET Frameworkは年号の英字略称を内部的には持っている(後述する)。そのことを確かめるには、例えば「DateTime.Parse("H27.6.10")」というコードを実行してみればよい(2015/06/10という値を持つDateTime構造体が得られる)。文字列のパーズはできるのに、表示はできないのである。この非対称性はよろしくないと筆者はフィードバックしてみたこともあるが、そのときはよい反応を得られなかった。
テーブルを構築するには、CultureInfoクラスのDateTimeFormatプロパティのGetEraメソッドを利用する。前出のコードに登場したJapaneseCalendarクラスのGetEraメソッドは、DateTime構造体を与えると年号の番号を返してくるものだった。DateTimeFormatプロパティのGetEraメソッドは、年号を表す文字列を与えると年号の番号を返してくれる。同じ名前のメソッドで紛らわしいが、どちらも返値は年号の番号である。この年号を表す文字列を与えると年号の番号を返してくれる方のGetEraメソッドに「A」から「Z」までの英字を与えて、年号の番号として正当な値が返ってくるかどうかを調べれば、年号の番号と英字略称のテーブルを構築できる(次のコード)。
// A〜Zまで試して、年号テーブルを作る
var eraTable = new Dictionary<int, string>();
for (char e = 'A'; e <= 'Z'; e++)
{
int eraIndex = cultureJp.DateTimeFormat.GetEra(e.ToString());
if (eraIndex > 0)
eraTable.Add(eraIndex, e.ToString());
}
// 作成した年号テーブルを確認する
foreach (var item in eraTable)
Console.WriteLine($"[{item.Key}]={item.Value}");
// 出力:
// [4]=H
// [1]=M
// [3]=S
// [2]=T
' A〜Zまで試して、年号テーブルを作る
Dim eraTable = New Dictionary(Of Integer, String)()
For code = AscW("A"c) To AscW("Z"c)
Dim e As String = ChrW(code)
Dim eraIndex As Integer = cultureJp.DateTimeFormat.GetEra(e)
If (eraIndex > 0) Then
eraTable.Add(eraIndex, e)
End If
Next
' 作成した年号テーブルを確認する
For Each item In eraTable
Console.WriteLine($"[{item.Key}]={item.Value}")
Next
' 出力:
' [4]=H
' [1]=M
' [3]=S
' [2]=T
年号の番号をキーとして、年号の英字略称を値とするテーブルを構築する(「eraTable」変数)。
CultureInfoクラスのDateTimeFormatプロパティのGetEraメソッドは、引数に与えられた文字列が年号と認識できたときには年号の番号を返す(認識できなかったときは-1が返る)。年号と認識された英文字だけをその年号の番号をキーとしてDictionaryクラス(System.Collections.Generic名前空間)のインスタンスに追加していけば、年号の番号と英字略称のテーブルが構築できる。
このようにしてテーブルを作る利点は、改元が行われたとき、Windowsアップデートによって自動的に新しい年号に対応されると期待できることである(新しい年号の英字略称が既存のH/M/S/Tのいずれかと重複しない限り)。テーブルをハードコーディングしてしまうと、改元時には大急ぎで新バージョンを配布しなければならなくなる。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。
上で構築した年号テーブルを参照すれば、日付を年号の英字略称で表示できる(次のコード)。
string eraAbbEnName = eraTable[era];
Console.WriteLine(eraAbbEnName + theDay.ToString("yy.MM.dd", cultureJp));
// 出力:
// H27.06.10
Dim eraAbbEnName As String = eraTable(era)
Console.WriteLine(eraAbbEnName + theDay.ToString("yy.MM.dd", cultureJp))
' 出力:
' H27.06.10
年号テーブル(「eraTable」変数)を年号の番号(「era」変数)で引いて英字略称を求める。和暦の年月日は、DateTime構造体のToStringメソッドを使って書式化する際に和暦カルチャーを渡して書式化する。最後にそれらを結合して表示する文字列を生成している。
補足:.NET Frameworkが持つ年号の英字略称から年号テーブルを作る
上のように年号テーブルを作らねばならないのは、年号の英字略称を取得する手段が.NET Frameworkでは公開されていないからである。しかしながら、内部的には英字略称の配列を持っているのだ。リフレクションを使ってそれにアクセスすることで、年号テーブルを作成することも不可能ではない(次のコード)。ただし、いつ何時、正しくない結果が返ってくるようになったり例外が出たりするようになるかもしれない。十分に注意してほしい(そんな心配をするくらいなら、上記の方法でテーブルを作る方が精神衛生的によいと思う)。
// リフレクションを使って、DateTimeFormatInfoクラスのプライベートメンバーである
// AbbreviatedEnglishEraNamesプロパティ(文字列の配列)を取得する
Type t = typeof(System.Globalization.DateTimeFormatInfo);
System.Reflection.PropertyInfo pi
= t.GetProperty("AbbreviatedEnglishEraNames",
System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance);
string[] abbreviatedEnglishEraNames
= (string[])pi.GetValue(cultureJp.DateTimeFormat, null);
// 取得したabbreviatedEnglishEraNames配列から、年号テーブルを作る
var eraTable = new Dictionary<int, string>();
for (int i = 0; i < abbreviatedEnglishEraNames.Length; i++)
eraTable.Add(i + 1, abbreviatedEnglishEraNames[i]);
// 作成した年号テーブルを確認する
foreach (var item in eraTable)
Console.WriteLine($"[{item.Key}]={item.Value}");
// 出力:
// [1]=M
// [2]=T
// [3]=S
// [4]=H
// [5]=?(Windows 10 1803)
' リフレクションを使って、DateTimeFormatInfoクラスのプライベートメンバーである
' AbbreviatedEnglishEraNamesプロパティ(文字列の配列)を取得する
Dim t As Type = GetType(System.Globalization.DateTimeFormatInfo)
Dim pi As System.Reflection.PropertyInfo _
= t.GetProperty("AbbreviatedEnglishEraNames",
System.Reflection.BindingFlags.NonPublic _
Or System.Reflection.BindingFlags.Instance)
Dim abbreviatedEnglishEraNames As String() _
= CType(pi.GetValue(cultureJp.DateTimeFormat, Nothing), String())
' 取得したabbreviatedEnglishEraNames配列から、年号テーブルを作る
Dim eraTable = New Dictionary(Of Integer, String)()
For i = 0 To (abbreviatedEnglishEraNames.Length - 1)
eraTable.Add(i + 1, abbreviatedEnglishEraNames(i))
Next
' 作成した年号テーブルを確認する
For Each item In eraTable
Console.WriteLine($"[{item.Key}]={item.Value}")
Next
' 出力:
' [1]=M
' [2]=T
' [3]=S
' [4]=H
' [5]=?(Windows 10 1803)
このようにして年号テーブルを構築することもできる。しかし、これは.NET Frameworkの非公開部分を使っており、バージョンアップの際に予告なく仕様が変わる可能性がある。十分に注意してほしい。
Windows 10バージョン1803では2019年5月からの新元号に対する仮対応がなされており、.NET Framework 4.0以降であれば年号の番号「5」と年号の仮の略称「?」が追加されている。新元号が公表されれば、Windowsアップデートで正式の名称に置き換えられるはずだ。そのときは、サポートが継続している他のバージョンにも新元号が追加されるだろう。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。
利用可能バージョン:.NET Framework 1.0以降
カテゴリ:クラスライブラリ 処理対象:日付と時刻
使用ライブラリ:DateTime構造体(System名前空間)
使用ライブラリ:JapaneseCalendarクラス(System.Globalization名前空間)
使用ライブラリ:CultureInfoクラス(System.Globalization名前空間)
使用ライブラリ:DateTimeFormatInfoクラス(System.Globalization名前空間)
関連TIPS:西暦と和暦を変換するには?
関連TIPS:日付や時刻を文字列に変換するには?
関連TIPS:日付や時刻の文字列をDateTimeオブジェクトに変換するには?
■更新履歴【2018/05/28】Windows 10 1803およびVisual Studio 2017で動作を確認し、Windows 10 1803での注意事項の追加、図版の追加および差し替え、補完文字列を使用するようにコードを変更などの改訂作業を行いました。
【2015/06/10】初版公開。
Copyright© Digital Advantage Corp. All Rights Reserved.