今更「和暦」→「西暦」変換を実装する:VBA小ネタ20190706

ちょっとだけ引っかかったのでその備忘録を兼ねてます。

西暦と和暦の相互変換を行う場合には、通常であればFormat関数を使います。と言うかそういう方針で考えています。 私自身の考え方ですが、できるだけメンテナンスフリー(またはシステム外に責任を押し付ける)な方法で実装をするのがいいと思っているので、西暦⇔和暦変換はWindowsレジストリ値を持ってくるほうがいいでしょ、つまり、Format関数でWindows固有の値を取得するのが楽、ということです。 しかし、Format関数を使えないケースがあって、その場合にコーディングをするという選択肢が発生する、という流れでいつも書いている感じです。

Format関数が機能しないケース

実はFormat関数で和暦年(i.e.「平成31年」「令和1年」)を以下のように指定すると、変換されないんです。

Debug.Print Format("平成31年","yyyy")

> 平成31年

おそらく、「元号と数字を含む文字列」と人間が勝手に解釈している文字列をそう認識していないだけなのだろうと思います。 ちなみに、「平成31年1月」「令和1年6月」のように指定すると変換をしてくれるので、年号だけの変換というのが未対応なのかしら、と思います。

実装してみる

... その前に一つお断り。今回の実装はそれほど精度が高くありません。と言うか精度なんて必要ないですけど。単純に入力された元号&年をそのまま西暦として返すだけです。

Function jEra2Greg(str As String) As Integer
' 明治~令和までの対応(Windowsの対応に合わせる)
' 入ってきたものすべてに対してここで処理する。
On Error GoTo jEra2Greg_error
  Dim strEra As String
  Dim strNarrow As String
  Dim strNum As Integer
  Dim checkStr, checkNum As String
  Dim intGreg As Integer
  
  strEra = Left(str, 2) ' 文字列の最初の2文字を判別
  strNarrow = StrConv(str, vbNarrow)
  For i = 1 To Len(strNarrow)
    checkStr= Mid(strNarrow, i, 1)
    If checkStrLike "[0-9]" Then
      checkNum = checkNum & checkStr
    End If
  Next i
  
  'strNum = Val(strNarrow)
  If checkNum = "" Then
    ' 「元年」の場合は数字が入らないので
    If Mid(str, 3, 1) = "元" Then
      '3文字目が「元」ならば「元年である」と推測をし
      strNum = 1
    Else
      strNum = checkNum 
    End If
  Else
    strNum = checkNum 
  End If
  
  Select Case strEra
    '計算式は、各元号の「元年」の西暦から1を減らしたものに、元号の年数を足したものとしている。
    Case "明治"
      intGreg = 1867 + strNum
    Case "大正"
      intGreg = 1911 + strNum
    Case "昭和"
      intGreg = 1925 + strNum
    Case "平成"
      intGreg = 1988 + strNum
    Case "令和"
      intGreg = 2018 + strNum
    Case Else
      GoTo jEra2Greg_error
  End Select
  
  Debug.Print strEra & strNum & "年→" & intGreg
  jEra2Greg = intGreg
  
  'jEra2Greg = MsgBox("エラーなし終了")
  Exit Function

jEra2Greg_error:
  'jEra2Greg = MsgBox("エラーあり終了")
  ' エラー処理を返す
End Function

一旦こんな感じで実装をしていますが、計算のロジックとしてちょっと疑問に思った点があります。

疑問1:それぞれの元号の基準となる「数値」

それぞれの元号の始まった西暦年を今回のコードには記載をしていません。もちろんコメントでも書いたとおり、1を引いた数字なので、各元号の開始西暦年はコードにある数字に1を足したもの、と言えます。

でもそれって正しいのかな?

基準となる数値は本来であれば、各元号の開始年を記入すべきだと思います。そうでなくても数字がマジックナンバーになっちゃうので(コメントは日本語で書いていますが、非日本語圏の人には読めません)、できればこの数値は西暦年に合わせたい、と思います。

疑問2:どこで「帳尻を合わせる」のか

元号の開始西暦年に和暦の年数を加算すると、当然1ずれるわけです。和暦は1からスタートしますので、初年は西暦年+1としたいですがこれだとおかしくなってしまうわけです。だから最初から西暦年から1を引いておいたのですが、もし基準となる西暦年を元号開始西暦年に合わせるとすると、どこかで1を引く作業をしなくてはいけません。どこで引き算をしたらいいのか...。

もし当初の簡易実装の流れを踏襲するなら、

  intGreg = [元号開始西暦年] - 1 + jEraVal

となるわけです。でも、もう一つのパターン、開始年に和暦年を足してから1を引く、というやり方もありそうです。言葉で説明するならこっちのほうがわかりやすそう。

  intGreg = [元号開始西暦年] + jEraVal -1

でもこれだと「なんで最後に1を引いてんの?」と言われそうだし...。

どっちが正しいんでしょうね。 *1きちんとプログラミングを(体系的に)学んだわけではなく、独学やOJTでなんとなく学んできたのでこういうのをきちんと説明できないんだよなぁ。*2

最後に*3

なかなか案件が終わりません。お互いに(私はともかくクライアント様もおそらくですが)終息に向かうように進めようとしているのですが、まぁ引っかかるところが多くて終わらないってことはよくあります。いろいろ難しいですよね。 別の案件も動き出していますし、あまり負担を掛けたくないんですけどねぇ...。

*1:コレは言い訳です。

*2:繰り返しますが言い訳です。

*3:若干愚痴っぽいですがすみません。