DEV Community

JKC
JKC

Posted on

Stop Hardcoding Sunday as Day One. Intl.Locale Has getWeekInfo()

TL;DR: new Intl.Locale("en-GB").getWeekInfo() tells you the week starts on Monday and the weekend is Saturday and Sunday. With Firefox 153, the whole Intl.Locale info family is Baseline Newly available, so you can delete that hand-rolled "first day of week" table.

The line nobody wants to touch

Every date picker has it somewhere.

const FIRST_DAY = 0; // Sunday. Probably. Ask product.
Enter fullscreen mode Exit fullscreen mode

Then a user in London opens the calendar and the week starts on the wrong day. Someone adds if (locale === "en-GB") ..., then a lookup table, then a ticket about Israel, where the weekend is Friday and Saturday. The table grows. Nobody is sure where the data came from.

The browser already ships that data. It has been sitting in the Unicode CLDR tables that power Intl.DateTimeFormat all along. Now there is a standard way to ask for it.

What it is

Intl.Locale has seven get methods that answer "how does this locale do things?":

Method What you get back
getWeekInfo() { firstDay, weekend }
getHourCycles() Hour cycles such as ["h12"] or ["h23"]
getTextInfo() { direction: "ltr" } or { direction: "rtl" }
getTimeZones() IANA time zones for the locale's region
getCalendars() Calendars in use, most preferred first
getCollations() Sort orders in use
getNumberingSystems() Digit systems in use

They came from the TC39 Intl Locale Info proposal, which is now Stage 4 and part of ECMA-402.

A minimal example

for (const tag of ["en-US", "en-GB", "he-IL", "ar-AF", "fa-IR"]) {
  console.log(tag, new Intl.Locale(tag).getWeekInfo());
}
Enter fullscreen mode Exit fullscreen mode

Run in Node 24.11:

en-US { firstDay: 7, weekend: [ 6, 7 ] }
en-GB { firstDay: 1, weekend: [ 6, 7 ] }
he-IL { firstDay: 7, weekend: [ 5, 6 ] }
ar-AF { firstDay: 6, weekend: [ 4, 5 ] }
fa-IR { firstDay: 6, weekend: [ 5 ] }
Enter fullscreen mode Exit fullscreen mode

Days are numbered 1 for Monday through 7 for Sunday. Notice fa-IR: a one-day weekend. Your two-element array assumption just left the chat.

The siblings work the same way:

new Intl.Locale("en-US").getHourCycles(); // ["h12"]
new Intl.Locale("en-GB").getHourCycles(); // ["h23"]
new Intl.Locale("ar").getTextInfo();      // { direction: "rtl" }
new Intl.Locale("th-TH").getCalendars();  // ["buddhist", "gregory"]
Enter fullscreen mode Exit fullscreen mode

A realistic example: a calendar header

Here is a week header that starts on the right day and marks the weekend, with no lookup table. The day labels are in English so the output stays readable here; drop the second argument to get them in the user's language.

// firstDay and weekend use 1 = Monday ... 7 = Sunday.
// Date#getDay() uses 0 = Sunday ... 6 = Saturday.
const isoDay = (date) => date.getDay() || 7;

function weekHeader(tag, labels = tag) {
  const locale = new Intl.Locale(tag);
  const { firstDay, weekend } = locale.getWeekInfo();
  const fmt = new Intl.DateTimeFormat(labels, { weekday: "short", timeZone: "UTC" });

  // 2024-01-01 was a Monday, so day n (1-7) is January n.
  return Array.from({ length: 7 }, (_, i) => {
    const day = ((firstDay - 1 + i) % 7) + 1;
    const label = fmt.format(new Date(Date.UTC(2024, 0, day)));
    return weekend.includes(day) ? `[${label}]` : label;
  }).join(" ");
}

function isWeekend(date, tag) {
  return new Intl.Locale(tag).getWeekInfo().weekend.includes(isoDay(date));
}

for (const tag of ["en-US", "en-GB", "he-IL", "fa-IR"]) {
  console.log(tag.padEnd(6), weekHeader(tag, "en"));
}

const friday = new Date(2026, 9, 23); // October 23, 2026
console.log(friday.toDateString(), "en-US:", isWeekend(friday, "en-US"), "he-IL:", isWeekend(friday, "he-IL"));
Enter fullscreen mode Exit fullscreen mode

Output from Node 24.11:

en-US  [Sun] Mon Tue Wed Thu Fri [Sat]
en-GB  Mon Tue Wed Thu Fri [Sat] [Sun]
he-IL  Sun Mon Tue Wed Thu [Fri] [Sat]
fa-IR  Sat Sun Mon Tue Wed Thu [Fri]
Fri Oct 23 2026 en-US: false he-IL: true
Enter fullscreen mode Exit fullscreen mode

Same Friday, two answers. Both correct.

The same idea fixes time pickers: getHourCycles()[0] tells you whether to show an AM/PM toggle (h12) or a 0 to 23 clock (h23), and getTextInfo().direction tells you which way to lay out the arrows.

Can I use it?

Yes. The feature is Baseline Newly available since July 21, 2026, when Firefox 153 shipped all seven methods. Chrome and Edge have had the method form since 130, and Safari since 17. Node has it from 24.0.

Older Chrome (99 to 129) and Safari 15.4 to 16 exposed the same data as getters named weekInfo, hourCycles and so on. If you need those users, feature-detect:

const weekInfo = (locale) => locale.getWeekInfo?.() ?? locale.weekInfo;
Enter fullscreen mode Exit fullscreen mode

Gotchas

  1. Days are ISO, not getDay(). firstDay uses 1 for Monday and 7 for Sunday, while Date#getDay() uses 0 for Sunday. Convert with date.getDay() || 7, or enjoy a calendar that is off by one only on Sundays.

  2. minimalDays is gone. MDN's getWeekInfo() page still shows a minimalDays property, but it was dropped from the proposal. MDN's own compat data says Chrome removed it in 136, and Node 24.11 does not return it. Do not build week numbering on it.

  3. The weekend is not always two days, or a Saturday. fa-IR returns [5], and ar-AF returns [4, 5]. Use weekend.includes(day), never day >= 6.

  4. getTimeZones() needs a region. new Intl.Locale("en-AU").getTimeZones() returned 12 zones in Node, but new Intl.Locale("en").getTimeZones() returned undefined. Handle the missing case.

  5. Unicode extensions win. In Node, new Intl.Locale("en-US-u-fw-mon").getWeekInfo().firstDay is 1, and en-US-u-hc-h23 gives ["h23"]. That is handy for a "week starts on" user setting, but it means the result is not always the region default.

  6. Each call returns a new object. locale.getWeekInfo() === locale.getWeekInfo() is false. That is exactly why the spec moved from getters to methods, so cache the result if you call it in a render loop.

Wrap-up

The week-start table in your codebase was always a guess about data the browser already had. Find your FIRST_DAY constant, swap it for getWeekInfo(), and let CLDR take the tickets.

Sources

This article was written with the help of AI and checked against the sources above.

Top comments (0)