Date Formatting โ Locale-Aware Date and Time Display
In this tutorial, you will learn about Date Formatting. We cover key concepts, practical examples, and best practices to help you master this topic.
Locale-aware date formatting displays dates and times according to cultural conventions using the Intl.DateTimeFormat API for global applications.
What You'll Learn
By the end of this tutorial, you'll understand how to use Intl.DateTimeFormat for locale-aware date and time formatting, how to handle different calendar systems, how to format relative time, and how to integrate date formatting with i18n libraries.
Why It Matters
Date formats vary dramatically worldwide. "03/04/2026" is March 4th in the US, April 3rd in Europe. Chinese uses year-month-day. Islamic calendars are used in many Muslim-majority countries. Showing dates in the wrong format confuses users and can lead to misinterpretation of deadlines, events, and schedules.
Real-World Use
A global scheduling platform displays "Monday, December 31, 2026" to US users, "31. Dezember 2026" to German users, "2026ๅนด12ๆ31ๆฅ" to Japanese users, and "31/12/2026" to French users. The same Date object is passed to Intl.DateTimeFormat with the user's locale, and all formatting is handled automatically.
Date Format Variations
graph LR
A[Date Display
by Locale] --> B[US: 12/31/2026
Month/Day/Year]
A --> C[UK: 31/12/2026
Day/Month/Year]
A --> D[Germany: 31.12.2026
Day.Month.Year]
A --> E[Japan: 2026/12/31
Year/Month/Day]
A --> F[China: 2026ๅนด12ๆ31ๆฅ
Year Month Day]
A --> G[Saudi Arabia: 31/12/2026
Umm al-Qura calendar]
style A fill:#4a90d9,color:#fff
Intl.DateTimeFormat Basics
// i18n/date-formatting.js โ Intl.DateTimeFormat usage
class DateFormatter {
constructor(locale = 'en-US') {
this.locale = locale;
}
// Predefined format styles
full(date) {
return new Intl.DateTimeFormat(this.locale, {
dateStyle: 'full'
}).format(date);
}
long(date) {
return new Intl.DateTimeFormat(this.locale, {
dateStyle: 'long'
}).format(date);
}
medium(date) {
return new Intl.DateTimeFormat(this.locale, {
dateStyle: 'medium'
}).format(date);
}
short(date) {
return new Intl.DateTimeFormat(this.locale, {
dateStyle: 'short'
}).format(date);
}
// Time formatting
time(date, timeStyle = 'short') {
return new Intl.DateTimeFormat(this.locale, {
timeStyle: timeStyle
}).format(date);
}
// Combined date + time
dateTime(date, dateStyle = 'long', timeStyle = 'short') {
return new Intl.DateTimeFormat(this.locale, {
dateStyle,
timeStyle
}).format(date);
}
// Custom format
custom(date, options) {
return new Intl.DateTimeFormat(this.locale, options).format(date);
}
// Weekday name
weekday(date, format = 'long') {
return new Intl.DateTimeFormat(this.locale, {
weekday: format
}).format(date);
}
// Month name
month(date, format = 'long') {
return new Intl.DateTimeFormat(this.locale, {
month: format
}).format(date);
}
// Demo all formats for a date
demo(date = new Date()) {
console.log(`Locale: ${this.locale}`);
console.log(`Full: ${this.full(date)}`);
console.log(`Long: ${this.long(date)}`);
console.log(`Medium: ${this.medium(date)}`);
console.log(`Short: ${this.short(date)}`);
console.log(`Time: ${this.time(date)}`);
console.log(`Weekday: ${this.weekday(date)}`);
}
}
// Demo across locales
const date = new Date('2026-12-31T15:30:00');
console.log('=== US English ===');
new DateFormatter('en-US').demo(date);
console.log('\\n=== UK English ===');
new DateFormatter('en-GB').demo(date);
console.log('\\n=== German ===');
new DateFormatter('de-DE').demo(date);
console.log('\\n=== Japanese ===');
new DateFormatter('ja-JP').demo(date);
console.log('\\n=== French ===');
new DateFormatter('fr-FR').demo(date);
console.log('\\n=== Arabic (Saudi Arabia) ===');
new DateFormatter('ar-SA').demo(date);
Relative Time Formatting
// i18n/relative-time.js โ Relative time with Intl.RelativeTimeFormat
class RelativeTimeFormatter {
constructor(locale = 'en-US') {
this.locale = locale;
this.rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
}
// Format a date relative to now
format(date, base = new Date()) {
const diffMs = date.getTime() - base.getTime();
const diffSec = Math.round(diffMs / 1000);
const diffMin = Math.round(diffSec / 60);
const diffHour = Math.round(diffMin / 60);
const diffDay = Math.round(diffHour / 24);
const diffWeek = Math.round(diffDay / 7);
const diffMonth = Math.round(diffDay / 30);
const diffYear = Math.round(diffDay / 365);
// Choose the best unit
if (Math.abs(diffSec) < 60) {
return this.rtf.format(diffSec, 'second');
} else if (Math.abs(diffMin) < 60) {
return this.rtf.format(diffMin, 'minute');
} else if (Math.abs(diffHour) < 24) {
return this.rtf.format(diffHour, 'hour');
} else if (Math.abs(diffDay) < 7) {
return this.rtf.format(diffDay, 'day');
} else if (Math.abs(diffWeek) < 5) {
return this.rtf.format(diffWeek, 'week');
} else if (Math.abs(diffMonth) < 12) {
return this.rtf.format(diffMonth, 'month');
} else {
return this.rtf.format(diffYear, 'year');
}
}
// Format with "numeric: auto" โ uses "yesterday" / "tomorrow" where possible
formatAuto(date, base = new Date()) {
const rtfAuto = new Intl.RelativeTimeFormat(this.locale, {
numeric: 'auto'
});
const diffDay = Math.round((date.getTime() - base.getTime()) / (1000 * 60 * 60 * 24));
if (Math.abs(diffDay) <= 1) {
return rtfAuto.format(diffDay, 'day');
}
return this.format(date, base);
}
// Demo
demo() {
const now = new Date();
const times = [
new Date(now.getTime() - 30000), // 30 seconds ago
new Date(now.getTime() - 5 * 60000), // 5 minutes ago
new Date(now.getTime() - 2 * 3600000), // 2 hours ago
new Date(now.getTime() - 86400000), // yesterday
new Date(now.getTime() + 86400000), // tomorrow
new Date(now.getTime() + 7 * 86400000), // next week
];
times.forEach(t => {
console.log(` ${this.formatAuto(t, now)}`);
});
}
}
console.log('=== English Relative Time ===');
new RelativeTimeFormatter('en-US').demo();
console.log('\\n=== French Relative Time ===');
new RelativeTimeFormatter('fr-FR').demo();
console.log('\\n=== Arabic Relative Time ===');
new RelativeTimeFormatter('ar-SA').demo();
console.log('\\n=== Japanese Relative Time ===');
new RelativeTimeFormatter('ja-JP').demo();
Custom Date Format Options
// i18n/custom-date-formats.js โ Fine-grained date formatting
const date = new Date('2026-12-31T15:30:45');
// Individual components
const locales = ['en-US', 'de-DE', 'ja-JP', 'ar-SA'];
locales.forEach(locale => {
console.log(`\\nLocale: ${locale}`);
// Weekday
console.log(` Weekday (long): ${new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(date)}`);
console.log(` Weekday (short): ${new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(date)}`);
// Month
console.log(` Month (long): ${new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)}`);
console.log(` Month (short): ${new Intl.DateTimeFormat(locale, { month: 'short' }).format(date)}`);
// Day
console.log(` Day (numeric): ${new Intl.DateTimeFormat(locale, { day: 'numeric' }).format(date)}`);
console.log(` Day (2-digit): ${new Intl.DateTimeFormat(locale, { day: '2-digit' }).format(date)}`);
// Year
console.log(` Year (numeric): ${new Intl.DateTimeFormat(locale, { year: 'numeric' }).format(date)}`);
// Time
console.log(` Hour (2-digit): ${new Intl.DateTimeFormat(locale, { hour: '2-digit', hour12: true }).format(date)}`);
console.log(` Hour (24-hour): ${new Intl.DateTimeFormat(locale, { hour: '2-digit', hour12: false }).format(date)}`);
console.log(` Minute: ${new Intl.DateTimeFormat(locale, { minute: '2-digit' }).format(date)}`);
});
// Calendar systems
const islamic = new Intl.DateTimeFormat('ar-SA', {
dateStyle: 'full',
calendar: 'islamic-umalqura'
});
console.log('\\nIslamic Calendar (ar-SA):', islamic.format(date));
const japanese = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
dateStyle: 'full'
});
console.log('Japanese Calendar:', japanese.format(date));
const buddhist = new Intl.DateTimeFormat('th-TH-u-ca-buddhist', {
dateStyle: 'full'
});
console.log('Buddhist Calendar (th-TH):', buddhist.format(date));
Integration with i18next
// i18n/i18next-dates.js โ Date formatting with i18next formatters
import i18next from 'i18next';
// Register custom formatters
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
'published': 'Published: {date, date}',
'published_long': 'Published: {date, datetime}',
'relative': 'Posted {date, relative}',
'event': 'Event on {date, date} at {date, time}'
}
}
},
interpolation: {
format: (value, format, lng) => {
if (value instanceof Date) {
if (format === 'date') {
return new Intl.DateTimeFormat(lng, { dateStyle: 'long' }).format(value);
}
if (format === 'datetime') {
return new Intl.DateTimeFormat(lng, {
dateStyle: 'long',
timeStyle: 'short'
}).format(value);
}
if (format === 'time') {
return new Intl.DateTimeFormat(lng, { timeStyle: 'short' }).format(value);
}
if (format === 'relative') {
const rtf = new Intl.RelativeTimeFormat(lng, { numeric: 'auto' });
const diff = Math.round((value.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
if (Math.abs(diff) <= 1) return rtf.format(diff, 'day');
return new Intl.DateTimeFormat(lng, { dateStyle: 'medium' }).format(value);
}
return value.toLocaleDateString(lng);
}
return value;
}
}
});
Common Mistakes
- Assuming MM/DD/YYYY is universal. The US is one of the few countries using month/day/year. Most of the world uses day/month/year or year/month/day. Always use Intl.DateTimeFormat rather than hardcoding format strings.
- Not considering calendar systems. Arabic locales may use the Islamic calendar. Thai uses the Buddhist calendar. Japanese uses era-based years (Reiwa 8). Intl.DateTimeFormat handles these when you pass the correct locale.
- Forgetting about 12h vs 24h time. The US uses 12h with AM/PM. Most of Europe uses 24h. Setting hour12: undefined lets the locale decide. Setting hour12: true forces AM/PM regardless of locale.
- Parsing dates from strings. "03/04/2026" is ambiguous. Always parse dates from structured data (ISO 8601 strings, timestamps, or Date objects) and format them for display using Intl.DateTimeFormat.
- Not handling time zones. A user in New York at 3:00 PM ET is a user in Tokyo at 4:00 AM JST. Store dates in UTC and format them in the user's timezone using timeZone option.
Practice Questions
- How does Intl.DateTimeFormat determine the date format for a locale?
- What is the difference between dateStyle: 'full' and 'short'?
- How does Intl.RelativeTimeFormat handle "yesterday" and "tomorrow"?
- Why is MM/DD/YYYY a problematic date format for global audiences?
- How do you handle different calendar systems (Islamic, Buddhist, Japanese)?
Challenge: Build a date formatting dashboard that accepts a date input, a locale selector (10+ locales), a format selector (full, long, medium, short, custom), and a relative time option. Show formatted output in real-time, including timezone support, weekday/month names, and calendar system variations. Use Intl.DateTimeFormat and Intl.RelativeTimeFormat exclusively.
FAQ
Mini Project
Build a global event scheduler: an input for date/time, a user locale selector, a timezone selector, and formatted output showing the date in 6+ locale formats simultaneously. Include relative time display ("3 days from now"), calendar system support, 12h/24h toggle, and a visual comparison of how the same timestamp appears differently across cultures.
What's Next
You've mastered date formatting. Next, learn about Number & Currency Formatting for locale-aware display of numbers, currencies, and percentages.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro