はじめに 「休憩時間控除」は“働いた時間と、そうでない時間を分ける線引き”
勤務時間の計算で一番よく出てくるのが「休憩時間を引く」という処理です。
ここを雑にやると、「働いていない時間にまで給料を払ってしまう」「逆に未払いになる」など、かなりクリティカルな問題になります。
でも、やること自体はシンプルで、
本質は「勤務時間帯」と「休憩時間帯」の“重なっている部分”を引くだけです。
これを DateTime と TimeSpan でどう表現するかを、順番にかみ砕いていきます。
基本の考え方:勤務時間 − 休憩時間 = 実働時間
まずは「出勤〜退勤」の TimeSpan を作る
一番の土台は、「出勤時刻」と「退勤時刻」の差です。
TimeSpan total = clockOut - clockIn;
C#ここで total は「その日に会社にいた時間」です。
この中から「休憩にあたる部分」を引いたものが、実際に働いた時間(実働時間)になります。
つまり、やりたいことはこうです。
実働時間 = (退勤 − 出勤) − 休憩時間の合計
この「休憩時間の合計」をどう計算するかが、休憩時間控除ユーティリティの中心です。
パターン1:固定休憩(例:12:00〜13:00 は必ず休憩)
「決まった時間帯は必ず休憩」の場合
まずは一番シンプルな、「毎日 12:00〜13:00 は休憩」というパターンです。
この場合、休憩は「時間帯」として表現できます。
public static TimeSpan GetWorkingTimeWithFixedBreak(
DateTime clockIn,
DateTime clockOut,
TimeSpan breakStart,
TimeSpan breakEnd)
{
if (clockOut < clockIn)
throw new ArgumentException("退勤時刻は出勤時刻より後である必要があります。");
TimeSpan total = clockOut - clockIn;
DateTime breakStartDateTime = clockIn.Date + breakStart;
DateTime breakEndDateTime = clockIn.Date + breakEnd;
DateTime overlapStart = clockIn > breakStartDateTime ? clockIn : breakStartDateTime;
DateTime overlapEnd = clockOut < breakEndDateTime ? clockOut : breakEndDateTime;
TimeSpan breakDuration = TimeSpan.Zero;
if (overlapEnd > overlapStart)
{
breakDuration = overlapEnd - overlapStart;
}
return total - breakDuration;
}
C#使い方の例です。
DateTime inTime = new DateTime(2026, 2, 18, 9, 0, 0); // 9:00
DateTime outTime = new DateTime(2026, 2, 18, 18, 0, 0); // 18:00
TimeSpan breakStart = new TimeSpan(12, 0, 0); // 12:00
TimeSpan breakEnd = new TimeSpan(13, 0, 0); // 13:00
TimeSpan work = GetWorkingTimeWithFixedBreak(inTime, outTime, breakStart, breakEnd);
Console.WriteLine(work.TotalHours); // 8
C#ここでの重要ポイントは、「休憩時間帯と勤務時間帯の“重なり”だけを引いている」ことです。
出勤が 11:30、退勤が 12:30 のような場合でも、
重なっている 12:00〜12:30 だけが休憩として控除されます。
パターン2:打刻休憩(休憩開始・終了を打刻する場合)
「休憩ボタンを押す」タイプのシステム
次に、「休憩開始」「休憩終了」を打刻するタイプです。
この場合、休憩は「複数の時間帯のリスト」として扱うのが自然です。
public static TimeSpan GetWorkingTimeWithBreakPunches(
DateTime clockIn,
DateTime clockOut,
IReadOnlyList<(DateTime BreakOut, DateTime BreakIn)> breaks)
{
if (clockOut < clockIn)
throw new ArgumentException("退勤時刻は出勤時刻より後である必要があります。");
TimeSpan total = clockOut - clockIn;
TimeSpan breakTotal = TimeSpan.Zero;
foreach (var b in breaks)
{
if (b.BreakIn < b.BreakOut)
throw new ArgumentException("休憩戻りは休憩開始より後である必要があります。");
DateTime start = b.BreakOut < clockIn ? clockIn : b.BreakOut;
DateTime end = b.BreakIn > clockOut ? clockOut : b.BreakIn;
if (end > start)
{
breakTotal += (end - start);
}
}
return total - breakTotal;
}
C#使い方の例です。
DateTime inTime = new DateTime(2026, 2, 18, 9, 0, 0); // 9:00
DateTime outTime = new DateTime(2026, 2, 18, 18, 0, 0); // 18:00
var breaks = new List<(DateTime, DateTime)>
{
(new DateTime(2026, 2, 18, 12, 0, 0), new DateTime(2026, 2, 18, 13, 0, 0)), // 昼休憩 1h
(new DateTime(2026, 2, 18, 15, 0, 0), new DateTime(2026, 2, 18, 15, 15, 0)) // 小休憩 15m
};
TimeSpan work = GetWorkingTimeWithBreakPunches(inTime, outTime, breaks);
Console.WriteLine(work.TotalHours); // 7.75
C#ここでの重要ポイントは、「休憩を“時間帯の集合”として扱い、勤務時間との重なりを合計して控除する」ことです。
この形にしておくと、休憩が何回あっても、日をまたいでも、同じロジックで処理できます。
パターン3:勤務時間に応じて自動で休憩を控除する
「6時間を超えたら45分、8時間を超えたら1時間」などのルール
就業規則によっては、
「勤務時間が◯時間を超えたら自動的に休憩◯分を控除する」
というルールもあります。
例えば、ざっくりとした例として、
6時間超〜8時間以下 → 45分休憩
8時間超 → 60分休憩
のようなルールを考えてみます。
public static TimeSpan GetAutoBreak(TimeSpan workBeforeBreak)
{
if (workBeforeBreak.TotalHours <= 6)
{
return TimeSpan.Zero;
}
else if (workBeforeBreak.TotalHours <= 8)
{
return TimeSpan.FromMinutes(45);
}
else
{
return TimeSpan.FromMinutes(60);
}
}
public static TimeSpan GetWorkingTimeWithAutoBreak(DateTime clockIn, DateTime clockOut)
{
if (clockOut < clockIn)
throw new ArgumentException("退勤時刻は出勤時刻より後である必要があります。");
TimeSpan total = clockOut - clockIn;
TimeSpan autoBreak = GetAutoBreak(total);
return total - autoBreak;
}
C#使い方の例です。
DateTime inTime = new DateTime(2026, 2, 18, 9, 0, 0);
DateTime outTime = new DateTime(2026, 2, 18, 17, 30, 0); // 8.5時間
TimeSpan work = GetWorkingTimeWithAutoBreak(inTime, outTime);
Console.WriteLine(work.TotalHours); // 7.5 (8.5h - 1h休憩)
C#ここでの重要ポイントは、「休憩時間そのものを打刻しない代わりに、“勤務時間の長さ”から休憩を控除するルールを関数化している」ことです。
この GetAutoBreak の中身が、まさに就業規則そのものになります。
丸めとの組み合わせ:「休憩控除後の時間」を丸める
勤務時間の丸めは“最後にまとめて”行う
多くの勤怠システムでは、
「勤務時間は5分単位」「15分単位」などの丸めルールがあります。
ここでやりがちなのが、
出勤・退勤・休憩の各時刻を先に丸めてしまうことですが、
これは誤差が積み重なりやすく危険です。
おすすめは、
- 出勤・退勤・休憩は“生の時刻”で扱う
- 休憩時間控除まで終わらせて「実働時間の TimeSpan」を出す
- 最後にその
TimeSpanを丸める
という順番です。
public static TimeSpan Floor(TimeSpan value, TimeSpan interval)
{
if (interval <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(interval));
long ticks = value.Ticks / interval.Ticks * interval.Ticks;
return new TimeSpan(ticks);
}
C#使い方のイメージです。
TimeSpan rawWork = GetWorkingTimeWithBreakPunches(inTime, outTime, breaks);
TimeSpan roundedWork = Floor(rawWork, TimeSpan.FromMinutes(5));
C#ここでの重要ポイントは、「控除も丸めも、すべて TimeSpan に対して行う」ことです。DateTime を直接いじり始めると、一気に混乱しやすくなります。
実務での設計ポイント:ルールを“1箇所に閉じ込める”
「休憩控除ロジック」を散らさない
休憩時間控除は、会社ごと・契約ごとにルールが違います。
固定休憩か、打刻休憩か、自動控除か、丸めはどうするか…。
これを画面やバッチの中にバラバラに if 文で書き始めると、
「画面Aと画面Bで計算結果が違う」といった事故が必ず起きます。
だからこそ、
休憩時間帯との重なりを計算するロジック
打刻休憩の合計を出すロジック
勤務時間に応じて自動控除するロジック
控除後の勤務時間を丸めるロジック
といったものを、
専用のユーティリティ(あるいはドメインサービス)に集約し、
「勤務時間を計算したいコードは必ずそこを通る」
という形にしておくのが、とても大事です。
まとめ 「休憩時間控除ユーティリティ」は“働いた時間の輪郭をはっきりさせる道具」
休憩時間控除は、
単に「休憩を引く」ではなく、
「どこからどこまでを“働いた時間”とみなすか」をコードで表現する作業です。
押さえておきたいポイントはこうです。
勤務時間は TimeSpan で扱い、「出勤〜退勤」と「休憩時間帯の重なり」を引き算する。
固定休憩も打刻休憩も、「休憩=時間帯」として扱い、勤務時間との重なりだけを控除する。
勤務時間に応じて自動で休憩を控除する場合は、そのルールを関数として切り出しておく。
丸めは最後に「控除後の実働時間」に対して行い、時刻そのものは丸めない。
休憩控除ロジックはユーティリティに集約し、プロジェクト全体で同じ計算を使い回す。
ここまで整理できれば、
「なんとなく休憩を引いている」状態から抜け出して、
“給与や法令に耐えうる、実務で使える休憩時間控除ユーティリティ”を
自分の C# コードの中に、落ち着いて組み込めるようになります。
