Java YearMonth.atEndOfMonth: ปิดรอบบิลโดยรู้ leap year
รอบบิลรายเดือนมีแค่ปีและเดือนจนกว่าจะกำหนดวันจริง การถือ 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 และข้ามปี