Skip to content

Why AI Calendars Default to the Wrong Start of Week

8 min read · updated August 11, 2026

Ask a model for a month view and you get seven column headers and a grid of dates. The dates are right. The columns start on the wrong day for roughly half the world, and the leading blank cells are therefore off by one to three positions — which puts every date in the month under the wrong weekday.

The grid is shifted by one column

This is the most damaging class of locale bug because it does not look like a bug. The calendar renders. Every number is present, in order, with the right count of days. The only thing wrong is which column each number sits in, and a user checking their own calendar against it will usually assume they misread it before assuming the software is broken.

The cause is that generated calendar code almost universally contains a literal like ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] and computes the leading offset as firstOfMonth.getDay(). Both halves encode a Sunday start. That is correct for a US reader and wrong for a German, French, Russian or Chinese one, and it is what a model produces by default because it is what the overwhelming majority of English tutorial code on the web does.

Where the answer actually lives

The first day of the week is not a property of a language. It is a property of a territory, and the canonical machine-readable source is the Unicode CLDR week data, specified in UTS #35, Part 4. That data has three fields per territory that matter here: firstDay, minDays and the weekend range. Only the first is about layout; minDays belongs to week numbering, which is a different bug with the same symptom.

In a browser or a modern Node runtime you can read it without shipping CLDR yourself. ECMA-402 exposes it on Intl.Locale:

const info = new Intl.Locale("en-US").getWeekInfo();
// { firstDay: 7, weekend: [6, 7], minimalDays: 1 }

new Intl.Locale("de-DE").getWeekInfo().firstDay; // 1
new Intl.Locale("ar-EG").getWeekInfo().firstDay; // 6

Two things about that return value catch people out. The numbering is ISO — 1 is Monday and 7 is Sunday — so a Sunday-first locale returns 7, not 0. And it disagrees deliberately with JavaScript’s own Date.prototype.getDay(), which returns 0 for Sunday. Mixing the two numbering schemes in one expression is the second most common way to get this wrong.

Sunday, Monday, Saturday, Friday

Four different answers are in active use. The CLDR default for territories without an explicit entry is Monday, which is also what ISO 8601 specifies, so Monday is both the largest group and the fallback.

  • Monday. Most of Europe, most of Africa, Russia, India, China, Australia and New Zealand, and anywhere CLDR has no override.
  • Sunday. The United States and Canada, Japan, South Korea, Brazil and much of Latin America, Israel, the Philippines, South Africa. This is a large group and it is the one English-speaking developers usually assume is universal.
  • Saturday. Much of the Middle East and North Africa — Egypt, Saudi Arabia, the UAE, Qatar, Kuwait, Bahrain, Jordan, Iraq, Algeria, Libya, Afghanistan. This is the group that is missed entirely by code written to toggle between two options, and toggling between Sunday and Monday is exactly what most “i18n” calendar props allow.
  • Friday. The Maldives is the case that proves the field really is territory data and not a two-value flag.
Territory assignments do change: several Gulf states moved their working week in recent years, and CLDR follows such changes in its twice-yearly releases. Read firstDay from the ICU or CLDR build you actually ship rather than copying a list into a constant.

ISO 8601 is a separate question

ISO 8601 defines the week as beginning on Monday. That is a fact about the interchange format, not a recommendation about display, and conflating the two produces a different wrong answer: a US-facing calendar rendered Monday-first because someone read that ISO says so.

The clean rule is that the wire format is ISO and the display is CLDR. A date stored or transmitted as 2026-W33-1 is unambiguous everywhere. The same week drawn for a reader in Cairo starts on a Saturday, and the two statements do not conflict because they are about different layers. If you need the ISO week number as well, that is its own set of rules and its own set of failures — see why the week number comes out wrong.

The off-by-one in the generated code

The specific line to look for is the calculation of how many blank cells precede the first of the month. Generated code says this:

// wrong for any locale that does not start on Sunday
const blanks = new Date(year, month, 1).getDay();

The correct version has to map both values into the same numbering and then take a modulus, because the difference can be negative:

// getDay(): 0 = Sunday .. 6 = Saturday
// getWeekInfo().firstDay: 1 = Monday .. 7 = Sunday
const firstDay = new Intl.Locale(locale).getWeekInfo().firstDay % 7; // Sun -> 0
const dow = new Date(year, month, 1).getDay();
const blanks = (dow - firstDay + 7) % 7;

The % 7 on firstDay converts ISO’s 7-for-Sunday to JavaScript’s 0-for-Sunday and leaves 1–6 untouched. The + 7 before the final modulus is what stops a Saturday-first locale producing a negative offset. Code that omits it works in testing because the tester was in a Monday or Sunday locale, and fails in Cairo.

The header row needs the same rotation, and it needs to come from the same source as the grid. A common half-fix rotates the headers with locale data while leaving the grid on getDay(), which produces a calendar that is not merely shifted but internally inconsistent — strictly worse than the original bug, because the labels now actively assert the wrong thing.

Fixing it properly

Ask for the weekday names from the same locale you asked for the first day, and never write them as a literal array:

const fmt = new Intl.DateTimeFormat(locale, { weekday: "short" });
// 1970-01-04 was a Sunday; walk forward from the locale's first day
const headers = Array.from({ length: 7 }, (_, i) =>
  fmt.format(new Date(Date.UTC(1970, 0, 4 + ((firstDay + i) % 7))))
);

When you prompt a model for calendar code, say the constraint explicitly: “the first day of the week must be read from locale data, not hard-coded; support Saturday-first locales.” Without that second clause you will frequently get a two-branch conditional that handles Sunday and Monday and silently treats everything else as Monday. With it, you generally get the modulus form above — the model knows the correct pattern, it simply does not reach for it unless the prompt implies more than two cases exist.