Dates & Intl Formatting
Exam schedules, train departures, bill due dates and "posted 3 days ago" labels all need dates. JavaScript's built-in Date object handles them, but it has a few famous traps. Then, to show dates and money the way Indian users expect, such as "26 January 2027" and "₹1,29,999.00", you use the Intl formatting tools.
In this lesson you will create dates, read and change their parts, calculate the days between two dates, and format dates and rupee amounts correctly.
Creating dates
A Date object stores one moment in time. There are four common ways to create one:
const now = new Date(); // this moment
const exam = new Date(2026, 9, 29); // 29 October 2026, local time
const train = new Date(2026, 9, 29, 21, 15); // 29 October 2026, 9:15 pm
const fromText = new Date("2026-10-29T09:30:00"); // ISO text, local timeInside, a date is just a number: the milliseconds since 1 January 1970 (UTC), called a timestamp. Date.now() gives the current timestamp, which is handy for measuring how long something takes.
const now = new Date();
console.log(now.toLocaleString("en-IN"));
console.log(Date.now());Run it twice and the output changes, because time has moved on.
The trap: months start at 0
When you pass numbers to new Date(), the month is counted from 0: January is 0, February is 1 and December is 11. Days of the month start at 1 as normal. This catches every JavaScript developer at least once.
const launch = new Date(2026, 9, 29); // 9 means October
console.log(launch.getFullYear());
console.log(launch.getMonth());
console.log(launch.getMonth() + 1);
console.log(launch.getDate());
console.log(launch.getDay());2026 9 10 29 4
| Method | Returns | Watch out |
|---|---|---|
getFullYear() | The year, such as 2026 | |
getMonth() | 0 to 11 | Add 1 for a human month number |
getDate() | Day of the month, 1 to 31 | Not getDay() |
getDay() | Day of the week, 0 to 6 | 0 is Sunday |
getHours(), getMinutes() | Time parts in local time |
Each getter has a matching setter, such as setDate() and setMonth(), which changes the date in place.
Another trap: date-only text is UTC
new Date("2026-10-29T09:30:00") is read as local time. But a date-only string, new Date("2026-10-29"), is read as midnight UTC, the world reference time. In India that is 5:30 am on the same day, but for a user in the USA it is still the evening of 28 October, so the wrong day appears. When you build a date from separate year, month and day values, pass numbers instead: new Date(2026, 9, 29).
Adding days and counting days between dates
Dates roll over automatically. If you set the day to 34 in December, JavaScript moves into January. This makes adding days easy:
const checkIn = new Date(2026, 11, 29); // 29 December 2026
const checkOut = new Date(checkIn); // a copy, so checkIn stays the same
checkOut.setDate(checkOut.getDate() + 5);
console.log(checkOut.toLocaleDateString("en-IN"));
const msPerDay = 24 * 60 * 60 * 1000;
const nights = Math.round((checkOut - checkIn) / msPerDay);
console.log("Nights:", nights);
console.log(new Date(2026, 1, 30).toLocaleDateString("en-IN"));3/1/2027 Nights: 5 2/3/2026
new Date(checkIn)makes a copy. Setters change a date in place, so always copy first if you still need the original.- Subtracting two dates gives the difference in milliseconds; divide by the milliseconds in a day.
Math.round()protects you in countries where clocks change by an hour for daylight saving time. - The last line asked for 30 February. February 2026 has 28 days, so the date rolled over to 2 March. Rollover is useful for adding days, but it also means JavaScript will not warn you about an impossible date.
To save a date or send it to a server, use toISOString(), which gives a standard UTC text such as 2026-10-29T04:00:00.000Z. Convert to a friendly format only when you show it to a person.
Formatting dates for people
toLocaleDateString() formats a date for a locale, a language and region code such as "en-IN" (English, India) or "en-US" (English, USA). Indian users expect the day first; American users expect the month first.
const republicDay = new Date(2027, 0, 26);
console.log(republicDay.toLocaleDateString("en-IN"));
console.log(republicDay.toLocaleDateString("en-US"));
console.log(republicDay.toLocaleDateString("en-IN", { dateStyle: "long" }));
console.log(republicDay.toLocaleDateString("en-IN", { dateStyle: "full" }));
console.log(republicDay.toLocaleDateString("en-IN", { weekday: "short", day: "numeric", month: "short" }));26/1/2027 1/26/2027 26 January 2027 Tuesday, 26 January 2027 Tue, 26 Jan
The options object controls the style. dateStyle gives ready-made formats ("short", "medium", "long", "full"), or you can pick each part yourself with weekday, day, month and year. Other locales work the same way: "hi-IN" gives Hindi month names. The exact text comes from the browser's built-in locale data, so it can differ very slightly between browsers.
Intl.DateTimeFormat and time zones
toLocaleDateString() is a shortcut for the Intl (internationalisation) API. When you format many dates the same way, create an Intl.DateTimeFormat once and reuse it. It also accepts a timeZone, so you can show one moment in different cities:
const meeting = new Date(Date.UTC(2026, 9, 29, 4, 0)); // 04:00 UTC
const parts = { day: "numeric", month: "short", hour: "numeric", minute: "2-digit" };
const inIndia = new Intl.DateTimeFormat("en-IN", { ...parts, timeZone: "Asia/Kolkata" });
const inLondon = new Intl.DateTimeFormat("en-GB", { ...parts, timeZone: "Europe/London" });
console.log("Pune:", inIndia.format(meeting));
console.log("London:", inLondon.format(meeting));Pune: 29 Oct, 9:30 am London: 29 Oct, 4:00
Date.UTC() builds a timestamp from UTC parts, so this example shows the same output on every computer. Without a timeZone option, dates are shown in the user's own time zone, which is usually what you want on a website.
Formatting rupees and numbers
Intl.NumberFormat formats numbers for a locale. With "en-IN", it uses the Indian lakh and crore grouping automatically.
const inr = new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" });
console.log(inr.format(129999));
console.log(inr.format(1299.5));
const inrWhole = new Intl.NumberFormat("en-IN", {
style: "currency",
currency: "INR",
maximumFractionDigits: 0,
});
console.log(inrWhole.format(4567890));
console.log((2500000).toLocaleString("en-IN"));
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log(usd.format(129999));₹1,29,999.00 ₹1,299.50 ₹45,67,890 25,00,000 $129,999.00
style: "currency"withcurrency: "INR"adds the ₹ sign and two decimal places.maximumFractionDigits: 0drops the paise for whole-rupee prices.number.toLocaleString("en-IN")is a quick way to add Indian grouping without a currency sign.- The same amount in
"en-US"uses thousands grouping: 129,999.
Never build these formats by hand with string methods. Intl handles grouping, decimals and symbols correctly for every locale.
Common mistakes
- Using 1 to 12 for months.
new Date(2026, 10, 29)is 29 November. Subtract 1 from a human month number. - Confusing
getDay()andgetDate().getDay()is the weekday (0 is Sunday);getDate()is the day of the month. - Parsing date-only text.
new Date("2026-10-29")is UTC midnight and can show the previous day in some countries. - Changing a date you still need. Setters mutate. Copy with
new Date(oldDate)first. - Formatting money by hand. Use
Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).
Key points
new Date(year, monthIndex, day)creates a local date; the month index starts at 0.- Dates are timestamps in milliseconds; subtract dates and divide by a day's milliseconds to count days.
- Date parts roll over, which makes adding days simple.
- Store dates with
toISOString(); show them withtoLocaleDateString("en-IN", options)orIntl.DateTimeFormat. Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" })gives correct rupee formatting.- Temporal, a better date API, is on its way.
Exercise
A library in Jaipur lends books for 14 days. Create a date for an issue date of 25 October 2026, calculate the due date, and print it in the long Indian format (like "8 November 2026"). Then calculate the fine for a book returned on 12 November 2026 at ₹5 per late day, and print the fine with Intl.NumberFormat in rupees.
Show solution
const issued = new Date(2026, 9, 25);
const due = new Date(issued);
due.setDate(due.getDate() + 14);
console.log("Due:", due.toLocaleDateString("en-IN", { dateStyle: "long" }));
const returned = new Date(2026, 10, 12);
const msPerDay = 24 * 60 * 60 * 1000;
const lateDays = Math.max(0, Math.round((returned - due) / msPerDay));
const inr = new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" });
console.log(`Late by ${lateDays} days, fine: ${inr.format(lateDays * 5)}`);Due: 8 November 2026 Late by 4 days, fine: ₹20.00
October is month 9 and November is month 10. Adding 14 days rolls over into November automatically, and Math.max(0, ...) makes sure an early return never gives a negative fine.
Quick Quiz
1. Which date does new Date(2026, 0, 15) create?
Months are counted from 0, so 0 is January.
2. What does getDay() return for a Sunday?
getDay() gives the weekday from 0 (Sunday) to 6 (Saturday).
3. How do you show 4567890 as "₹45,67,890.00"?
Intl.NumberFormat with the en-IN locale adds the rupee sign, Indian grouping and two decimal places.
4. Why should you copy a date before calling setDate() on it?
setDate() and the other setters mutate the date, so the original value would be lost.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
