処理を一時停止する

Application.WaitかTimerを使用して処理を一時停止するコードとそれに関する注意点

'5秒間一時停止
Application.Wait (DateAdd("s", 5, Now))
Dim 開始時間 As Double
Const 停止時間 As Double = 0.5

'待ち時間の開始時間の取得
開始時間 = Timer
'開始時間から停止時間数値を超えるまで無限ループ
Do Until Timer - 開始時間 >= 停止時間
DoEvents
Loop

処理を一時停止するには「Application.Wait」メソッド「Timer」関数を応用するかのどちらかを使用します

それぞれに利点があるので、解説します

コード解説(Application.Wait使用)

こちらはこの処理を一時停止させることを目的としたメソッドです
引数が必須で1つ、指定するのは時刻データ(Time)です

時刻データの引数になるので、日付データは含まれないため日をまたぐ場合は別途処理が必要になりますが、一時停止という観点からそれはほぼないと思います

そして、引数の時間に達したときにこの関数は「True」を返すとともに処理を開始します
つまり、どれだけ停止させるというよりは、いつ再開させるという考え方です

目的のメソッドであるため、1行で実装できる点が便利です
ただ、この停止時間中はExcelの操作は一切行えない状態になります
バックグラウンド処理は実行されるようですが、Excelはフリーズしたような印象を受ける形になります
その認識の無い人が遭遇した場合、タスクキルしてしまうかもしれません

その辺の注意点さえ気にしていれば、停止処理はこれで十分です

コード解説(Timer関数使用)

Timer関数を使用する場合は、もともと目的の関数ではないので少し煩雑になります

「Dim 開始時間 As Double」
「Const 停止時間 As Double = 0.5」
1行目は開始時間を代入する変数です
2行目は定数を使用して停止時間を指定する箇所で、秒数を指定します
ここでは「0.5」秒が指定されます

「開始時間 = Timer」
停止時間の開始時間を取得しています

「Do Until Timer – 開始時間 >= 停止時間」
「DoEvents」
「Loop」

ループ処理で待機させます
1行目での終了条件が差し引き時間秒数が停止時間秒数を超えているかです
2行目は後述します
3行目でループされます

このループは差し引き時間まで無限にループさせる処理です
実際のループ回数は計り知れませんが、その際に2行目のコードを入れることで「Application.Wait」よりも柔軟な使用が可能になります

と、いうのもApplication.Waitの解説にあるように停止中はExcel自体が停止するためフリーズした状態になります
これを回避するのが「DoEvents」関数です
この関数はVBAがOSに処理を一時的に渡す関数です

この関数を使用することにより、Excelの操作が可能になるのでフリーズしたような動作をすることが無くなります

また、もう一つ重要な点として「描画処理」も行われる点にも優位性があります
例えば停止処理を入れつつループ処理を実行する場合に重要です

デジタル時計を表示させる場合は、停止時間を「1」にして1秒ごとに時刻データを送信し続けます
そうすると、ある時点から描画処理が行われなくなり画面がフリーズしたような状態になり、処理終了後に描画処理が行われて最終結果が表示されます
OSに処理を渡すことで、OSは描画処理も実行しますのでここが重要になる場合もあります

それぞれの使い分けとSleep関数について

上記のように利点が異なります

使い分けとしては
簡便なコーディング開始時刻の指定、停止中にExcelの操作を許可しない場合はApplication.Waitを使用
1秒未満の停止時間の指定、Excelの操作を可能描画処理を行う場合はTimer関数を使用

処理を停止する場面はいろいろあると思いますが、停止前と開始後でセルのデータやアクティブブックが入れ替わっていて処理がおかしくなるようなら操作の許可は出来ないわけです
ただ、これに関してはTimer関数のコードからDoEvents関数を削除すれば同じようになります

Application.Waitは停止時間ではなく開始時刻を指定するので、特定の時刻に処理を開始したい場合には優位
Timer関数側ではミリ秒単位の停止時間の指定が可能なので、そこが優位ですね

ただこの2つのコードには大きな問題があります
CPUの消費量が高いことです

Application.Waitメソッドの内部的な動きは分かりませんが、Timer関数では大量にDoループが走っています
タスクマネージャーで確認しましたが、どちらも同じ消費量だったのでこれに関しての優位性はありませんでした

特にDoEvents関数で他の処理も行った場合の影響は大きそうですので、そのあたりは少し頭に入れておいてください

そして、そこでこの問題を解決してくれるのが「Sleep」関数です

これはWinAPI関数の1つです
WinAPIなので、指定の必須の関数宣言を必要としさらにbit環境にも対応しておかないといけないので初心者には少し荷が重いかもしれません

ただ、この関数はその名の通り休ませる関数なのでCPUの消費量は上がりません
WinAPIの理解とCPU占有による弊害が見逃せない状況になったら一度使用を検討してみましょう

そうでなければ、VBA標準コードのみで実装できる記事コードで十分です

処理時間を計測する

Timer関数を使用して、処理時間をミリ秒単位で計測するコード

'タイマーのスタート
Dim 処理開始 As Double
処理開始 = Timer

'処理時間の取得
Debug.Print Timer - 処理開始

処理時間の計測にはTimer関数を使用します

この関数は0時からの経過時間を返す関数で、引数はありません
これは時間を返す関数ですが実際のデータは小数点値を含むSingle型で返されます

ミリ秒までの時間計算ができるため処理時間の計測に向いています(Macでは秒刻みの計測しかできないようです)
ちなみに、Single型で返されますがちゃんとそれ以下の範囲も存在しているため変数の型はDouble型で取得させて計算したほうが精度が高くなります

コード解説

「Dim 処理開始 As Double」
「処理開始 = Timer」

1行目で変数の宣言、上記にもあるように型はDouble型を使用してください
2行目で変数に開始前の時間を代入することで、計測の開始時間とします
この2行目のコードまでに時間計測を行わない処理があれば、その後にこのコードは移動してください

「Debug.Print Timer – 処理開始」
ここで取得していた変数の時間と現在の時間を計算して、差の時間で処理時間を計測することが出来ます
出力はイミディエイト画面に出力されます、メッセージボックスだと処理が停止してそのメッセージを終了させるまでも計測の時間に含まれるので向いていません

処理時間計測の重要性

基本的に処理時間の計算を使う場面は、処理を作成しその処理がどれぐらい時間をかかるかというのを計算するというよりは実際に作った処理の時間が思ったよりも長かったという場合にどこで処理の時間が長くかかっているかを調べる場合に使用します

処理時間の改善に関しては、原因がさまざまあり単純にExcelの画面更新を抑止すればいいとか、再計算を無くせばいいというものではありません
実際計測してみると、画面更新の抑止が意味が無かったりする場合もあります

一定の処理ごとの時間計算をゼロからきっちり計算させる場合は時間計算をした時点で一度変数をリセットする必要があります
これは単純に2行目のコードをもう一度実行するだけでいいです

そこから得た処理の方法と手順に改善の余地がないかをWeb等の情報で検討してみてください、過去に偉人たちがすでに解決してくれていることばかりのはずなので
ただ、ブックを開くなどどうしようもない処理もあるのでその場合はブックを処理前に開いておくとか方法を検討します

また上記にもあるようにメッセージボックスで処理が一時中断するものがある場合は、そこの時間は考慮しないようにうまくこのコードを配置してください

あと、0時からの経過時間なので日をまたぐ計算を行う場合は、一度日をまたぐ前に数値を保持しておく必要があります
そんなに長い時間の計測をするかどうかは分かりませんが・・・

セルを初期化する

Clearメソッドを使用してセルの情報すべてを削除して初期状態にするコード

'A1をクリア
Range("A1").Clear
'アクティブセルをクリア
ActiveCell.Clear
'選択範囲をクリア
Selection.Clear

指定セルの入力内容と書式設定をクリアするには「Clear」メソッドを使用します

セルをクリアする動き
セルをクリアする動き

このコードを使用すると、セルの入力内容と書式設定(条件付き書式含む)などの設定全てがクリアされます
ようはセルを初期化する状態になります

セルを削除すると、フォーマットが崩れたりする可能性がある場合にはこちらでセルを初期化してあげると良いです

このコードは上記の通り初期化レベルのコードなので、見た目だけでなくコメントやセルの詳細設定も初期化されます
セルのロック状態まで初期化される点には注意が必要かもしれません

セルの書式設定をクリア

ClearFormatsメソッドを使用して、セルの書式設定をクリアするコード

'A1の書式をクリア
Range("A1").ClearFormats
'アクティブセルの書式をクリア
ActiveCell.ClearFormats
'選択範囲の書式をクリア
Selection.ClearFormats

指定のセルの書式設定をクリアするには「ClearFormats」メソッドを使用します

セルの書式設定をクリアする
セルの書式をクリアする動き

セルの書式設定は、見た目の設定です
それのみをクリアするので、入力内容はクリアされません

ただ、表示形式で日付データの様に入力内容の見た目が変化している場合には元の入力値にも戻るため、セルの「Text」プロパティを取得している場合は注意が必要です

また、このコードでクリアされるのは条件付き書式もクリアされますので一見なにも起きていないようでもしっかりクリアされています

セルの入力内容をクリア

ClearContentsメソッドを使用して入力内容のみ消去するコード

'A1の入力内容をクリア
Range("A1").ClearContents
'アクティブセルの入力内容をクリア
ActiveCell.ClearContents
'選択範囲の入力内容をクリア
Selection.ClearContents

指定のセルの入力内容をクリアするには「ClearContents」メソッドを使用します

セルの入力値をクリアする動き
セルの入力内容をクリアする動き

このメソッドを実行すると、指定範囲のセルの入力内容が消去されます
対象となるのは、入力内容なので数式や値になります

画像の動きを確認してください
A1セルのクリア処理で、背景色は消えていません
書式設定は残したままで、入力内容のみをクリアする場合に使用するメソッドです

クリア処理の処理時間

'A1の入力内容をクリア
Range("A1") = ""

セルの入力内容をクリアするのにこのコードのように「””」(空白)を代入する事でも実行できます

3つ目のコードのように範囲選択を行っていれば、一括して消去することができますが、選択する動作は処理速度の低下を招きますので範囲が確定している場合は、1つ目のコードのようにアドレスで実行するほうが実行速度は速いです

そして、このクリアと空白の代入では空白の代入の方が処理速度は速かったです
何度か実行したところ空白代入の方が20%ほど速かったです

とはいえ、わかりやすいコーディングのためなら実行速度なんてどうでもいいです
数万回もループするような処理であれば、20%も大きな差がありますがそもそもクリア処理にそんな処理はありえないので、このメソッドを使ったほうが良いと思います

セルに数式を入力

Formulaプロパティを使用してセルに数式を入力するコード、A1・R1C1についても解説

'選択範囲にSUM関数を入力(A1形式)
Selection.Formula = "=SUM(A1:B1)"
'選択範囲にSUM関数を入力(R1C1形式)
Selection.FormulaR1C1 = "=SUM(RC[-3]:RC[-2])"

セルに数式を入力するには「Formula(A1形式)」または「FormulaR1C1(R1C1形式)」のどちらかを使用します

セルに数式を入力する動き
数式を入力する動き(セルの範囲選択は手動)

実際の動きを確認してください
例コードでは、単一のセルではなく選択範囲にまとめて入力しています
これはアクティブセルにコードの関数を入力した後に「Ctrl+Enter」で一括入力した時と同じ動きになります

この関数は同じ行のA列とB列の数値を加算してるだけなので、結果の数値を見てもらうとちゃんと引数のセルアドレスの行方向が更新されていることが分かります
これは1つ入力して、オートフィルをしても同じ結果になります

A1形式とR1C1形式について

例コード2つの使い分けに関しては、括弧書きにあるように引数のセル範囲をどちらの形式で表現するかの違いです
どちらを使用しても結果は同じなので慣れているほうを使ってください

A1形式とはExcelの標準のワークシートの表現方法で、1番左上のセルを「A1」として表現する形式で、絶対参照です

これは人間にやさしい設計で編み出されていますので、アドレスから位置関係がイメージしやすくなっています

また、セルアドレスに「$」を付与すればちゃんとワークシート上の絶対参照になるので必要であれば付与してください

対してR1C1形式は行と列の両方を数値で表現しますので、A1形式でいう「A1」は「1,1」(Rが行数値、Cが列数値)という表現になる形式です
「A2」なら「2,1」になります、行列の位置が逆転します

例コードの「RC[-3]:RC[-2]」は選択範囲の3列左~2列左という指定になるため、こちらは相対参照になります

これはコンピューターにやさしい設計です、数値で全てを処理するコンピューターはA列というものを1列目と認識するために、「A=1」と置き換えてから列数をはじき出すのでA1形式はすこしわずらわしいみたいですよ、きっと

実際処理速度に多少の差はある(R1C1形式が優位)ようですが、まあ気にする程ではないのでやはり慣れたほうを使うのがいいです

ただ、行も列も共に数値で扱うことの出来るR1C1形式はループ処理に非常に向いているので少しは使ってみて慣れておいて損は無いです
A1形式は人間にやさしいので言わずもがなですね

Array関数で配列を扱う

配列を作成するArray関数の使用法と配列に関しての解説

'配列を代入して選択範囲に一括出力
Dim 配列 As Variant
配列 = Array(1, "+", 2, "=", 3)
Selection = 配列

Array関数とは、配列を作成する関数です

関数の書式

引数(太字は必須引数)
(arglist)
戻り値の型 Variant

引数「arglist」にはデータを指定します、例の様に要素を「,」で区切って入力していくことで要素数を増やすことが出来ます

配列について

この配列というのは変数での使用が主になります
オブジェクト変数と同じくらい理解が難しい内容なので、この記事はある程度VBAの理解が出来てからで大丈夫です

配列というのは複数の要素からなるデータ群のことです

Array関数で作成する配列は1次元の配列になります
この次元というのは、1次元が一方向へのデータの集合体となりますので、2次元は2方向へのデータの集合体という認識になります

2次元の配列というのは、要はExcelのワークシートと同じイメージです
Excelのワークシートは縦と横にデータを入力していくことができるデータ群です
なのでArray関数で作るデータというのは、Excelのワークシートの行や列方向へのデータの作成ということになります

Array関数でセル範囲に入力する動き
配列を代入して出力する動き

この画像は配列を代入させて、それを選択範囲に一括で出力しています
コード最後の「Selection = 配列」で一括出力ができます
ここでの配列は5要素を持ち、整数型と文字列型の混在した配列データとなっています
なおArray関数で代入する場合は、要素が全て文字列型で統一されていたとしても代入する変数はVariant型にする必要があります
(その辺の解説はこちらの記事を見てください)

また、上記にもあるようにArray関数で作成するのは1次元配列なので選択範囲が3行あっても1行ごとの内容はすべて同じになっています
そして、配列の要素数以上のセルには「#N/A」が入力されます

そしてこの配列が扱うデータというのは、数値や文字列だけでなくオブジェクトも扱いますので、シートやセルなども配列の要素にすることができます
特にシートの複数選択の際にはArray関数を使用してシートの指定を行います
これに関してはマクロの記録でワークシートの複数選択を行った際に、自動的にArray関数が作成されていることで確認できます
なおこれはシート名の文字列型を配列データとして扱っており、シート自体を配列として取得しているわけではありません

配列を使う理由について

この配列という考え方は、正直なくても処理の作成は全く問題なく行うことができます

シートの複数選択にしても、シートごとに1枚ずつ処理をしていけばいいだけなので配列を使用しなくても処理を作ることは可能です

ですがこの配列というものをあえて使う目的は、配列のデータをメモリ上で操作することが可能になる事です

例えばシートの複数選択に関して言えばシートごとに処理を行う場合はシートを切り替えながら処理をしていく必要があります
それを複数選択で、一括処理を行えばシートの切り替えが必要なくなり処理速度の向上を図れます

画面更新の抑止や再計算を停止する方法での処理速度の向上の方法がありますが、メモリ上でデータを更新し一括でデータを出力することで、これらの処理速度向上の処理を行わずとも圧倒的に速く処理を行うことができるようになります

この圧倒的に速くという言葉通り、二つの方法を使用できる技量があれば選択の余地がないほど処理速度に差があります
記事中程の画像の動きでもそうですが、セル1つ1つに入力するより一括入力を行う方が速いです

VBAの初心者を脱した辺りで、処理速度に興味がいった時点で考えるのがセル選択を減らすことです、セル選択はそれだけで重たい処理になります
その次にこの出力回数を減らすことを考えてみてください
そのためによく使用するのがこのArray関数です

前日・翌日などの日付の計算

DateAdd関数を使用して日付データの計算を行うコード、時刻の計算も行えます

'前日の日付を取得
Debug.Print DateAdd("d", -1, Date)
'翌日の日付を取得
Debug.Print DateAdd("d", 1, Date)

日付データを計算するには「DateAdd」関数を使用します

関数の書式

引数(太字は必須引数)
(interval, number, date)
戻り値の型 variant型(date)

その他の計算範囲は以下になります

'1年後の日付を取得
Debug.Print DateAdd("yyyy", 1, Date)
'1月後の日付を取得
Debug.Print DateAdd("m", 1, Date)
'1週後の日付を取得
Debug.Print DateAdd("ww", 1, Date)
'3時間後の日付を取得
Debug.Print DateAdd("h", 3, Now)
'30分後の日付を取得
Debug.Print DateAdd("n", 30, Now)
'300秒後の日付を取得
Debug.Print DateAdd("s", 300, Now)

関数の解説

1つ目の引数「interval」に計算したい範囲を文字列で指定します
ここはDatePart関数の記事を確認してください
(DatePart関数の記事はこちらから)

3つ目の引数「date」は、計算前の日付データです

2つ目の引数「number」は、引数「date」に計算する数値を指定します
一応小数点数値も指定できますが、整数値に丸め処理が行われるため、使用する意味はありません
ここで「-」を数値に付けることで、減算を行うことが出来ます
付けなければ加算になります

日数であれば、シリアル値は整数値になっているので普通に四則演算で事足りますがその結果が数値として返されるか日付データとして返されるかは計算の状況によります
対して、この関数は結果をdate型で返すので安心設計です

また、時刻データは小数点数値であるため正確に1分や13秒などの時刻を計算するのは困難です

この関数を使用すれば、そこを整数値で計算させることができるので時刻の計算にはまずこの関数を使用するのがいいです

また、時刻の計算に関して例コードではNow関数を使用していますが、Time関数という現在時刻を取得する関数でも計算は可能です
ですが、日付をまたぐ計算を行った場合には日付が初期値が入ってしまい、うまく計算ができない可能性がありますので時刻データではなく日付データで計算したほうが分かり易いと思います
ただ、時刻のみのデータに日付データも付与される形になるのでセルに入力する際は注意が必要です

この関数を使用して計算を行うと上位の日付がしっかり繰り上がっていく点にも利点があります
月は12進法、日にちは28から31進法、時は24進法、分・秒は60進法と、書いてみると日付データの繰り上げがいかに面倒かがよくわかります
(進法とは、その数値で桁上がりをすることをいいます。日常で使う「…8,9,10,11…」の様に9から10で、桁が上がるものを10進法といいます)
もちろん減算時の繰り下げも同様に行われます

日付データから曜日と週数を取得

DatePart関数を使用して曜日の数値と週数を取得するコード、週数に関してのバグに関する注意

'本日の曜日数値を取得(日曜始点)
Debug.Print DatePart("w", Date)
'本日の曜日数値を取得(月曜始点)
Debug.Print DatePart("w", Date, vbMonday)
'本日の週数を取得
Debug.Print DatePart("ww", Date)
'本日の週数を取得(月曜始点)
Debug.Print DatePart("ww", Date, vbMonday)

週数と曜日数値を取得するには「DatePart」関数引数「interval」でそれぞれ「”w”」と「”ww”」を使用します

関数の書式

引数(太字は必須引数)
(interval, date, firstDayOfWeek, firstWeekOfYear)
戻り値の型 variant型(Integer)

関数の解説

関数の引数の1つ目と2つ目の解説は別の記事で行っています
(解説記事はこちらから)

引数の3つ目「firstDayOfWeek」週の始まりを何曜日にするかの設定です
既定では日曜日となっています

指定する際はVBAの定数を使用すると分かり易いです
ワークシート関数と同じように数値での指定をする場合は、日曜日=1でそこから順番に数値と並んでいますので、もし月曜日を始点にする場合は「2」もしくは「vbMonday」(定数)を指定します

ここに関しては計算する際の環境に合わせて任意に合わせてください
曜日数値を取得する際はここで設定した数値で返されるので、設定次第で返される曜日数値も変化する形になります

次に4つ目の引数「firstWeekOfYear」ですが、これは週数を取得する際に年の最初の週をどこにするかの設定です

まず、指定できる内容が3種類あります(「vbUseSystem」はどれかに当てはまるため割愛します)

「vbFirstJan1」は1月1日が週の始まりになります
「vbFirstFourDays」は4日以上ある週を始まりとします
「vbFirstFullWeek」は全ての曜日がある週を始まりとします

DatePart関数の引数の違いによる取得の違い
実際に取得してみた画像

文章では少しイメージしづらいので、取得したものを画像にしました

B列が1月1日から週の計算が始まる設定です
10行目の1月1日から1週目になっています、その前日は53週目ですね
これが既定の設定になりますので、引数を省略した場合の動きになります
ただ、これは1週間=7日にはならないことに注意が必要です
53週目は3日で、1週目は4日になっています
また2020年の第1週目の日~火曜日は存在しないことになりますので、週で業務を管理する際にはこれではうまくいかない可能性が高いですね

C列は後述します

D列は全ての曜日が含まれる週を第1週とします
その為、既定の始まりである最初の日曜日から週を計算する形になります
年が変わっても、週が変わるわけではないので週管理で考えると非常に分かり易いと思います

ただ、これでは年に対する考え方が全くなくなります
そこで使用するのが、C列の設定です
週の4日以上含む週を第1週目とする計算方法です

「vbFirstFourDays」のバグについて

C列の内容になりますが、ここでバグがあるため要注意です
新年に4日以上ある週を第1週とする場合、12/29(日)が本来であれば1週目1日目となります

画像の赤く塗られたセルの箇所になります
画像を確認してもらうと分かるように、「53」となり次の月曜日が「1」となっています
1月5日を確認して分かるように、ちゃんと週の始点は日曜日に指定されています

このバグはこの関数だけでなくFormat関数でも発生するようなので、そちらでも注意してください

この処理に対する対応策は、条件分岐により53を1に強制的に変更して取得する方法しかないようです

以下のページにこのバグの詳細が記載されています
「Format 関数または DatePart 関数で、先週の最後の月曜日の週番号を返すことができません」-MicrosoftOfficeトラブルシューティング

日付データから和暦を取得

Format関数を使用して、日付データから和暦を取得するコード、一応数種類の元号形式あります

'現在の和暦を取得
Debug.Print Format(Now, "ggge")
'指定日付の和暦を取得
Debug.Print Format(#1/1/2019#, "ggge")

日付データから和暦を取得するには「Format」関数を使用します

関数の書式

引数(太字は必須引数)
(expression, format, firstDayOfWeek, firstWeekOfYear)
戻り値の型 variant型(string)

関数の解説

Format関数は、ワークシート関数のText関数と同じような目的で使用します

引数「expression」変換元のデータを指定します、ここで指定したデータを次の引数で指定した形式に変換して返します

引数「format」が、変換後の表示形式になります
表示形式の指定は、「ggg」で元号を表示して「e」で和暦年数を表示しますのであわせて指定することで、元号と年数を指定することが出来ます

3つ目と4つ目の引数は週の設定のため、今回は使用しません

2つ目の例コードでは、指定日付を指定することで特定の年月日から和暦を取得します、使用例でいうと「平成31」が取得されます

なお、「ggg」の文字数で指定内容が変更されます
下記にコードと一緒に記載しておきますので、必要に合わせて利用してください
基本的には文字数だけの違いなので、トップのものからgを削ってもいいです

'現在の元号を取得
Debug.Print Format(Now, "ggg")

「令和」が取得されます

'現在の元号の1文字目のみ取得
Debug.Print Format(Now, "gg")

「令」が取得されます

'現在の元号のアルファベットを取得
Debug.Print Format(Now, "g")

「R」が取得されます

'現在の和暦年数を取得
Debug.Print Format(Now, "e")

「1」が取得されます

全て記事作成日の2019年12月12日時点で取得される内容です

元年表示について

また、もうあと半月で2019年も終わってしまいますが2019年の間は「令和1年」であり、元号を使用する場合は「令和元年」と「元年」表示をするのが一般的です

ただこれは表示形式の指定がありませんので、日付で条件分岐をさせて直接「1」を「元」に変換する必要があります

VBAであれば、この取得処理の前段階で指定日付を元に表示形式を分岐させればいいのですが、なんせあと半月だけなのでここにこそっとコードは置いておきます

'元年表示の分岐処理
If Date >= #1/1/2020# Then
Debug.Print Format(Date, "ggge")
Else
Debug.Print Format(Date, "ggg元")
End If

2020年の1月1日より前であれば元号の年数は取得せずに「元」表示としているだけです、2020年以前なので2018年もそれ以前も全て元年表示となるので使えるタイミングはそれほどありませんね
ちなみに、このコードは年月日のみを対象としているのでNow関数ではなくDate関数を使用して日付を取得しています

一応表示形式の条件分岐を行って、それぞれの形式に変換する表示形式の書き方がここのWebページにあります
「日本の年号変更と元号」-MicrosoftOffice サポートページ