連載
» 2015年06月10日 05時00分 UPDATE

.NET TIPS:日付の年号を略称で表示するには?[C#、VB]

日本では日付を年号やその略称を使って表示したいことがよくある。本稿では、年号やその略称を用いて日付を表示する方法をいくつか紹介する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Platform Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.NET 1.0以降


 業務アプリ開発で時折出くわす要件に「日付を和暦で表示する/印字する」というものがある。.NET Frameworkの場合、「平成27年6月10日」のように表示するのは書式化するときにカルチャーを与えることで可能なのだが、「平27.6.10」や「H27.6.10」のように年号(=元号)を略称で表示するには少々工夫が必要になる。本稿では、以上3パターンの文字列を作成する方法を解説する。なお、利用するAPI自体は.NET 1.0から存在するものであるが、本稿のコードはVisual Studio(以降、VSと略す)2012で検証しており、VS 2005以降で登場した言語機能も使用していることをお断りしておく。

和暦を扱う準備

 和暦を扱うには、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("表示したい日(西暦): {0:yyyy/MM/dd}", theDay);
// 出力:
// 表示したい日(西暦): 2015/06/10

// JapaneseCalendarクラスのインスタンスを作る
var calendarJp = new System.Globalization.JapaneseCalendar();

// JapaneseCalendarクラスは日付に該当する年号の番号を算出できる
int era = calendarJp.GetEra(theDay); 
Console.WriteLine("Era Number: {0}", 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("表示したい日(西暦): {0:yyyy/MM/dd}", theDay)
' 出力:
' 表示したい日(西暦): 2015/06/10

' JapaneseCalendarクラスのインスタンスを作る
Dim calendarJp = New System.Globalization.JapaneseCalendar()

' JapaneseCalendarクラスは日付に該当する年号の番号を算出できる
Dim era As Integer = calendarJp.GetEra(theDay)
Console.WriteLine("Era Number: {0}", era)
' 出力:
' Era Number: 4

' 和暦カルチャー:
' CultureInfoクラスのインスタンスを日本語用として作成し、
' そのDateTimeFormatプロパティにJapaneseCalendarインスタンスをセットしておく
Dim cultureJp = New System.Globalization.CultureInfo("ja-JP", False)
cultureJp.DateTimeFormat.Calendar = calendarJp

和暦を扱う準備をするコードの例(上:C#、下:VB)
和暦を扱うには、JapaneseCalendarクラスのインスタンスと、それをDateTimeFormatプロパティに設定した日本語用のCultureInfoクラスのインスタンスが必要になる。
太字にしたローカル変数は、この後に紹介するコードの中で使用する。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。

 上のコードの「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

年号をそのまま表示するコードの例(上:C#、下:VB)
DateTime構造体のToStringメソッドを使って書式化する際に、和暦カルチャー(=「cultureJp」変数)を渡せば和暦表示になる。比較のために、CultureInfoインスタンスを渡さない場合も併記した。
書式指定文字列「gg」は年号の表示になる。ただし、カルチャーを指定していないときは、システムの既定のカルチャーに従う。筆者の環境では、システムのカレンダーの種類として「西暦 (日本語)」をセットしてあったので、カルチャー未指定時には年号の部分が「西暦」と表示された。
書式指定文字列「yy」は2桁の年表示になる。和暦カルチャーを指定したときは、自動的に和暦の年に換算される。

 上のコード例では、カルチャー未指定時に年号が「西暦」と表示された。これはコントロールパネルで設定しているシステムのカルチャーに従っているのである(次の画像)。

システムのカレンダーの種類の設定(Windows 7) システムのカレンダーの種類の設定(Windows 7)
システムのカルチャーは、コントロールパネルから設定できる。Windows 7の場合は、コントロールパネルで[時計、言語、および地域]を選び、そこで[地域と言語]リンクをクリックすると[地域と言語]ダイアログ(図の左)が出てくる。そこで[追加の設定]ボタン(赤丸内)をクリックすると[形式のカスタマイズ]ダイアログ(図の右)が出てくるので、その[日付]タブ(赤丸内)を選ぶと、[カレンダーの種類]が選択できる(赤丸内)。Windows 8.1ではコントロールパネルで[時計、言語、および地域]カテゴリの下にある[日付、時刻、または数値の形式の変更]リンクをクリックすれば、同様な設定を行える。
カルチャーを指定せずに書式指定文字列「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:{0}{1:00}.{2:00}.{3:00}", eraName, yearJp, monthJp, dayJp);
// 出力:
// 方法その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:{0}{1:00}.{2:00}.{3:00}", eraName, yearJp, monthJp, dayJp)
' 出力:
' 方法その2:平成27.06.10

年号をそのまま表示する冗長なコードの例(上:C#、下:VB)
通常はこのような長いコードを書く必要はない。
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

年号の漢字略称を表示するコードの例(上:C#、下:VB)
書式指定文字列に「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("[{0}]={1}", 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("[{0}]={1}", item.Key, item.Value)
Next
' 出力:
' [4]=H
' [1]=M
' [3]=S
' [2]=T

年号の番号と英字略称のテーブルを構築するコードの例(上:C#、下:VB)
年号の番号をキーとして、年号の英字略称を値とするテーブルを構築する(「eraTable」変数)。
CultureInfoクラスのDateTimeFormatプロパティのGetEraメソッドは、引数に与えられた文字列が年号と認識できたときには年号の番号を返す(認識できなかったときは-1が返る)。年号と認識された英文字だけをその年号の番号をキーとしてDictionaryクラス(System.Collections.Generic名前空間)のインスタンスに追加していけば、年号の番号と英字略称のテーブルが構築できる。
このようにしてテーブルを作る利点は、改元が行われたとき、Windows Updateによって自動的に新しい年号に対応されると期待できることである(新しい年号の英字略称が既存の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

構築した年号テーブルを使って年号の英字略称を表示するコードの例(上:C#、下:VB)
年号テーブル(「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("[{0}]={1}", item.Key, item.Value);
// 出力:
// [1]=M
// [2]=T
// [3]=S
// [4]=H

' リフレクションを使って、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("[{0}]={1}", item.Key, item.Value)
Next
' 出力:
' [1]=M
' [2]=T
' [3]=S
' [4]=H

.NET Frameworkの非公開メンバーから年号テーブルを構築するコードの例(上:C#、下:VB)
このようにして年号テーブルを構築することもできる。しかし、これは.NET Frameworkの非公開部分を使っており、バージョンアップの際に予告なく仕様が変わる可能性がある。十分に注意してほしい。
なお、この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オブジェクトに変換するには?


■この記事と関連性の高い別の.NET TIPS


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。