DEV Community

Alex Chen
Alex Chen

Posted on

Working with Dates and Times in JavaScript: The Practical Guide

Working with Dates and Times in JavaScript: The Practical Guide

Dates in JS are notoriously confusing. Here's how to handle them correctly.

The Problem with Date

// ❌ The old way (still works, but painful)
const now = new Date();
const year = now.getFullYear();       // 2026
const month = now.getMonth() + 1;     // 5 (months are 0-indexed!)
const day = now.getDate();            // 16
const hours = now.getHours();         // Local time

// Parsing dates is a minefield:
new Date('2026-05-16');              // OK (ISO format)
new Date('05/16/2026');              // Depends on locale!
new Date('May 16, 2026');           // Depends on locale!
new Date('2026-05-16T00:00:00Z');    // UTC — different from local time!

// Math with dates:
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1); // Mutates the original date!
Enter fullscreen mode Exit fullscreen mode

Solution 1: date-fns (Lightweight)

npm install date-fns
Enter fullscreen mode Exit fullscreen mode
import { format, addDays, subMonths, isAfter, isWeekend } from 'date-fns';

const now = new Date();

// Format (no more manual string building!)
format(now, 'yyyy-MM-dd');           // "2026-05-16"
format(now, 'MMMM do, yyyy');        // "May 16th, 2026"
format(now, 'h:mm a');               // "7:30 PM"
format(now, 'EEEE, MMMM d, yyyy h:mm a');
// "Friday, May 16, 2026 7:30 PM"

// Add/subtract (immutable — returns new date!)
const nextWeek = addDays(now, 7);
const lastMonth = subMonths(now, 1);

// Compare
isAfter(nextWeek, now);              // true
isWeekend(now);                      // false or true

// Difference
import { differenceInDays, differenceInHours } from 'date-fns';
differenceInDays(new Date('2026-06-01'), now);
differenceInHours(new Date(), somePastDate);
Enter fullscreen mode Exit fullscreen mode

Solution 2: Temporal API (Future Standard)

// Coming to JS engines soon! Check browser support.
import { Temporal } from '@js-temporal/polyfill';

const now = Temporal.Now.plainDateTimeISO();
// 2026-05-16T19:30:00

const birthday = Temporal.PlainDate.from({
  year: 1995,
  month: 8,
  day: 15,
});

birthday.add({ years: 30 });          // 2025-08-15
birthday.until(Temporal.Now.plainDateISO()).days; // Days until/since

// Time zones handled properly!
const nyTime = Temporal.Now.zonedDateTimeISO('America/New_York');
const tokyoTime = nyTime.withTimeZone('Asia/Tokyo');

// No more UTC vs local confusion!
Enter fullscreen mode Exit fullscreen mode

Common Patterns

Relative Time ("3 hours ago")

function timeAgo(date) {
  const seconds = Math.floor((Date.now() - date.getTime()) / 1000);

  if (seconds < 60) return `${seconds}s ago`;
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
  if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
  if (seconds < 31536000) return `${Math.floor(seconds / 2592000)}mo ago`;
  return `${Math.floor(seconds / 31536000)}y ago`;
}

timeAgo(new Date(Date.now() - 5000));   // "5s ago"
timeAgo(new Date(Date.now() - 7200000)); // "2h ago"
timeAgo(new Date(Date.now() - 86400000)); // "1d ago"
Enter fullscreen mode Exit fullscreen mode

Countdown Timer

function countdown(targetDate) {
  const now = new Date();
  const diff = targetDate - now;

  if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0 };

  return {
    days: Math.floor(diff / (1000 * 60 * 60 * 24)),
    hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
    minutes: Math.floor((diff / (1000 * 60)) % 60),
    seconds: Math.floor((diff / 1000) % 60),
  };
}

// Usage with setInterval for live countdown
const target = new Date('2026-12-31T23:59:59');
setInterval(() => {
  console.log(countdown(target));
}, 1000);
Enter fullscreen mode Exit fullscreen mode

Is This Date Valid?

function isValidDate(date) {
  return date instanceof Date && !isNaN(date.getTime());
}

isValidDate(new Date('2026-02-30'));       // false (Feb 30 doesn't exist)
isValidDate(new Date('invalid'));          // false
isValidDate(new Date());                    // true
Enter fullscreen mode Exit fullscreen mode

Format Duration (ms → readable)

function formatDuration(ms) {
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
  if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
  if (ms < 86400000) return `${Math.floor(ms / 3600000)}h ${Math.round((ms % 3600000) / 60000)}m`;
  return `${Math.floor(ms / 86400000)}d`;
}

formatDuration(1500);      // "1.5s"
formatDuration(65000);     // "1m 5s"
formatDuration(3661000);   // "1h 1m"
formatDuration(90000000);  // "1d"
Enter fullscreen mode Exit fullscreen mode

Business Days Only (Skip Weekends)

function addBusinessDays(date, days) {
  const result = new Date(date);
  let added = 0;

  while (added < days) {
    result.setDate(result.getDate() + 1);
    const day = result.getDay();
    if (day !== 0 && day !== 6) added++; // Skip Sunday (0) and Saturday (6)
  }

  return result;
}

addBusinessDays(new Date('2026-05-15'), 3);
// Skips weekend → returns Monday or Tuesday
Enter fullscreen mode Exit fullscreen mode

Database ↔ JavaScript

// PostgreSQL timestamp → JS Date
const dbTimestamp = '2026-05-16T19:30:00.000Z';
const jsDate = new Date(dbTimestamp);

// JS Date → ISO string for API/database
const isoString = new Date().toISOString();
// "2026-05-16T11:30:00.000Z" (always UTC!)

// Display in user's timezone
const localString = new Date().toLocaleString('en-US', {
  timeZoneName: 'short',
});
// "5/16/2026, 7:30:00 PM GMT+8"

// User-friendly format
const friendly = new Date().toLocaleDateString('en-US', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});
// "Friday, May 16, 2026"
Enter fullscreen mode Exit fullscreen mode

Timezone Handling

// Get user's timezone
Intl.DateTimeFormat().resolvedOptions().timeZone;
// "Asia/Shanghai" or "America/New_York" etc.

// Convert between timezones
function convertTZ(date, tz) {
  return new Date(date.toLocaleString('en-US', { timeZone: tz }));
}

// Store as UTC, display as local
const serverTime = new Date().toISOString(); // Always store UTC!
const localDisplay = new Date(serverTime).toLocaleString();
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Task Native JS date-fns
Now new Date()
Format Manual format(d, pattern)
Add days setDate() (mutates!) addDays(d, n)
Diff Manual math differenceInDays(a, b)
Parse new Date(str) parse(str)
Compare <, > isBefore, isAfter
Start of month Manual startOfMonth(d)
End of month Manual endOfMonth(d)
Is valid? !isNaN(d.getTime()) isValid(d)

How do you handle dates in your projects? Any horror stories?

Follow @armorbreak for more JavaScript content.

Top comments (0)