バッチファイルで処理の所要時間を測定したいとき、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桁のときは通常表示 |
各部分は文字列スライスで取り出せます。
@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%の最小単位は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%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 秒 ✓
%DATE%も組み合わせるか、後述のPowerShell方式を使ってください。ミリ秒精度が必要な場合:PowerShell Stopwatchを使う
%time%の精度は10ms単位(センチ秒)が限界です。1ms単位での計測が必要な場合は、PowerShellの[System.Diagnostics.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ブロック全体で計測させて結果だけ受け取る方法も使えます。
@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に渡すと計算結果がおかしくなります。
set "HH=%time:~0,2%" rem HH が " 9" のとき set /a CS=HH*360000 rem エラーまたは誤計算
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進数と解釈します。例えば08や09は8進数として無効になりエラーになります。
set "SS=08" set /a VAL=SS rem エラー: 08 は無効な8進数
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時間表示)になることがあります。海外環境で実行するスクリプトでは注意が必要です。
@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%だとブロック先頭の値が展開されてしまいます。
@echo off
for /l %%i in (1,1,3) do (
set "T=%time%"
echo %T% rem 3回とも同じ時刻になる
)
@echo off
setlocal enabledelayedexpansion
for /l %%i in (1,1,3) do (
set "T=%time%"
echo !T! rem 都度最新の時刻が表示される
)
関連記事
- 【bat】バッチファイルで日付・時刻をファイル名に挿入する方法完全ガイド(%DATE%・%TIME%の詳細・wmic・PowerShellによる取得方法)
- 【bat】バッチファイルでログ出力する方法完全ガイド(タイムスタンプ付きログの実装パターン)
- 【bat】バッチファイルでプログレスバーを表示する方法完全ガイド(処理進捗の可視化)
- 【bat】外部コマンドの結果を変数に格納する方法完全ガイド(for /f でPowerShellの結果を変数に取り込む方法)
よくある質問
%time%の精度はセンチ秒(1/100秒)ですが、実際の読み取りタイミングによって数センチ秒の誤差が生じます。またOSのスケジューリングやpingコマンドの精度にも依存します。10ms単位で十分な場合は%time%で問題ありません。1ms以下の精度が必要な場合はPowerShellのStopwatchを使ってください。%DATE%も組み合わせて日付差を計算するか、PowerShellのStopwatchを使う方が確実です。PowerShellのStopwatchは内部でティックカウンターを使うため日またぎの影響を受けません。set /aで次のように計算します。分:
DIFF_CS / 6000秒(端数):
DIFF_CS %% 6000 / 100センチ秒(端数):
DIFF_CS %% 100ミリ秒に変換したい場合はセンチ秒×10で近似値が得られます(精度は10ms)。
%time%自体はタスクスケジューラ経由でも正常に取得できます。ただし結果をコンソールに表示するだけでは誰も見られないため、ログファイル(>>リダイレクト)に出力する実装が必要です。本記事の「ログファイルへの出力テンプレート」を参考にしてください。%time%を取得して遅延展開(setlocal enabledelayedexpansion)なしに%変数名%で参照すると、ブロック先頭の値が使われて差分が0になる場合があります。!変数名!で参照してください。
