Python calendar.monthrange: หาวันสุดท้ายของเดือนโดยไม่บวก 31 วัน
โค้ดหาวันสิ้นเดือนด้วยการบวก 31 วันแล้วถอยกลับทำงานได้บางกรณี แต่ต้องจัดการข้ามเดือนและ leap year เอง calendar.monthrange(year, month) คืน weekday ของวันแรกกับจำนวนวันในเดือน จึงสร้างวันสุดท้ายได้ตรงสัญญาครับ
WHY: ให้ปฏิทินรับผิดชอบกฎปฏิทิน
เดือนเหมือนถาดไข่ที่บางใบมี 28, 29, 30 หรือ 31 ช่อง การเดาด้วยถาดใหญ่สุดแล้วถอยกลับเพิ่ม arithmetic ที่ไม่จำเป็น Standard library รู้กฎ Gregorian calendar อยู่แล้ว เราจึงขอจำนวนช่องจริงแล้วสร้าง date เพียงครั้งเดียว
import calendar
import datetime as dt
import unittest
def month_end(year: int, month: int) -> dt.date:
_, days = calendar.monthrange(year, month)
return dt.date(year, month, days)
class MonthEndTests(unittest.TestCase):
def test_regular_and_leap_months(self) -> None:
self.assertEqual(month_end(2026, 4), dt.date(2026, 4, 30))
self.assertEqual(month_end(2024, 2), dt.date(2024, 2, 29))
self.assertEqual(month_end(2100, 2), dt.date(2100, 2, 28))
def test_invalid_month_is_rejected(self) -> None:
with self.assertRaises(calendar.IllegalMonthError):
month_end(2026, 13)
if __name__ == "__main__":
unittest.main()
HOW: แยก date ออกจาก instant
monthrange ทำงานกับปีและเดือนตามปฏิทิน ไม่รู้ timezone ถ้าโจทย์คือ “สิ้นเดือนตามเวลาประเทศไทย” ให้สร้าง date ก่อน แล้วประกอบกับเวลาและ ZoneInfo("Asia/Bangkok") ตอนแปลงเป็น instant อย่าใช้ UTC midnight แล้วสมมติว่าเป็นวันเดียวกับทุกภูมิภาค
weekday ที่คืนมาใช้เลขจันทร์เป็นศูนย์ถึงอาทิตย์เป็นหก ถ้าไม่ใช้ให้ตั้งชื่อ _ เพื่อบอกเจตนา การรับเดือนจากผู้ใช้ควร validate และคืนข้อความระดับโดเมน ไม่ส่ง exception ภายในไปตรง ๆ
Recap
- ใช้จำนวนวันจาก
monthrangeแทนเดา 31 วัน - ทดสอบ leap year รวมปีครบร้อย
- แยกวันปฏิทินออกจาก timezone-aware instant
- แปลง error ให้เหมาะกับ boundary ของแอป