Leap Year in C: Gregorian Rule and Five Programs

A leap year in C can be checked with the Gregorian rule: a year is leap if it is divisible by 400, or divisible by 4 but not by 100. Thus 2000 is a leap year, but 1900 and 2100 are not. A leap year has 366 days, with February 29 as the extra date.

February 29 illustrates the leap-year calendar case.

Logical-operator program

The program accepts one decimal year from 1 through 9999, with optional surrounding whitespace and leading zeros. It rejects year 0, negative years, signs, decimal fractions and extra tokens. It applies the Gregorian rule throughout that range (the proleptic Gregorian calendar); it does not claim every country actually used that calendar in earlier centuries. Historical dates may require the local calendar and adoption date.

#include <stdio.h>
#include <ctype.h>

/* One unsigned decimal token, surrounding whitespace, then EOF. */
static int read_bounded(unsigned int *out, unsigned int limit) {
    int ch;
    unsigned int value = 0;
    do { ch = getchar(); } while (ch != EOF && isspace(ch));
    if (ch < '0' || ch > '9') return 0;
    do {
        unsigned int digit = (unsigned int)(ch - '0');
        if (value > limit / 10 ||
            (value == limit / 10 && digit > limit % 10)) return 0;
        value = value * 10 + digit;
        ch = getchar();
    } while (ch >= '0' && ch <= '9');
    while (ch != EOF && isspace(ch)) ch = getchar();
    if (ch != EOF) return 0;
    *out = value;
    return 1;
}

int main(void) {
    unsigned int year;
    if (!read_bounded(&year, 9999) || year == 0) {
        fputs("Enter one year from 1 to 9999.\n", stderr);
        return 1;
    }
    int leap = year % 400 == 0 ||
               (year % 4 == 0 && year % 100 != 0);
    printf("%u: %s\n", year, leap ? "leap year" : "common year");
    return 0;
}

Run logical leap-year check online

Sample input

2000

Expected output

Reference output for the displayed program, not a live execution result.

2000: leap year

The remainder operator % tests divisibility. The parentheses make the exception clear: divisible by 100 cancels the every-four-years rule, unless divisible by 400 restores it. The parser checks the whole batch input through EOF and bounds the value before arithmetic. If you type input in a terminal, finish stdin with your terminal's EOF mechanism or pipe the input; the online runner sends a completed stdin value.

Nested if statements

This version follows the decision tree explicitly. First ask whether the year is divisible by 4. If not, it is common. Otherwise check the century exception, then the 400-year exception to that exception.

#include <stdio.h>
#include <ctype.h>

/* One unsigned decimal token, surrounding whitespace, then EOF. */
static int read_bounded(unsigned int *out, unsigned int limit) {
    int ch;
    unsigned int value = 0;
    do { ch = getchar(); } while (ch != EOF && isspace(ch));
    if (ch < '0' || ch > '9') return 0;
    do {
        unsigned int digit = (unsigned int)(ch - '0');
        if (value > limit / 10 ||
            (value == limit / 10 && digit > limit % 10)) return 0;
        value = value * 10 + digit;
        ch = getchar();
    } while (ch >= '0' && ch <= '9');
    while (ch != EOF && isspace(ch)) ch = getchar();
    if (ch != EOF) return 0;
    *out = value;
    return 1;
}

int main(void) {
    unsigned int year;
    if (!read_bounded(&year, 9999) || year == 0) {
        fputs("Enter one year from 1 to 9999.\n", stderr);
        return 1;
    }
    int leap;
    if (year % 4 == 0) {
        if (year % 100 == 0) {
            if (year % 400 == 0) leap = 1;
            else leap = 0;
        } else leap = 1;
    } else leap = 0;
    printf("%u: %s\n", year, leap ? "leap year" : "common year");
    return 0;
}

Run nested leap-year check online

Sample input

2000

Expected output

Reference output for the displayed program, not a live execution result.

2000: leap year

Conditional-operator version

The conditional operator is right-associative. This expression tests 400 first, then 100, then 4. Compact syntax is not automatically clearer; prefer the logical or nested version if a reader is still learning operator precedence.

#include <stdio.h>
#include <ctype.h>

/* One unsigned decimal token, surrounding whitespace, then EOF. */
static int read_bounded(unsigned int *out, unsigned int limit) {
    int ch;
    unsigned int value = 0;
    do { ch = getchar(); } while (ch != EOF && isspace(ch));
    if (ch < '0' || ch > '9') return 0;
    do {
        unsigned int digit = (unsigned int)(ch - '0');
        if (value > limit / 10 ||
            (value == limit / 10 && digit > limit % 10)) return 0;
        value = value * 10 + digit;
        ch = getchar();
    } while (ch >= '0' && ch <= '9');
    while (ch != EOF && isspace(ch)) ch = getchar();
    if (ch != EOF) return 0;
    *out = value;
    return 1;
}

int main(void) {
    unsigned int year;
    if (!read_bounded(&year, 9999) || year == 0) {
        fputs("Enter one year from 1 to 9999.\n", stderr);
        return 1;
    }
    int leap = year % 400 == 0 ? 1 :
               year % 100 == 0 ? 0 : year % 4 == 0;
    printf("%u: %s\n", year, leap ? "leap year" : "common year");
    return 0;
}

Run conditional leap-year check online

Sample input

2000

Expected output

Reference output for the displayed program, not a live execution result.

2000: leap year

Reusable leap-year function

Keep the calendar rule in one function when using it in more than one place. The function's contract is a validated year from 1 through 9999; the caller below enforces that contract. A nonzero return means leap.

#include <stdio.h>
#include <ctype.h>

/* One unsigned decimal token, surrounding whitespace, then EOF. */
static int read_bounded(unsigned int *out, unsigned int limit) {
    int ch;
    unsigned int value = 0;
    do { ch = getchar(); } while (ch != EOF && isspace(ch));
    if (ch < '0' || ch > '9') return 0;
    do {
        unsigned int digit = (unsigned int)(ch - '0');
        if (value > limit / 10 ||
            (value == limit / 10 && digit > limit % 10)) return 0;
        value = value * 10 + digit;
        ch = getchar();
    } while (ch >= '0' && ch <= '9');
    while (ch != EOF && isspace(ch)) ch = getchar();
    if (ch != EOF) return 0;
    *out = value;
    return 1;
}

int is_leap(unsigned int year) {
    return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}

int main(void) {
    unsigned int year;
    if (!read_bounded(&year, 9999) || year == 0) {
        fputs("Enter one year from 1 to 9999.\n", stderr);
        return 1;
    }
    int leap = is_leap(year);
    printf("%u: %s\n", year, leap ? "leap year" : "common year");
    return 0;
}

Run leap-year function online

Sample input

2000

Expected output

Reference output for the displayed program, not a live execution result.

2000: leap year

Edit first and last in this no-stdin example. Both endpoints are included. The range must contain at most 400 years and remain inside 1–9999. These bounds limit output and prevent the loop counter from wrapping. Check endpoint order before subtracting unsigned values. A range with no qualifying years prints an explicit message.

#include <stdio.h>

int is_leap(unsigned int year) {
    return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}

int main(void) {
    unsigned int first = 1896, last = 1904;
    if (first < 1 || last > 9999 || first > last || last - first > 399) {
        fputs("Use an ordered range of at most 400 years within 1..9999.\n", stderr);
        return 1;
    }
    int found = 0;
    for (unsigned int year = first; year <= last; ++year) {
        if (is_leap(year)) {
            printf("%s%u", found ? " " : "", year);
            found = 1;
        }
    }
    if (!found) printf("No leap years");
    putchar('\n');
    return 0;
}

Run leap years in a range online

Expected output

Reference output for the displayed program, not a live execution result.

1896 1904

Century edge cases and trace

Year Divisible by 4? By 100? By 400? Result
1896 Yes No No Leap
1900 Yes Yes No Common
2000 Yes Yes Yes Leap
2024 Yes No No Leap
2025 No No No Common
2100 Yes Yes No Common
2400 Yes Yes Yes Leap

For 1900 the first alternative (% 400 == 0) is false. The second alternative starts true (% 4 == 0), but its % 100 != 0 part is false. The complete expression is false. For 2000 the first alternative is already true, so || short-circuits; the result is leap.

The Gregorian cycle has 97 leap years per 400 years: 100 multiples of four, minus four century years, plus the century divisible by 400. This gives an average calendar year of 365.2425 days. The rule approximates the seasonal year; “the Earth takes exactly 365.25 days” is not an accurate explanation.

Complexity and common mistakes

Each single-year check performs a constant number of operations: O(1) time and O(1) auxiliary space. Testing a range of k years is O(k) time and O(1) extra space, excluding the output.

  • Testing only % 4 misclassifies 1900 and 2100.
  • Rejecting every century year misclassifies 2000 and 2400.
  • Writing year % 4 = 0 confuses assignment with comparison and is invalid here.
  • A leap year test is not a complete date validator: the month, day, leap-day rule and calendar policy still matter.
  • C's time/date library and operating-system timestamp limits are separate from this arithmetic example; this program does not convert years to timestamps.

Try 1, 4, 100, 400 and 9999: common, leap, common, leap and common. Change the range to 2001–2003: it should print No leap years. For 2000–2399, count 97 leap years. Test 0, -2000, 10000 and 2000 2004: all should fail input validation.

See program structure, functions, and factorial for another bounded-input arithmetic program. Open a run link to load the exact code and input into the online C compiler.

Frequently asked questions

Is every fourth year a leap year?

Not in the Gregorian calendar: century years must also be divisible by 400. The simpler every-four-years rule belongs to the Julian calendar, not this program's policy.

Can this program handle BCE dates or year zero?

No. Its documented range is 1–9999 with no year zero. Astronomical year numbering and historical calendar conversion require a different policy and implementation.

Related examples