I think months from 0 in POSIX were intentional, not mistakes. The things you might want to do with an integer month number include:
1. Look up some attribute or property of the month in a table (number of days in the month in a non-leap year, name of the month, list of fixed holidays in the month, etc).
2. Determine if the month is before or after a different month.
3. Determine how many months one month is after or before another month.
4. Determine which month is N months before/after a given month.
5. Print the month number for a human.
In languages that use 0-based indexing:
#1 is more convenient with 0-based months.
#2-4 only need that the month numbers form an arithmetic progression, so are indifferent to where we start counting.
#5 is more convenient with 1-based months.
So it comes down to is it better to have your low level date functions require that you have to remember to subtract 1 whenever you do something from #1, or to add 1 whenever you do something from #5?
I'd expect most programs do a lot more #1 than #5, so the code is going to be cleaner if you go with 0-based months.
In Javascript's Date API, the month number is 0-based (January is 0), but the day number is 1-based (the first day of January is 1, not 0). That inconsistency is unexpected and makes it harder to use.
I'm glad most modern date APIs use 1-based numbers to represent the month and the day (not to mention the year from 1 AD onwards).
> 1. Look up some attribute or property of the month in a table (number of days in the month in a non-leap year, name of the month, list of fixed holidays in the month, etc).
The first two cases basically exist internally within the date-time library, so it's not a particularly common example for end users to be doing. It's also not particularly hard to do #1 with 1-based months, because you can either subtract one before indexing, or you can just have entry 0 be a null-ish value.
The thing is, month names already have an implicit numerical mapping: ask anyone, even most programmers, what number to assign to the month of January, and you're likely to get 1. So an integer representing a month name is going to be presumed to be 1-based unless documented otherwise, and having it return 0 instead is going to confuse more programmers than not.
In other words, the trade-off isn't "is the code going to be cleaner for this case or that case" but rather "do we make an API that is going to confuse everyone's first impression of it, or do we make the code marginally more complex in one specific scenario?"
> The thing is, month names already have an implicit numerical mapping: ask anyone, even most programmers, what number to assign to the month of January, and you're likely to get 1.
Couldn't you make a similar argument then that when representing English text, 'A' should be 1? That's the number most people would give when asked to assign numbers to the alphabet. Not many people are going to say 65 which is 'A' in ASCII and Unicode.
Programs are not people.
I generally prefer, and believe it leads to less bugs, to represent things in programs in the ways that best fit the operations that the program will do doing on them, translating between that representation and external representations upon input and output.
> Couldn't you make a similar argument then that when representing English text, 'A' should be 1?
It actually is!
>>> hex(ord('A'))
'0x41'
>>> hex(ord('a'))
'0x61'
The five-bit intuitive numerical mapping of the letter is prefixed with the bits 10 (for uppercase) or 11 (for lowercase). A similar thing happens with digits, where the four-bit intuitive numerical mapping of the digit is prefixed with the bits 011.
For letters, this leads to a "hole" at 0x40 and 0x60. Instead of making the letters zero-based (that is, 'A' being 0x40 and 'a' being 0x60), they decided to keep the intuitive mapping (which starts at 1), and fill the "hole" with a symbol.
#5 does not seem like something you should be writing anyway, there ought to be a standard library mechanism for that. So even if you do that often you might not need to care about it.
1. Look up some attribute or property of the month in a table (number of days in the month in a non-leap year, name of the month, list of fixed holidays in the month, etc).
2. Determine if the month is before or after a different month.
3. Determine how many months one month is after or before another month.
4. Determine which month is N months before/after a given month.
5. Print the month number for a human.
In languages that use 0-based indexing:
#1 is more convenient with 0-based months.
#2-4 only need that the month numbers form an arithmetic progression, so are indifferent to where we start counting.
#5 is more convenient with 1-based months.
So it comes down to is it better to have your low level date functions require that you have to remember to subtract 1 whenever you do something from #1, or to add 1 whenever you do something from #5?
I'd expect most programs do a lot more #1 than #5, so the code is going to be cleaner if you go with 0-based months.