C# Tips | 日付・時間処理:月末取得

C# C#
スポンサーリンク

はじめに 「月末取得」は“締めと集計のゴール地点”

月初が「スタート」だとしたら、月末は「ゴール」です。
売上の締め、勤怠の締め、請求期間の終わり、サブスクの課金期間の終わり――
どれも「この月の月末はどこか?」が決まっていないと、ロジックが書けません。

C# では、「翌月の月初から1日引く」という考え方で、
どんな月でも安全に月末を求めることができます。
ここをユーティリティとして押さえておくと、月次処理のコードが一気にスッキリします。


基本の考え方:翌月の月初 − 1日 で月末を出す

なぜ「翌月の月初 − 1日」が安全なのか

2月は28日だったり29日だったり、
4月は30日、7月は31日…と、月ごとに日数が違います。
「その月が何日まであるか」を自分で数えるのは現実的ではありません。

そこで使う定番パターンがこれです。

  1. 対象日付の「月初」を求める
  2. そこから AddMonths(1) で「翌月の月初」を求める
  3. そこから AddDays(-1) で1日戻る → それが「その月の月末」

コードにするとこうなります。

using System;

DateTime any = new DateTime(2026, 2, 10); // 2026/02/10

DateTime monthStart     = new DateTime(any.Year, any.Month, 1);
DateTime nextMonthStart = monthStart.AddMonths(1);
DateTime monthEnd       = nextMonthStart.AddDays(-1);

Console.WriteLine(monthStart);     // 2026/02/01
Console.WriteLine(nextMonthStart); // 2026/03/01
Console.WriteLine(monthEnd);       // 2026/02/28(うるう年でなければ)
C#

このやり方なら、
2月・うるう年・30日までの月・31日までの月、すべて正しく扱えます。


ユーティリティメソッドとしての「月末取得」

任意の日付から月末を求めるメソッド

毎回「月初 → 翌月 → 1日戻す」を手書きするのは面倒なので、
素直にユーティリティにしてしまいましょう。

public static class MonthUtil
{
    public static DateTime GetMonthStart(DateTime date)
    {
        return new DateTime(date.Year, date.Month, 1, 0, 0, 0, date.Kind);
    }

    public static DateTime GetMonthEnd(DateTime date)
    {
        DateTime monthStart     = GetMonthStart(date);
        DateTime nextMonthStart = monthStart.AddMonths(1);
        DateTime monthEnd       = nextMonthStart.AddDays(-1);

        return monthEnd;
    }
}
C#

使い方はとても直感的です。

DateTime d = new DateTime(2026, 2, 10, 15, 30, 0, DateTimeKind.Local);

DateTime monthStart = MonthUtil.GetMonthStart(d);
DateTime monthEnd   = MonthUtil.GetMonthEnd(d);

Console.WriteLine(monthStart); // 2026/02/01 0:00:00
Console.WriteLine(monthEnd);   // 2026/02/28 0:00:00
C#

「このメソッドを通せば月末が返ってくる」という約束が、
コード上にハッキリ見えるのが大事です。


「今月」「先月」「来月」の月末を取る

今月の月末

今月の月末は、「今月の月初」から組み立てるときれいに書けます。

public static class MonthUtil
{
    public static DateTime GetThisMonthStartLocal()
    {
        DateTime today = DateTime.Today; // Local, 0:00
        return new DateTime(today.Year, today.Month, 1, 0, 0, 0, DateTimeKind.Local);
    }

    public static DateTime GetThisMonthEndLocal()
    {
        DateTime thisMonthStart  = GetThisMonthStartLocal();
        DateTime nextMonthStart  = thisMonthStart.AddMonths(1);
        DateTime thisMonthEnd    = nextMonthStart.AddDays(-1);
        return thisMonthEnd;
    }
}
C#

使い方はこうです。

DateTime thisMonthEnd = MonthUtil.GetThisMonthEndLocal();
Console.WriteLine(thisMonthEnd); // 2026/02/28 0:00:00 など
C#

先月・来月の月末

「今月の月初」を基準に AddMonths(±1) すれば、
先月・来月の月末も簡単に求められます。

public static class MonthUtil
{
    public static DateTime GetNextMonthEndLocal()
    {
        DateTime thisMonthStart  = GetThisMonthStartLocal();
        DateTime nextMonthStart  = thisMonthStart.AddMonths(1);
        DateTime nextNextStart   = thisMonthStart.AddMonths(2);
        DateTime nextMonthEnd    = nextNextStart.AddDays(-1);
        return nextMonthEnd;
    }

    public static DateTime GetPreviousMonthEndLocal()
    {
        DateTime thisMonthStart  = GetThisMonthStartLocal();
        DateTime prevMonthStart  = thisMonthStart.AddMonths(-1);
        DateTime thisMonthStartAgain = thisMonthStart; // 読みやすさのため
        DateTime prevMonthEnd    = thisMonthStartAgain.AddDays(-1);
        return prevMonthEnd;
    }
}
C#

実際には、
「任意の日付の月末を取る GetMonthEnd」を軸にして、
GetMonthEnd(DateTime.Today.AddMonths(-1)) のように組み合わせてもOKです。


月末と「期間」の組み合わせ(集計・締め処理での使い方)

「今月の期間」を [月初, 月末] で表現する

売上や勤怠などの集計では、
「今月の期間」をひとまとめで扱いたいことが多いです。

例えば、こんな小さな型を用意しておくと便利です。

public readonly struct MonthRange
{
    public DateTime Start { get; }
    public DateTime End   { get; }

    public MonthRange(DateTime start, DateTime end)
    {
        Start = start;
        End   = end;
    }
}

public static class MonthUtil
{
    public static MonthRange GetMonthRange(DateTime any)
    {
        DateTime start = GetMonthStart(any);
        DateTime end   = GetMonthEnd(any);
        return new MonthRange(start, end);
    }
}
C#

使い方の例です。

DateTime target = new DateTime(2026, 2, 10);

MonthRange range = MonthUtil.GetMonthRange(target);

Console.WriteLine($"開始: {range.Start}"); // 2026/02/01
Console.WriteLine($"終了: {range.End}");   // 2026/02/28
C#

この MonthRange をそのまま
「SQLのWHERE条件」「APIのリクエスト」「レポートのヘッダ」などに使えるようにしておくと、
月次処理のコードがかなり整理されます。


UTCとローカル、どの時間軸の月末を扱うか

「どのタイムゾーンの月末か」を最初に決める

月末も、UTCとローカルで意味が変わります。
例えば「日本時間での月末」と「UTCでの月末」は、
タイムゾーンによっては日付がずれることがあります。

日本時間の月末を基準にしたい場合は、
まずUTCから日本時間に変換してから月末を取る、という流れになります。

using System;

public static class MonthUtil
{
    public static DateTime GetThisMonthEndJstFromUtcNow()
    {
        DateTime utcNow = DateTime.UtcNow;

        TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
        DateTime nowJst  = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jst);

        DateTime monthStartJst     = new DateTime(nowJst.Year, nowJst.Month, 1, 0, 0, 0, DateTimeKind.Unspecified);
        DateTime nextMonthStartJst = monthStartJst.AddMonths(1);
        DateTime monthEndJst       = nextMonthStartJst.AddDays(-1);

        return monthEndJst;
    }
}
C#

「どのタイムゾーンの月末か」をユーティリティ名やコメントに明示しておくと、
後から読んだ人が迷わずに済みます。


まとめ 「月末取得ユーティリティ」は“月次ロジックの終点を固定するもの”

月末取得は、
締め処理・集計・請求・サブスクなど、
月単位のロジックの「終点」を決めるための重要なピースです。

押さえておきたいポイントは、次のようなイメージです。

任意の日付の月末は「その月の月初 → 翌月の月初 → 1日戻す」で安全に求められる。
今月・先月・来月の月末は、「今月の月初」を基準に AddMonths(±1)AddDays(-1) を組み合わせるときれいに書ける。
月初と月末をセットにした「月の期間」型(MonthRange など)を用意しておくと、集計や締め処理のコードが読みやすくなる。
UTC基準かローカル基準か(どのタイムゾーンの月末か)を意識し、ユーティリティ名にもそれを反映しておくと安全。

ここを押さえておくと、
「その場しのぎで日付をいじっている」状態から一歩進んで、
“月次処理の始点(月初)と終点(月末)をきれいに定義した、実務で使える日付ユーティリティ”を
自分のC#コードの中に自然に組み込めるようになります。

タイトルとURLをコピーしました