BeginnerJavaScript · Lesson 6 of 7

Numbers, Dates & Intl Formatting

Floating-point pitfalls, safe money handling, the Date object and locale-aware formatting with Intl.

JavaScript has one number type for integers and decimals, stored as 64-bit floating point. That is why 0.1 + 0.2 is not exactly 0.3, and why toFixed can surprise you. For money, store whole shillings (or cents) as integers and only format them for display. Convert text with Number(text), which gives NaN for anything that isn't a number; parseInt(text, 10) stops at the first non-digit.

A Date stores one moment in time (milliseconds since 1 January 1970 UTC). Watch out for two traps: months are 0-based in new Date(year, month, day) (10 is November), and new Date("2026-03-14") is read as midnight UTC while new Date(2026, 2, 14) is midnight in the computer's local time zone. Subtracting two dates gives milliseconds; to add days, change the date with setDate(getDate() + n).

Intl formats numbers, currencies, percentages, dates and relative times for any locale and time zone, with no library. Create a formatter once (new Intl.NumberFormat(...), new Intl.DateTimeFormat(...)) and reuse it. Pass timeZone: "Africa/Dar_es_Salaam" to show East Africa Time whatever the visitor's computer is set to.

dates.jsJavaScript
// Numbers: one type for integers and decimals (64-bit floating point)
console.log(0.1 + 0.2);                         // 0.30000000000000004
console.log((0.1 + 0.2).toFixed(2));            // "0.30" (a string)
console.log((1.005).toFixed(2));                // "1.00", not "1.01": 1.005 is stored as 1.00499...
console.log(Number("42"), Number("42abc"), parseInt("42abc", 10));   // 42 NaN 42
console.log(Number.isInteger(5.0), Number.MAX_SAFE_INTEGER);

// Money: keep whole shillings (or cents) as integers, format only for display
const fees = [150000, 75000, 25500];
const total = fees.reduce((sum, f) => sum + f, 0);
const tzs = new Intl.NumberFormat("en-TZ", { style: "currency", currency: "TZS", maximumFractionDigits: 0 });
console.log(tzs.format(total));
console.log(new Intl.NumberFormat("en", { style: "percent", maximumFractionDigits: 1 }).format(2 / 3));

// Dates: months are 0-based in the constructor (0 = January)
const exams = new Date(2026, 10, 2);             // 2 November 2026, local time
const registered = new Date("2026-03-14T09:30:00+03:00");
const DAY = 24 * 60 * 60 * 1000;
console.log(Math.round((exams - registered) / DAY), "days until exams");
console.log(registered.toISOString());          // always UTC: 2026-03-14T06:30:00.000Z

// Add days by changing the date, never by assuming 30-day months
const deadline = new Date(registered);
deadline.setDate(deadline.getDate() + 42);

const long = new Intl.DateTimeFormat("en-GB", {
  weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone: "Africa/Dar_es_Salaam",
});
console.log("Fee deadline:", long.format(deadline));
const time = new Intl.DateTimeFormat("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: "Africa/Dar_es_Salaam" });
console.log("Paid at", time.format(registered), "EAT");

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
console.log(rtf.format(-1, "day"), "|", rtf.format(3, "week"));
Runs in your browser · JavaScript

Key points

  • Floating point is approximate: keep money in whole units and format at the edge.
  • Date months are 0-based; date-only ISO strings are UTC, constructor numbers are local time.
  • Intl.NumberFormat, DateTimeFormat and RelativeTimeFormat handle currency, locale and time zone.

Exercise

Write daysUntil(isoDay, today) and instalments(total, count, firstDue), which splits a fee into monthly payments (the last payment takes any remainder, and a due date of 31 January becomes 28 February, not 3 March). Print the plan as a table with dates and TZS amounts formatted by Intl.

Show solution

Try the exercise yourself first — then compare your approach with this one.

Every date is built in UTC (T00:00:00Z and the getUTC…/setUTC… methods), so the plan is identical on every computer. addMonths clamps the day to the last day of the target month, using day 0 of the following month. The amounts are whole shillings: each instalment is the rounded-down share and the last one takes the remainder, so the total always adds up exactly.

receipt.jsJavaScript
const DAY = 24 * 60 * 60 * 1000;
const tzs = new Intl.NumberFormat("en-TZ", { style: "currency", currency: "TZS", maximumFractionDigits: 0 });
const dateFmt = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });

// Dates are built in UTC so the result is the same in every time zone.
function utcDate(isoDay) {
  return new Date(isoDay + "T00:00:00Z");
}

function daysUntil(isoDay, today) {
  return Math.round((utcDate(isoDay) - utcDate(today)) / DAY);
}

function addMonths(date, months) {
  const d = new Date(date);
  const day = d.getUTCDate();
  d.setUTCDate(1);
  d.setUTCMonth(d.getUTCMonth() + months);
  const lastDay = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0)).getUTCDate();
  d.setUTCDate(Math.min(day, lastDay));          // 31 Jan + 1 month = 28/29 Feb, not 3 March
  return d;
}

function instalments(total, count, firstDue) {
  const base = Math.floor(total / count);
  return Array.from({ length: count }, (_, i) => ({
    due: addMonths(utcDate(firstDue), i),
    amount: i === count - 1 ? total - base * (count - 1) : base,   // last one takes the remainder
  }));
}

const today = "2026-01-20";
console.log("Days until exams:", daysUntil("2026-11-02", today));

const plan = instalments(250000, 3, "2026-01-31");
for (const { due, amount } of plan) {
  console.log(dateFmt.format(due).padEnd(12), tzs.format(amount).padStart(14));
}
console.log("Total".padEnd(12), tzs.format(plan.reduce((s, p) => s + p.amount, 0)).padStart(14));
Runs in your browser · JavaScript

Check your understanding

  1. What does new Date(2026, 1, 14) create?

  2. Why should a fees system store 25500 rather than 255.00 for an amount?

  3. How do you show a date in East Africa Time on a computer set to another time zone?

  4. What does Number("42abc") return?

Ask AI