ボタン1つの打刻で、月末の集計まで終わらせる
- VBAでボタンを押すだけで出勤・退勤の時刻を自動記録できる
- 勤務時間と残業時間をDateDiffで自動計算できる
- 月の日付リストを自動生成し、月末に合計勤務時間を一括集計できる
この勤怠マクロが合うチーム・合わないチーム
自分がこの勤怠管理マクロを作ったのは、小規模チームの勤怠管理を任されたのがきっかけだった。以下のような場面で特に役に立つ。
- 少人数チーム(5〜20人程度)の勤怠をExcelで管理している:専用の勤怠管理システムを導入するほどではないが、手入力は面倒というケースにぴったり
- 月末の勤務時間集計に毎回30分以上かかっている:1行ずつ「退勤 – 出勤 – 休憩」を計算して、電卓で合計を出す作業から解放される
- 出勤・退勤の入力忘れや計算ミスが頻発している:ボタン1つで Now 関数が現在時刻を記録するので、入力ミスがなくなる
- 毎月の日付リスト作成を手作業でやっている:年月を入力するだけで1日〜末日の日付・曜日を自動生成し、土日もグレーアウトしてくれる
合わないチーム:この場合は勤怠システムを選ぶ
一方で、Excel+VBAでの勤怠管理には明確な限界がある。次に当てはまるなら、無理にマクロで頑張らず勤怠管理システムの導入を検討したほうがいい。
| 状況 | 判断 | 理由 |
|---|---|---|
| 1〜3人、自分の勤務時間を把握したいだけ | 最小版だけで十分 | 日付リストや集計は月1回手作業でも数分で終わる |
| 5〜20人の固定時間制チーム | この記事の実務版 | 打刻・計算・集計の自動化で月末作業がほぼ消える |
| 法的な労働時間記録として使いたい | 勤怠管理システム | 労働時間の把握には客観的な記録(ICカード等)が推奨されており、Excelの自己申告打刻では要件を満たしにくい |
| フレックス・シフト制・夜勤が中心 | システム、または大幅カスタマイズ | コアタイム判定や日付またぎ計算でロジックが一気に複雑になる |
自分の判断基準は「打刻の客観性が問われる場面かどうか」だ。チーム内の工数把握や残業の見える化ならExcelで十分速いが、給与計算や監査の根拠になる記録なら最初からシステムに任せたほうが安全だと考えている。
月末30分の集計が1分になる(Before / After)



Before(手作業):
- 毎朝、出勤時刻をセルに手入力(「9:05」「9:12」…)
- 退勤時にも手入力
- 月末に1行ずつ「退勤 – 出勤 – 休憩」を計算
- 合計を電卓で出す
- 入力忘れや計算ミスが発生
After(自動化):
- 「出勤」ボタンを押す → 現在時刻が出勤セルに自動入力
- 「退勤」ボタンを押す → 現在時刻が退勤セルに自動入力、勤務時間が自動計算
- 月末に「集計」ボタンを押す → 合計勤務時間・残業時間が自動集計
5人ほどのチームの勤怠をExcelで管理していた。毎朝出勤時刻を手入力し、月末に1行ずつ勤務時間を計算。たった5人分でも30分かかり、入力忘れや計算ミスが起きていた。ボタン1つで打刻できるようにしてからは、入力ミスがなくなり、月末の集計も一瞬で終わるようになった。
勤怠管理の手入力は、VBAに任せればボタン1つで終わる。
作り始める前の準備
ファイルのコピーを取ってから
マクロ実行前に、Excelファイルのコピーを別フォルダに保存しておく。特に GenerateDateList は既存データをクリアする機能を持つため、過去月の勤怠が入ったファイルで試すのは避ける。
シート「勤怠」を用意する
勤怠管理表のシート(シート名:「勤怠」)に以下のヘッダーを1行目に入力する。
| A1 | B1 | C1 | D1 | E1 | F1 | G1 | H1 |
|---|---|---|---|---|---|---|---|
| 日付 | 曜日 | 出勤 | 退勤 | 休憩(分) | 勤務時間 | 残業時間 | 備考 |
実務版コードの GenerateDateList を使えば、ヘッダーも含めて自動生成されるため、手動入力は不要。
STEP1:出勤・退勤の打刻を動かしてみる(最小版)
まずは最小構成で動きを確認する。A2に日付、C2に出勤時刻、D2に退勤時刻を記録し、F2に勤務時間を表示する。
'============================================================
' ■ 出勤・退勤の記録と勤務時間の計算(最小版)
' → 今日の行に出勤/退勤時刻を記録し、勤務時間を自動計算
'============================================================
'--- 出勤を記録する ---
Sub ClockIn()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("勤怠")
'--- 今日の日付をA2に、現在時刻をC2に記録 ---
ws.Range("A2").Value = Date
ws.Range("A2").NumberFormat = "yyyy/mm/dd"
ws.Range("B2").Value = WeekdayName(Weekday(Date))
'--- 既に出勤が記録されている場合は上書き確認 ---
If ws.Range("C2").Value <> "" Then
Dim ans As VbMsgBoxResult
ans = MsgBox("出勤時刻が既に記録されています。上書きしますか?", _
vbYesNo + vbQuestion)
If ans = vbNo Then Exit Sub
End If
ws.Range("C2").Value = Now
ws.Range("C2").NumberFormat = "hh:mm"
MsgBox "出勤を記録しました: " & Format(Now, "hh:mm"), vbInformation
End Sub
'--- 退勤を記録して勤務時間を計算する ---
Sub ClockOut()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("勤怠")
'--- 出勤が未記録なら警告 ---
If ws.Range("C2").Value = "" Then
MsgBox "出勤時刻が記録されていません。先に出勤を記録してください。", vbExclamation
Exit Sub
End If
'--- 退勤時刻を記録 ---
ws.Range("D2").Value = Now
ws.Range("D2").NumberFormat = "hh:mm"
'--- 勤務時間を計算(分単位で取得し、時:分に変換)---
Dim startTime As Date
Dim endTime As Date
startTime = ws.Range("C2").Value
endTime = ws.Range("D2").Value
Dim workMinutes As Long
workMinutes = DateDiff("n", startTime, endTime)
'--- 休憩60分を引く ---
Dim breakMinutes As Long
breakMinutes = 60
ws.Range("E2").Value = breakMinutes
workMinutes = workMinutes - breakMinutes
If workMinutes < 0 Then workMinutes = 0
'--- 勤務時間をセルに記録 ---
ws.Range("F2").Value = Int(workMinutes / 60) & ":" & _
Format(workMinutes Mod 60, "00")
MsgBox "退勤を記録しました: " & Format(Now, "hh:mm") & vbCrLf & _
"勤務時間: " & ws.Range("F2").Value, vbInformation
End Sub
最小版で書き換える3カ所
| 変数・箇所 | 説明 | 初期値 |
|---|---|---|
Sheets("勤怠") |
シート名。自分のシート名に合わせる | "勤怠" |
breakMinutes = 60 |
休憩時間(分)。自社ルールに合わせる | 60 |
Range("C2") / Range("D2") |
出勤/退勤を記録するセル | C2 / D2 |
最小版の動き
- ClockIn:
Nowで現在時刻を取得し、出勤セル(C2)に記録。既に記録がある場合は上書き確認 - ClockOut: 退勤セル(D2)に現在時刻を記録し、
DateDiff("n", 出勤, 退勤)で勤務時間を分単位で計算 - 休憩時間を引いて、時:分の形式でF2に出力
最小版コードの詳細解説
ClockIn の仕組み
Now はVBAの組み込み関数で、PCの現在日時(日付+時刻)を返す。Date は日付のみを返すので、ここでは時刻も必要なため Now を使っている。NumberFormat = "hh:mm" を設定しないとExcelのシリアル値(例:0.375)が表示されてしまうので、必ずセットする。
上書き確認の MsgBox(..., vbYesNo + vbQuestion) は、誤ってボタンを二度押ししたときのガード。勤怠データは後から修正しづらいので、この確認ダイアログがあるだけで「うっかり上書き」を防げる。
ClockOut の勤務時間計算
DateDiff("n", startTime, endTime) がこのマクロの核心部分。"n" は「分(miNute)」を意味する引数で、2つの時刻の差を分単位の整数で返してくれる。時間単位("h")だと端数が切り捨てられてしまうため、分単位で計算してから後で時:分に変換するのがコツ。
Int(workMinutes / 60) で時間の部分を、workMinutes Mod 60 で分の部分を取り出し、"8:30" のような文字列としてセルに書き込んでいる。この方式だとExcelのSUM関数では合計できないが、実務版の MonthlySummary マクロが文字列を分に再変換して集計するので問題ない。
重要: セルの書式が「標準」のままだと時刻がシリアル値(小数)で表示される。コード内で NumberFormat = "hh:mm" を設定しているが、手動で書式変更する場合は セルの書式をVBAで変更する方法 を参照。
なお、最小版は記録先をA2〜F2の1行に固定している。毎日打刻すると前日の記録が上書きされてしまうため、あくまで「NowとDateDiffの動きを理解する」ための試運転用と考えてほしい。日付ごとに行を分けて1ヶ月分を記録するには、次の実務版で「今日の日付の行を探して記録する」仕組みに切り替える。この違いが理解できていれば、実務版のコードが長くても構造は追いやすいはずだ。
STEP2:日付リスト生成から月末集計まで(実務版)
実務では1ヶ月分の日付リストが必要。日付の自動生成、土日のグレーアウト、打刻、勤務時間・残業時間の計算、月末集計をまとめて実装する。
日付リストの自動生成では DateSerial を使う。日付の差分計算の詳しい仕組みは 2つの日付の差分・加算・比較を計算する方法 で解説している。
検証環境: Microsoft 365(Windows 11)で、5人チーム×1ヶ月(31日分)の運用を1年以上続けて動作確認している。日付リストの生成も月末集計も体感は一瞬(1秒未満)で、この規模なら速度を気にする必要はない。
'============================================================
' ■ 勤怠管理表の自動化(実務版)
' → 日付リスト生成 / 出勤・退勤打刻 / 勤務時間・残業時間計算 / 月末集計
' → 土日はグレーアウト(祝日は対象外)
'============================================================
'--- 定数 ---
Const SHEET_NAME As String = "勤怠"
Const DATA_START_ROW As Long = 2
Const BREAK_MINUTES As Long = 60 ' 休憩時間(分)
Const STANDARD_MINUTES As Long = 480 ' 所定労働時間(分)= 8時間
'============================================================
' 日付リストを自動生成する(月初〜月末)
'============================================================
Sub GenerateDateList()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(SHEET_NAME)
'--- 対象年月を取得(InputBoxで指定) ---
Dim targetYM As String
targetYM = InputBox("勤怠表を作成する年月を入力してください(例: 2026/03)", _
"年月の指定", Format(Date, "yyyy/mm"))
If targetYM = "" Then Exit Sub
'--- 年と月を分割 ---
Dim targetYear As Integer
Dim targetMonth As Integer
targetYear = CInt(Left(targetYM, 4))
targetMonth = CInt(Right(targetYM, 2))
'--- 月末日を取得(翌月の0日 = 今月の末日) ---
' ※ 12月でも Month+1=13 → DateSerial が自動で繰り上げるため正常動作
Dim lastDay As Date
lastDay = DateSerial(targetYear, targetMonth + 1, 0)
Dim totalDays As Integer
totalDays = Day(lastDay)
'--- ヘッダーを設定 ---
ws.Range("A1:H1").Value = Array("日付", "曜日", "出勤", "退勤", _
"休憩(分)", "勤務時間", "残業時間", "備考")
ws.Range("A1:H1").Font.Bold = True
'--- 既存データをクリア ---
If ws.Cells(DATA_START_ROW, 1).Value <> "" Then
Dim ans As VbMsgBoxResult
ans = MsgBox("既存のデータをクリアして新しい勤怠表を作成しますか?", _
vbYesNo + vbQuestion)
If ans = vbNo Then Exit Sub
End If
ws.Range("A" & DATA_START_ROW & ":H" & DATA_START_ROW + 40).ClearContents
ws.Range("A" & DATA_START_ROW & ":H" & DATA_START_ROW + 40).Interior.ColorIndex = xlNone
'--- 日付リストを生成 ---
Dim i As Long
Dim targetDate As Date
Dim wDay As Integer
For i = 1 To totalDays
targetDate = DateSerial(targetYear, targetMonth, i)
Dim r As Long
r = DATA_START_ROW + i - 1
ws.Cells(r, 1).Value = targetDate
ws.Cells(r, 1).NumberFormat = "yyyy/mm/dd"
ws.Cells(r, 2).Value = WeekdayName(Weekday(targetDate))
'--- 休憩時間のデフォルト値 ---
ws.Cells(r, 5).Value = BREAK_MINUTES
'--- 土日判定でグレーアウト ---
wDay = Weekday(targetDate, vbMonday)
If wDay >= 6 Then
ws.Range("A" & r & ":H" & r).Interior.Color = RGB(220, 220, 220)
ws.Cells(r, 8).Value = IIf(wDay = 6, "土曜", "日曜")
End If
Next i
'--- 集計行を追加 ---
Dim summaryRow As Long
summaryRow = DATA_START_ROW + totalDays + 1
ws.Cells(summaryRow, 1).Value = "合計"
ws.Cells(summaryRow, 1).Font.Bold = True
'--- 列幅を自動調整 ---
ws.Columns("A:H").AutoFit
MsgBox targetYear & "年" & targetMonth & "月の勤怠表を作成しました。" & vbCrLf & _
"日数: " & totalDays & "日", vbInformation
End Sub
'============================================================
' 出勤を打刻する
'============================================================
Sub StampClockIn()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(SHEET_NAME)
'--- 今日の行を探す ---
Dim targetRow As Long
targetRow = FindTodayRow(ws)
If targetRow = 0 Then
MsgBox "今日の日付が勤怠表に見つかりません。" & vbCrLf & _
"先に「日付リスト生成」を実行してください。", vbExclamation
Exit Sub
End If
'--- 土日チェック ---
Dim wDay As Integer
wDay = Weekday(Date, vbMonday)
If wDay >= 6 Then
Dim confirmHoliday As VbMsgBoxResult
confirmHoliday = MsgBox("今日は" & WeekdayName(Weekday(Date)) & "です。" & vbCrLf & _
"休日出勤として記録しますか?", vbYesNo + vbQuestion)
If confirmHoliday = vbNo Then Exit Sub
End If
'--- 上書き確認 ---
If ws.Cells(targetRow, 3).Value <> "" Then
Dim confirmOverwrite As VbMsgBoxResult
confirmOverwrite = MsgBox("出勤時刻が既に記録されています(" & _
Format(ws.Cells(targetRow, 3).Value, "hh:mm") & ")。" & vbCrLf & _
"上書きしますか?", vbYesNo + vbQuestion)
If confirmOverwrite = vbNo Then Exit Sub
End If
'--- 出勤時刻を記録 ---
ws.Cells(targetRow, 3).Value = Now
ws.Cells(targetRow, 3).NumberFormat = "hh:mm"
MsgBox "出勤を記録しました: " & Format(Now, "hh:mm"), vbInformation
End Sub
'============================================================
' 退勤を打刻して勤務時間を計算する
'============================================================
Sub StampClockOut()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(SHEET_NAME)
'--- 今日の行を探す ---
Dim targetRow As Long
targetRow = FindTodayRow(ws)
If targetRow = 0 Then
MsgBox "今日の日付が勤怠表に見つかりません。" & vbCrLf & _
"先に「日付リスト生成」を実行してください。", vbExclamation
Exit Sub
End If
'--- 出勤未記録チェック ---
If ws.Cells(targetRow, 3).Value = "" Then
MsgBox "出勤時刻が記録されていません。先に出勤を記録してください。", vbExclamation
Exit Sub
End If
'--- 退勤時刻を記録 ---
ws.Cells(targetRow, 4).Value = Now
ws.Cells(targetRow, 4).NumberFormat = "hh:mm"
'--- 勤務時間を計算 ---
Dim startTime As Date
Dim endTime As Date
startTime = ws.Cells(targetRow, 3).Value
endTime = ws.Cells(targetRow, 4).Value
Dim workMinutes As Long
workMinutes = DateDiff("n", startTime, endTime)
'--- 休憩時間を引く ---
Dim breakMin As Long
breakMin = ws.Cells(targetRow, 5).Value
If breakMin = 0 Then breakMin = BREAK_MINUTES
workMinutes = workMinutes - breakMin
If workMinutes < 0 Then workMinutes = 0
'--- 勤務時間をセルに記録 ---
ws.Cells(targetRow, 6).Value = Int(workMinutes / 60) & ":" & _
Format(workMinutes Mod 60, "00")
'--- 残業時間を計算 ---
Dim overtimeMinutes As Long
overtimeMinutes = workMinutes - STANDARD_MINUTES
If overtimeMinutes < 0 Then overtimeMinutes = 0
If overtimeMinutes > 0 Then
ws.Cells(targetRow, 7).Value = Int(overtimeMinutes / 60) & ":" & _
Format(overtimeMinutes Mod 60, "00")
Else
ws.Cells(targetRow, 7).Value = "0:00"
End If
MsgBox "退勤を記録しました: " & Format(Now, "hh:mm") & vbCrLf & _
"勤務時間: " & ws.Cells(targetRow, 6).Value & vbCrLf & _
"残業時間: " & ws.Cells(targetRow, 7).Value, vbInformation
End Sub
'============================================================
' 月末集計(合計勤務時間・合計残業時間を計算)
'============================================================
Sub MonthlySummary()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(SHEET_NAME)
'--- データの最終行を取得 ---
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
'--- 合計行を探す ---
Dim summaryRow As Long
Dim i As Long
For i = DATA_START_ROW To lastRow
If ws.Cells(i, 1).Value = "合計" Then
summaryRow = i
Exit For
End If
Next i
If summaryRow = 0 Then
summaryRow = lastRow + 2
ws.Cells(summaryRow, 1).Value = "合計"
ws.Cells(summaryRow, 1).Font.Bold = True
End If
'--- 勤務時間・残業時間を分単位で集計 ---
Dim totalWorkMinutes As Long
Dim totalOvertimeMinutes As Long
Dim workDays As Long
totalWorkMinutes = 0
totalOvertimeMinutes = 0
workDays = 0
For i = DATA_START_ROW To summaryRow - 1
'--- 勤務時間の文字列("8:00" 形式)を分に変換 ---
If ws.Cells(i, 6).Value <> "" And ws.Cells(i, 6).Value <> "0:00" Then
Dim parts() As String
parts = Split(CStr(ws.Cells(i, 6).Value), ":")
If UBound(parts) >= 1 Then
totalWorkMinutes = totalWorkMinutes + CInt(parts(0)) * 60 + CInt(parts(1))
workDays = workDays + 1
End If
End If
'--- 残業時間を集計 ---
If ws.Cells(i, 7).Value <> "" And ws.Cells(i, 7).Value <> "0:00" Then
Dim oParts() As String
oParts = Split(CStr(ws.Cells(i, 7).Value), ":")
If UBound(oParts) >= 1 Then
totalOvertimeMinutes = totalOvertimeMinutes + CInt(oParts(0)) * 60 + CInt(oParts(1))
End If
End If
Next i
'--- 合計をセルに記録 ---
ws.Cells(summaryRow, 6).Value = Int(totalWorkMinutes / 60) & ":" & _
Format(totalWorkMinutes Mod 60, "00")
ws.Cells(summaryRow, 7).Value = Int(totalOvertimeMinutes / 60) & ":" & _
Format(totalOvertimeMinutes Mod 60, "00")
ws.Cells(summaryRow, 8).Value = "出勤日数: " & workDays & "日"
ws.Range("A" & summaryRow & ":H" & summaryRow).Font.Bold = True
MsgBox "月次集計が完了しました。" & vbCrLf & _
"合計勤務時間: " & ws.Cells(summaryRow, 6).Value & vbCrLf & _
"合計残業時間: " & ws.Cells(summaryRow, 7).Value & vbCrLf & _
"出勤日数: " & workDays & "日", vbInformation
End Sub
'============================================================
' 今日の日付に該当する行を探す(ヘルパー関数)
'============================================================
Function FindTodayRow(ws As Worksheet) As Long
Dim i As Long
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For i = DATA_START_ROW To lastRow
If IsDate(ws.Cells(i, 1).Value) Then
If CDate(ws.Cells(i, 1).Value) = Date Then
FindTodayRow = i
Exit Function
End If
End If
Next i
FindTodayRow = 0
End Function
自社ルールに合わせる4つの定数
| 変数・箇所 | 説明 | 初期値 |
|---|---|---|
SHEET_NAME |
シート名 | "勤怠" |
BREAK_MINUTES |
休憩時間(分) | 60 |
STANDARD_MINUTES |
所定労働時間(分)。8時間=480分 | 480 |
DATA_START_ROW |
データ開始行 | 2(1行目はヘッダー) |
実務版の5つのマクロの役割
- GenerateDateList: 指定した年月の日付リストを自動生成。
DateSerial(年, 月+1, 0)で月末日を取得し、1日から末日までをループで出力。土日の行をグレーアウトし、休憩時間のデフォルト値を設定 - StampClockIn:
Nowで現在時刻を取得し、今日の行の出勤セルに記録。土日なら休日出勤の確認、既存記録があれば上書き確認 - StampClockOut: 退勤時刻を記録し、
DateDiff("n", 出勤, 退勤)で勤務時間を分単位で計算。休憩を引いて勤務時間と残業時間を算出 - MonthlySummary: 全行の勤務時間・残業時間を分に変換して合計し、集計行に出力。出勤日数もカウント
- FindTodayRow: 今日の日付に一致する行を検索するヘルパー関数
実務版コードの詳細解説
GenerateDateList では DateSerial(targetYear, targetMonth + 1, 0) で月末日を取得しているが、この「翌月の0日 = 今月の末日」というテクニックは VBA の日付処理では定番中の定番。2月のうるう年判定もこれだけで自動対応できるので、自分で日数を計算する必要がない。
StampClockIn と StampClockOut では、処理の最初に FindTodayRow を呼んで今日の行を探している。この関数を別プロシージャとして切り出しているのがポイント。出勤と退勤の両方で同じ「今日の行を探す」処理が必要なので、重複コードを避けるためにヘルパー関数にしている。こうしておくと、将来「日付の比較ロジックを変えたい」となったときに1箇所だけ直せば済む。
MonthlySummary では勤務時間を「8:00」のような文字列から分に変換して合計している。なぜ文字列で保存しているかというと、Excel のシリアル値で時間を扱うと24時間を超えたときに表示がおかしくなることがあるため。「時:分」の文字列形式なら「176:30」のように24時間超でも正しく表示できる。ただしSUM関数では合計できないので、集計はマクロで行う設計にしている。
土日の判定には Weekday(日付, vbMonday) を使っている。第2引数に vbMonday を指定すると月曜=1, 火曜=2, …, 土曜=6, 日曜=7 になるため、wDay >= 6 で土日を判定できる。日付・曜日の判定の詳しい仕組みは 日付・曜日の判定で月末処理を自動化する方法 を参照。
月次集計のあとの検算:出勤日数×8時間と見比べる
集計マクロを信じて終わりにせず、自分は毎月1つだけ検算をしている。合計勤務時間が「出勤日数 × 所定8時間 + 合計残業時間」とだいたい一致しているかを見るだけだ。たとえば出勤20日・残業10時間なら、合計勤務時間は170時間前後になるはず。ここが大きくずれるときは、ほぼ確実に打刻漏れの行(出勤だけ記録されて退勤が空欄の行)が混ざっている。退勤が空欄の行は勤務時間が計算されないため、合計が不自然に少なくなる。月の途中でも週1回この目視チェックをしておくと、月末に「先週の火曜の退勤時刻、覚えてる?」と聞いて回る事態を防げる。慣れれば1人あたり10秒で終わる検算なので、集計ボタンとセットの習慣にしてしまうのがいい。
打刻ボタンをシートに置いて完成させる
コードを貼っただけでは、毎回 Alt + F8 でマクロ名を選ぶことになり、チームメンバーには使ってもらえない。シート上にボタンを3つ置いて仕上げる。
- リボンの「開発」タブ →「挿入」→ フォームコントロールの「ボタン」を選び、シートの空きスペースにドラッグで配置する(開発タブが無い場合は「ファイル」→「オプション」→「リボンのユーザー設定」で有効化)
- 配置した瞬間に「マクロの登録」ダイアログが出るので、1つ目のボタンに
StampClockInを割り当てる - 同じ手順でボタンをあと2つ作り、
StampClockOutとMonthlySummaryを割り当てる。ボタンの表示文字は「出勤」「退勤」「月末集計」に変えておく
ボタンの位置は1行目より上ではなく、表の右横(J列あたり)に置くのがおすすめ。GenerateDateList が表領域(A〜H列)をクリアしても、ボタンの位置に影響しないからだ。詳しい設置方法やデザインの整え方は マクロをボタン1つで実行する方法 にまとめている。
導入した週は、念のため紙のメモとダブルで記録してもらい、週末に突き合わせてから完全移行した。ツールの信頼を最初に作っておくと、その後の定着が早い。
5人チームで1年運用して決めた3つのルール
コードが動くことと、チームで問題なく回ることは別物だった。運用初月にいくつかトラブルが出て、結果として次の3つのルールに落ち着いた。同じ形で導入する人は最初から決めておくと揉めない。
- 勤怠ファイルは1人1ファイル:最初は1つのファイルを共有して全員が打刻する形にしたが、誰かが開きっぱなしにすると他の人が保存できない問題が毎日起きた。1人1ファイルに分けてからはゼロ。月末に各ファイルの集計行を1枚のシートに転記するだけなので、集計の手間もほとんど増えない。
- 打刻を手修正したら備考欄に理由を書く:「打刻忘れ・実際は9:00出勤」のように書く。修正自体は悪いことではないが、記録が残っていないと月末に「この時刻、合ってる?」の確認で結局時間を取られる。
- 月初の日付リスト生成は担当を1人決める:各自バラバラに
GenerateDateListを実行すると、実行し忘れた人だけ「今日の行が見つかりません」で打刻できない。月初の朝に担当者が声をかける運用にしてから、問い合わせがなくなった。
シリアル値・マイナス勤務時間:ハマりやすい罠5つ
| # | 落とし穴 | 原因 | 対策 |
|---|---|---|---|
| 1 | 出勤時刻がシリアル値(0.375)で表示される | セルの書式が「標準」のまま | NumberFormat = "hh:mm" を設定する。コード内で対応済みだが、手動で書式変更する場合は セルの書式をVBAで変更する方法 を参照 |
| 2 | 勤務時間がマイナスになる | 退勤時刻が出勤時刻より前(日付をまたぐ深夜勤務など) | コード内で If workMinutes < 0 Then workMinutes = 0 としている。深夜勤務に対応するなら日付込みで計算する必要がある |
| 3 | Now関数の時刻がPCの時計とずれている | PCの時計が不正確 | Windowsの「日付と時刻の設定」→「時刻を自動的に設定する」をオンにする |
| 4 | 勤怠表に今日の行が見つからない | 日付リストが未生成、または別の月の表を開いている | 先に GenerateDateList を実行して当月の日付リストを生成する |
| 5 | 月末集計の合計値がおかしい | 勤務時間が文字列("8:00")で入っているため、SUM関数では合計できない | セルのSUM関数ではなくマクロの MonthlySummary で集計する |
自分もシリアル値の罠にハマった。Now関数で打刻したつもりが、セルに 0.375 と表示されて何時なのか分からず焦った。書式を「hh:mm」に変えたら 9:00 と正しく表示された。Excelの時刻は内部的にシリアル値(0〜1の小数)で管理されている。書式設定さえすれば問題ない。
もう1つ注意しておきたいのが、シート名「勤怠」が存在しない場合のエラー。コード内で ThisWorkbook.Sheets("勤怠") とシート名を指定しているので、シート名が「勤怠管理」や「Sheet1」のままだと「インデックスが有効範囲にありません」というエラーが出る。定数 SHEET_NAME を自分のシート名に合わせるか、シート名を「勤怠」に変更してほしい。シート名の変更方法はシート名の取得・一括変更・存在チェックする方法を参照。
また、複数人で同じファイルを同時に編集している場合に注意。共有ブック機能を使っていると、VBAの動作が不安定になることがある。勤怠ファイルは1人1ファイルにするか、各自が自分のPCからのみ打刻する運用にするのが安全。
VBAで勤怠管理の時刻計算がおかしいときの対処法
「出勤9:00・退勤18:00で計算したのに勤務時間が変な値になる」という場合、原因はセルの表示形式が「標準」のまま、時刻がシリアル値(小数)で表示されていること。Excelは時刻を内部的に0〜1の小数で管理しており、9:00は 0.375、18:00は 0.75 になる。対処法は、セルの書式を "hh:mm" に設定すること。コード内で NumberFormat = "hh:mm" を入れておけば自動的に正しく表示される。自分もこれを知らなくて最初は混乱した。
VBAで勤務時間がマイナスやエラーになるときの対処法
「日付をまたぐ深夜勤務の場合に勤務時間がマイナスになる」という場合、原因は退勤時刻が出勤時刻より小さい値になっていること。たとえば出勤22:00(0.917)・退勤翌6:00(0.25)だと、差がマイナスになる。対処法は、退勤時刻が出勤時刻より小さい場合に1日分(1440分)を加算するロジックを入れること。簡易的にはマイナスを0にリセットする方法もあるが、夜勤対応が必要な場合は日付込みで計算する仕組みに変更するのがベスト。
VBAの勤怠マクロでシートが見つからないエラーの対処法
「マクロを実行したら『インデックスが有効範囲にありません』と出る」という場合、原因はコード内で指定しているシート名と実際のシート名が一致していないこと。コード冒頭の定数 SHEET_NAME が "勤怠" になっているのに、シート名が「勤怠管理」や「Sheet1」のままだとエラーになる。対処法は、定数を自分のシート名に合わせるか、シートのタブをダブルクリックして名前を「勤怠」に変更すること。
よくある質問(打刻ミス・休憩・所定労働時間)
Q1: 打刻を間違えた場合はどうする?
セルの値を直接修正すれば良い。出勤セル(C列)や退勤セル(D列)に正しい時刻を手入力し、退勤マクロをもう一度実行すれば勤務時間が再計算される。
Q2: 休憩時間を変更したい
コード冒頭の定数 BREAK_MINUTES を変更する。例えば45分休憩なら Const BREAK_MINUTES As Long = 45 にする。日によって休憩時間が異なる場合は、E列の値を直接書き換えれば、退勤打刻時にその値が使われる。
Q3: 所定労働時間を7.5時間にしたい
コード冒頭の定数 STANDARD_MINUTES を変更する。7.5時間 = 450分なので Const STANDARD_MINUTES As Long = 450 にする。
Q4: 翌月の勤怠表を事前に作りたい
GenerateDateList を実行すると年月を入力するダイアログが表示される。翌月の年月(例:2026/04)を入力すれば翌月分の日付リストが生成される。
Q5: この勤怠表を法的な勤怠記録として使える?
この勤怠表はExcelでの簡易管理用。法的な労働時間管理には、客観的な記録方法(ICカード、勤怠管理システムなど)が推奨されている。あくまでチーム内の簡易的な勤務時間の把握に使用すること。冒頭の「合うチーム・合わないチーム」の判断表も参考にしてほしい。
Q6: 祝日はグレーアウトされないの?
このコードが自動判定するのは土日だけ。祝日は年によって日付が変わるため、コードだけでは判定できない。対応するなら、別シートに祝日の日付リストを用意しておき、GenerateDateList の中で日付を突き合わせてグレーアウトする方式になる。祝日リストは内閣府が公開しているCSVをベースに年1回更新するのが現実的だ。当面は、祝日に出勤しない運用なら空欄のままでも集計に影響はない。
Q7: 外出が多くて自分のPCで打刻できない日はどうする?
後から手入力すればいい。C列・D列に時刻を直接入力し、退勤マクロを実行すれば勤務時間は同じように計算される。このマクロの計算部分はセルの値を読むだけなので、打刻ボタン経由か手入力かを区別しない。直行直帰が多いメンバーには「翌朝に前日分を手入力し、備考に直行直帰と書く」運用にしていた。手入力が特定の曜日に集中するようなら、その曜日だけ紙の記録を併用するなど、無理にツールに寄せないほうが運用は続く。
Q8: 勤務時間を15分単位で丸めたい
StampClockOut の中で勤務時間を計算した直後に、workMinutes = Int(workMinutes / 15) * 15 の1行を入れると15分単位の切り捨て丸めになる。30分単位なら15を30に変える。ただし、労働時間の一律切り捨ては労務管理上問題になり得るので、丸めルールは必ず自社の就業規則に合わせること。「記録は分単位のまま残し、丸めは集計側で行う」ほうが後から検証できて安全だ。
まとめ:NowとDateDiffだけで勤怠は自動化できる
Nowで現在時刻を取得し、ボタン1つで出勤・退勤を記録できるDateDiff("n", 出勤, 退勤)で勤務時間を分単位で計算できるDateSerialとWeekdayで月の日付リストを自動生成し、土日を判定できる- 月末に集計マクロを実行すれば、合計勤務時間と残業時間が一瞬で出る
- 勤務時間は「時:分」の文字列形式で保存しているので、24時間超でも正しく表示できる
- 集計後は「出勤日数×所定時間+残業」との突き合わせで打刻漏れを検知する
自分のチームではこの勤怠表を導入してから、月末の集計作業が30分から1分に短縮された。入力ミスもほぼゼロになり、「計算が合わない」と月末に残業するストレスから解放された。まずは最小版で動きを確認して、慣れたら実務版に切り替えるのがおすすめ。
集計結果を別ファイルに転記したい場合はセルのデータを別シート・別ブックに転記する方法、勤怠データをCSVで出力して他システムに連携したい場合はCSVファイルを書き出す方法と組み合わせると便利。
勤怠表と組み合わせる関連記事
- マクロをボタン1つで実行する方法 — 出勤・退勤・集計をそれぞれボタンに割り当て
- セルの書式をVBAで変更する方法 — 時刻表示の書式設定
- セルのデータを別シート・別ブックに転記する方法 — 集計結果の転記
- CSVファイルを書き出す方法 — 勤怠データの外部連携
次のステップ:祝日対応と外部連携
- 祝日もグレーアウトしたい → 日付・曜日の判定で月末処理を自動化する方法:祝日リストを別シートに用意し、日付を突き合わせて判定するパターンが追加できる
- 勤怠データをCSV出力したい → CSVファイルを書き出す方法:他の勤怠管理システムやクラウドサービスにデータ連携する際に便利
- 日付や時刻の計算をもっと知りたい → 2つの日付の差分・加算・比較を計算する方法:DateDiff・DateAddの使い方を体系的に整理している
フレックス・シフト制への拡張はご相談を
「フレックスタイム制に対応したい」「シフト制で複数パターンの勤務時間を管理したい」「複数人の勤怠をまとめて管理したい」「勤怠データをCSV出力して他システムに連携したい」など、業務に合わせたカスタマイズが必要な場合は、ココナラで相談できる。
相談時に伝えると話が早い情報:
- Excel のバージョン / OS
- 勤務体系(固定時間 / フレックス / シフト制)
- 管理対象の人数
- 出力したい帳票の形式


コメント