นากต้นฉบับเลื่อนตราประทับไปช่องสุดท้ายของแผงเดือนที่ยาวไม่เท่ากัน

Java YearMonth.atEndOfMonth: ปิดรอบบิลโดยรู้ leap year

พี่โชว์Engineering

รอบบิลรายเดือนมีแค่ปีและเดือนจนกว่าจะกำหนดวันจริง การถือ LocalDate วันที่หนึ่งแล้วบวกเดือนลบวันทำได้ แต่สร้างขั้นเกินจำเป็น YearMonth.atEndOfMonth() คืนวันสุดท้ายตาม leap-year rule โดยตรงครับ

WHY: ใช้ชนิดข้อมูลให้ตรงความหมาย

เหมือนคูปองที่เขียน “ตุลาคม 2026” ยังไม่ต้องแกล้งใส่วันที่หนึ่ง YearMonth เก็บความหมายนี้ตรง ๆ และค่อย materialize เป็น LocalDate ตอนต้องออกใบแจ้งหนี้

import java.time.LocalDate;
import java.time.YearMonth;

public class Main {
    static LocalDate billingDate(int year, int month) {
        return YearMonth.of(year, month).atEndOfMonth();
    }

    public static void main(String[] args) {
        assert billingDate(2026, 4).equals(LocalDate.of(2026, 4, 30));
        assert billingDate(2024, 2).equals(LocalDate.of(2024, 2, 29));
        assert billingDate(2100, 2).equals(LocalDate.of(2100, 2, 28));
        assert YearMonth.from(billingDate(2026, 10)).equals(YearMonth.of(2026, 10));
        System.out.println("billing boundaries verified");
    }
}

HOW: วันที่สิ้นเดือนยังไม่ใช่วินาทีตัดรอบ

LocalDate ไม่มี timezone ถ้าตัดรอบ “สิ้นวันกรุงเทพฯ” ต้องนำไปประกอบกับ ZoneId.of("Asia/Bangkok") ตามกติกาธุรกิจ แล้วเก็บ instant ที่ได้ อย่าใช้ 23:59:59.999 เป็นขอบปิด เพราะ precision ของฐานข้อมูลอาจต่างกัน ใช้ช่วงครึ่งเปิดตั้งแต่ต้นเดือนนี้ถึงต้นเดือนถัดไปปลอดภัยกว่า

YearMonth ยังใช้ plusMonths, lengthOfMonth และ parse รูปแบบ yyyy-MM ได้ แต่ input ภายนอกต้องจับ DateTimeException แล้วคืน validation error ที่เหมาะสม ทดสอบเดือนกุมภาพันธ์ ปีครบร้อย และการเปลี่ยนปีเสมอ

Recap

  • เก็บรอบรายเดือนด้วย YearMonth
  • ใช้ atEndOfMonth แทนเดาจำนวนวัน
  • แยก local date ออกจาก timezone/instant
  • ใช้ช่วงครึ่งเปิดสำหรับ query เวลา
  • ทดสอบ leap year และข้ามปี