Okay, I put WAY too much time into researching this, and I almost know what's happening. ALMOST.

First of all, JavaScript does this for legacy reasons. In C's <time.h> we have `struct tm`, which does the same thing.

But why?

We see the rabbit hole, time to jump in. https://twitter.com/garybernhardt/status/1329178764757045248
If we look at _The C Programming Language_, we see that all of them start from 0 EXCEPT `tm_mday`, the day of month, which starts from 1. This tells me the interesting story is behind `tm_mday`. Why's it inconsistent? The book doesn't say why.
But this is the second edition; the first edition doesn't have it at all! The 2nd edition adds that time.h comes from the ANSI standard. So I hunted down the C89 standard and... no explanation. We'll have to dig deeper.
Now there's something interesting about C89. There's 17 years between C's debut and its first standard, leaving lots of time for compilers to diverge. C89 had to be backwards compatible with all of them, leading to all the undefined behavior. Could that be our culprit here?
We're not *quite* looking at compilers, though. <time.h> is a POSIX library, so we actually want to compare the library across operating systems to find incompatibilities. Then I stumbled into the Unix Tree, which let me sift through a bunch of Unices. https://minnie.tuhs.org/cgi-bin/utree.pl
To my surprise, pretty much all of the divergent OSes use the same structure for `tm`. So making days of the month 1-indexed comes really, really early on. So I kept going up the chain to find the first Unix which didn't have this.
I stopped at Unix 5, from 1974. It didn't even *have* a <time.h> file! In its <ctime.c> file, it stores the date as an array of integer values. The `tm` struct only starts appearing in Unix 7.

Unix 5 ALSO has 1-indexed days of month.
In fact, it does something really weird. After calculating the month in d1, d0 is now the 0-indexed day-of-month. Then they store d0 PLUS ONE. It's MORE WORK for them to store a 1-indexed day-of-month! This is super weird.
Can we go further back in the history? Unfortunately, the trail runs cold here. We don't have copies of Unix 4 or Unix 3 to compare to, and Unix 2 doesn't even track this. This is as far back as we can go, at least along the Unix line.

But wasn't Unix inspired by Multics?
I found the Multics source at http://web.mit.edu/multics-history/source/Multics/ldd/include/time_value.incl.pl1. A picture of the `time_value` code is attached. They store everything 1-indexed. So no leads there.

Okay, back to Unix 5. Maybe we can look at how it *used* the datetime.
`ctime(t)` just returned the `asctime`, or ascii time, of the time array. I attached the code; highlighted line is where they copy over the day of month. To get the month and day of week, they multiple a value by an offset and copy over three bytes. Very tightly written.
Notice for copying the day of week they compute `3*t[6]`, while for copying the month they first do `tp = &t[4]` and then compute `*tp*3`. That puts the `tp` pointer in just the right memory location that they can retrieve day-of-month and HMS just by decrementing it.
This all suggests an incredible need to optimize everything. Every clock cycle matters, every byte counts. Unsurprising, given it was the early 1970s and computers might have a few kilobytes of RAM. All of the months, written out in full, would take about 75 bytes
Interestingly, they kept the same optimization in V7, even after moving to structs. I guess it worked because of memory layouts? Later OSes just did everything explicitly, prob because resources were more plentiful. https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/libc/gen/ctime.c
So that's my best guess: the programmers were working with constrained resources and could optimize `asctime` tricky pointer arithmetic on the month and day-of-week, so made them 0-indexed. Day-of-month is just for displaying to the user, so is 1-indexed.
Some evidence in favor of this: `asctime` didn't return the day of the year, so the user never saw it. But the time array stores it to calculate when daylight savings starts. So the day-of-year was 0-indexed.
What's the main takeaway of all this? Dunno. Maybe "many weird footguns in programs come from bizarre legacy constraints." Or maybe "your grandchildren will rue the day you optimized something."
For the record, this took me two hours to research and another hour to write up. That's like 0.0005% of my precious time on earth consumed by the weird rabbit hole. History is bad for me
Okay, this is blowing up, time to shill. I don't have a soundcloud but do have a newsletter, where I do other deep dives into history but also formal methods and the philosophy of software engineering https://buttondown.email/hillelwayne/ 
You can follow @hillelogram.
Tip: mention @twtextapp on a Twitter thread with the keyword “unroll” to get a link to it.

Latest Threads Unrolled:

By continuing to use the site, you are consenting to the use of cookies as explained in our Cookie Policy to improve your experience.