Introduction: Why Time is a Pain
\n\nEveryone who has ever written code in JavaScript has encountered this pain. You show the user the time 12:00, and they write: "It's 14:00 for me, everything is broken." Or you save a date to the database, and when loading it shifts back by an hour. Sound familiar?
\n\nWorking with time zones in JavaScript is a topic that many developers put off "for later." And that's a mistake. Because if you don't figure it out right away, you'll end up rewriting half the application later.
\n\nIn this article, I'll tell you how to properly work with timezones in JS, show real code examples, and give you a few tricks that will save you hours of debugging. Let's go!
\n\nWhy Does JavaScript Handle Dates So Strangely?
\n\nLet's start with the main thing: the Date object in JavaScript is, to put it mildly, not a gift. It stores time in milliseconds since January 1, 1970 UTC. And that would be fine, but:
- \n
getMonth()returns months from 0 to 11 (January is 0, December is 11) \n - The
getHours(),getMinutes()methods return the time in the user's local time zone \n - And the
getUTCHours(),getUTCMinutes()methods return the time in UTC \n
And this is where the confusion begins. Let's say you write:
\n\nconst now = new Date();\nconsole.log(now.getHours()); // 14 — if you are in MSK\nconsole.log(now.getUTCHours()); // 11 — UTC\n\nIf you save now to the database and then retrieve it on a server in a different timezone, you'll get a mess. Therefore, the first rule of working with dates and time zones in JavaScript is to always store time in UTC and convert it on the client.
Intl.DateTimeFormat: The Built-in Savior
\n\nMany people don't know, but modern JavaScript has a powerful built-in API for working with date formatting with timezone awareness. It's Intl.DateTimeFormat. It supports all major time zones and can format dates beautifully.
Basic Example
\n\nconst date = new Date('2025-03-15T12:00:00Z');\n\nconst formatter = new Intl.DateTimeFormat('ru-RU', {\n timeZone: 'America/New_York',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n});\n\nconsole.log(formatter.format(date)); \n// "March 15, 2025, 08:00" — time in New York\n\nNotice: we pass the date in UTC (the letter Z at the end), and Intl.DateTimeFormat itself converts it to the desired timezone. This is ideal for displaying time to users from different regions.
List of All Timezones
\n\nIf you don't know which timezone to specify, you can get the full list:
\n\nconsole.log(Intl.supportedValuesOf('timeZone'));\n// ["Africa/Abidjan", "Africa/Accra", ..., "Pacific/Wallis"]\n\nThis list includes over 500 timezones. Use it to make sure your timezone is supported.
\n\nMoment.js: A Classic, But With Caveats
\n\nFor a long time, moment.js was the standard for working with dates in JavaScript. It can parse, format, manipulate dates, and of course, work with time zones via the moment-timezone plugin.
Installation
\n\nnpm install moment moment-timezone\n\nExample with Timezones
\n\nconst moment = require('moment-timezone');\n\nconst now = moment();\nconsole.log(now.tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));\n// 2025-03-15 14:00 — Moscow time\n\nconst nyTime = moment.tz('2025-03-15 12:00', 'America/New_York');\nconsole.log(nyTime.clone().tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));\n// 2025-03-15 19:00 — 7 hour difference\n\nImportant: moment.js is a legacy library. The authors themselves recommend switching