LearnFrenzy
Sign in Take a free test

Pro Patterns for LWC JavaScript · Lesson 22 of 25

Dates, Time Zones and Intl Formatting in LWC

5 min read Updated Nov 8, 2026 Saurabh Samir

Dates cause more subtle bugs than almost anything else in JavaScript: a close date that shows a day early for users in California, a "last updated" time that's five and a half hours off, amounts formatted with American commas for an Indian sales team. The fixes are well known – store and send UTC, format at the edge, and let Intl do the formatting for the user's locale and time zone.

In this lesson

  • How Salesforce Date and Datetime values arrive
  • Date pitfalls: months, parsing, mutation
  • Formatting in the user's locale and time zone
  • Currency, compact numbers and relative time

Date vs Datetime from Salesforce

Salesforce typeArrives asMeaning
Date (CloseDate)"2026-11-30"a calendar day – no time, no time zone
Datetime (CreatedDate)"2026-11-08T09:30:00.000Z"an exact moment, always in UTC (the Z)

The difference matters. A Datetime should be shown in the user's time zone. A Date should be shown as that same calendar day for everyone – converting it through time zones is how a deadline "moves" to the previous day.

const closeDate = '2026-11-30';                       // a Salesforce Date

// ✗ date-only strings are parsed as UTC midnight…
const asUtc = new Date(closeDate);
// …so in a time zone behind UTC it's still the 29th
console.log(asUtc.toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles' }));

// ✓ keep a Date a date: split it, or format it in UTC
const [y, m, d] = closeDate.split('-').map(Number);
console.log(new Date(y, m - 1, d).toDateString());     // local calendar date (months are 0-based!)
console.log(asUtc.toLocaleDateString('en-US', { timeZone: 'UTC', dateStyle: 'medium' }));

Output

11/29/2026
Mon Nov 30 2026
Nov 30, 2026

Datetime in the user's time zone

A user's Salesforce time zone (in their personal settings) can differ from their computer's. Import it from @salesforce/i18n/timeZone together with the locale and pass both to Intl.DateTimeFormat:

// in a component:
// import LOCALE from '@salesforce/i18n/locale';      e.g. 'en-IN'
// import TIME_ZONE from '@salesforce/i18n/timeZone'; e.g. 'Asia/Kolkata'
const createdDate = '2026-11-08T09:30:00.000Z';

const users = [
    { locale: 'en-IN', timeZone: 'Asia/Kolkata' },
    { locale: 'en-US', timeZone: 'America/New_York' },
    { locale: 'de-DE', timeZone: 'Europe/Berlin' },
];

for (const { locale, timeZone } of users) {
    const fmt = new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', timeZone });
    console.log(`${locale.padEnd(6)} ${fmt.format(new Date(createdDate))}`);
}

Output

en-IN  8 Nov 2026, 3:00 pm
en-US  Nov 8, 2026, 4:30 AM
de-DE  08.11.2026, 10:30

One timestamp, three correct displays. Never shift a timestamp by adding hours yourself – daylight saving rules make that wrong twice a year in many countries.

Base components do this for you

In templates, lightning-formatted-date-time, lightning-formatted-number and lightning-relative-date-time already use the user's locale and time zone. Use Intl in JavaScript when you build strings yourself: labels, CSV exports, messages, chart axes.

Date pitfalls in one place

const due = new Date(Date.UTC(2026, 0, 31));           // 31 January 2026 (month 0 = January)

// 1. Date objects are mutable – setX changes the original
const nextMonth = new Date(due);                        // copy first
nextMonth.setUTCMonth(due.getUTCMonth() + 1);
console.log(due.toISOString().slice(0, 10), '→', nextMonth.toISOString().slice(0, 10));   // overflow!

// 2. differences in days: use UTC timestamps to avoid DST surprises
const start = Date.UTC(2026, 2, 1);
const end = Date.UTC(2026, 3, 1);
console.log((end - start) / 86_400_000, 'days');

// 3. invalid dates don't throw
const bad = new Date('31/12/2026');
console.log(Number.isNaN(bad.getTime()) ? 'invalid date' : bad.toISOString());

// 4. sending a Date to Apex: an ISO string is safest
console.log(new Date(Date.UTC(2026, 10, 8, 14, 0)).toISOString());

Output

2026-01-31 → 2026-03-03
31 days
invalid date
2026-11-08T14:00:00.000Z

Adding one month to 31 January gives 3 March, because "31 February" overflows. For real date maths – business days, month ends – consider computing it in Apex or carefully clamping the day.

Numbers, currency and relative time

const amount = 1234567.891;

console.log(new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(amount));
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount));
console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount));
console.log(new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 }).format(amount));
console.log(new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 1 }).format(0.4567));

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day'), '|', rtf.format(3, 'hour'), '|', rtf.format(-2, 'week'));

const list = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
console.log(list.format(['Sales', 'Service', 'Marketing']));

Output

₹12,34,567.89
$1,234,567.89
1.234.567,89 €
1.2M
45.7%
yesterday | in 3 hours | 2 weeks ago
Sales, Service, and Marketing

In a component, take the currency from the record (CurrencyIsoCode in multi-currency orgs) or from @salesforce/i18n/currency, and the locale from @salesforce/i18n/locale. Create formatters once – at module level or in a field – rather than inside a getter that runs on every render.

Practice

Try it yourself

  1. Users in New York see an Opportunity's close date of 1 December as 30 November. What's the bug and the fix?

    Show answer

    The code does new Date('2026-12-01').toLocaleDateString(): the date-only string is parsed as UTC midnight, which is still 30 November in New York. Format Dates with timeZone: 'UTC', build the date from its parts, or use lightning-formatted-date-time with time-zone="UTC".

  2. Write a getter that shows an Opportunity's Amount in the record's own currency for the user's locale.

    Show answer

    get amountText() { const { Amount, CurrencyIsoCode } = this.opp; return Amount == null ? '—' : new Intl.NumberFormat(LOCALE, { style: 'currency', currency: CurrencyIsoCode ?? CURRENCY }).format(Amount); } – with LOCALE and CURRENCY imported from @salesforce/i18n (and ideally a cached formatter per currency).

Quick check

Which month is new Date(2026, 1, 1)?

February – months are 0-based.

In which time zone does a Salesforce Datetime arrive?

UTC (ISO string ending in Z).

Where do you get the user's Salesforce time zone in LWC?

import TIME_ZONE from '@salesforce/i18n/timeZone'.

Summary

  • Dates are calendar days – don't convert them through time zones; Datetimes are UTC moments.
  • Format with Intl using the user's locale and time zone from @salesforce/i18n.
  • Copy Date objects before changing them; calculate day differences in UTC.
  • Use Intl.NumberFormat, RelativeTimeFormat and ListFormat instead of hand-built strings.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular