bat ファイルで日付を扱う方法 (PowerShell 編)

2022年1月9日 2023年11月8日

あらすじ

年月 (YYYYMM 形式) を入力するとその年月のフォルダ名のフォルダを別の場所にコピーして、翌月のフォルダ名のフォルダを作成するといったような内容の bat ファイルがありました。
たとえば「 202201 」と入力すると 202201 という名前のフォルダをコピーして、 202202 というフォルダを作成するという風な感じです。
月次報告用の資料をまとめる業務を半自動化するために作成された bat ファイルなのですが、これが正しく動作しないという現象が発生しました。

202112 と入力したところ 202201 というフォルダが作成されるはずが 202113 というフォルダが作成されてしまったのです。
結論からいうと、「翌月」を算出する処理がコマンドラインから入力された年月に +1 するだけというとっても乱暴な作りだったことが原因でした。

こんな感じでした。

bat
set /P user_input="年月を入力してね: "
set /a next_month=%user_input%+1

202201 と入力すると変数 next_month は 202202 となるのでこの場合は正しく動作します。
ですが、202112 と入力すると変数 next_month は 202113 となってしまいます。202201 とはなりません。

数値 202112 に 1 を足せば 202113 になるのは当然ですよね。
人間がこの数字の羅列を見たら日付に変換して考えますが、コンピュータからしたらただの整数値です。

bat ファイルで日付を扱う方法を考える

プログラミング言語で日付を扱う場合は数値から日付型を生成できますが、bat ファイル単体だと日付型のようなものは使用できません。

日付の処理は bat ファイル単体だと大変なので、PowerShell を使う方法を紹介します。
bat ファイルから PowerShell のスクリプト(ps1 ファイル)を呼び出して bat の処理に戻すというような流れになります。

bat ファイルから ps1 ファイルを実行して戻り値を変数に格納する

bat ファイルから ps1 ファイルを実行するだけなら次のようにすればできます。

bat
powershell -ExecutionPolicy Bypass -File test1.ps1

Powershell からの戻り値を変数に格納するには次のようにします。

bat
rem 変数 data に戻り値を格納
FOR /F "usebackq" %%i IN (`powershell -ExecutionPolicy Bypass -File test1.ps1`) DO SET data=%%i

PowerShell に引数を渡したい場合はファイル名の後ろに追加するだけで OK です。

bat
set /P user_input="何か入力してね: "
FOR /F "usebackq" %%i IN (`powershell -ExecutionPolicy Bypass -File test1.ps1 %user_input%`) DO SET data=%%i

PowerShell でコマンドライン引数を受け取る

PowerShell では予約語 args にコマンドライン引数が格納されます。
第 1 引数は args[0]、第 2 引数は args[1] といった感じで取得できます。

Powershell
Write-Output args[0]

PowerShell で日付の計算をする

まずは文字列を DateTime 構造体に変換します。

Powershell
$dt1 = [DateTime]::ParseExact('202112','yyyyMM', $null)
Write-Output $dt1 # 2021年12月1日 0:00:00

日にちまで指定する場合は次のようにします。

Powershell
$dt2 = [DateTime]::ParseExact('20211231','yyyyMMdd', $null)
Write-Output $dt2 # 2021年12月31日 0:00:00

DateTime 構造体にすることで日付の加減算が簡単にできるようになります。
.NET の DateTime 構造体と同様のメソッドが使用できるようです。
https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=net-6.0#methods

月を加減算するには addMonths メソッドを使用します。

Powershell
$dt1.addMonths(1) # 2022年1月1日 0:00:00
$dt1.addMonths(-1) # 2021年11月1日 0:00:00

$dt2.addMonths(3) # 2022年3月31日 0:00:00
$dt2.addMonths(-3) # 2021年9月30日 0:00:00

PowerShell から bat へ値を返す

bat へ値を返すには return を使うだけです。
ただし、bat で受け取れるのは文字列だけなので少し注意が必要です。

DateTime 構造体をそのまま返すと強制的に文字列に変換されたものが返されます。
「2022年1月1日」みたいな感じの文字列が帰ってくるはずです。

そのため、DateTime 構造体の toString メソッドで必要な形式の文字列に変換します。

Powershell
$dt = [DateTime]::ParseExact('202112','yyyyMM', $null)
return $dt.addMonths(1).toString("yyyyMM")

こうすることで bat 側で 「 202201 」というような形式の文字列が受け取れます。

年月を入力すると翌月を表示する bat ファイルの例

以上を踏まえたうえで bat ファイルと ps1 ファイルを作ってみましょう。

test1.bat
@echo off

set /P user_input="年月を入力してね(YYYYMM): "

FOR /F "usebackq" %%i IN (`powershell -executionpolicy bypass -File test.ps1 %user_input%`) DO SET next_month=%%i

echo %user_input% の翌月は %next_month% だよ。

pause
test1.ps1
try {
  $dt = [DateTime]::ParseExact($args[0],'yyyyMM', $null)
  return $dt.addMonths(1).toString("yyyyMM")
}
catch {
}

test1.bat を実行して確認してみましょう。
正しい形式で年月を入力しないと変数 next_month は空白文字になります。

変数 next_month が空白文字かどうかを判定することで入力チェックの処理を作ることもできるのでぜひ挑戦してみてください。