Search code examples
calendar

Universal formula to calculate the number of days in a month taking into account leap years


Does someone know a math formula to calculate the number of days in a month like this one

28 + (x + Math.floor(x/8)) % 2 + 2 % x + 2 * Math.floor(1/x);

but which also takes in to account leap years? It should also take into account that the Gregorian calendar omits 3 leap days every 400 years, which is the length of its leap cycle.


Solution

  • It isn't very hard to add a term (m == 2) * leapyear(yyyy) to the expression to determine the correct number of days in February of a leap year. This C code shows a way to do it:

    #include <stdio.h>
    #include <stdbool.h>
    
    static inline bool leapyear(int yy)
    {
        if (yy %   4 != 0) return false;
        if (yy % 100 != 0) return true;
        if (yy % 400 != 0) return false;
        return true;
    }
    
    static inline int old_dim(int mm)
    {
        return (28 + (mm + (mm/8)) % 2 + 2 % mm + 2 * (1/mm));
    }
    
    static inline int new_dim(int mm, int yyyy)
    {
        return (28 + (mm + (mm/8)) % 2 + 2 % mm + 2 * (1/mm) + ((mm == 2) * leapyear(yyyy)));
    }
    
    int main(void)
    {
        /*28 + (x + Math.floor(x/8)) % 2 + 2 % x + 2 * Math.floor(1/x);*/
        for (int mm = 1; mm <= 12; mm++)
            printf("mm = %2d, DIM = %2d\n", mm, old_dim(mm));
    
        for (int yyyy = 1900; yyyy < 2101; yyyy += 5)
        {
            for (int mm = 1; mm <= 12; mm++)
                printf("yyyy = %4d, mm = %2d: DIM = %2d\n", yyyy, mm, new_dim(mm, yyyy));
        }
    
        return 0;
    }
    

    The output for mm = 2 (filtered from the full output) is:

    yyyy = 1900, mm =  2: DIM = 28
    yyyy = 1905, mm =  2: DIM = 28
    yyyy = 1910, mm =  2: DIM = 28
    yyyy = 1915, mm =  2: DIM = 28
    yyyy = 1920, mm =  2: DIM = 29
    yyyy = 1925, mm =  2: DIM = 28
    yyyy = 1930, mm =  2: DIM = 28
    yyyy = 1935, mm =  2: DIM = 28
    yyyy = 1940, mm =  2: DIM = 29
    yyyy = 1945, mm =  2: DIM = 28
    yyyy = 1950, mm =  2: DIM = 28
    yyyy = 1955, mm =  2: DIM = 28
    yyyy = 1960, mm =  2: DIM = 29
    yyyy = 1965, mm =  2: DIM = 28
    yyyy = 1970, mm =  2: DIM = 28
    yyyy = 1975, mm =  2: DIM = 28
    yyyy = 1980, mm =  2: DIM = 29
    yyyy = 1985, mm =  2: DIM = 28
    yyyy = 1990, mm =  2: DIM = 28
    yyyy = 1995, mm =  2: DIM = 28
    yyyy = 2000, mm =  2: DIM = 29
    yyyy = 2005, mm =  2: DIM = 28
    yyyy = 2010, mm =  2: DIM = 28
    yyyy = 2015, mm =  2: DIM = 28
    yyyy = 2020, mm =  2: DIM = 29
    yyyy = 2025, mm =  2: DIM = 28
    yyyy = 2030, mm =  2: DIM = 28
    yyyy = 2035, mm =  2: DIM = 28
    yyyy = 2040, mm =  2: DIM = 29
    yyyy = 2045, mm =  2: DIM = 28
    yyyy = 2050, mm =  2: DIM = 28
    yyyy = 2055, mm =  2: DIM = 28
    yyyy = 2060, mm =  2: DIM = 29
    yyyy = 2065, mm =  2: DIM = 28
    yyyy = 2070, mm =  2: DIM = 28
    yyyy = 2075, mm =  2: DIM = 28
    yyyy = 2080, mm =  2: DIM = 29
    yyyy = 2085, mm =  2: DIM = 28
    yyyy = 2090, mm =  2: DIM = 28
    yyyy = 2095, mm =  2: DIM = 28
    yyyy = 2100, mm =  2: DIM = 28
    

    This correctly considers 1900 and 2100 as non-leap years, but 2000 as a leap year.

    yyyy = 1900, mm =  1: DIM = 31
    yyyy = 1900, mm =  2: DIM = 28
    yyyy = 1900, mm =  3: DIM = 31
    yyyy = 1900, mm =  4: DIM = 30
    yyyy = 1900, mm =  5: DIM = 31
    yyyy = 1900, mm =  6: DIM = 30
    yyyy = 1900, mm =  7: DIM = 31
    yyyy = 1900, mm =  8: DIM = 31
    yyyy = 1900, mm =  9: DIM = 30
    yyyy = 1900, mm = 10: DIM = 31
    yyyy = 1900, mm = 11: DIM = 30
    yyyy = 1900, mm = 12: DIM = 31
    
    …
    
    yyyy = 2000, mm =  1: DIM = 31
    yyyy = 2000, mm =  2: DIM = 29
    yyyy = 2000, mm =  3: DIM = 31
    yyyy = 2000, mm =  4: DIM = 30
    yyyy = 2000, mm =  5: DIM = 31
    yyyy = 2000, mm =  6: DIM = 30
    yyyy = 2000, mm =  7: DIM = 31
    yyyy = 2000, mm =  8: DIM = 31
    yyyy = 2000, mm =  9: DIM = 30
    yyyy = 2000, mm = 10: DIM = 31
    yyyy = 2000, mm = 11: DIM = 30
    yyyy = 2000, mm = 12: DIM = 31