Date of Birth Validation Test Cases: Leap Years & Age

Download 14 date-of-birth test cases with fixed reference dates and expected ages. Cover leap years, invalid dates, future dates and birthday boundaries.

By Vincent RuanPublished August 6, 2026Last updated October 1, 20269 min read

Download date of birth validation test cases

14 fixed cases · Version 1 · Updated 2026-09-06. Each row includes the original input, test context, expected action, expected value and reason.

Test policy: Parse an exact YYYY-MM-DD Gregorian date without timestamp conversion or overflow. Reject impossible dates, empty values and dates after the reference date in context. For accepted values, expectedValue is completed years as a decimal string; age zero is valid. There is no minimum eligibility age or maximum age in this example. In common years, the February 29 birthday advances on March 1 under this fixture policy. This is a declared software convention, not a statement of legal eligibility in any jurisdiction. Import dates as text.

Preview cases and import instructions

JSON is an array of records. CSV uses the same keys and values. Load CSV through an import dialog with every column set to Text: automatic conversion can change leading zeros, plus signs, dates or strings that resemble formulas. JSON preserves Unicode and escaped control characters for automated tests. Empty expectedValue means rejection; it is not a replacement value to store.

Run the input through your implementation with the documented context, compare the action and expectedValue, then check storage and export round trips. Adjust policy-dependent cases to your product before using them as pass/fail gates. Keep the v1 filename with your test results.

First five cases. Quoted inputs use JSON notation so spaces remain visible.
CaseInputExpectedValue
dob-leap-400"2000-02-29"accept"26"
dob-leap-century"1900-02-29"reject""
dob-leap-common"2001-02-29"reject""
dob-april-31"2000-04-31"reject""
dob-eighteen"2008-09-06"accept"18"

Policy background: Microsoft: Excel's 1900 leap-year behavior. Sources explain the standards and design tradeoffs; they do not endorse every application choice in this fixture set.

Illustration for this article: Download 14 date-of-birth test cases with fixed reference dates and expected ages. Cover leap years, invalid dates, future dates and birthday boundaries.

After we published names that break software, the natural question was which field to distrust next. The answer is the one that looks safest: date of birth. A birthday has a fixed format and only 366 possible month-day values. Yet it has frozen a whole line of music players, taken down a cloud platform, and quietly broken age calculations in systems that passed every normal test.

The difference is what software does with the field. A name is mostly stored and displayed. A birthday is calculated against: age, eligibility, cohorts, even the zodiac sign on a generated profile. Every one of those calculations replays the calendar's exceptions.

Why dates of birth are harder than they look

The calendar is an API with special cases baked in centuries ago. Months come in four lengths. One day exists only three years in four. The leap rule has an exception, and the exception has an exception. And there are competing ways to write a date down. A stored birthday touches all of them, again and again, for as long as the record lives.

The failures are also lopsided. Rejecting a valid date locks a real person out of a signup form. Accepting an invalid one plants a value that breaks later, in whatever job next does arithmetic on it.

The edge cases that actually break systems

29 February

About one birth in 1,461 lands on a leap day, so leap-day users are real. The rule behind their birthday is the one developers most often get half right. A year is a leap year if it divides by 4, except centuries, unless the century divides by 400. So 2000 was a leap year, 1900 was not, and 2100 will not be. A home-made divisible-by-4 check works perfectly for seventy-odd more years, then fails [leap-year-wiki].

Even lawmakers have had to patch this case. In Hong Kong, Taiwan and the United Kingdom, a leap-day birthday counts as 1 March in common years. Hong Kong's ordinance says so outright, and Taiwan's civil code implies it. Contracts and coming-of-age rules need an answer for a birthday that does not exist three years in four [leap-year-wiki]. Your age code needs the same explicit decision, and a test that pins it.

The leap day's greatest hits are well documented [leap-bug-wiki]. On 31 December 2008, the 366th day of a leap year, Microsoft's Zune 30 players froze on start-up worldwide. The clock driver's loop subtracted 365 days at a time and had no branch for a remainder of exactly 366. It spun until the calendar rolled over at midnight.

On 28 February 2012, Pacific time, already 29 February in UTC, Windows Azure went down. Certificates were being issued with a validity of exactly one year, by changing the year without changing the day. 29 February 2012 plus one year is 29 February 2013, which does not exist. The failures spread into an outage that lasted most of a day. Neither bug was in date parsing. Both were in date arithmetic, the part that runs long after the form said everything was fine.

The year 1900, and Excel's deliberate bug

Spreadsheets are where birthday data gets mangled, and the best-known example is deliberate. Excel's 1900 date system treats 1900 as a leap year. Serial number 60 is 29 February 1900, a day that never happened. Microsoft documents this and kept it on purpose, for compatibility with Lotus 1-2-3, which made the same error first [excel-1900].

The effects on birthday data are real. Weekdays for early-1900 dates are wrong by a day. And the 1900 system cannot store any date before 1 January 1900. Paste a centenarian's 1899 birthday into a date column and it quietly becomes text, which then sorts and filters differently from every value around it. If your data passes through a spreadsheet anywhere, and someday it will, your fixtures should include the dates that expose this.

Ages that are off by one

The most common birthday bug never crashes anything. Subtracting the birth year from the current year overstates a person's age until their birthday, for up to 364 days. Where 13, 18, 21 or 65 unlocks something, that error grants or denies eligibility. Correct age math compares the full month and day. The boundary is exact: a person turns 18 on their birthday, not the day before or after. Only fixtures pinned to the boundary prove it: a birth date exactly 18 years before the reference date, and the same date one day later.

The second off-by-one comes from timezones. A date of birth is a calendar date, not a moment in time: nobody's birthday moves when they fly. But store it in a datetime column and it gains a hidden midnight, usually in UTC. Show that value in any timezone west of Greenwich and it becomes the previous day: born 10 May, shown 9 May. The fix is to keep birth dates as plain ISO 8601 dates, such as 1990-05-10, from end to end [iso8601]. A bug report titled 'my birthday is wrong by one day' almost always has this cause.

1 January, and other defaults that look like data

Plot the birthdays in any large real database and 1 January towers over every other date. Some of that is software. Epoch values like 1970-01-01, the zero of Unix time, leak out of code that confused 'unset' with 'the beginning of time' [unix-time]. But much of it is honest data.

When a person's real date of birth is unknown, officials have long written 1 January. That is common in refugee and immigration paperwork from places where births go unregistered. Of the nearly 80,000 refugees admitted to the United States in 2009, about 11,000 had a 1 January birthday on file [npr-jan1]. The lesson cuts both ways. A pile of 1 January birthdays is not proof of corruption. And a matching system that treats 'same date of birth' as strong evidence will merge thousands of different people who share only a clerk's habit.

Two-digit years and ambiguous formats

The Year 2000 problem was, at heart, a date-of-birth problem. Two-digit years forced every system to guess the century. Rules that read '25' as 1925 or 2025, depending on a cut-off year, filed centenarians as newborns and the reverse [y2k-wiki]. Two-digit years are mostly gone from storage. They survive in date pickers, CSV exports and scanned paper forms.

Their cousin, format ambiguity, is fully alive: 03/04/2001 is 4 March in New York and 3 April in London. Writing dates in ISO 8601 removes the question [iso8601], but only in systems you control. So test data should include dates with a day above 12, because only those cannot be swapped silently. The next epoch is already scheduled. 32-bit Unix timestamps overflow on 19 January 2038, which is closer to today than the Y2K clean-up is behind us [unix-time].

A birthday fixture set worth copying

Date of birthWhat it exercisesExpected behavior
2000-02-29The 400-year rule — 2000 really was a leap yearAccepted; age correct in common years
1996-02-29Ordinary leap dayAccepted; birthday handling defined for common years
1900-02-29The century rule — this day never existedRejected; catches divisible-by-4-only leap logic and Excel's serial 60
2001-02-29Leap day in a common yearRejected
2000-04-31Day 31 in a 30-day monthRejected
Reference date minus exactly 18 yearsThe eligibility boundaryPerson is 18 today — not yesterday
One day after thatThe other side of the boundaryPerson is 17 until tomorrow
1970-01-01Unix epoch sentinel collisionAccepted, but distinguishable from an unset default
1900-01-01Legacy default and spreadsheet epochAccepted; survives an export-import round trip
1899-12-31Pre-1900 centenarianPreserve as ISO date text; do not rely on Excel date serials
2001-12-13Day value over 12Cannot be month-day transposed silently
Tomorrow's dateFuture date of birthRejected at the boundary, not just 'far future'
Date-of-birth fixtures and the failure each one exercises. 'Reference date' means the date your age logic compares against.

Run every fixture through the whole life of a record, not just the form. Create it, show it in at least two timezones, calculate the age on both sides of the birthday, and export and re-import it in every format you support. The 29 February and 1899 rows fail most often in the export round trip. The boundary rows fail most often in a batch job someone wrote years after the form.

How our generator handles birthdays — including two bugs we shipped

Every profile from our identity generator has a birthday, an age and a zodiac sign, and generation is deterministic from a seed. The engine picks a target age between 18 and 80 and a month and day anywhere in the calendar. It then works out the birth year that makes the age exact on a fixed reference date. There is no clock read, so the same seed always produces the same person. In a check of 10,000 profiles on 1 October 2026, every age matched its birthday.

One result is deliberate: the engine never produces 29 February. The leap day exists only in leap years, so allowing it would tie those birth years to leap years and skew the age spread. A random generator would hit it only once in about 1,461 draws anyway, which makes it a poor way to test that path. For leap-day coverage, take the fixtures from the table above. Random data is the wrong tool for a boundary you can name.

We write this with some humility, because we shipped two of these bugs ourselves. Our first version picked the year first. It then limited the month and day to fall on or before the reference date. The default reference date was 1 January, so the 'anywhere in the calendar' draw collapsed to one value. For a while, every identity this site generated without an explicit reference date was born on 1 January. All of them were Capricorns.

The same routine also capped every month at 28 days, so nobody was ever born on the 29th, 30th or 31st. Both bugs produced flawless individual records: every date valid, every age exact. That is exactly why row-by-row validation never flagged them. Today's engine spreads birthdays across all 365 month-days, with 1 January at its expected share of about one in 365.

The conclusion matches the one we reached about names: validate less, test more. Reject the dates that cannot exist. Accept everything that can, including the leap day, the centenarian and the person who turns 18 today. Spend the effort you save on fixtures that walk the boundaries, and on totals that catch what row checks never will. For the fields next to this one, read names that break software and phone numbers that break software. For a thousand consistent profiles to run your own histograms on, the generator is free and reproducible from a seed.

References & sources

  1. Leap year — leap day birthdays and the Gregorian rule — Wikipedia
  2. Leap year problem — Wikipedia
  3. Excel incorrectly assumes that the year 1900 is a leap year — Microsoft Learn
  4. Why So Many Immigrants Have Birthdays On Jan. 1 — NPR
  5. Year 2000 problem — Wikipedia
  6. ISO 8601 — date and time format — Wikipedia
  7. Unix time — Wikipedia

Frequently asked questions

Is 29 February a valid date of birth?

Yes. Roughly one person in 1,461 is born on a leap day, so any system that stores dates of birth will meet one. Accept 29 February in leap years and reject it in common years. Then decide, explicitly, what the birthday is in years without a 29 February. The downloadable fixtures use 1 March. Legal eligibility rules need a separate, jurisdiction-specific check.

Why do so many database records show a 1 January birthday?

Two reasons. First, when a real date of birth is unknown, as is common in refugee and immigration paperwork, officials have long filled in 1 January. Genuine records pile up on that date. Second, software defaults: epoch values like 1970-01-01, and placeholder dates typed to get past a required field. A spike on 1 January is a data-quality signal, not a fact about people. Matching logic that relies on date of birth should expect it.

What dates of birth should I include in test fixtures?

At minimum, include 29 February 2000, which is valid because of the 400-year rule. Add 29 February 1900 and 29 February 2001, both invalid for different reasons. Add a date exactly 18 years before your reference date and the day after it. Add 1 January 1970 and 1 January 1900, a pre-1900 date, and a future date that must be rejected. Run each through creation, display, age calculation and an export and import.

How do you calculate someone's age from a date of birth correctly?

Compare the full month and day with the reference date, not just the years. Subtracting the birth year from the current year overstates the age until the birthday, for up to 364 days. That matters wherever 13, 18, 21 or 65 unlocks something. A person turns 18 on their birthday, not the day before or after. Test with dates pinned exactly 18 years back and one day either side. Store the birthday as a plain ISO 8601 date, such as 1990-05-10, never as a timestamp. Otherwise a timezone west of Greenwich shows it a day early.

What is the Excel 1900 leap year bug?

Excel's 1900 date system treats 1900 as a leap year. Serial number 60 is 29 February 1900, a day that never existed. Microsoft kept the error on purpose, for compatibility with Lotus 1-2-3, which made it first. For birthdays, weekdays for early-1900 dates are off by one. No date before 1 January 1900 can be stored as a date at all, so an 1899 birthday pasted into a date column quietly becomes text. If your data passes through a spreadsheet, include those dates in your fixtures.

Why doesn't this site's generator produce 29 February birthdays?

By design. Our engine keeps the age exact by deriving the birth year from the month and day. 29 February only exists in leap years, so allowing it would tie those people to leap years and skew the age spread. Random dates would hit the leap day only about once in 1,461 draws anyway. To test the leap-day path, add those fixtures by hand rather than hoping a generator lands on them.

Update history

  1. Re-checked the generator claims over 10,000 identities and every outside source, corrected the date of the Azure outage, and rewrote the prose in much shorter sentences.
  2. Added the 14 downloadable test cases, the question-style FAQs and a section on calculating age correctly.
  3. Linked the new phone-number test cases from the closing section.
  4. Published, including the two birthday bugs we shipped in our own generator.