← Back to Museum
📅

The February 29th Crash

Medium Severity Leap Year BugFebruary 2012
Leap YearDate ValidationAzurePlayStationSystem Outage
💀

The Crime

February 29th: the day that exists only once every four years, yet manages to crash systems with clockwork regularity. From Microsoft Azure's global outage to PlayStation consoles becoming expensive paperweights, leap day exposes the dangerous assumptions developers make about calendar arithmetic and date validation.

🌍

Real-World Impact

Microsoft Azure Global Outage (February 29, 2012)

Incident: Azure Storage service crashed globally due to leap year certificate validation bug

Duration: 2.5 hours of complete outage, 8+ hours for full recovery

Root Cause: Certificate validation code couldn't handle February 29th dates

Affected Services: Azure Storage, SQL Azure, Service Bus, Access Control

Impact: Thousands of applications offline, estimated $millions in lost revenue

Microsoft Quote: "The leap year. It's a special case that only happens every four years, and we missed it."

PlayStation Network Leap Year Bug (March 1, 2010)

Incident: PS3 consoles worldwide became unusable due to leap year calculation error

Affected Models: Original "Fat" PS3 consoles (not PS3 Slim)

Symptoms: Consoles couldn't connect to PSN, games wouldn't start, system clock reset

Root Cause: Internal clock treated 2010 as a leap year (it's not)

Duration: 24 hours until automatic fix (date rolled over to March 2)

Impact: Millions of consoles affected, no permanent damage but widespread panic

European Banking Network (February 29, 2016)

Incident: ATM network across multiple countries failed during leap day

Affected Countries: Germany, Netherlands, Belgium

Root Cause: Date parsing library couldn't handle "29/02/2016" format

Duration: 6 hours of intermittent failures

Impact: 15,000+ ATMs offline, customer service chaos

Recovery: Emergency patch deployed, manual date override

Enterprise ERP System (February 29, 2020)

Incident: Fortune 100 company's payroll system crashed on leap day

Root Cause: Hardcoded assumption that February has maximum 28 days

Impact: 50,000+ employees not paid on time, HR crisis

Emergency Fix: Manual payroll processing, $2M in overtime costs

Quote: "We tested everything except the one day that only exists every four years."

🔍

Technical Analysis

The Leap Year Rules (It's Complicated)

Rule 1: Divisible by 4

Years divisible by 4 are leap years. 2020, 2024 ✓

Rule 2: Except Century Years

Years divisible by 100 are NOT leap years. 1900, 2100 ✗

Rule 3: Except Exception Years

Years divisible by 400 ARE leap years. 2000, 2400 ✓

Common Mistake

Most developers only implement Rule 1, missing the century exceptions. This causes bugs every 100 years!

Common Failure Patterns

1. Hardcoded Day Limits

Code that assumes February always has 28 days: if (month == 2 && day > 28)

2. Incomplete Leap Year Logic

Only checking divisibility by 4: year % 4 == 0

3. Date Parsing Failures

Libraries that reject "29/02/YYYY" as invalid without proper leap year checking

4. Certificate Validation

SSL/TLS certificates with February 29 expiry dates causing validation failures

5. Database Constraints

CHECK constraints that don't account for leap years: day <= 28

Azure Incident: The Certificate Validation Bug

Feb 28 23:59Azure Storage operating normally
Feb 29 00:00⚠️ Certificate validation code encounters February 29th
Feb 29 00:01🚨 Validation fails: "Invalid date in certificate"
Feb 29 00:02💥 Storage service crashes globally
Feb 29 02:30🔧 Emergency patch deployed
Feb 29 08:00✅ Full service restoration
💻

Code Examples

❌ Problematic: Hardcoded February Limit

// This will crash on February 29th
function validateDate(month, day, year) {
    const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    if (day > daysInMonth[month - 1]) {
        throw new Error("Invalid date");
    }
    
    return true;
}

// February 29, 2024 will be rejected as invalid!

❌ Problematic: Incomplete Leap Year Logic

// This is wrong! Missing century rules
function isLeapYear(year) {
    return year % 4 === 0;
}

// This will incorrectly say 1900 is a leap year
// 1900 % 4 === 0, but 1900 is NOT a leap year!

✅ Safe: Proper Leap Year Calculation

function isLeapYear(year) {
    // Correct leap year logic with all three rules
    if (year % 400 === 0) return true;    // Rule 3: Divisible by 400
    if (year % 100 === 0) return false;   // Rule 2: Not divisible by 100
    if (year % 4 === 0) return true;      // Rule 1: Divisible by 4
    return false;
}

function getDaysInMonth(month, year) {
    const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    if (month === 2 && isLeapYear(year)) {
        return 29;  // February in leap year
    }
    
    return daysInMonth[month - 1];
}

function validateDate(month, day, year) {
    if (month < 1 || month > 12) {
        throw new Error("Invalid month");
    }
    
    const maxDays = getDaysInMonth(month, year);
    if (day < 1 || day > maxDays) {
        throw new Error(`Invalid day for ${month}/${year}`);
    }
    
    return true;
}

✅ Safe: Database Schema with Leap Year Support

-- PostgreSQL: Safe date constraints
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    event_date DATE NOT NULL,
    
    -- Don't use hardcoded day limits!
    -- CONSTRAINT bad_check CHECK (EXTRACT(DAY FROM event_date) <= 28)
    
    -- Instead, let the database handle date validation
    CONSTRAINT valid_date CHECK (event_date IS NOT NULL)
);

-- Function to validate leap year dates
CREATE OR REPLACE FUNCTION is_valid_date(y INT, m INT, d INT) 
RETURNS BOOLEAN AS $$
BEGIN
    -- Let PostgreSQL do the validation
    PERFORM make_date(y, m, d);
    RETURN TRUE;
EXCEPTION 
    WHEN others THEN
        RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

✅ Safe: Python with Built-in Date Handling

from datetime import datetime, date
import calendar

def validate_date_safe(year, month, day):
    """Use Python's built-in date validation"""
    try:
        # This will raise ValueError for invalid dates
        date(year, month, day)
        return True
    except ValueError as e:
        print(f"Invalid date: {e}")
        return False

def get_days_in_month_safe(year, month):
    """Use calendar module for accurate day counts"""
    return calendar.monthrange(year, month)[1]

# Test with leap year dates
print(validate_date_safe(2024, 2, 29))  # True - valid leap day
print(validate_date_safe(2023, 2, 29))  # False - not a leap year
print(validate_date_safe(1900, 2, 29))  # False - century year exception
print(validate_date_safe(2000, 2, 29))  # True - 400-year exception

# Safe way to check if year is leap year
def is_leap_year_safe(year):
    return calendar.isleap(year)

✅ Safe: Certificate Date Validation

// C# - Azure-style certificate validation fix
public static bool ValidateCertificateDate(DateTime certDate)
{
    try 
    {
        // Use .NET's built-in date validation
        // This properly handles leap years
        var validDate = new DateTime(certDate.Year, certDate.Month, certDate.Day);
        
        // Check if certificate is still valid
        return validDate >= DateTime.UtcNow;
    }
    catch (ArgumentOutOfRangeException)
    {
        // Invalid date in certificate
        return false;
    }
}

// The bug was likely something like this:
public static bool ValidateCertificateDateBuggy(int year, int month, int day)
{
    // Hardcoded validation that fails on Feb 29
    if (month == 2 && day > 28)
    {
        return false;  // BUG: Rejects valid leap day!
    }
    
    // ... rest of validation
    return true;
}
🛡️

Prevention Strategies

1. Use Standard Libraries

  • • Never implement date logic from scratch
  • • Use language built-ins (Date, DateTime, calendar)
  • • Trust battle-tested libraries over custom code
  • • Avoid hardcoded day/month limits

2. Test Leap Year Scenarios

  • • Include February 29th in test data
  • • Test century years (1900, 2100)
  • • Test 400-year exceptions (2000, 2400)
  • • Automate leap year edge case testing

3. Validate All Date Inputs

  • • Validate dates at system boundaries
  • • Use try/catch for date construction
  • • Reject invalid dates gracefully
  • • Log date validation failures

4. Monitor Date-Related Operations

  • • Alert on date parsing failures
  • • Monitor certificate expiry validation
  • • Track date-related error rates
  • • Set up leap year monitoring
🎓

Lessons Learned

1. February 29th Is Not an Edge Case

Leap day happens every 4 years like clockwork. It's a regular, predictable event that should be part of standard testing.

2. Date Logic Is Harder Than It Looks

Leap year rules have three conditions, not one. Century years, 400-year exceptions, and other calendar quirks make date arithmetic complex.

3. Trust Standard Libraries

Don't reinvent date validation. Use your language's built-in date libraries - they've already solved these problems.

4. Test the Calendar Extremes

Include leap days, century years, and month boundaries in your test data. If your system handles dates, it must handle ALL dates.

🔗

Related Crimes

📅

Age Calculation Catastrophe

When leap years break age calculations (Coming Soon)

🎯

Y2K Leap Year Oversight

Y2K fixes that broke leap year logic (Coming Soon)

📢 Share Your Leap Year Horror Story

Has February 29th crashed your systems? Share your leap year disaster story and help others avoid the same fate. The best submissions get featured in our hall of shame.