Search code examples
kotlindatedatetimemathcalendar

Understanding number of days in a month calculation


I found this code in a Gist, that calculates the last day in a month (or in other words: the number of days in a month):

private fun lastDayInMonth(month: Int, year: Int): Int {
    return if (month != 2) {
        31 - (month - 1) % 7 % 2
    } else {
        if (year and 3 == 0 && (year % 25 != 0 || year and 15 == 0)) {
            29
        } else {
            28
        }
    }
}

Can someone explain how and why this works? Which algorithm is that, does it have any name or well known author?


Solution

  • The rules for number of days in a month

    First let's look at what the rules are before turning to the code. They come in two parts:

    1. Ignoring that February has 28 or 29 days rather than 30, the months (a) alternate 31 then 30 days for seven months, reaching 31 days in July, then (b) restart the pattern, alternating 31 then 30 days for five months.

    2. To determine February, we need to know if it's a leap year. The rule for leap years is1:

      Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400.

      This is in three parts: a year is leap if divisible by 4, but not if divisible by 100, but is if divisible by 400.

    The code

    The code follows a similar approach.

    1. First, the code works out the days in a non-February month. It uses the expression (month - 1) % 7 % 2, which oscillates between 0 and 1, adjusting 31 to 30 days in appropriate months. % 2 does the regular alternation and % 7 "resets" for August and later (part (b) above).

    2. For leap years, the code follow the same three-part inclusion/exclusion approach. It does this by:

      1. Working out if the year is divisible by 4 by checking if year and 3 == 0, using the bitwise AND. To see this works, observe that a year is divisible by 4 if and only if it has 00 in its last two binary digits, and such numbers are the only such numbers which give 0 when compared to 3 under bitwise AND.

      2. Working out if the year is divisible by 100 if checking if it is divisible by 25. This must be the same as we know from 1. that the year is divisible by 4 if this part is to change the final result, and a number if divisible by 4 and 25 if and only if it is divisible by 100.

      3. Working out if the year is divisible by 400 by checking if it is divisible by 16 (using the bitwise AND method similarly to 1.). If this is true, and it is going to change the outcome of the expression, then it must also be the case that the year is divisible by 25. This means this is equivalent to working out if the year is divisible by 400, since a number is divisible by 400 if and only if it is divisible by both 25 and 16 (observe 25 x 16 = 400).

    Conclusion

    So, the calculation has expressed the month-day calculation fairly concisely. I doubt that the bitwise operations are any faster than a modulo operation though with a modern compiler.

    Which algorithm is that, does it have any name or well known author?

    It's not really an algorithm but expressing the well-known rules in code in a concise fashion.


    1 https://en.wikipedia.org/wiki/Gregorian_calendar