はじめに 「日付加算」は“締切・有効期限・リマインド”の土台になる
業務システムで「◯日後」「◯ヶ月後」「◯時間後」は、
締切、有効期限、リマインド日時、サブスクリプションの更新日など、あらゆるところに出てきます。
C# では DateTime や DateTimeOffset に対して
- 日を足す(AddDays)
- 月を足す(AddMonths)
- 時間・分・秒を足す(AddHours / AddMinutes / AddSeconds)
- 任意の時間量を足す(TimeSpan)
といったメソッドが用意されています。
ここでは、初心者向けに
現在日時からの加算
特定日付からの加算
月加算のクセ(末日の扱い)
業務でよくある「営業日加算」の考え方
を、例題を交えながらかみ砕いて説明します。
基本:DateTime に日・時間を足す
現在日時から「◯日後」「◯時間後」を求める
一番シンプルな形です。
using System;
DateTime now = DateTime.Now;
DateTime threeDaysLater = now.AddDays(3);
DateTime twoHoursLater = now.AddHours(2);
DateTime tenMinutesLater = now.AddMinutes(10);
Console.WriteLine(now); // 2026/02/10 20:00:00 など
Console.WriteLine(threeDaysLater); // 2026/02/13 20:00:00
Console.WriteLine(twoHoursLater); // 2026/02/10 22:00:00
Console.WriteLine(tenMinutesLater);// 2026/02/10 20:10:00
C#AddDays(3) のように、
「元の日時に対して、指定した日数を足した新しい DateTime を返す」イメージです。
元の DateTime は変更されません(DateTime はイミュータブル=不変型)。
常に「新しい値が返ってくる」と覚えておいてください。
マイナスを渡せば「◯日前」「◯時間前」
引数にマイナスを渡すと、逆方向になります。
DateTime fiveDaysAgo = now.AddDays(-5);
DateTime oneHourAgo = now.AddHours(-1);
Console.WriteLine(fiveDaysAgo); // 5日前
Console.WriteLine(oneHourAgo); // 1時間前
C#「◯日前」「◯時間前」も、同じメソッドで書けるのがポイントです。
月加算:AddMonths の“末日ルール”を理解する
「1ヶ月後」は単純な日数加算ではない
「1ヶ月後」は、単純に 30 日足せばいいわけではありません。
月によって日数が違うからです。
C# では、AddMonths が「カレンダー上の月」を意識して計算してくれます。
DateTime d1 = new DateTime(2026, 2, 10); // 2026/02/10
DateTime oneMonthLater = d1.AddMonths(1);
Console.WriteLine(oneMonthLater); // 2026/03/10
C#ここまでは直感通りですが、
重要なのは「末日近辺」の挙動です。
1月31日の1ヶ月後はどうなるか
DateTime d2 = new DateTime(2026, 1, 31); // 2026/01/31
DateTime oneMonthLater2 = d2.AddMonths(1);
Console.WriteLine(oneMonthLater2); // 2026/02/28(うるう年でなければ)
C#2月には31日がないので、
「存在しない日付」になってしまいます。
このとき AddMonths は、
「その月の末日に丸める」というルールで動きます。
つまり、
1月31日 + 1ヶ月 → 2月の末日(28日 or 29日)
3月31日 + 1ヶ月 → 4月30日
のようになります。
この“末日ルール”は、
サブスクリプションの更新日や請求日など、
「毎月◯日」というロジックを書くときにとても重要です。
TimeSpan を使った柔軟な加算
「◯日と◯時間と◯分」をまとめて扱う
AddDays や AddHours を連続で呼んでもいいのですが、
「2日と3時間と15分後」のような複合的な加算には TimeSpan が便利です。
DateTime now = DateTime.Now;
TimeSpan span = new TimeSpan(days: 2, hours: 3, minutes: 15, seconds: 0);
DateTime target = now.Add(span);
Console.WriteLine(now);
Console.WriteLine(target); // 2日と3時間15分後
C#TimeSpan は「時間の長さ」を表す型で、DateTime に対して Add(TimeSpan) で足し算できます。
TimeSpan.FromDays(…) や FromHours(…) などのヘルパーもあります。
TimeSpan span2 = TimeSpan.FromDays(10) + TimeSpan.FromHours(5);
DateTime target2 = now.Add(span2);
C#「期間を変数として持っておきたい」場合は、TimeSpan を使うとコードが整理しやすくなります。
実務ユーティリティとしてのまとめ方
「現在から◯日後/◯ヶ月後」を返すユーティリティ
あちこちで
DateTime.Now.AddDays(3)
DateTime.Now.AddMonths(1)
C#と書いていると、
テストしづらかったり、「ローカルかUTCか」が曖昧になったりします。
そこで、「現在時刻の取得」と「加算」をユーティリティにまとめておくと便利です。
using System;
public static class DateAddUtil
{
public static DateTime NowLocalPlusDays(int days)
=> DateTime.Now.AddDays(days);
public static DateTime NowUtcPlusDays(int days)
=> DateTime.UtcNow.AddDays(days);
public static DateTime NowLocalPlusMonths(int months)
=> DateTime.Now.AddMonths(months);
public static DateTime NowUtcPlusMonths(int months)
=> DateTime.UtcNow.AddMonths(months);
public static DateTime AddBusinessDays(DateTime start, int businessDays)
{
int direction = businessDays >= 0 ? 1 : -1;
int remaining = Math.Abs(businessDays);
DateTime current = start;
while (remaining > 0)
{
current = current.AddDays(direction);
if (current.DayOfWeek != DayOfWeek.Saturday &&
current.DayOfWeek != DayOfWeek.Sunday)
{
remaining--;
}
}
return current;
}
}
C#使い方の例です。
DateTime threeDaysLater = DateAddUtil.NowLocalPlusDays(3);
DateTime oneMonthLaterUtc = DateAddUtil.NowUtcPlusMonths(1);
Console.WriteLine(threeDaysLater);
Console.WriteLine(oneMonthLaterUtc);
// 営業日加算の例:今日から3営業日後
DateTime today = DateTime.Today;
DateTime threeBizDaysLater = DateAddUtil.AddBusinessDays(today, 3);
Console.WriteLine(threeBizDaysLater);
C#ここで AddBusinessDays は、
「土日を飛ばして◯営業日後を求める」簡易版です(祝日は考慮していません)。
こうした「業務ルールを含んだ加算」をユーティリティに閉じ込めておくと、
呼び出し側のコードがとても読みやすくなります。
業務でよくある「営業日加算」の考え方
土日を飛ばすだけでも立派なユーティリティ
先ほどの AddBusinessDays のように、
「土日を除いてカウントする」だけでも、
多くの業務では十分役に立ちます。
例えば、「3営業日以内に返信する」というルールなら、
DateTime deadline = DateAddUtil.AddBusinessDays(DateTime.Today, 3);
C#のように書けます。
祝日も考慮したい場合
祝日も除外したい場合は、
別途「祝日カレンダー」を持っておき、
ループの中で「その日が祝日かどうか」を判定する必要があります。
イメージとしてはこうです。
public static DateTime AddBusinessDaysWithHolidays(
DateTime start,
int businessDays,
Func<DateTime, bool> isHoliday)
{
int direction = businessDays >= 0 ? 1 : -1;
int remaining = Math.Abs(businessDays);
DateTime current = start;
while (remaining > 0)
{
current = current.AddDays(direction);
bool isWeekend =
current.DayOfWeek == DayOfWeek.Saturday ||
current.DayOfWeek == DayOfWeek.Sunday;
if (!isWeekend && !isHoliday(current))
{
remaining--;
}
}
return current;
}
C#isHoliday には、
「その日が祝日なら true を返す関数」を渡します。
祝日ロジック自体は別のユーティリティに切り出しておくと、
テストやメンテナンスがしやすくなります。
UTCとローカルのどちらで加算するかを意識する
内部処理はUTC、表示はローカル、加算はどっち?
日時加算でも、
「UTCで加算するのか」「ローカルで加算するのか」を意識する必要があります。
例えば、
「有効期限は“日本時間で”3日後の23:59まで」といった要件なら、
日本時間(JST)に変換してから加算するほうが自然です。
DateTime utcNow = DateTime.UtcNow;
// まずJSTに変換(例)
TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTime nowJst = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jst);
// 日本時間で3日後の23:59
DateTime expireJst = nowJst.Date.AddDays(3).AddHours(23).AddMinutes(59);
// 保存はUTCに戻してから
DateTime expireUtc = TimeZoneInfo.ConvertTimeToUtc(expireJst, jst);
C#「どの時間軸で加算するか」を間違えると、
サマータイムやタイムゾーンをまたいだときにズレが出ます。
業務要件として、
「どの国・どのタイムゾーンの“◯日後”なのか」を
最初に決めておくことがとても大事です。
まとめ 「日付加算ユーティリティ」は“時間のルールをコードに落とし込む器”
日付加算は、一見シンプルですが、
締切、有効期限、営業日、タイムゾーンなど、
業務ルールがぎゅっと詰まるポイントです。
押さえておきたいのは次のようなことです。
AddDays / AddHours / AddMinutes で、現在や任意の日付から簡単に「◯日後/◯時間後」を求められること。AddMonths は「末日ルール」があり、存在しない日付はその月の末日に丸められること。
複合的な加算には TimeSpan を使うと、期間を変数として扱いやすいこと。
営業日加算のような業務ルールを含む処理は、ユーティリティメソッドに閉じ込めておくと、呼び出し側がすっきりすること。
UTCかローカルか、どのタイムゾーンの“◯日後”なのかを意識して加算しないと、時間のズレが起きること。
ここを押さえておくと、
「なんとなく AddDays している」状態から一歩進んで、
“業務ルールをきちんと反映した、信頼できる日付加算ユーティリティ”を
自分のC#コードの中に組み込めるようになっていきます。
