Pro Patterns for LWC JavaScript · Lesson 22 of 25
Dates, Time Zones and Intl Formatting in LWC
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 type | Arrives as | Meaning |
|---|---|---|
| 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' }));
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))}`);
}
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());
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']));
₹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
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 withtimeZone: 'UTC', build the date from its parts, or uselightning-formatted-date-timewithtime-zone="UTC".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); }– withLOCALEandCURRENCYimported 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.