date format localization

Localizing Numbers and Dates with Intl.NumberFormat and Intl.DateTimeFormat

|

Read this article in Arabic ➜ Word count: ~1900 • Reading time: 9 minutes Localizing Numbers and Dates with Intl Tell JavaScript where your user is coming from — and it will handle the rest Note: This article stands on its own and can be read without any prerequisites, but if you’d like to understand…

Word count: ~1900 • Reading time: 9 minutes

Localizing Numbers and Dates with Intl

Tell JavaScript where your user is coming from — and it will handle the rest


Note: This article stands on its own and can be read without any prerequisites, but if you’d like to understand the difference between the two numeral systems and how fonts affect their rendering, start with Arabic vs. Indian Digits and Fonts.

In this article from Zy Yazan Platform, we get to the heart of the matter: instead of writing a condition for every country and maintaining manual conversion tables, JavaScript gives you a ready-made API that already knows how a Saudi user writes their date, how a Moroccan reads a number, and what the currency symbol looks like in the UAE. That API is called Intl.

JavaScript code and browser interaction

Part One: What Is the Intl API — and What Are Its Limits?

Intl is a global object built into JavaScript since 2012, supported in all modern browsers and Node.js environments. No external libraries required.

Intl includes several constructors; the most relevant for our purposes are:

  • Intl.NumberFormat — for formatting numbers, currencies, and percentages
  • Intl.DateTimeFormat — for formatting dates and times
  • Intl.Collator — for comparing and sorting text alphabetically (outside the scope of this article)

Before diving into examples, one important boundary to establish: Intl knows how to display numbers, but it doesn’t know what to display. Storing currencies, calculating taxes, and determining the right currency for a given user — that’s the job of your database and application design, and it’s the subject of our third article.

Part Two: Intl.NumberFormat — Numbers by Region

Basic usage is straightforward. You create a formatter tied to a locale, then call format() on any number:

// Same number — three different outputs
const number = 1234567.89;

// Saudi Arabia: Arabic-Indic digits, Arabic thousands separator
const saudiFormat = new Intl.NumberFormat('ar-SA');
console.log(saudiFormat.format(number));
// ← ١٬٢٣٤٬٥٦٧٫٨٩

// Morocco: Latin digits, Western separators
const moroccanFormat = new Intl.NumberFormat('ar-MA');
console.log(moroccanFormat.format(number));
// ← 1.234.567,89

// United States for comparison
const usFormat = new Intl.NumberFormat('en-US');
console.log(usFormat.format(number));
// ← 1,234,567.89

Notice how ar-SA and ar-MA — both “Arabic” locales — produce completely different formats. That’s exactly what we discussed in the previous article about the varying numeric conventions across the Arab world.

Advanced Formatting Options

Intl.NumberFormat accepts an options object as a second argument for fine-grained control:

// Control decimal places
const preciseFormat = new Intl.NumberFormat('ar-SA', {
  minimumFractionDigits: 2,  // never fewer than 2 decimal places
  maximumFractionDigits: 2,  // never more than 2 decimal places
});
console.log(preciseFormat.format(1500));
// ← ١٬٥٠٠٫٠٠

// Percentages
const percentFormat = new Intl.NumberFormat('ar-SA', {
  style: 'percent',
  maximumFractionDigits: 1,
});
console.log(percentFormat.format(0.157));
// ← ١٥٫٧٪

// Large numbers in compact notation
const compactFormat = new Intl.NumberFormat('ar-SA', {
  notation: 'compact',
  compactDisplay: 'short',
});
console.log(compactFormat.format(1500000));
// ← ١٫٥ مليون

Currency Formatting

This is where Intl.NumberFormat really shines. Passing style: 'currency' places the currency symbol automatically in the correct position — before or after the number — and sets the standard decimal places for that currency:

const amount = 1250;

// Saudi Riyal
const sarFormat = new Intl.NumberFormat('ar-SA', {
  style: 'currency',
  currency: 'SAR',
});
console.log(sarFormat.format(amount));
// ← ر.س ١٬٢٥٠٫٠٠

// UAE Dirham
const aedFormat = new Intl.NumberFormat('ar-AE', {
  style: 'currency',
  currency: 'AED',
});
console.log(aedFormat.format(amount));
// ← د.إ.‏ ١٬٢٥٠٫٠٠

// Egyptian Pound
const egpFormat = new Intl.NumberFormat('ar-EG', {
  style: 'currency',
  currency: 'EGP',
});
console.log(egpFormat.format(amount));
// ← ج.م.‏ ١٬٢٥٠٫٠٠

What you’re seeing isn’t magic — it’s data stored in a massive repository called CLDR (Common Locale Data Repository), funded by major tech companies and updated regularly to reflect the conventions of every region on earth.

Part Three: Intl.DateTimeFormat — Dates Without Manual Translation

Dates are more complex than numbers for two reasons: first, the order of day, month, and year varies across cultures. Second, the Arab world uses two calendars — Gregorian and Hijri — and some contexts require showing both.

Basic Usage

const date = new Date(2026, 4, 15); // May 15, 2026

// Saudi Arabia — full date in Arabic
const saudiDate = new Intl.DateTimeFormat('ar-SA', {
  dateStyle: 'full',
});
console.log(saudiDate.format(date));
// ← الجمعة، ١٧ ذو القعدة ١٤٤٧ هـ

// UAE — day/month/year Gregorian
const uaeDate = new Intl.DateTimeFormat('ar-AE', {
  day: '2-digit',
  month: '2-digit',
  year: 'numeric',
});
console.log(uaeDate.format(date));
// ← ١٥/٠٥/٢٠٢٦

// Egypt — medium style
const egyptDate = new Intl.DateTimeFormat('ar-EG', {
  dateStyle: 'medium',
});
console.log(egyptDate.format(date));
// ← ١٥ مايو ٢٠٢٦

Notice ar-SA: the browser switched automatically to the Hijri calendar because that’s the default setting for the Saudi locale in CLDR data.

Controlling the Calendar

You can force a specific calendar regardless of the locale default:

const date = new Date(2026, 4, 15);

// Force Hijri calendar for any locale
const hijriFormat = new Intl.DateTimeFormat('ar-SA-u-ca-islamic', {
  dateStyle: 'long',
});
console.log(hijriFormat.format(date));
// ← ١٧ ذو القعدة ١٤٤٧ هـ

// Force Gregorian calendar even in Saudi Arabia
const gregorianInSaudi = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', {
  dateStyle: 'long',
});
console.log(gregorianInSaudi.format(date));
// ← ١٥ مايو ٢٠٢٦

// Show both dates together on an invoice
function dualDate(date) {
  const hijri = new Intl.DateTimeFormat('ar-SA-u-ca-islamic', {
    dateStyle: 'medium'
  }).format(date);
  const gregorian = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', {
    dateStyle: 'medium'
  }).format(date);
  return `${hijri} — ${gregorian}`;
}
console.log(dualDate(new Date(2026, 4, 15)));
// ← ١٧ ذو القعدة ١٤٤٧ هـ — ١٥ مايو ٢٠٢٦

The dualDate function will appear in our workshop project (article four) when we build the complete invoice system.

Time Formatting

const now = new Date();

// 12-hour format
const time12 = new Intl.DateTimeFormat('ar-SA', {
  hour: 'numeric',
  minute: '2-digit',
  hour12: true,
});
console.log(time12.format(now));
// ← ٣:٤٥ م

// 24-hour format (more common in technical contexts)
const time24 = new Intl.DateTimeFormat('ar-EG', {
  hour: '2-digit',
  minute: '2-digit',
  hour12: false,
});
console.log(time24.format(now));
// ← ١٥:٤٥

Part Four: Auto-Detecting the User’s Locale

Until now we’ve been passing the locale manually. In real applications, you usually want to read it from the browser or system settings:

// Read the primary browser language
const userLocale = navigator.language; // e.g. "ar-SA" or "ar-EG"

// Read all preferred languages in order
const userLocales = navigator.languages; // e.g. ["ar-SA", "ar", "en-US"]

// Use the user's locale directly
const autoFormat = new Intl.NumberFormat(userLocale, {
  style: 'currency',
  currency: 'SAR', // currency is NOT auto-detected — you decide this
});
console.log(autoFormat.format(1500));

// Fallback: if language is unknown, default to 'ar'
const safeLocale = navigator.language || 'ar';

One important distinction: Intl auto-detects number formatting, but the appropriate currency is still a business decision you make. A user in Saudi Arabia might be paying in dollars; a user in Egypt might pay in euros. This is subtle but critical in any payment system.

Part Five: Performance — When to Create and When to Reuse Intl Objects

Creating an Intl.NumberFormat or Intl.DateTimeFormat object has a computational cost. If you’re formatting thousands of numbers in a table, don’t create a new object for every cell:

// ❌ Slow — creates a new object for every number
prices.forEach(price => {
  const formatted = new Intl.NumberFormat('ar-SA', {
    style: 'currency',
    currency: 'SAR'
  }).format(price);
});

// ✅ Right — create once, reuse many times
const sarFormatter = new Intl.NumberFormat('ar-SA', {
  style: 'currency',
  currency: 'SAR'
});

prices.forEach(price => {
  const formatted = sarFormatter.format(price);
});

This principle — create once, use many times — applies to all Intl objects and makes a noticeable difference in long tables and lists.

Part Six: Quick Reference — Main Arabic Locales

Country Locale Code Numeral System Default Calendar Currency Code
Saudi Arabia ar-SA Arabic-Indic (١٢٣) Hijri SAR
United Arab Emirates ar-AE Arabic-Indic (١٢٣) Gregorian AED
Egypt ar-EG Arabic-Indic (١٢٣) Gregorian EGP
Morocco ar-MA Latin (123) Gregorian MAD
Algeria ar-DZ Latin (123) Gregorian DZD
Kuwait ar-KW Arabic-Indic (١٢٣) Gregorian KWD

This table is a practical reference you’ll reach for when configuring your system. Notice that Saudi Arabia is the only country here that defaults to the Hijri calendar in CLDR data — which explains why a Saudi invoice system needs special date handling.

Part Seven: Intl.NumberFormat().formatToParts() — When You Need More Control

Sometimes you don’t want the full formatted string — you want access to its individual parts so you can style them differently. For example: displaying the currency symbol in a different color, or rendering the decimal portion in a smaller font size:

const formatter = new Intl.NumberFormat('ar-SA', {
  style: 'currency',
  currency: 'SAR',
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);
/*
[
  { type: 'currency',  value: 'ر.س' },
  { type: 'literal',   value: ' '   },
  { type: 'integer',   value: '١٬٢٣٤' },
  { type: 'decimal',   value: '٫'   },
  { type: 'fraction',  value: '٥٦'  }
]
*/

// Example: build custom HTML from the parts
function styledCurrency(amount, locale, currency) {
  const parts = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
  }).formatToParts(amount);

  return parts.map(part => {
    if (part.type === 'currency') {
      return `<span class="currency-symbol">${part.value}</span>`;
    }
    if (part.type === 'fraction') {
      return `<span class="currency-fraction">${part.value}</span>`;
    }
    return part.value;
  }).join('');
}

This advanced technique is especially useful in invoice and financial report interfaces where visual distinction between parts of a number matters.

Date format localization across different regions


Summary and Next Step

The Intl API solves the visible problem — how to display a number — but it doesn’t solve the hidden one: how are numbers, currencies, and percentages stored in the database? Where does the currency symbol go — before the number or after? And should a percentage be stored as 0.15 or 15? Those are the dilemmas waiting in our next article.

Recommended next step:

Continue with: Tables, Currencies, and Percentage Dilemmas


References and Sources:

  1. MDN Docs — Intl.NumberFormat: developer.mozilla.org
  2. MDN Docs — Intl.DateTimeFormat: developer.mozilla.org
  3. CLDR Repository — Locale Data: cldr.unicode.org
  4. Browser support for the Intl API: caniuse.com — Intl.NumberFormat
Zy Yazan Platform © 2026

Localization Series

Financial Data Localization Guide — 4 Articles

Article 1
1 / 4

Arabic vs. Indian Digits and Fonts: What’s the Difference — and Why Does It Matter in Code?

A deep look into numerical representations, font rendering variations, and their structural impacts on programming languages.

Article 2
2 / 4

Localizing Numbers and Dates with Intl.NumberFormat and Intl.DateTimeFormat

Leveraging native standardization libraries to localize dates, times, and numeric structures based on geographic locales.

Article 3
3 / 4

Tables, Currencies, and Percentages: What’s Really Happening Behind the Screen?

Behind-the-scenes exploration of structural challenges, field calculations, and display parameters for sensitive financial values.

Article 4
4 / 4

Workshop Project: A Multi-Country Invoice System for Saudi Arabia, UAE, and Egypt

Practical integration guide for aligning local accounting parameters, invoice generation rules, and layout logic for targeted Arab regions.

Series: Financial Data Localization Guide — 4 Articles  |  Zyyazan Platform © 2026

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *