← writing

Understanding JavaScript Dates, Epochs, UTC, ISO 8601 and Timezones

I’ve been meaning to write about dates for a while, but somehow never got around to it. While building my Trade Journal Analysis Web App, I ran into a classic developer headache: timezones. I was pulling stock data from Yahoo Finance and displaying ticker information over a date range.

That little issue sent me down the rabbit hole of how JavaScript, UTC, ISO formats, and browsers handle time. That’s when I ended up writing my post about the "Year 2038 Problem and Date Dilemma" back in 2024 Sep.

Let's face it - whether we like it or not, the web pages we interact with every day are powered by JavaScript. JavaScript is the living, breathing engine of the modern web. HTML gives structure, CSS gives style, but JavaScript gives life.

React, Vue, Angular are JavaScript frameworks turning HTML into dynamic UI components. APIs and AJAX calls constantly update content without full reloads. Even things like scrolling behavior, form validation, and analytics rely on JavaScript. In a way, JavaScript has become the runtime environment of the web.

Because JavaScript essentially runs on our local browser, when initializing a datetime object, JS automatically uses our local timezone to interpret and display the time. That means the same code can produce slightly different results depending on where it’s executed. Also, JavaScript’s behavior around timezones can feel… quirky. For example, it often labels times as GMT, even though they’re internally tracked as UTC.

const now = new Date(); 
console.log(now.toString());          // Tue Oct 14 2025 13:02:52 GMT-0400 (Eastern Daylight Time)
console.log(now.toLocaleString());    // 10/14/2025, 1:04:46 PM
console.log(now.toUTCString());       // Tue, 14 Oct 2025 17:04:46 GMT
console.log(now.toLocaleDateString()); // 10/14/2025 (user’s local date only)
console.log(now.toISOString());       // 2025-10-14T17:04:46.837Z
(“GMT-0400” is the same as “UTC-04”)

now.toISOString() is ISO 8601 UTC, the gold standard for APIs. It is unambiguous, timezone-safe, and JSON-compatible.

Locale Formatting Differences "en-US" --> 10/14/2025 (mm/dd/yyyy) "en-GB" --> 14/10/2025 (dd/mm/yyyy) "en-CA" --> 2025-10-14 (yyyy-mm-dd) "fr-FR" --> 14/10/2025 (dd/mm/yyyy) "ja-JP" --> 2025/10/14 (yyyy/mm/dd)

Why This Matters But, this local behavior can cause subtle bugs. A backend server running in UTC might interpret timestamps differently from what our browser displays. So, if we send dates to an API without normalizing them, the results can shift by several hours depending on the user’s location.

The best approach is 1. Store and exchange times in UTC The backend should always send datetime in ISO 8601 UTC format, that is, the communication with any systems should only be in ISO 8601 UTC format. APIs and databases think in UTC.

{ "createdDate": "2025-10-14T17:04:46.837Z" }
2. Convert to local time only at the display layer as users think in local time.
const localDate = new Date("2025-10-14T17:04:46.837Z").toLocaleDateString("en-CA"); 
Vice versa.

In layman terms, when asking users to pick a date range, the input should be in their local timezone as this makes it intuitive and human-friendly. However, when sending those dates to the backend (for example, to query data between two dates), we should convert them to UTC first. This ensures everyone, regardless of where they are, is talking about the same moment in time.

Btw, do you know 📅 represents the world emoji day (July 17th)?