2020/03/20

西暦・和暦対照表




1.背景と概要

「西暦〇〇年は平成の何年?」というような対照表は、色々なサイトで紹介されています。多くは一覧表になっていたり、西暦を入力すると和暦が計算される、などの機能です。
今回は、そんなどこにでもある対照表をExcelで作ってみました。表では無く動的に動かせるものとし、且つ書類を作成しながら参照できるような形にしてみました。

マクロを実行すると、図1-1の左側のダイアログ「西暦・和暦対照表」が表示されます。上から「西暦」「昭和」「平成」「令和」が5年に渡って縦に対照できるようにし、年を変更するには上部のスクロールバーを動かします。
和暦の実期間は赤字、元号が変わったのちは黒字にし、最下段には今年を基準とした±年を表示させました。
また西暦・和暦の年の部分をクリックすると、その年のカレンダー(図1-1の右側のダイアログ)が表示され、月変更ボタンを押すとカレンダーが切り替わります。
ダイアログは両方ともモードレス(ダイアログを表示させたまま、シート上操作が可能)で表示していますので、Excel作業中に参照できますし、またカレンダー表示後に「西暦・和暦対照表」側を消しても、カレンダーは独立して動作します。


図1-1



2.プログラム

今回のマクロは「クラスモジュール(Class1)」「標準モジュール(Module1)」「フォームモジュール(UserForm1 及び 2)」に跨ります。

2-1.クラスモジュール

図1-1の左側のダイアログ(UserForm1)には、[ 西暦・昭和・平成・令和・今年基準の±年 ] x 前後合わせて5年=25個のLabel を使用しています。その25個のLabelのどれかをクリックすると、その年のカレンダーを表示するようにしてあります。
各Labelごとに全25個のイベントプロシージャを作るのは効率的ではありませんので、どのLabelをクリックしても共有のイベントプロシージャで受け取れるようにクラスモジュールを使用します。

イベントを共有するには、以下の手続きが必要となります。

1)イベントを持つオプションボタン型の変数として、Lab(el)変数を宣言(図2-1の3行目)
2)実LabelをLab変数に格納するプロシージャを作成(図2-1の5~7行目)
3)フォームでLabel配列をClass1型として作成(図2-5の23行目)
4)フォームのInitializeで、OptionButton配列にOptionButtonを登録(図2-5の30行目)
5)OptionButtonが押されたイベントを使って処理する(図2-1の9~15行目)

クラスを使用した共有イベントについては、以下も参照下さい。
・「セルの罫線を矢印キーで引く
・「セルへの日付入力をカレンダー日付クリックで選定する

  1. '========== ⇩① イベントを持つLabel型の変数を宣言 ==============
  2. Option Explicit
  3. Private WithEvents Lab As MSForms.Label   ’←イベントを持つLabel型の変数を宣言
  4. '========== ⇩② 各Labelを、イベントを持つ変数に格納 ==============
  5. Public Sub SetLab(ByVal NewLab As MSForms.Label)
  6.  Set Lab = NewLab               ’←各Labelを、イベントを持つ変数に格納
  7. End Sub
  8. '========== ⇩③ Labelのイベントを取得 ===============
  9. Private Sub Lab_click()
  10.  Dim YYYY As Long
  11.  YYYY = (Mid(Lab.Name, 6) - 1) Mod 5 - 2   ’←クリックされたLabel位置から、中心とのズレを計算
  12.  YYYY = UserForm1.ScrollBar1.Value + YYYY  ’←クリックされた西暦年をYYYYに代入
  13.  Call UserForm2.Cal_Show(YYYY)        ’←西暦年を引数にしてUserForm2を呼び出す
  14. End Sub
図2-1

2行目は、変数の宣言を強制するものです。
3行目は、イベントを持つLabel型の変数として「Lab」を宣言しています。こうすることで、最終的にLabelをクリックされたイベントを1つのイベントプロシージャ(9~15行目)で受け取れます。

5~7行目は、3行目で作成した変数に実際のLabelを登録(図2-5の30行目)するための式となります。フォームのコントロールであるLabelはオブジェクトですので、Setステートメントを使います。

9~15行目は共有イベントプロシージャで、登録してあるLabelをクリックした場合に反応します。どのLabelがクリックされたのかは、Lab.Name(Labelのオブジェクト名)やLab.Caption(Labelの表示名)で判別します。

今回はクリックしたオブジェクト名(Label1~Label25)を取得した後、その値を図2-2のように「YYYY = (Mid(Lab.Name, 6) - 1) Mod 5 - 2」の数式で5行x5列の中心列からのズレ(±年)を求め、スクロールバーの値に加えることで、列の西暦年を取得します。


図2-2

ちなみに、「A mod B」というのは「AをBで割った時の余り」の式で、繰り返して並んでいる場面では重宝するものです。
なお図2-2を見ていると、数式は「YYYY = (Mid(Lab.Name, 6) ) Mod 5 - 3」でも良い気がしてきますが、例えばLabel15の場合ですと、YYYY=-3 となってしまい正しい年が得られないことが分かります。
最終的に得られた「クリックした西暦年(12行目の左辺のYYYY)」を引数にして、14行目でカレンダーを呼び出しています。


2-2.標準モジュール

標準モジュールは、「西暦・和暦対照表」であるUserForm1を呼び出すだけ(図2-3)です。今回は対照表を参考にしながらExcel作業を可能にするため、モードレスで起動させています。
ちなみにExcelにアドイン登録する方法を3項目で説明しますが、マクロ呼び出しを可能にするために、Cal_JPプロシージャはPublicにしています。

  1. '========== ⇩④ UserForm1(西暦・和暦対照表)の起動 ================
  2. Public Sub Cal_JP()
  3.  UserForm1.Show 0     ’←モードレスでUserForm1を起動
  4. End Sub
図2-3


2-3.フォームモジュール1

UserForm1は「西暦・和暦対照表」です。まず、フォームにコントロールを配置します(図2-4)。


図2-4

今回比較するのは「西暦」「昭和」「平成」「令和」ですが、一番下側に「今年から見て何年前(後)」を加えて全5種にしました。単年比較も良いのですが、少し幅を広げて「-2年~+2年」で比較できるようにし、合計で「5種x5年」を表示します。
その「5種x5年」をLabel1~Label25として並べ、上には年を動かすスクロールバーを配置しました。
(なお図2-4のLabelのCaptionは、見易いようにオブジェクト名と合わせてありますが、Captionはマクロで変更してしまいますので並び方さえ合っていればOKです。)


次にコードですが、宣言部・初期化部(Initializeイベントプロシージャ)は、図2-5となります。
  1. '========== ⇩⑤ 宣言部 =============
  2. Option Explicit
  3. Private Nen(1 To 25) As MSForms.Label  ’←年ラベルの配列をLabel型として宣言(表示用)
  4. Private NenC(1 To 25) As New Class1  ’←Label配列をClass1型として作成(イベント用)
  5. '========== ⇩⑥ フォーム初期化イベントプロシージャ =============
  6. Private Sub UserForm_Initialize()    ’←フォームの初期化
  7.  Dim i As Integer            ’←カウンター変数
  8.  For i = 1 To 25             ’←Label25個を
  9.   Set Nen(i) = Controls("Label" & i)   ’←配列に入れる
  10.   NenC(i).SetLab Controls("Label" & i)   ’←イベントのあるNenC変数に格納する
  11.   Nen(i).Font.Size = 12           ’←表示Labelのフォントサイズを設定
  12.   Nen(i).TextAlign = fmTextAlignCenter   ’←表示Labelを中央揃えに設定
  13.  Next i
  14.  With Me.ScrollBar1     ’←スクロールバーの
  15.   .Min = 1900        ’←最小を1900年
  16.   .Max = 2100        ’←最大を2100年
  17.   .LargeChange = 5     ’←バー間クリックで±5年動く
  18.   .SmallChange = 1     ’←両端クリックで±1年動く
  19.   .Value = Year(Now())   ’←バーの値を今年にする
  20.  End With
  21.  Me.Caption = "西暦・和暦対象表"  ’←フォームのタイトルを変更
  22. End Sub
図2-5

まず宣言部での22行目は、Labelをまとめるための配列をMsForms.Label型として宣言します。コントロールである各Labelは、iを1~25の内から選ぶことで「Controls("Label" & i)」で表されますが、文字列の足し算を毎回行うのはコードが長くなりますし、また処理速度も遅いためにオブジェクトの配列にする事にしました。
23行目は、全25個のLabelをイベント発生可能なClass1型の配列として宣言しています。

Initializeイベントプロシージャでは For~Next で回しながら、29行目でLabel型の配列に格納し、また30行目では各Labelをイベント発生可能な変数Lab(図2-1の3行目で宣言した変数)に登録をしています。

29行目でLabel型配列に格納した各Labelに対して、32行目では Font.Size を揃え、33行目では文字表示位置を揃えています。(フォーム上でコントロールを配置しながら、これらのプロパティを変更しても良いのですが、「どのプロパティをどう変更したのか分からなくなる」のを防ぐために、できるだけInitializeイベントの中で変更させています。

36~42行目は、スクロールバーのプロパティ変更です。
37~38行目は、1900年~2100年までを表示できるように設定しています。
また39~40行目は、スクロールバーの動き量の調整です。表示が5年分なのでLargeChangeは5(年)にしました。

41行目のスクロールバーのValue値ですが、今年の西暦年(記述している時点では 2020 )を設定しています。
実はフォームの配置時にはスクロールバーの初期Value値は「1900」にしてあります。その状態から「2020」に「Value値を変更」するので「ScrollBar1_Change」のイベントが発生する事になります。
「ScrollBar1_Change」イベントが発生すると、西暦・和暦の計算と表示が実行されます。もし初期Value値を2020にしてしまうと、あとから2020の設定をしても「値に変更が無いためにイベントが発生しない」ために、初期表示用に別途「西暦・和暦の計算と表示」を実行させるコードが必要となってしまうのです。
(もちろん1900である必要は無く、今年よりも少ない値であればOKです)

44行目は、ダイアログボックスのタイトル部に「タイトル」を書き込んでいます。(機能には無関係です)


図2-6は、フォーム上に配置したスクロールバーを動かした時のイベントプロシージャです。
  1. '========== ⇩⑦ スクロールバーのイベントプロシージャ ============
  2. Private Sub ScrollBar1_Change()   ’←スクロールバーを動かした時のイベントプロシージャ
  3.  Call JP_Cal(ScrollBar1.Value, 0, 0, False)       '西暦
  4.  Call JP_Cal(ScrollBar1.Value, 1925, 1)         '昭和
  5.  Call JP_Cal(ScrollBar1.Value, 1988, 2)         '平成
  6.  Call JP_Cal(ScrollBar1.Value, 2018, 3)         '令和
  7.  Call JP_Cal(ScrollBar1.Value, Year(Now()), 4, False) '今年との±差
  8. End Sub
図2-6

対照表は5行(西暦・昭和・平成・令和・今年との差)ありますので、5種類の呼び出しがあります。
呼び出される「JP_Cal」プロシージャについては図2-8で説明しますが、呼び出す側としての必要項目をまとめてみると、図2-7の様になります。

基準値 調整値  表示位置 マイナス値は非表示和暦期間は赤字
 西暦  スクロールバー値(表示西暦)  そのまま(=0) 1行目(×)×
昭和  ↑ - 1925 2行目
平成  ↑ - 19883行目
令和  ↑ - 20184行目
±差  ↑ - 今年の西暦値5行目××
図2-7

この図2-7の項目に合わせて、呼び出される「JP_Cal」プロシージャの引数を定めました。
図2-8の57~61行目にもコメントしてありますが、図2-7のタイトルと対比すると以下になります。
「基準値」= YYYY
「調整値」= Start_Year (和暦が始まる年の前年という意味)
「表示位置」= Start_Array (ゼロ基準としました)
「マイナス非表示」+「和暦期間は赤字」=Col (代表してColor処理 の意味:Trueで実施、Falseは処理無し)

呼び出す側の48~52行目の引数については、この図2-7に従って指示しています。
スクロールバーを動かした時のイベントですので、基準値(YYYY)がスクロールバーのValue値になるのはお分かりになると思います。

次の調整値(Start_Year)は「基準値から引く値」として指示しています。
調整値について1つだけ「令和」で確認しておくと、西暦2019年が令和1年(元年)ですので、「2019-2018=1」となり、調整値=2018 となります。図2-6の51行目の引数と合っています。

表示位置(Start_Array)は表示する行位置、色処理等(Col)については処理するのであればTrue(指定しなければTrue)・無視するのであればFalseを指定しています。



では、対照表を計算・表示するプロシージャが図2-8です。

  1. '========== ⇩⑧ 対照表の5つのLabelに西暦・和暦を記入するプロシージャ =============
  2. Private Sub JP_Cal(YYYY As Integer, Start_Year As Integer, _
  3.           Start_Array As Integer, Optional Col As Boolean = True)
  4.   '引数: YYYY     :西暦年
  5.   '   Start_Year   :和暦元年の前年(西暦)
  6.   '   Start_Array  :表示段の位置(西暦段は0、昭和は1)
  7.   '   Col      :「色付け、マイナス値の消去、1年→元年の変更」を実行するか
  8.   '            引数Colは省略可能引数で、省略時はFalse(=実行せず)
  9.  Dim i As Integer ’←カウンター変数
  10.  For i = 1 To 5
  11.   With Nen(i + Start_Array * 5)
  12.    .Caption = YYYY - Start_Year + i - 3 ’←スクロールバー値(西暦)-和暦開始年=和暦年を各Labelに記入
  13.    If Col = True Then ’←「色付け、マイナス値の消去、1年→元年の変更」がONの時
  14.                 '↓表示年=和暦年だったら(和暦の期間だったら)
  15.     If .Caption = Format(DateSerial(ScrollBar1.Value + i - 3, 1, 1), "e") Then
  16.      .ForeColor = RGB(255, 0, 0)  ’←文字を赤くする
  17.     Else                ’←違っていたら
  18.      .ForeColor = RGB(0, 0, 0)   ’←文字を黒くする
  19.     End If
  20.     If .Caption <= 0 Then       ’←表示年がゼロ以下だったら
  21.      .Caption = ""          ’←表示を消す
  22.     ElseIf .Caption = 1 Then     ’←表示年が1だったら
  23.      .Caption = "元"         ’←「元」に変更する
  24.      .ForeColor = RGB(255, 0, 0) '1月1日では元号が変わらない為、赤色にする
  25.     End If
  26.    End If
  27.   End With
  28.  Next
  29. End Sub
図2-8

65~66行目のFor~Next と With は、西暦・和暦の年を記入する先であるLabelの位置を特定している部分です。
例えば平成について計算・表示する場合(図2-6の50行目の呼び出し)を図2-9で説明します。平成の表示は3行目ですから「Start_Array」の引数には2(ゼロ行からスタートとしている為)を指示しています。

65行目の「For i = 1 To 5」は、セルを横に1つ1つ動かしていくイメージです。
66行目の With ステートメントで指定しているオブジェクトは「 Nen ( i + Start_Array * 5 )」で、第3引数であるStart_Arrayには「2」が渡されていますので「Nen(i + 10)」という事になります。65行目のForの中でiを1~5まで繰り返していますから、Withステートメントで指定している対象オブジェクトも「Nen(11)」→「Nen(12)」→「Nen(13)」→「Nen(14)」→「Nen(15)」と横に動いていきます。

Nen( 1 to 25 )配列には、数字部分を合わせる形でLabel1~Label25が順に格納されています(図2-5の29行目)ので、「Nen(11)~Nen(15)」⇒「Label 11 ~ Label 15」と言うことになり、「平成の3行目の5つのLabel」が対象オブジェクトである事が分かります。


図2-9

67行目は「.Caption = YYYY - Start_Year + i - 3」ですが、代入される「Caption」 の先頭にピリオド(.)がついていますので、Withで指定したオブジェクト「平成の3行目の5つのLabel」のCaption ということになります。
=の右辺は「 YYYY - Start_Year + i - 3 」で、スクロールバーの値が2020であった場合には、YYYY=2020 、Start_Year=1988(平成) となりますので、計算すると「 29 + i 」で i=1 to 5 ですので、「30~34」が「Label 11 ~ Label 15」に振り分けられます。

5つのLabel の真ん中の列がスクロールバーと同じ年になるようにしているつもりなので、そうなっているか確認してみると、5つのLabelの真ん中(Label13)には「32」が入る事になり、西暦2020年=平成32年 と正しく計算されています。

次に69行目の「If Col = True Then」は、83行目の「End If」までの範囲をIF文で囲っています。
Colは、このプロシージャの第4引数で、しかも「Optional Col As Boolean = True」となっていますので、指定しない場合はTrueになります。
図2-6の50行目の「平成として本プロシージャを呼び出し」では、引数は3つしか指定していないので、Col=True という事になります。ですから「平成の場合には69~83行目は実行する」ことになります。
(逆に図2-6の48行目の「西暦」及び52行目の「今年との±差」では「第4引数にFalseを設定」しているため、実行されない」ことになります)

IF文の中で実施しているのは2項目です。
イ)71~75行目:該当する和暦の期間内であれば、文字を赤色にする。違えば黒色にする。
ロ)77~82行目:表示年がマイナスだったら文字削除、表示年が1だったら「元」(元年の元)に変更する。

まずイ)のIF文ですが「.Caption = Format(DateSerial(ScrollBar1.Value + i - 3, 1, 1), "e")」となっており、対象はLabelの文字(Caption)です。
=の右辺の中で「DateSerial(ScrollBar1.Value + i - 3, 1, 1)」は、例えばスクロールバーを2020にした場合では、「DateSerial(2017+i, 1 , 1 )」となります。
iは1~5ですから、日付として「2018/1/1 , 2019/1/1 , 2020/1/1 , 2021/1/1 , 2022/1/1」が得られます。

次にその外側の「Format(年月日, "e" )」ですが、「年月日を和暦に変更し、その「年」を返す」という式です。例えば2020年1月1日でしたら「令和2年1月1日」の年の「2」を、2018年1月1日でしたら「平成30年1月1日」ですので年の「30」を返します。
2019年は途中から平成→令和に替わりましたので日付で異なりますが、1月1日と指定していますので「31」を返します。
5つのLabelの文字と、Format(年月日, "e" )の値を比較してみると図2-10になります。

Label11Label12Label13Label14Label15
(西暦)20182019202020212022
Labelの文字(平成)3031323334
 Format(年月日, "e") 3031234
図2-10

この値が同じであったら72行目の「.ForeColor = RGB(255, 0, 0)」で文字を赤くし、異なっていたら74行目の「.ForeColor = RGB(0, 0, 0)」で文字を黒くします。この結果、「Label11」と「Label12」は赤文字になり、「Label13」・「Label14」・「Label15」は黒文字になります。



次にロ)のIF文(表示年がマイナスだったら文字削除、1だったら「元」に変更)です。
Labelの表示文字(.Caption)の値で判別していますので、今度はダイアログの4行目の「令和」で確認していきたいと思います。
スクロールバー値が2020の場合、令和に変換した値は「Label16」~「Label20」に書かれます。その状況は図2-11になります。

Label16Label17Label18Label19Label20
(西暦)20182019202020212022
Labelの文字(令和)01234
 Format(年月日, "e")30(平成) 黒色31(平成) 黒色2(令和) 赤色3(令和) 赤色4(令和) 赤色
図2-11

77~82行目のIF文に従って、令和の行のLabelに書かれた文字(数字)を分別・処理してみます。
まず77行目は「If .Caption <= 0 Then」ですので、Label16の「0」は「""(空)」に変更され
79行目は「ElseIf .Caption = 1 Then」ですので、Label17の「1」を「元」に変更、且つ赤文字に変更されます。

尚、「令和元年」=「平成31年」ですが、図2-10で分かる通り「1月1日ではまだ平成です。そのため71~72行目の処理では「令和1年の「1」は赤くならない」ので、81行目でわざわざ文字色を赤に変更しています。
この元年の赤字処理は、基本的には「1月1日は前年の元号」であるが故の処理なのですが、今後法律等で1月1日を改元日にしたとしても「新たな元号」のLabel文字「1」が71行目のIF文の処理で「先に赤文字になってしまう」(=81行目の処理が無駄になる)だけであり、結果としては同じになることになります。



2-4.フォームモジュール2

UserForm1のLabel1~Label25のどれかをクリックすると、図2-1の9~15行目のLab_clickイベントプロシージャが動き、図2-1の14行目の「Call UserForm2.Cal_Show(YYYY)」により「UserForm2のCal_Showプロシージャ」が呼び出されます。この時、引数としてYYYY(西暦年)を渡します。

フォームを呼び出すときは「 Show UserForm2 」等とShowメソッドで呼び出すのが一般的ですが、引数を受け渡す場合には工夫が必要です。以下のような方法が考えられます。

1)シートのセルを使用(標準モジュール等から引数を一旦セルに書き込み、フォーム側で読み取る)
2)Public変数を使う(標準モジュール等からPublic変数に引数を代入し、フォーム側で読み取る)
3)フォームのコントロールを使う(標準モジュールからフォーム上のLabel等に引数を書き込み、その後Showする)
4)フォーム上のSubプロシージャーに引数を渡し、そのプロシージャの中でフォームをShowする

どの方法でも良いと思います。ただし1)の方法はセルに書き込みを行う為、ファイルを閉じる時に保存要否を尋ねられますので、その処理についても考慮する必要があります。
今回は4)の方法を使い、西暦年(YYYY)を渡しながらカレンダーを表示させます。


また、逆にフォーム側から標準モジュール側へ値を渡すときもほぼ同じで、以下のような方法があります。

1)シートのセルを使用(フォーム側から引数を一旦セルに書き込み、標準モジュール等で読み取る)
2)Public変数を使う(フォーム側からPublic変数に引数を代入し、標準モジュール等で読み取る)
3)フォームのコントロールを使う(フォームをHideした後、標準モジュール側から読み取る)
4)Show時にフォーム側のFunctionプロシージャーを呼び出す(Functionの返り値として標準モジュール側に値が渡る)

なお、3)の例としては「セルへの日付入力をカレンダー日付クリックで選定する」を参照下さい。


まず、カレンダーを作るために、フォーム上のコントロールを図2-12のように配置します。

図2-12

カレンダーの日付部分(図2-12の「01」~「42」)には、Labelを6行x7列並べます。LabelのCaptionは図2-12では2桁の数字にしていますが、オリジナル状態(Label42など)のままでOKです。
(フォントサイズとの関係で、Labelの大きさが問題ないかを確認しておく必要はあるかもしれません。)
また、最上部にはカレンダー月を表示するLabel(図2-12では、Label50)を、上両サイドには月を変更するCommandButtonを配置しています(右がCommandButton1、左がCommandButton2)。月変更はスクロールバーを利用しても良いと思います。


次に、UserForm2に記述するコードです。
  1. '========== ⇩⑨ 変数宣言部 =================
  2. Option Explicit
  3. Private Cal_Label(1 To 42) As MSForms.Label  ’←カレンダーの日付ラベルを格納する配列を宣言
  4. '========== ⇩⑩ フォーム初期化イベントプロシージャ ===============
  5. Private Sub UserForm_Initialize()     ’←UserForm2の初期化
  6.  Dim i As Integer             ’←カウンター変数
  7.  For i = 1 To 42
  8.   Set Cal_Label(i) = Me.Controls("Label" & i)  ’←カレンダー日付ラベルを配列に格納
  9.   Cal_Label(i).TextAlign = fmTextAlignRight   ’←カレンダー日付の数字は右寄せ(数値表記)
  10.  Next i
  11.  For i = 1 To 42 Step 7
  12.   Cal_Label(i).BackColor = RGB(255, 192, 203)  ’←日曜日は赤色にする
  13.  Next i
  14.  For i = 7 To 42 Step 7
  15.   Cal_Label(i).BackColor = RGB(173, 216, 230)  ’←土曜日は水色にする
  16.  Next i
  17.  Me.Label50.TextAlign = fmTextAlignCenter    ’←月の表示は中央揃え
  18.  Me.CommandButton1.Caption = "次月"       ’←右ボタン
  19.  Me.CommandButton2.Caption = "先月"       ’←左ボタン
  20. End Sub
図2-13

89行目は、42個のLabelの配列を宣言しています。
カレンダーの日付を表示するために今回42個のLabelを使っていますが、その1つ1つにアクセスする為には「Controls("Label" & i)」などと文字列の足し算("Label" & i iは数字)をした後に「その名前のついたコントロール」という流れでLabelを特定するしかありません。
しかし、これではコードが長くなりますし、且つ「文字列の足し算は処理時間が長い」というデメリットもあります。なによりコードが読み難いと思いますので、今回は日付に使うLabelを配列にし、インデックス(=Label番号)で特定しようと考えました。

そこで、MSForms.Labelという型で配列 Cal_Label(Calendar Label のつもり)を宣言しています。


Initializeイベントでは、まず95行目で、42個のLabelの実体を89行目で宣言した Cal_Label に代入しています。
同時に96行目で「右寄せ」設定をしています。Labelだけでなくフォームコントロールの表面の文字(Caption)は文字列ですので、通常は左寄せです。数字っぽく見せるために、ここでは「右寄せ」設定をしています。

94~97行目のFor~Nextで全42個のLabelを配列 Cal_Label に格納しましたので、これ以降は配列を使って設定を行っていきます。

99~101行目では、背景色を赤にして日曜日であることを強調させています。99行目の「For i = 1 To 42 Step 7」は「1から始めて7つ飛びに」という意味ですから「1・8・15・22・29・36」番のLabelとなります。ちょうど日曜日に相当するLabelです。

103~105行目では、土曜日相当のLabelの背景色を水色にしています。103行目の「For i = 7 To 42 Step 7」は、「7・14・21・28・35・42」番のLabelとなります。

107行目は「Me.Label50.TextAlign = fmTextAlignCenter」と、カレンダー表示月を記入するLabel50の文字列位置を中央揃えにしています。
108行目は、右上のCommandButton1に「次月」を、左上のCommandButton2に「前月」を記入しています。


西暦・和暦対照表(UserForm1)上の年(Label1~Label15)をクリックすると、図2-1dのLab_clickイベントプロシージャにより呼び出されるのが、図2-14のCal_Showプロシージャです。
どの年のカレンダーを表示すれば良いかは、引数であるYYYYで西暦年を渡されます。
  1. '========== ⇩⑪ 西暦・和暦対照表から呼び出されるプロシージャ ==============
  2. Public Sub Cal_Show(ByRef YYYY As Long)   ’←UserForm1のラベルクリックで呼ばれるプロシージャ
  3.  Call Calendar(YYYY)            ’←カレンダーを計算
  4.  Me.Show 0                 ’←UserForm2をモードレスで表示
  5. End Sub
図2-14

113行目では、表示させるカレンダーの西暦年(YYYY)を引数にして、カレンダー計算・表示プロシージャである「Calendar」を呼び出します。
「Calendar」プロシージャは、実は図2-15で見るように2つの引数を渡すことになっているのですが、2つ目の引数には「Optional」がついており省略可能です。省略した場合は「1(=1月)」としています。
つまり、113行目のCalendar呼び出しは Calendar( YYYY , 1 )として呼び出したのと同じ事になります。

「Calendar」プロシージャで YYYY年1月のカレンダーを作ったのち、114行目で自分(=UserForm2)を表示します。ここでは「カレンダーを表示したままでも、Excelシートで作業が出来る」ことを狙い、モードレスで表示しています。

図2-1の14行目の「Call UserForm2.Cal_Show(YYYY)」で呼び出したUserForm2は、この時点で表示されます。


図2-14の113行目で呼び出される「Calendarプロシージャ」が、図2-15です。
また、UserForm2のCommandButton(次月・前月ボタン)をクリックした際にも、この「Calendarプロシージャ」が呼び出されます。
  1. '========== ⇩⑫ カレンダーの計算と表示 ===============
  2. Private Sub Calendar(YYYY As Long, Optional MM As Long = 1)  ’←カレンダー計算プロシージャ
  3.  Dim Cal_First_Day As Date    ’←月カレンダーの初日
  4.  Dim First_Week As Long     ’←初日の曜日
  5.  Dim Last_Day As Long      ’←月の最終日
  6.  Dim i As Integer        ’←カウンター変数
  7.  Dim j As Integer        ’←カウンター変数
  8.  Cal_First_Day = DateSerial(YYYY, MM, 1)       ’←月カレンダーの初日計算
  9.  YYYY = Year(Cal_First_Day)              ’←年の再計算
  10.  MM = Month(Cal_First_Day)               ’←月の再計算
  11.  First_Week = Weekday(Cal_First_Day, 1)        ’←初日の曜日計算
  12.  Last_Day = Day(DateSerial(YYYY, MM + 1, 0))     ’←月の最終日計算
  13.  For i = 1 To 42
  14.   Cal_Label(i).Visible = False            ’←一旦、全Labelを非表示にする
  15.  Next i
  16.  j = First_Week             ’←初日の曜日のLabelから日付をつける
  17.  For i = 1 To Last_Day         ’←日付は1から最終日まで
  18.   Cal_Label(j).Caption = i       ’←日付を記入
  19.   Cal_Label(j).Visible = True      ’←日付を記入したものを表示する
  20.   j = j + 1
  21.  Next i
  22.  Me.Label50.Caption = MM & " 月"      ’←月を表示する。月と数字の間には半角スペース1つ有り
  23.  Me.Caption = YYYY & "年(" & Format(DateSerial(YYYY, MM, 1), "ggge年)")  ’←年を表示
  24. End Sub
図2-15

この「Calendar」プロシージャは、2つの引数を受け取ります。第一引数YYYYは西暦年、第二引数MMは月です。
尚、2つ目の引数には「Optional」を設定し、省略した場合は「1月」としています。

119~121行目では、カレンダー作成に必要な3つの変数「Cal_First_Day」「First_Week」「Last_Day」の宣言をしています。この変数の意味は以下の通りです。
1)「Cal_First_Day」:カレンダー表示月の初日
2)「First_Week」 :初日の曜日(初日を2020年1月1日とすると、水曜日(Weekday関数で4 )になります
3)「Last_Day」  :カレンダー表示月の最終日(初日を2020年1月1日とすると、31日になります)

この値を使って、カレンダーを作る手順を追ってみます。

まず、カレンダーを書く台紙を作ります。カレンダーのサイズを考えてみると、横列は日曜から始まって土曜まで7日あります。また行数については、例えば「初日の1日が最も遅い土曜日始まりで、且つ最長の31日まである月」を考えると、図2-16のように縦は6行あれば足りることが分かります。(正確に言えば、Labelは37個あれば足りることになります)


図2-16

次に、「初日」(Cal_First_Day)をWeekday関数を使って求めたものが「曜日に対応した数値」(First_Week)となりますが、その曜日値(1~7)に対応する曜日の場所に「1」を配置きます。(図2-17の左側)
その後に続けて2・3・4・・・と並べていき、初日(Cal_First_Day)の月の最終日(Last_Day)が来たら終わり(図2-17の右側)。これでカレンダーの完成です。

図2-17

この考え方を数式に直したのが125行目以降になります。

まず125行目の「Cal_First_Day = DateSerial(YYYY, MM, 1)は、引数として与えられた年(YYYY)と月(MM)を使って日付型の値を得ます。
126~127行目は125行目で求めたCal_First_Dayを使用して年(YYYY)と月(MM)を求めなおしています。一見「引数で与えられたYYYYとMMではダメなの?」と思われると思います。
実は引数のMMは1~12の値では無く、0~13の値となります(今回のシステムの場合)。これはカレンダー上部の「次月」「前月」ボタンを押した時に、表示されている月に対して+1(次月)や-1(前月)を加えた数値をCalendarプロシージャの第二引数のMM(月)として渡している為です。
つまり、「2020年12月」の次月は「2021年1月」ですが、「2020年13月」として渡しているのです。でも計算上は合っているのです。

しかし、13月では人間の目には異様に感じますので、125行目で使用している「DateSerial(年,月,日)」関数を使って、2020年13月1日を2021年1月1日に変換しているのです。

129行目の「First_Week = Weekday(Cal_First_Day, 1)」では、先ほど述べましたWeekday関数で初日の曜日値を求めています。
ここで得られる値は1~7ですが「1=日曜日 と一概には言えません」。Weekday関数の第二引数には「週の最初の曜日を何曜日にするかを指定」することで、返される値が何曜日になるかが決まります。
129行目の数式では第二引数に「1」を指定(第二引数を省略すると1になります)していますので、1が返されればそれは日曜日、ということになります。

130行目の「Last_Day = Day(DateSerial(YYYY, MM + 1, 0))」は、その月の最終日(=その月の日数)を求めています。
式は「次月の初日の1日前」という意味ですが、最終日の求め方には何種類かが存在します。「月の最終日の計算方法」を参照して下さい。

132~135行目では、全Labelを非表示にしています。その後の140行目で、日付が書かれた必要なLabelを表示させています。
この方法以外にも、図2-18の135行目の様に「全Labelの表示を空にする」方法もあります。
  1.  For i = 1 To 42
  2.   'Cal_Label(i).Visible = False           ’←一旦、全Labelを非表示にする
  3.   Cal_Label(i).Caption = ""         ’←一旦、全Labelの文字を空にする(追加)
  4.  Next i
  5.  j = First_Week                  ’←初日の曜日のLabelから日付をつける
  6.  For i = 1 To Last_Day              ’←日付は1から最終日まで
  7.   Cal_Label(j).Caption = i            ’←日付を記入
  8.   'Cal_Label(j).Visible = True         ’←日付を記入したものを表示する
  9.   j = j + 1
  10.  Next i
図2-18

この場合は141行目の「必要なLabelは表示する」コードが不要となりますが、見た目の違いが発生します。
図2-15の「.Visible = False」では使わないLabelを非表示にしてますので背景色も付きませんが、図2-18の「.Caption = ""」では背景色が設定できてしまう為に日付の無い部分にも土日色が付きます(図2-19)。どちらが良いかはユーザ次第です。


.Visible = False の場合
   
.Caption = "" の場合
図2-19

137行目が図2-17で言うところの「初日を曜日に置く」コードです。138~142行目では、数字を1つずつ増やしながら(今回コードのiに相当)、初日曜日の後ろに続けて(今回コードのjに相当)記入していきます。
138行目のFor文はLast_Dayまでですから、最終日で記入が終わります。
数字(=日付)を記入したLabelに対しては、140行目でLabelを表示(.Visible = True)させています。

最後に、144行目でカレンダーの上に月を表示し、145行目で西暦・和暦をタイトル部に記入しています。
(126~127行目は、この144~145行目の為だけに存在する、と言っても良いと思います。)



カレンダーの右上と左上にある「次月」「前月」ボタンを押した時のイベントプロシージャが図2-20です。

  1. '========== ⇩⑬ 「次月」ボタンをクリックしたイベント =============
  2. Private Sub CommandButton1_Click()      ’←「月を足す」ボタン
  3.  Dim YYYY As Long
  4.  Dim MM As Long
  5.  YYYY = Left(Me.Caption, 4)         ’←現表示カレンダーの年を取得
  6.  MM = Left(Me.Label50.Caption, Len(Me.Label50.Caption) - 2)  ’←現表示カレンダーの月を取得
  7.  Call Calendar(YYYY, MM + 1)    ’←現表示カレンダーの月に1を足してカレンダー再表示
  8. End Sub
  9. '========== ⇩⑭ 「前月」ボタンをクリックしたイベント ====================
  10. Private Sub CommandButton2_Click()     ’←「月を引く」ボタン
  11.  Dim YYYY As Long
  12.  Dim MM As Long
  13.  YYYY = Left(Me.Caption, 4)        ’←現表示カレンダーの年を取得
  14.  MM = Left(Me.Label50.Caption, Len(Me.Label50.Caption) - 2)  ’←現表示カレンダーの月を取得
  15.  Call Calendar(YYYY, MM - 1)  ’←現表示カレンダーの月に1を引いてカレンダー再表示
  16. End Sub
図2-20

149~156行目は「次月」ボタンをクリックした時のイベントプロシージャです。
152行目は「YYYY = Left(Me.Caption, 4)」となっており、「Me.Caption」はカレンダーのタイトル部の文字列を示しています。例えば2020年であれば「2020年(令和2年)」と表示されているはずですので、先頭から4文字を抜き取ってYYYY(西暦)としています。

153行目は「MM = Left(Me.Label50.Caption, Len(Me.Label50.Caption) - 2)」と少し長いですが、分解し整理してみます。
まず「Me.Label50.Caption」はLabel50に書かれた月ですので、例えば「1_月」や「10_月」の様な文字列です。数字(1 や 10)と文字(月)の間には、半角スペースが1つ入っています。(144行目参照)
また、その後ろの「Len(Me.Label50.Caption)」は、その文字列の長さです。
「スペース+月」で2文字分になりますので、文字列の長さから2を引いて、Left関数で文字列をすくい上げると数値だけが受け取れます。(図2-21参照)

 ① Me.Label50.Caption  ② Len(①)-2  Left(①, ②) 
 1_月 (間にスペース1つ)3-2=11
 10_月 (間にスペース1つ)4-2=210
図2-21

これで、現在表示している年(YYYY)と月(MM)が取得できましたので、この数値を使って155行目でCalendarプロシージャを呼び出しています。その引数としては年(YYYY)はそのままで、月(MM)に1を足して渡しています。
Calendarプロシージャ側は、この年・月に従ってカレンダーを再表示させています。
また「前月」ボタンを押した時は158~165行目のイベントプロシージャが動きます。「次月」イベントと異なるのは164行目で月(MM)から1を引いて渡している部分です。これにより前月のカレンダーを再表示させます。

 

3.Excelに登録する

マクロは完成しましたが「Excelを使っている時に使えるように」するためには、「〇〇.xlsm」というファイルの状態ではなく、「アドイン」という形でExcelに登録をしなければなりません。
Excelに登録する形としては他にも「個人用マクロブック」がありますが、1つのファイルにすべてのマクロを収めなければならず、今回のようなある程度大きなマクロには不向きです。ですので、今回はアドイン登録の方法について説明をします。

尚、アドイン・個人用マクロブックについては「セルの罫線を矢印キーで引く」も参照願います。


3-1.Excelのアドインファイルにする

まず図3-1のように、マクロ作成したファイル(拡張子 .xlsm)を「名前を付けて保存」でアドインファイル(拡張子 .xlam)として保存します。
保存先は、保存形式をアドインファイルを選択すると「自動的に保存すべきフォルダに移動」するようですが、もし移動しない場合は「C:¥Users¥(ユーザ名)¥AppData¥Roaming¥Microsoft¥AddIns」を選んで下さい。


アドインファイルとして保存
図3-1


続いて図3-2のように、Excelの開発タブの中の「Excelアドイン」を開き、先ほど保存したファイル名の「有効なアドイン」にレ点をつけ、OKボタンを押して下さい。


保存したアドインを有効化
図3-2


次に、Excelに新しい自分用のタブを作ります。図3-3のように、どこのリボンでも良いので、リボンの上でマウスの右ボタンをクリックし「リボンのユーザー設定」を選択して下さい。


リボン上でマウス右クリックし「リボンのユーザー設定」を選択
図3-3


すると、Excelのオプションとして「リボンのユーザー設定」ダイアログが出てきますので、図3-4のように、左欄の上の「コマンドの選択」欄で「ユーザー設定のタブとグループ」を選択します。すると「新しいタブ(ユーザー設定)」と「ユーザー設定のグループ」の2種類が現れます。
初めて自分用のタブを作る人は上側の「新しいタブ(ユーザー設定)」を選択し、追加ボタンを押します。

既に自分用のタブがある人は、下側の「ユーザー設定のグループ」で「新しいグループ(ユーザー設定)」を追加しても良いですし、既にあるグループを使用するならば、タブの設定は不要です。


左欄で「ユーザー設定のタブとグループ」を選択し、追加ボタンを押す
図3-4


ここで一度Excelを閉じ(マクロ入りの.xlsm を閉じる事になる)、新たにExcelの新規ブックを開きます。
開いたら、先ほどと同じようにリボンの上でマウス右ボタンを押して「リボンのユーザー設定」を選択し、「リボンのユーザー設定」ダイアログボックスを表示させます。
左欄の「コマンドの選択」で、今度は「マクロ」を選択します(図3-5)。するとアドインしたファイル(拡張子 .xlam )内の実行できるプロシージャ名が出てきます。

左欄で「マクロ」を選択
図3-5


実行できるプロシージャとは、「Public 」且つ「引数を持たない」プロシージャですので、今回のマクロの中ではメインプロシージャである「Cal_JP」プロシージャのみということになります。
まず、その「Cal_JP」を選択(図3-6)します。

標準モジュールの「Cal_JP」を選択
図3-6


次に追加先の「新しいグループ」を選択(図3-7)します。
追加元・追加先ともグレー色か青色で選択状態になったら「追加」ボタンを押します。

追加先「新しいグループ」を選択し、追加ボタンを押す
図3-7


最後に右下の「OKボタン」を押して「リボンのユーザー設定」ダイアログを閉じます。
Excelの「新しいタブ」を開いてみると、西暦・和暦対照表マクロが登録されているはずです。そのアイコンをクリックすれば、対照表ダイアログが表示されます。(図3-8)

図3-8


尚「プロシージャ名やアイコンの絵を変えたい」という方は、「リボンのユーザー設定」で「新しいグループ」の下にぶらさがっているマクロ名(今回はCal_JP)をクリックした後「名前の変更」をクリックして、図3-9の様に表示名とアイコンを変更する事が出来ます。

図3-9



4.最後に

結構便利なツールなのですが、Excelを立ち上げていないと使えない点はどうしようもなく、次回はIE等で使えるものも考えたいと思ってます。

また、カレンダーの表示速度が意外に遅く、次月ボタンを12回連続で押しても次年まで行かない現象が出ています(ゆっくり押せばよいのですが)。ScreenUpdatingやら色々工夫をしては見たのですが、ここまでしか行きませんでした。またスピードUP策を思いついたら試してみたいと思います。


西暦・和暦対照表(it-022.xlsm)

セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。