Skip to content

Date Formatting โ€” Locale-Aware Date and Time Display

DodaTech Updated 2026-06-28 8 min read

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. How does Intl.DateTimeFormat determine the date format for a locale?
  2. What is the difference between dateStyle: 'full' and 'short'?
  3. How does Intl.RelativeTimeFormat handle "yesterday" and "tomorrow"?
  4. Why is MM/DD/YYYY a problematic date format for global audiences?
  5. 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

What is Intl.DateTimeFormat?

Intl.DateTimeFormat is a built-in JavaScript API that formats dates and times according to locale-specific conventions. It supports dateStyle, timeStyle, custom options, and different calendar systems. It's available in all modern browsers and Node.js.

How do I format dates in the user's timezone?

Pass the timeZone option: { timeZone: 'America/New_York' }. Use Intl.supportedValuesOf('timeZone') to get a list of valid timezone names. For the user's timezone, use Intl.DateTimeFormat().resolvedOptions().timeZone.

What's the difference between dateStyle and individual options?

dateStyle: 'full' sets weekday, year, month, day to their appropriate widths automatically. Individual options (weekday, year, month, day) give you fine-grained control. You can mix both approaches.

Does Intl.DateTimeFormat support non-Gregorian calendars?

Yes. Use the calendar option: 'gregory', 'islamic-umalqura', 'buddhist', 'japanese', 'chinese', 'indian', etc. The calendar is specified as a locale extension: 'ar-SA-u-ca-islamic-umalqura' or the options parameter.

How do I format durations (not dates)?

Use Intl.DurationFormat (available in newer environments) for durations like '2 hours, 30 minutes'. For older environments, construct the string manually or use a library like luxon with duration formatting.

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