Great work!

XP to next level

BugEater

Month Boundary Transitions: 28, 29, 30, and 31

Learning Objectives

  • Understand the distribution of month lengths in the Gregorian calendar and which months create boundary risks
  • Identify the specific date arithmetic bugs that occur when adding months crosses a month-end boundary
  • Write test cases for systems that perform month-based date calculations

Month Lengths Reference

The Gregorian calendar has months of three different fixed lengths plus one variable:

Days Months
31 January, March, May, July, August, October, December
30 April, June, September, November
28/29 February

A simple mnemonic: odd months (except September and November) have 31 days. The "knuckle rule" — counting months on fists — encodes the same pattern. What matters for testing is not the mnemonic but the consequence: adding "1 month" to a date does not always produce a date in the next month with the same day number.

The March 31 Problem

Consider a subscription that renews every month. If the user subscribes on January 31, when does it renew?

  • January 31 + 1 month = February 31 → doesn't exist

Different systems handle this differently:

  • Clamp to last day: February 28 (or 29 in leap year)
  • Overflow to next month: March 2 (or March 3 in leap year)
  • Error: throws an exception or returns null

None of these is universally "correct" — it depends on business rules. But if the system clamps, then the user subscribed on January 31 renews on February 28, then (adding 1 month) on March 28 — not March 31. They permanently lost three days from their subscription cycle.

Another example: 2024-03-31 + 1 month. March has 31 days; April has 30. April 31 doesn't exist. What does the system do?

SQL DATE_ADD Pitfalls

SQL's DATE_ADD function behavior varies by database:

-- MySQL: clamps to last valid day
SELECT DATE_ADD('2024-01-31', INTERVAL 1 MONTH);
-- Result: 2024-02-29 (2024 is a leap year)

-- PostgreSQL: same clamping behavior
SELECT '2024-01-31'::date + INTERVAL '1 month';
-- Result: 2024-02-29

This is usually the least surprising behavior, but it means the day-of-month is not preserved across all months. Systems that assert "renews on the same day each month" will fail in months shorter than the subscription start day.

Critical Test Cases for Month-End Boundaries

Input date Operation Expected Risk
2024-01-31 +1 month Feb 29 (clamp) What does the system do with Feb 31?
2024-03-31 +1 month Apr 30 (clamp) April has only 30 days
2024-08-31 +1 month Sep 30 (clamp) September has only 30 days
2024-02-29 +1 year Feb 28 2025 Leap day + 1 year
2024-01-30 +1 month Feb 29 Edge: within Feb capacity in leap year
2023-01-30 +1 month Feb 28 Edge: Feb in non-leap year

Testing Strategy

When testing month-addition logic, always test:

  1. Start dates on the 28th, 29th, 30th, and 31st of a month
  2. Operations that land in February (both leap and non-leap years)
  3. Operations that land in 30-day months when starting from a 31-day month
  4. Verify that the system's clamping behavior matches the documented business rule

Quiz

A subscription service starts on January 31. If the system uses "clamp to last valid day" for month addition, what is the renewal date one month later?

Which of these date operations is most likely to cause a month-boundary transition bug?

A user's monthly renewal is set to the 31st. What happens in September (which has 30 days) if the system uses "overflow" behavior?

When testing a "add N months" function, which starting dates are the highest priority to test?