はじめに 「勤務時間計算」は“お金と信頼に直結するロジック”
勤務時間の計算は、
「出勤時刻と退勤時刻の差を取れば終わり」
…では、まったく終わりません。
休憩時間を引く
深夜時間を分ける
所定労働時間と残業時間を分ける
丸め(5分単位・15分単位)をどうするか
こういった“細かいルール”が積み重なって、
最終的に「給与」「残業代」「法令順守」に直結します。
ここでは、まず「一番シンプルな勤務時間計算」から始めて、
そこに「休憩」「丸め」「所定時間と残業の分離」を少しずつ足していく形で、
初心者向けにかみ砕いて説明していきます。
基本の形:出勤と退勤の差から勤務時間を出す
TimeSpan で「差」を表現する
まずは、休憩も何もない、
「出勤〜退勤がそのまま勤務時間」という一番シンプルなケースです。
public static TimeSpan GetWorkingTimeBasic(DateTime clockIn, DateTime clockOut)
{
if (clockOut < clockIn)
{
throw new ArgumentException("退勤時刻は出勤時刻より後である必要があります。");
}
return clockOut - clockIn;
}
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 work = GetWorkingTimeBasic(inTime, outTime);
Console.WriteLine(work); // 09:00:00
Console.WriteLine(work.TotalHours); // 9
Console.WriteLine(work.TotalMinutes); // 540
C#ここでの重要ポイントは、
「勤務時間は TimeSpan で持つ」
という感覚を身につけることです。
DateTime は「いつ」TimeSpan は「どれくらい」
この役割分担を意識しておくと、
勤務時間計算のロジックが整理しやすくなります。
休憩時間を引く:固定休憩のケース
「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 だけが休憩として引かれます。
打刻ベースの休憩:休憩開始・終了時刻がある場合
「休憩ボタンを押す」タイプのシステム
次に、
「休憩開始」「休憩終了」の打刻があるケースを考えます。
この場合は、
「勤務時間 = 出勤〜退勤 − すべての休憩の合計」
という形になります。
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#ここでの重要ポイントは、
「休憩も“時間帯のリスト”として扱い、勤務時間との重なりを合計する」
という構造です。
このパターンを押さえておくと、
休憩が複数あっても、日をまたいでも、柔軟に対応できます。
勤務時間の丸め:5分単位・15分単位など
「1分単位ではなく、キリのいい単位にそろえたい」
実務では、
「勤務時間は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 work = TimeSpan.FromMinutes(482); // 8時間2分
TimeSpan rounded = Floor(work, TimeSpan.FromMinutes(5));
Console.WriteLine(work.TotalMinutes); // 482
Console.WriteLine(rounded.TotalMinutes); // 480 (8時間ちょうど)
C#ここでの重要ポイントは、
「丸めは“最後にまとめて”やる」ことです。
出勤・退勤・休憩の各時刻を丸めてしまうと、
誤差が積み重なってしまいます。
まずは“生の時刻”で勤務時間を計算し、
最後に勤務時間の TimeSpan を丸める、
という順番を守ると、ロジックが安定します。
所定労働時間と残業時間を分ける
「8時間までは通常勤務、それを超えた分は残業」
勤務時間が出せるようになったら、
次のステップは「所定時間」と「残業時間」の分離です。
例えば、
1日の所定労働時間が 8時間 の場合、
勤務時間が 9時間 なら「8時間+1時間残業」と分けたい、というイメージです。
public readonly struct WorkDuration
{
public TimeSpan Regular { get; }
public TimeSpan Overtime { get; }
public WorkDuration(TimeSpan regular, TimeSpan overtime)
{
Regular = regular;
Overtime = overtime;
}
public override string ToString()
=> $"通常: {Regular.TotalHours}h, 残業: {Overtime.TotalHours}h";
}
public static WorkDuration SplitRegularAndOvertime(TimeSpan work, TimeSpan regularLimit)
{
if (work <= regularLimit)
{
return new WorkDuration(work, TimeSpan.Zero);
}
TimeSpan regular = regularLimit;
TimeSpan overtime = work - regularLimit;
return new WorkDuration(regular, overtime);
}
C#使い方の例です。
TimeSpan work = TimeSpan.FromHours(9); // 9時間勤務
TimeSpan regularLimit = TimeSpan.FromHours(8); // 所定8時間
WorkDuration result = SplitRegularAndOvertime(work, regularLimit);
Console.WriteLine(result); // 通常: 8h, 残業: 1h
C#ここでの重要ポイントは、
「勤務時間を“意味のある2つの TimeSpan”に分解する」
という発想です。
この WorkDuration をさらに日次・月次で合計していけば、
「今月の通常時間」「今月の残業時間」を簡単に集計できます。
実務での注意点:ルールを“コードに埋め込む”のではなく“集約する”
あちこちでバラバラに if 文を書かない
勤務時間計算は、
会社ごとの就業規則や契約によってルールが変わります。
固定休憩か、打刻休憩か
丸めは切り捨てか、四捨五入か
所定時間は 7.5時間か、8時間か
深夜時間(22:00〜5:00)をどう扱うか
これらを、画面やバッチの中にバラバラに if 文で書き始めると、
必ずどこかで食い違いが出ます。
だからこそ、
勤務時間計算
休憩計算
丸め
所定・残業の分離
といったロジックは、
専用のユーティリティ(あるいはドメインサービス)の中に集約し、
「勤務時間を計算したいコードは、必ずそこを通る」
という形にしておくのが大事です。
まとめ 「勤務時間計算ユーティリティ」は“時間とお金の境界線をコードに刻むもの」
勤務時間計算は、
単に「退勤−出勤」ではなく、
休憩、丸め、所定時間、残業時間といった“ビジネスルールの塊”です。
押さえておきたいポイントを整理すると、こうなります。
勤務時間は TimeSpan で扱い、「いつ」と「どれくらい」を分けて考える。
休憩は“時間帯”として扱い、勤務時間との“重なり”だけを引く。
丸めは最後に TimeSpan に対して行い、出勤・退勤の時刻自体は丸めない。
所定時間と残業時間は、小さな型(WorkDuration など)に分けて表現すると集計しやすい。
ルールはユーティリティや専用クラスに集約し、プロジェクト全体で同じロジックを使い回す。
ここまで押さえておけば、
「なんとなく時間を引き算している」状態から一歩進んで、
“給与や法令に耐えうる、実務で使える勤務時間計算ユーティリティ”を
自分の C# プロジェクトの中に、落ち着いて組み込めるようになります。
