【bat】バッチファイルで処理の所要時間を測定する方法完全ガイド|%time%・ミリ秒精度・日またぎ・複数区間・PowerShell連携まで徹底解説

バッチファイルで処理の所要時間を測定したいとき、Windowsには組み込みの計測コマンドがありません。しかし%time%環境変数と文字列操作を組み合わせると、開始・終了の時刻差から経過時間を計算できます。

本記事では%time%の基本構造から数値への変換方法、日をまたいだ場合の対処、PowerShellによるミリ秒精度の計測、複数区間のラップタイム取得まで、現場で実際に使えるコードとともに解説します。

この記事で分かること

  • %time%の構造(HH:MM:SS.cc)とセンチ秒への変換方法
  • 開始・終了時刻の差分から経過時間を算出する基本パターン
  • 先頭スペース問題・ロケール依存の落とし穴と対処法
  • 日をまたいだ場合(24時間超)のオーバーフロー対処
  • PowerShell Stopwatchによるミリ秒精度の高精度計測
  • 複数区間のラップタイム・分割タイムを記録する方法
  • ログ付き実用テンプレート
スポンサーリンク

%time%の基本構造と数値変換

%time%のフォーマット

%time%は現在時刻を文字列で返します。フォーマットはロケールによって異なりますが、日本語Windowsでは以下の形式です。

フォーマット 注意点
H:MM:SS.cc 9:05:01.23 時が1桁のとき先頭がスペース(例:  9:05:01.23
HH:MM:SS.cc 14:30:00.00 時が2桁のときは通常表示

各部分は文字列スライスで取り出せます。

%time%の各部分の取り出し方
@echo off
rem %time% の例: " 9:05:01.23" または "14:30:00.00"
rem 位置:         0123456789 01

set "HH=%time:~0,2%"   rem 時(先頭スペースの可能性あり)
set "MM=%time:~3,2%"   rem 分
set "SS=%time:~6,2%"   rem 秒
set "CC=%time:~9,2%"   rem センチ秒(1/100秒)

echo 時: [%HH%] 分: [%MM%] 秒: [%SS%] センチ秒: [%CC%]
実行例(コンソール表示)
時: [ 9] 分: [05] 秒: [01] センチ秒: [23]
時: [14] 分: [30] 秒: [00] センチ秒: [00]

センチ秒への変換と差分計算の仕組み

時刻の差分を計算するため、時刻全体をセンチ秒(1/100秒)単位の整数に変換します。センチ秒はset /aで整数演算できるので扱いやすい単位です。

単位 計算式 1日の最大値
センチ秒(cc) HH×360000 + MM×6000 + SS×100 + CC 8,640,000 cs
ミリ秒(ms) センチ秒 × 10(精度は10ms) 86,400,000 ms
%time%の精度はセンチ秒(1/100秒): %time%の最小単位は100分の1秒(10ms相当)です。1ms単位が必要な場合はPowerShellのStopwatchを使います(本記事後半で解説)。

基本パターン:開始から終了までの経過時間を計測する

経過時間の基本計測(秒・分・時を分けて表示)
@echo off
setlocal enabledelayedexpansion

:: ===== 開始時刻を取得 =====
set "T0=%time%"
set "H0=%T0:~0,2%" & set "H0=%H0: =0%"
set "M0=%T0:~3,2%"
set "S0=%T0:~6,2%"
set "C0=%T0:~9,2%"
set /a "START_CS=H0*360000 + M0*6000 + S0*100 + C0"

:: ===== 計測対象の処理 =====
echo 処理中...
ping -n 4 127.0.0.1 >nul

:: ===== 終了時刻を取得 =====
set "T1=%time%"
set "H1=%T1:~0,2%" & set "H1=%H1: =0%"
set "M1=%T1:~3,2%"
set "S1=%T1:~6,2%"
set "C1=%T1:~9,2%"
set /a "END_CS=H1*360000 + M1*6000 + S1*100 + C1"

:: ===== 差分計算 =====
set /a "DIFF_CS=END_CS - START_CS"

:: 日をまたいだ場合の補正(後述)
if !DIFF_CS! lss 0 set /a "DIFF_CS=DIFF_CS + 8640000"

:: ===== 結果を時・分・秒・センチ秒に分解 =====
set /a "EL_H=DIFF_CS / 360000"
set /a "EL_M=DIFF_CS %% 360000 / 6000"
set /a "EL_S=DIFF_CS %% 6000 / 100"
set /a "EL_C=DIFF_CS %% 100"

echo 所要時間: %EL_H%時間 %EL_M%分 %EL_S%秒 %EL_C%センチ秒
実行例(コンソール表示)
処理中...
所要時間: 0時間 0分 3秒 1センチ秒
先頭スペースを必ず除去する: set "H0=%H0: =0%"は時が1桁のときにスペースを0に置換する処理です。これがないとset /a " 9*360000"のように計算が失敗します。

シンプルに使える計測関数パターン

開始・終了の計測コードを毎回書くのは冗長です。サブルーチンとしてまとめると再利用しやすくなります。

計測サブルーチン付きテンプレート
@echo off
setlocal enabledelayedexpansion

:: 計測開始
call :TIME_START

:: ===== 計測対象の処理 =====
echo 処理A実行中...
ping -n 3 127.0.0.1 >nul
echo 処理A完了

:: 計測終了・表示
call :TIME_END "処理A"
exit /b 0

::--------------------------------------------
:TIME_START
set "T_S=%time%"
set "T_SH=%T_S:~0,2%" & set "T_SH=%T_SH: =0%"
set /a "_TS=T_SH*360000 + 1%T_S:~3,2%*60 - 6000 + 1%T_S:~6,2% - 100 + 1%T_S:~9,2% - 100"
exit /b

:TIME_END
set "T_E=%time%"
set "T_EH=%T_E:~0,2%" & set "T_EH=%T_EH: =0%"
set /a "_TE=T_EH*360000 + 1%T_E:~3,2%*60 - 6000 + 1%T_E:~6,2% - 100 + 1%T_E:~9,2% - 100"
set /a "_TD=_TE - _TS"
if !_TD! lss 0 set /a "_TD=_TD + 8640000"
set /a "_TDS=_TD / 100, _TDC=_TD %% 100"
echo [TIME] %~1: %_TDS%.%_TDC% 秒
exit /b
実行例(コンソール表示)
処理A実行中...
処理A完了
[TIME] 処理A: 2.01 秒
1桁の分・秒も正しく計算する方法: 上記の1%T_S:~3,2%*60 - 6000というパターンは「先頭に1をつけて3桁にしてから演算」するテクニックです。05という文字列は105になり、そこから100を引くと正しい5が得られます。これにより先頭ゼロによる8進数解釈を回避しています。

日をまたいだ場合の対処

処理が23:59台に開始して翌日0:00以降に終了すると、差分がマイナスになります。この場合は1日分のセンチ秒(8,640,000)を加算して補正します。

日またぎの補正コード
:: 差分がマイナスなら1日分(8,640,000 cs = 24時間)を加算
set /a "DIFF_CS=END_CS - START_CS"
if %DIFF_CS% lss 0 set /a "DIFF_CS=DIFF_CS + 8640000"

rem 例: 開始 23:59:58.00 → 終了 00:00:03.50
rem   END_CS  =  350 cs
rem   START_CS= 8639800 cs
rem   差分    = 350 - 8639800 = -8639450 (マイナス)
rem   補正後  = -8639450 + 8640000 = 550 cs = 5.50 秒 ✓
2日以上の処理には対応できない: この補正は最大1日分のオーバーフローにしか対応できません。24時間を超える処理の計測が必要な場合は%DATE%も組み合わせるか、後述のPowerShell方式を使ってください。

ミリ秒精度が必要な場合:PowerShell Stopwatchを使う

%time%の精度は10ms単位(センチ秒)が限界です。1ms単位での計測が必要な場合は、PowerShellの[System.Diagnostics.Stopwatch]クラスを活用します。

PowerShell Stopwatchでミリ秒計測
@echo off
setlocal enabledelayedexpansion

:: PowerShell Stopwatch で開始
for /f "usebackq delims=" %%A in (`powershell -NoProfile -Command "[System.Diagnostics.Stopwatch]::GetTimestamp()"`) do set "TS_START=%%A"
for /f "usebackq delims=" %%A in (`powershell -NoProfile -Command "[System.Diagnostics.Stopwatch]::Frequency"`) do set "TS_FREQ=%%A"

:: ===== 計測対象の処理 =====
echo 処理中...
ping -n 2 127.0.0.1 >nul

:: PowerShell で終了時刻を取得し経過ミリ秒を計算
for /f "usebackq delims=" %%A in (`powershell -NoProfile -Command ^"[math]::Round(([System.Diagnostics.Stopwatch]::GetTimestamp() - %TS_START%) * 1000 / %TS_FREQ%)^"`) do set "ELAPSED_MS=%%A"

echo 所要時間: %ELAPSED_MS% ms

より簡潔に書くなら、PowerShellブロック全体で計測させて結果だけ受け取る方法も使えます。

PowerShellでワンライナー計測(シンプル版)
@echo off
rem PowerShell側で計測・処理・結果出力をすべて行う
for /f "usebackq delims=" %%A in (`powershell -NoProfile -Command ^"
    $sw = [System.Diagnostics.Stopwatch]::StartNew();
    Start-Sleep -Milliseconds 1500;
    $sw.Stop();
    Write-Output $sw.ElapsedMilliseconds
^"`) do set "ELAPSED_MS=%%A"

echo 所要時間: %ELAPSED_MS% ms
実行例(コンソール表示)
所要時間: 1502 ms
計測方法 精度 複雑さ 推奨用途
%time%のみ 10ms(センチ秒) 数秒〜数分レベルの処理計測
PowerShell Stopwatch 1ms以下 1秒未満の高精度計測が必要な場合

複数区間のラップタイムを記録する

処理が複数のフェーズに分かれている場合、各区間の所要時間(ラップタイム)と開始からの累計時間を同時に記録すると、ボトルネックの特定に役立ちます。

複数区間のラップタイム計測
@echo off
setlocal enabledelayedexpansion

:: 計測開始
call :TS _TOTAL_START
set "_LAP_PREV=!_TOTAL_START!"

:: ===== フェーズ1 =====
echo [Phase 1] データ取得中...
ping -n 2 127.0.0.1 >nul
call :TS _NOW
call :SHOW_LAP "Phase 1" !_LAP_PREV! !_NOW! !_TOTAL_START!
set "_LAP_PREV=!_NOW!"

:: ===== フェーズ2 =====
echo [Phase 2] データ処理中...
ping -n 4 127.0.0.1 >nul
call :TS _NOW
call :SHOW_LAP "Phase 2" !_LAP_PREV! !_NOW! !_TOTAL_START!
set "_LAP_PREV=!_NOW!"

:: ===== フェーズ3 =====
echo [Phase 3] 結果出力中...
ping -n 2 127.0.0.1 >nul
call :TS _NOW
call :SHOW_LAP "Phase 3" !_LAP_PREV! !_NOW! !_TOTAL_START!

exit /b 0

:TS  varname
set "_T=%time%"
set "_TH=%_T:~0,2%" & set "_TH=%_TH: =0%"
set /a "%~1=_TH*360000 + 1%_T:~3,2%*60 - 6000 + 1%_T:~6,2% - 100 + 1%_T:~9,2% - 100"
exit /b

:SHOW_LAP  label prev_cs now_cs total_start_cs
set /a "_lap=%~3 - %~2"
if !_lap! lss 0 set /a "_lap=!_lap! + 8640000"
set /a "_cum=%~3 - %~4"
if !_cum! lss 0 set /a "_cum=!_cum! + 8640000"
set /a "_ls=!_lap!/100, _lc=!_lap! %% 100"
set /a "_cs=!_cum!/100, _cc=!_cum! %% 100"
echo [LAP] %~1: !_ls!.!_lc! 秒  (累計: !_cs!.!_cc! 秒)
exit /b
実行例(コンソール表示)
[Phase 1] データ取得中...
[LAP] "Phase 1": 1.00 秒  (累計: 1.00 秒)
[Phase 2] データ処理中...
[LAP] "Phase 2": 3.01 秒  (累計: 4.01 秒)
[Phase 3] 結果出力中...
[LAP] "Phase 3": 1.00 秒  (累計: 5.01 秒)

ログファイルへの出力を含む実用テンプレート

定期実行バッチでは所要時間をログファイルに記録しておくと、処理時間の変化(増加傾向など)を後から確認できます。

所要時間をログに記録する実用テンプレート
@echo off
setlocal enabledelayedexpansion

set "LOG=C:\logs\job_timing.log"
set "JOB_NAME=DailyDataExport"

if not exist "C:\logs" mkdir "C:\logs"

:: 開始
set "T0=%time%"
set "_H=%T0:~0,2%" & set "_H=%_H: =0%"
set /a "_TS=_H*360000 + 1%T0:~3,2%*60 - 6000 + 1%T0:~6,2% - 100 + 1%T0:~9,2% - 100"
echo %DATE% %T0% [START] %JOB_NAME% >> "%LOG%"

:: ===== メイン処理 =====
echo 処理実行中...
ping -n 5 127.0.0.1 >nul
set "PROC_STATUS=SUCCESS"
if %ERRORLEVEL% neq 0 set "PROC_STATUS=FAILED"

:: 終了
set "T1=%time%"
set "_H=%T1:~0,2%" & set "_H=%_H: =0%"
set /a "_TE=_H*360000 + 1%T1:~3,2%*60 - 6000 + 1%T1:~6,2% - 100 + 1%T1:~9,2% - 100"
set /a "_TD=_TE - _TS"
if !_TD! lss 0 set /a "_TD=!_TD! + 8640000"
set /a "_S=!_TD!/100, _C=!_TD! %% 100"

echo %DATE% %T1% [END] %JOB_NAME% - %PROC_STATUS% - %_S%.%_C% sec >> "%LOG%"
echo 所要時間: %_S%.%_C% 秒 [%PROC_STATUS%]
ログファイルの出力例
2026/03/21  2:00:00.05 [START] DailyDataExport
2026/03/21  2:00:04.27 [END] DailyDataExport - SUCCESS - 4.22 sec
2026/03/22  2:00:00.10 [START] DailyDataExport
2026/03/22  2:00:05.88 [END] DailyDataExport - SUCCESS - 5.78 sec

日付ファイル名にする方法はバッチファイルで日付・時刻をファイル名に挿入する方法を参考にしてください。より詳細なログ出力の実装はバッチファイルでログ出力する方法完全ガイドで解説しています。

よくある落とし穴と対処法

落とし穴①:先頭スペースで計算がずれる

時が1桁(0〜9時)のとき%time%の先頭はスペース( )になります。スペースをそのままset /aに渡すと計算結果がおかしくなります。

NG: 先頭スペースを除去せずに計算
set "HH=%time:~0,2%"
rem HH が " 9" のとき
set /a CS=HH*360000  rem エラーまたは誤計算
OK: スペースを0に置換してから計算
set "HH=%time:~0,2%"
set "HH=%HH: =0%"   rem " 9" → "09"
set /a CS=HH*360000  rem 正常: 09*360000 = 3240000

落とし穴②:set /aで8進数として解釈されるケース

set /aは先頭に0がつく数値を8進数と解釈します。例えば0809は8進数として無効になりエラーになります。

NG: 先頭ゼロ付き数値をそのまま使う
set "SS=08"
set /a VAL=SS   rem エラー: 08 は無効な8進数
OK: 1を先頭に付けてから100を引く(「1プレフィックス法」)
set "T=%time%"
rem "1%T:~3,2%" で "1MM" の形にして8進数問題を回避
set /a "MM=1%T:~3,2% - 100"   rem "105" - 100 = 5、"112" - 100 = 12
set /a "SS=1%T:~6,2% - 100"
set /a "CC=1%T:~9,2% - 100"

落とし穴③:ロケールによるフォーマットの違い

英語版Windowsや一部の設定では%time%のフォーマットがHH:MM:SS.ccではなくH:MM:SS.cc AM/PM(12時間表示)になることがあります。海外環境で実行するスクリプトでは注意が必要です。

ロケールを強制して時刻を取得(PowerShell利用)
@echo off
rem PowerShellで24時間形式を強制取得
for /f "usebackq delims=" %%A in (`powershell -NoProfile -Command "[datetime]::Now.ToString('HH:mm:ss.ff')"`) do set "NOW=%%A"
echo 現在時刻(24時間固定): %NOW%

落とし穴④:ループ内でdelayed expansionが必要

forループやifブロック内で時刻変数を更新・参照する場合、setlocal enabledelayedexpansion!VAR!が必要です。%VAR%だとブロック先頭の値が展開されてしまいます。

NG: %VAR% ではループ内で更新されない
@echo off
for /l %%i in (1,1,3) do (
    set "T=%time%"
    echo %T%   rem 3回とも同じ時刻になる
)
OK: !VAR! で都度展開する
@echo off
setlocal enabledelayedexpansion
for /l %%i in (1,1,3) do (
    set "T=%time%"
    echo !T!   rem 都度最新の時刻が表示される
)

関連記事

よくある質問

Q. バッチファイルで時間を測ると毎回結果が変わります。精度に問題がありますか?
A. %time%の精度はセンチ秒(1/100秒)ですが、実際の読み取りタイミングによって数センチ秒の誤差が生じます。またOSのスケジューリングやpingコマンドの精度にも依存します。10ms単位で十分な場合は%time%で問題ありません。1ms以下の精度が必要な場合はPowerShellのStopwatchを使ってください。
Q. 日をまたいでも正確に測るにはどうすればよいですか?
A. 本記事の「日またぎ補正」(8,640,000を加算)で最大1日分のオーバーフローに対応できます。24時間を超える処理の場合は%DATE%も組み合わせて日付差を計算するか、PowerShellのStopwatchを使う方が確実です。PowerShellのStopwatchは内部でティックカウンターを使うため日またぎの影響を受けません。
Q. センチ秒を分・秒・ミリ秒の形式で表示したいです。
A. set /aで次のように計算します。
分: DIFF_CS / 6000
秒(端数): DIFF_CS %% 6000 / 100
センチ秒(端数): DIFF_CS %% 100
ミリ秒に変換したい場合はセンチ秒×10で近似値が得られます(精度は10ms)。
Q. タスクスケジューラ経由で実行したとき所要時間が取れません。
A. %time%自体はタスクスケジューラ経由でも正常に取得できます。ただし結果をコンソールに表示するだけでは誰も見られないため、ログファイル(>>リダイレクト)に出力する実装が必要です。本記事の「ログファイルへの出力テンプレート」を参考にしてください。
Q. 処理時間が0秒と表示されることがあります。なぜですか?
A. 処理が非常に高速(1センチ秒未満)の場合は0と表示されます。またforループやifブロック内で%time%を取得して遅延展開(setlocal enabledelayedexpansion)なしに%変数名%で参照すると、ブロック先頭の値が使われて差分が0になる場合があります。!変数名!で参照してください。