Back to Blog
DatesTimezonesJavaScriptBackend

Date and Timezone Handling: Avoiding Common Pitfalls

Handle dates and timezones correctly. Learn best practices for storing, displaying, and calculating dates across timezones.

B
Bootspring Team
Engineering
February 27, 2026
3 min read

Date handling is notoriously tricky. This guide covers best practices for working with dates across timezones.

Golden Rules#

  1. Store in UTC: Always store dates in UTC in your database
  2. Convert on display: Convert to user's timezone only for display
  3. Use ISO 8601: For API communication

Storing Dates#

1// Always store as UTC 2const now = new Date(); // Already in UTC internally 3 4// PostgreSQL 5// Use TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) 6await db.query(` 7 INSERT INTO events (name, starts_at) 8 VALUES ($1, $2) 9`, [name, now.toISOString()]);

Displaying Dates#

1// Convert to user's timezone for display 2function formatForUser(date: Date, timezone: string): string { 3 return new Intl.DateTimeFormat('en-US', { 4 timeZone: timezone, 5 dateStyle: 'medium', 6 timeStyle: 'short', 7 }).format(date); 8} 9 10formatForUser(new Date(), 'America/New_York'); 11// "Jan 15, 2024, 3:30 PM" 12 13formatForUser(new Date(), 'Europe/London'); 14// "Jan 15, 2024, 8:30 PM"

Using date-fns with Timezones#

1import { format, parseISO } from 'date-fns'; 2import { formatInTimeZone, toZonedTime } from 'date-fns-tz'; 3 4// Parse ISO string (always UTC) 5const date = parseISO('2024-01-15T15:30:00Z'); 6 7// Format in specific timezone 8const formatted = formatInTimeZone( 9 date, 10 'America/New_York', 11 'MMM d, yyyy h:mm a zzz' 12); 13// "Jan 15, 2024 10:30 AM EST" 14 15// Get date in specific timezone for calculations 16const nyDate = toZonedTime(date, 'America/New_York');

API Communication#

1// Always use ISO 8601 format 2interface Event { 3 id: string; 4 name: string; 5 startsAt: string; // "2024-01-15T15:30:00Z" 6} 7 8// Client sends 9const payload = { 10 startsAt: selectedDate.toISOString(), 11}; 12 13// Server responds 14const response = { 15 startsAt: event.starts_at.toISOString(), 16};

Common Pitfalls#

1// ❌ Don't create dates from strings without timezone 2new Date('2024-01-15'); // Parsed as local midnight, not UTC! 3 4// ✅ Use ISO format with timezone 5new Date('2024-01-15T00:00:00Z'); // Explicitly UTC 6 7// ❌ Don't compare dates as strings 8'2024-01-15' < '2024-02-01'; // Works but fragile 9 10// ✅ Compare as Date objects 11new Date(a).getTime() < new Date(b).getTime(); 12 13// ❌ Don't assume user's timezone 14const localTime = new Date().toLocaleString(); // Uses browser timezone 15 16// ✅ Explicitly specify timezone 17const userTime = new Intl.DateTimeFormat('en-US', { 18 timeZone: user.timezone, 19}).format(date);

Storing User Timezone#

1// Get user's timezone 2const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 3// "America/New_York" 4 5// Store in user profile 6await db.users.update({ 7 where: { id: userId }, 8 data: { timezone: userTimezone }, 9});

Date Calculations#

1import { addDays, startOfDay, endOfDay } from 'date-fns'; 2import { zonedTimeToUtc } from 'date-fns-tz'; 3 4// Get start of day in user's timezone 5function getStartOfDayUTC(date: Date, timezone: string): Date { 6 const zonedDate = toZonedTime(date, timezone); 7 const startOfZonedDay = startOfDay(zonedDate); 8 return zonedTimeToUtc(startOfZonedDay, timezone); 9} 10 11// Query for events "today" in user's timezone 12const todayStart = getStartOfDayUTC(new Date(), 'America/New_York'); 13const todayEnd = getStartOfDayUTC(addDays(new Date(), 1), 'America/New_York'); 14 15const events = await db.events.findMany({ 16 where: { 17 startsAt: { 18 gte: todayStart, 19 lt: todayEnd, 20 }, 21 }, 22});

Testing Dates#

1import { vi } from 'vitest'; 2 3// Mock current time 4vi.useFakeTimers(); 5vi.setSystemTime(new Date('2024-01-15T12:00:00Z')); 6 7// Test 8expect(getCurrentTime()).toBe('2024-01-15T12:00:00.000Z'); 9 10vi.useRealTimers();

Store UTC, display local, use ISO 8601, and always be explicit about timezones.

Share this article

Help spread the word about Bootspring