Java Tips | 日付・時間:月初取得

Java Java
スポンサーリンク

「月初取得」は“その月の1日を安全に取り出す”こと

業務システムでは、「今月の月初」「請求月の月初」「締め月の月初」をよく使います。
ここでやってはいけないのは、day = 1; のように自分で日付をいじることです。

正解は、LocalDateTemporalAdjusters を使って、
「その月の1日」を“意味のある名前のメソッド”で取り出すことです。

これを覚えておくと、月次処理・集計・締め処理のコードが一気に読みやすくなります。


LocalDate から月初を取得する基本パターン

dayOfMonth(1) で「その月の1日」にする

一番シンプルな書き方はこれです。

import java.time.LocalDate;

public class MonthStartBasic {

    public static void main(String[] args) {
        LocalDate anyDay = LocalDate.of(2025, 3, 26);

        LocalDate monthStart = anyDay.withDayOfMonth(1);

        System.out.println("任意の日付 : " + anyDay);      // 2025-03-26
        System.out.println("その月の月初: " + monthStart);  // 2025-03-01
    }
}
Java

ここで重要なポイントは二つあります。

一つ目は、「with〜系メソッドは“そのフィールドだけを差し替えた新しい値”を返す」ということです。
withDayOfMonth(1) は「日だけ 1 に変えた LocalDate」を返します。
元の anyDay は変わりません(不変オブジェクト)。

二つ目は、「その月に 1 日が存在しない、ということはありえない」ので、
withDayOfMonth(1) は常に安全に使える、ということです。
withDayOfMonth(31) だと、2 月などで例外になりますが、1 は必ず存在します。)


TemporalAdjusters を使った「月初」の書き方

firstDayOfMonth() を使う

TemporalAdjusters には、「月初」「月末」「次の月初」などを
“名前付き”で表現するためのメソッドが用意されています。

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class MonthStartAdjuster {

    public static void main(String[] args) {
        LocalDate anyDay = LocalDate.of(2025, 3, 26);

        LocalDate monthStart =
                anyDay.with(TemporalAdjusters.firstDayOfMonth());

        System.out.println("任意の日付 : " + anyDay);      // 2025-03-26
        System.out.println("その月の月初: " + monthStart);  // 2025-03-01
    }
}
Java

withDayOfMonth(1) と結果は同じですが、
「firstDayOfMonth という名前で意図がはっきりする」のが大きなメリットです。

コードを読む人が、
「これは“月初を取っている”んだな」と一瞬で理解できます。


「今月の月初」を取得する

LocalDate.now() と組み合わせる

「今日が属する月の月初」を取りたい場合です。

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class ThisMonthStart {

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();

        LocalDate monthStart =
                today.with(TemporalAdjusters.firstDayOfMonth());

        System.out.println("今日   : " + today);
        System.out.println("今月初: " + monthStart);
    }
}
Java

業務的には、
「今月の売上集計」「今月のログ抽出」「今月のバッチ対象期間」などで、
この「今月の月初」が起点になることが多いです。


「任意の年月の月初」を直接作る

LocalDate.of(年, 月, 1) で作る

「2025年3月の月初」のように、
年月がはっきりしている場合は、最初から 1 日を指定してしまうのもアリです。

import java.time.LocalDate;

public class SpecificMonthStart {

    public static void main(String[] args) {
        LocalDate monthStart = LocalDate.of(2025, 3, 1);

        System.out.println("2025年3月の月初: " + monthStart); // 2025-03-01
    }
}
Java

ここでのポイントは、
「月初は“その月の1日”なので、LocalDate.of(年, 月, 1) で素直に表現できる」
ということです。

「ある日付から月初を求める」のではなく、
「そもそも月初を直接作る」方が自然な場面も多いです。


LocalDateTime / ZonedDateTime での月初取得

LocalDateTime の月初(時刻はそのまま or 0時にする)

LocalDateTime でも withDayOfMonth(1)firstDayOfMonth() が使えます。

import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;

public class LocalDateTimeMonthStart {

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 15, 30);

        LocalDateTime monthStartSameTime =
                dt.with(TemporalAdjusters.firstDayOfMonth());

        LocalDateTime monthStartAtMidnight =
                dt.with(TemporalAdjusters.firstDayOfMonth())
                  .withHour(0).withMinute(0).withSecond(0).withNano(0);

        System.out.println("元日時          : " + dt);                   // 2025-03-26T15:30
        System.out.println("月初 同じ時刻   : " + monthStartSameTime);   // 2025-03-01T15:30
        System.out.println("月初 0時ちょうど: " + monthStartAtMidnight); // 2025-03-01T00:00
    }
}
Java

業務では、
「月初の 0:00 から月末の 23:59:59 までを対象にする」
といった要件が多いので、
「日付だけでなく時刻もどうするか」を意識して設計するとよいです。

ZonedDateTime の月初(タイムゾーン付き)

タイムゾーン付きの ZonedDateTime でも同様です。

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjusters;

public class ZonedDateTimeMonthStart {

    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.of(
                2025, 3, 26,
                15, 30, 0, 0,
                ZoneId.of("Asia/Tokyo")
        );

        ZonedDateTime monthStart =
                zdt.with(TemporalAdjusters.firstDayOfMonth())
                   .withHour(0).withMinute(0).withSecond(0).withNano(0);

        System.out.println("元日時 : " + zdt);
        System.out.println("月初0時: " + monthStart);
    }
}
Java

海外システムと連携する場合や、
「日本時間の月初」「UTC の月初」を区別したい場合は、
ZonedDateTime で月初を扱う方が安全です。


実務での「月初取得」あるあるパターン

月次バッチの対象期間を求める

例えば「今月の 1 日 0:00 から、今月の月末 23:59:59 まで」を対象にしたい場合です。

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;

public class MonthlyRangeExample {

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();

        LocalDateTime from = today.with(TemporalAdjusters.firstDayOfMonth())
                                  .atStartOfDay();

        LocalDateTime to = today.with(TemporalAdjusters.lastDayOfMonth())
                                .atTime(23, 59, 59);

        System.out.println("対象開始: " + from);
        System.out.println("対象終了: " + to);
    }
}
Java

ここでのポイントは、
「月初取得」と「月末取得」をセットで考えると、月次処理の期間がきれいに書ける
ということです。


まとめ:月初取得で身につけてほしい感覚

月初取得は、「その月の1日」を安全かつ意図が伝わる形で取り出すことです。

LocalDate なら withDayOfMonth(1)
with(TemporalAdjusters.firstDayOfMonth())
「今月の月初」なら LocalDate.now().with(firstDayOfMonth())
LocalDateTime / ZonedDateTime では、日付に加えて「時刻をどうするか」も決める。
年月が分かっているなら LocalDate.of(年, 月, 1) で素直に作る。

あなたのコードのどこかに、
day = 1; のように自前で月初を作っている箇所があれば、
そこを一度 withDayOfMonth(1)firstDayOfMonth() に置き換えられないか眺めてみてください。

それだけで、
「カレンダー計算をきちんと API に任せられるエンジニア」に、また一歩近づけます。

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