Tom Gould

Writing

Why the Solar Calendar has 13 months of 28 days

The rules behind the Solar Calendar, why the year starts on the first Monday after the spring equinox, and why the rebuild swapped special days for a leap week.

The Solar Calendar has 13 months, each exactly 28 days long. The year starts on the first Monday on or after the March equinox. This week I rebuilt it from scratch, and the rebuild changed one of its rules. Here’s how it works and why.

13 times 28

28 days is exactly four weeks, and 13 months of 28 days is 364 days, which is exactly 52 weeks. So every month is identical. The 1st is always a Monday and the 28th always a Sunday, in every month of every year. A date tells you its weekday without looking it up.

Every month is the same: 28 days, starting on a Monday. Filled days have passed this year; the sun marks today.

The idea isn’t new. Moses Cotsworth proposed a 13-month, 28-day calendar in the early 1900s, the International Fixed Calendar. One of the sets of month names in the app borrows his extra month, Sol.

The app is built around that sameness. In the month view the big numbers 1 to 28 never move, and only the small Gregorian dates and the month name change. The year view is a single table, one row for each day of the month and one column for each month, which only works because every month is the same shape.

Starting at the equinox

A calendar of 364 days falls behind the sun by about a day and a quarter each year, so something has to bring it back. I wanted the year tied to the seasons, so it starts at the spring equinox in March, when day and night are about the same length. And I wanted weeks never to break, so it starts on a Monday: the first Monday on or after the equinox.

The equinox happens at one instant, which falls on different dates in different time zones. The calendar takes the equinox’s date in UTC, so it’s the same everywhere.

The rule that changed

The first version, from 2025, kept extra days outside the weeks, like Cotsworth’s Year Day: an Equinox Day, and a Solstice Day in leap years. When I reviewed it, the model didn’t add up. If every year starts on a Monday, the gap between one year’s start and the next is always a whole number of weeks: 364 days or 371, never 365 or 366. So the special days had nowhere to go. “Year Day” landed on the first day of the next year, and in 371-day years six days had no solar date at all.

The rebuild accepts what the rules imply. A normal year is 13 months of 28 days. When the next first Monday after the equinox is 371 days away, the year ends with a seven-day leap week. That happens in 35 of the 200 years from 1900 to 2100, about one year in six. Solar 2026 is a normal year, from Monday 23 March 2026 to Sunday 21 March 2027. Solar 2028 ends with a leap week, 19 to 25 March 2029.

Iceland’s old calendar used the same fix: a 364-day year, with a leap week called sumarauki, “summer addition”, added every few years. The Old Norse set of month names in the app uses that name for the leap week.

Calculating the equinox

The first version read equinox dates from a JSON file, and it was wrong far too often. Checked against Jean Meeus’s algorithm, it disagreed in 67 of 201 years, usually by a day. In nine of those years the solar year started a week out.

The rebuild calculates the equinox instead, using the formulas in chapter 27 of Meeus’s Astronomical Algorithms. They’re accurate to about a minute and take microseconds. The tests cross-check them against the astronomy-engine library. With the equinox in hand, the start of the year is a few lines:

export function yearStart(year: number): DayNumber {
  const equinox = equinoxDay(year);
  return equinox + ((7 - weekday(equinox)) % 7);
}

export function yearLength(year: number): number {
  return yearStart(year + 1) - yearStart(year);
}

A year has a leap week exactly when yearLength is 371.

No arithmetic on Date objects

The old printed PDFs repeated 30 March 2025 and skipped 26 October 2025, the days the UK clocks change. That’s the classic bug of doing date arithmetic with JavaScript Date objects, which carry a time and a time zone.

So the new core never adds days to a Date. Every date is an integer: the number of days since 1 January 1970. Converting to and from year, month and day uses Howard Hinnant’s published algorithms, and the weekday is simple arithmetic:

/** 0 = Monday to 6 = Sunday. Day 0 (1970-01-01) was a Thursday. */
export function weekday(n: DayNumber): number {
  return (((n + 3) % 7) + 7) % 7;
}

A Date is only used to read the current time and to format times for display. Time zones and clock changes can’t shift a day, because there’s nothing for them to shift.

The same rules everywhere

The first version had two near-copied pages and a PHP port that gave a different answer to the same question. Now there’s one small TypeScript core with no DOM in it, and everything uses it: the month and year views, printing, the converter, and the Cloudflare Worker that builds the subscribable calendar feed and the “today” badge.

You can try it at solar.dealmobile.co.uk, and the code is on GitHub.