Skip to main content
Dev Notes

Dealing with Dates in JavaScript

Dev Notes Disclaimer: Each artile in the Dev Notes section of my webiste may or may not be unfinished work. I don't always have time to write a full post. If you see something that looks like a half-baked idea, it probably is! If you have any questions or suggestions, feel free to reach out.

JavaScript dates are like that one coworker who means well but always shows up late and forgets daylight savings. This guide covers the essential things to know so you can spend less time debugging new Date() weirdness and more time building features that actually matter.

The Basics: Date 101

const now = new Date(); // Current date and time
const fromString = new Date("2025-05-07"); // ISO format preferred
const fromParts = new Date(2025, 4, 7); // Month is zero-based (4 = May)

Important Gotchas:

  • Month is zero-based (0 = January)
  • Avoid Date.parse("MM/DD/YYYY") – results can vary by browser and locale
  • ISO format (YYYY-MM-DD) is your safest bet

Time Zones: Where Things Get Weird

JavaScript Date objects are always created in local time, but toISOString() and some other methods use UTC.

const date = new Date("2025-05-07T12:00:00Z");
console.log(date.toISOString()); // UTC
console.log(date.toLocaleString()); // Local time

Common Pitfalls:

  • Backend sends UTC → JS auto-converts to local
  • getHours() returns local time
  • getUTCHours() returns UTC

Use Cases:

  • Store dates in UTC (or ISO 8601 format)
  • Convert to local time only when displaying

Internationalization (i18n): Showing Dates the Right Way

Use Intl.DateTimeFormat or toLocaleString() for human-friendly, locale-aware formatting.

const date = new Date("2025-05-07T12:00:00Z");

date.toLocaleDateString("en-US"); // "5/7/2025"
date.toLocaleDateString("fr-FR"); // "07/05/2025"

new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",
  month: "long",
  day: "numeric",
  weekday: "long"
}).format(date);
// "2025年5月7日水曜日"

Best Practice:

  • Let the browser handle locale formatting
  • Use language and region codes (en-GB, es-MX, etc.)

Formatting Dates for Display

Native Date formatting is limited. Consider a library for consistency and flexibility:

Native (limited)

date.toLocaleString("en-US", {
  year: "numeric",
  month: "short",
  day: "2-digit"
});
// "May 07, 2025"
  • date-fns – Tree-shakable, modern, functional
  • luxon – Timezone and i18n support out of the box
  • dayjs – Lightweight Moment.js alternative
import { format } from "date-fns";
format(new Date(), "yyyy-MM-dd"); // "2025-05-07"

Time Zone Support

Native Intl with timeZone option:

date.toLocaleString("en-US", { timeZone: "America/New_York" });
// "5/7/2025, 8:00:00 AM"

Libraries like Luxon:

import { DateTime } from "luxon";
DateTime.fromISO("2025-05-07T12:00:00Z")
  .setZone("America/Los_Angeles")
  .toLocaleString(DateTime.DATETIME_MED);
// "May 7, 2025, 5:00 AM"

Common Gotchas Recap

When You Need Testable, Predictable Dates

  • Avoid using new Date() directly in components
  • Wrap it in a function so you can mock it for testing
export function getNow() {
  return new Date();
}

Real-World Challenge: Saving Dates Accurately

In both the IVFCRYO and Logatot projects, we ran into a deceptively simple problem: recording a date or timestamp and having it mean the same thing for everyone involved. Turns out, when users in different time zones are saving "the same" date, you're either:

  • accidentally shifting it behind or ahead depending on the user's device time,
  • or storing the same calendar date but ending up with inconsistent meaning across systems.

What Went Wrong

For example, if a user entered 2025-05-07 as the event date:

new Date("2025-05-07"); // gets parsed as midnight UTC

That might actually render as May 6, 2025, 8:00 PM if you're on the East Coast of the U.S. That's a big problem for things like:

  • logging critical biological specimen milestones (IVFCRYO),
  • recording daily child care activities like naps and meals (Logatot).

The Fix: Save the Time Zone

Our workaround? Ask users to explicitly set their time zone as part of their profile. Then we:

Capture the exact time entered, using JavaScript to preserve local meaning.

Use the saved time zone to adjust that time if it needs to be stored or compared in UTC (e.g., for reporting, syncing across systems, etc.).

const localTime = new Date(); // This reflects user's local device time
const utcTime = localTime.toISOString(); // Save this in DB

Separately, we store:

Intl.DateTimeFormat().resolvedOptions().timeZone
// Example: "America/New_York"

Now, when showing dates later, we can rehydrate them like so:

new Date(storedUtcTime).toLocaleString("en-US", {
  timeZone: userTimeZone
});

Optional Workarounds (Without Asking for Time Zone)

If asking for a time zone feels like too much user friction, you have a couple options:

  • Auto-detect the browser time zone on login or profile save with Intl.DateTimeFormat().resolvedOptions().timeZone.
  • Store only the local date parts (year, month, day) as separate fields and reconstruct them as needed, avoiding Date parsing quirks entirely. This is how Ruby on Rails used to handle dates in the scaffolding process. It's a lot easier on the backend, but it can be a pain for the user.
  • Use Date.getFullYear(), getMonth(), and getDate() to safely extract date parts based on the device's local time.

Takeaways

Plain Date objects can behave inconsistently depending on parsing method and user time zone.

Always be clear: are you saving a point in time, or a calendar date as experienced by the user?

Either:

  • convert everything to UTC + store time zone, or
  • treat dates as local data and avoid converting to Date objects unless absolutely necessary.