Formatting numbers and dates
The app shows amounts, dates and times. How they look depends on language and country. In Switzerland CHF comes before an amount, an apostrophe separates the thousands, and dates are written with periods. JavaScript has the Intl object for this, which knows these rules. The English examples format dates for en-US and the German ones for de-CH. Amounts in francs use de-CH in both, because the association is in Bern.
Learning goals
After this unit you can
- format amounts in francs and percentages with
Intl.NumberFormat, - show dates and times with
Intl.DateTimeFormatand distances such as "yesterday", - explain why a date can fall on the previous day in another time zone,
- format dates without a time so that the right day shows everywhere.
Amounts and percentages
Section titled “Amounts and percentages”Intl.NumberFormat takes a language with a region and options. de-CH stands for German as used in Switzerland.
const chf = new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' })console.log(chf.format(20))console.log(chf.format(1234.5))
const percent = new Intl.NumberFormat('en-US', { style: 'percent' })console.log(percent.format(0.35))With style: 'currency' and currency: 'CHF' the currency code comes before the amount, and there are always two decimal places. Large amounts get an apostrophe as the thousands separator. With style: 'percent', 0.35 becomes the text 35%. You can use one formatter for many numbers, so you create it only once.
toFixed(2) from the other units is enough for simple cases, but adds neither thousands separators nor a currency.
Dates and times
Section titled “Dates and times”A Date stores a point in time as the number of milliseconds since January 1, 1970, in UTC. Only the formatting decides how this point in time looks.
const date = new Date('2026-11-12T00:00:00Z')
console.log(date.toLocaleDateString('en-US', { timeZone: 'UTC' }))
const long = new Intl.DateTimeFormat('en-US', { weekday: 'long', day: 'numeric', month: 'long', timeZone: 'UTC' })console.log(long.format(date))
const relative = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' })console.log(relative.format(-1, 'day'), relative.format(3, 'day'))toLocaleDateString returns the date in the notation of the language, for en-US with the month first. Intl.DateTimeFormat takes options for which parts to show, here the full weekday, the day and the month. Intl.RelativeTimeFormat phrases distances such as "yesterday" or "in 3 days". With numeric: 'auto' it says "yesterday" instead of "1 day ago".
Time zones
Section titled “Time zones”JavaScript reads a date without a time, such as '2026-11-12', as midnight in UTC. If you format it in a time zone west of UTC, that point in time is still on the previous day there.
// A date without a time is read as midnight UTCconst date = new Date('2026-11-12')
const format = (timeZone) => date.toLocaleDateString('en-US', { timeZone })console.log(format('UTC'), format('Europe/Zurich'), format('America/New_York'))In UTC and in Zurich it is November 12, in New York it is still the evening of November 11. Without the option timeZone, the browser uses the time zone of the device. For members in Switzerland that would work, but not on a server in America or for tests on a machine with a different time zone. For plain dates such as a return date you therefore set timeZone: 'UTC', as in the chapter on props. For points in time such as the start of the opening hours, use timeZone: 'Europe/Zurich'.
Common mistakes
Section titled “Common mistakes”| Mistake | What helps |
|---|---|
| A date without a time shows up as the previous day in some time zones. | Format with timeZone: 'UTC'. |
Amounts are put together with 'CHF ' + deposit, and 12.5 becomes CHF 12.5. | Use Intl.NumberFormat with style: 'currency'. |
The month is off by one, and new Date(2026, 11, 12) is in December. | Months count from 0 in new Date(year, month, day). A string such as '2026-11-12' is clearer. |
A new Intl.NumberFormat is created for every number. | Create the formatter once and reuse it. |
| An AI assistant suggests a library such as Moment.js for formatting. | Intl is enough for display. Moment.js is no longer developed. |
Exercises
Section titled “Exercises”Exercise Getting started
Format the deposits 20, 50 and 12.5 as amounts in francs and print each one on a line of its own.
Solution
const chf = new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' })const deposits = [20, 50, 12.5]
for (const deposit of deposits) console.log(chf.format(deposit))The formatter is created once before the loop and then formats all three amounts. 12.5 becomes CHF 12.50, because amounts in francs always have two decimal places.
Exercise Going further
The Wednesday opening hours start on November 4, 2026, at 4 p.m. UTC. Show this as weekday and time in the Swiss time zone.
Solution
const opening = new Date('2026-11-04T16:00:00Z')
const time = new Intl.DateTimeFormat('en-US', { weekday: 'long', hour: 'numeric', minute: '2-digit', timeZone: 'Europe/Zurich' })console.log(time.format(opening))The output is Wednesday 5:00 PM. In November Switzerland is on standard time, one hour ahead of UTC. timeZone: 'Europe/Zurich' works out this offset and also takes the switch to daylight saving time into account.
Exercise Challenge
Print the names tent, Drill and axe as a list the way you would write them in a sentence, with commas and an "and" before the last name. Also format the number 1234567.891 with thousands separators for Switzerland.
Solution
const names = ['tent', 'Drill', 'axe']
const list = new Intl.ListFormat('en-US', { style: 'long', type: 'conjunction' })console.log(list.format(names))console.log(new Intl.NumberFormat('de-CH').format(1234567.891))Intl.ListFormat knows the rules of the language. American English puts a comma before the "and", German doesn't. Intl.NumberFormat without options shows up to three decimal places and separates the thousands with apostrophes.
Text and figures are licensed under CC BY-SA 4.0, the code under the MIT License and the course data under CC0 1.0. The figures use the font Kalam under the SIL Open Font License 1.1. The sources are on GitHub.