Developer Tools

How Do I Format Unix Timestamps for Display in Different Regions?

Learn to format Unix timestamps for different locales, timezones, and regions, including date/time conventions and localization best practices.

By Inventive HQ Team

To format a Unix timestamp for different regions, convert it to a Date object, then hand it to a locale-aware formatter — Intl.DateTimeFormat in JavaScript, zoneinfo/Babel in Python — with the target IANA timezone (like Europe/Berlin or Asia/Tokyo) and the target locale (like de-DE or ja-JP). The formatter handles date order (MM/DD vs DD/MM vs YYYY-MM-DD), 12-hour vs 24-hour time, digit and separator conventions, and translated month/day names automatically — the same underlying instant, correctly localized, without a single hand-written string template.

That's the summary; here's what it can't give you: which locale codes actually exist, why your "correct" conversion is showing the wrong calendar day, how to avoid rebuilding a formatter on every call, and where the standard library still leaves gaps (relative time, historical offsets, non-Gregorian calendars). That's what the rest of this article covers.

One Unix timestamp, formatted four different ways A central clock representing the instant 2024-01-01T00:00:00Z radiates lines to four cards showing the same instant formatted as US, UK/EU, ISO 8601, and Japanese conventions. Same instant (1704067200) — four regional formats one Date object, formatted with Intl.DateTimeFormat per locale 2024-01-01T00:00:00Z United States (en-US) 01/01/2024, 12:00 AM United Kingdom (en-GB) 01/01/2024, 00:00 ISO 8601 (international) 2024-01-01T00:00:00Z Japan (ja-JP) 2024年01月01日 0:00
Loading interactive tool...

Regional Format Conventions At a Glance

RegionDate orderTime formatCommon separatorExample (same instant)
United StatesMM/DD/YYYY12-hour (AM/PM)/01/01/2024, 12:00 AM
United KingdomDD/MM/YYYY24-hour/01/01/2024, 00:00
GermanyDD.MM.YYYY24-hour.01.01.2024, 00:00
FranceDD/MM/YYYY24-hour/01/01/2024 00:00
JapanYYYY年MM月DD日24-hour年/月/日2024年01月01日 0:00
ChinaYYYY-MM-DD24-hour-2024-01-01 0:00
ISO 8601 (standard)YYYY-MM-DD24-hour, T separator- and T2024-01-01T00:00:00Z

The pattern to notice: almost nobody outside the US puts the month first, almost nobody outside the US and a handful of other countries uses 12-hour AM/PM by default, and ISO 8601 is the one format that's unambiguous everywhere — which is exactly why it's the right choice for APIs and storage, and the wrong choice for a UI.

Why Regional Formatting Matters

Unix timestamps are universally understood by computers but meaningless to users. Displaying "1704067200" tells nothing; "January 1, 2024, 12:00 PM" is human-readable, but its format varies globally. Regional formatting requires considering:

  • Date order: MM/DD/YYYY (US) vs DD/MM/YYYY (Europe) vs YYYY/MM/DD (Asia)
  • Time format: 12-hour (with AM/PM) vs 24-hour
  • Timezone display: Local time, UTC offset, or timezone abbreviation
  • Language: Number names, day names, month names in user's language
  • Number format: Comma vs period as decimal separator

Basic Timestamp Formatting

JavaScript Formatting:

const timestamp = 1704067200;
const date = new Date(timestamp * 1000);

// ISO format (international standard)
console.log(date.toISOString()); // 2024-01-01T00:00:00.000Z

// Default locale format
console.log(date.toString()); // Mon Jan 01 2024 00:00:00 GMT+0000

// Custom formatting
console.log(date.toLocaleDateString()); // 1/1/2024 (locale-dependent)
console.log(date.toLocaleTimeString()); // 12:00:00 AM (locale-dependent)
console.log(date.toLocaleString()); // 1/1/2024, 12:00:00 AM

Python Formatting:

from datetime import datetime
import pytz

timestamp = 1704067200
dt = datetime.fromtimestamp(timestamp, tz=pytz.UTC)

# ISO format
print(dt.isoformat())  # 2024-01-01T00:00:00+00:00

# String formatting
print(dt.strftime("%Y-%m-%d %H:%M:%S"))  # 2024-01-01 00:00:00

Locale-Specific Formatting

Different regions have distinct conventions:

JavaScript with Locales:

const timestamp = 1704067200;
const date = new Date(timestamp * 1000);

// US English
console.log(date.toLocaleString('en-US'));
// 1/1/2024, 12:00:00 AM

// British English
console.log(date.toLocaleString('en-GB'));
// 01/01/2024, 00:00:00

// German
console.log(date.toLocaleString('de-DE'));
// 1.1.2024, 00:00:00

// French
console.log(date.toLocaleString('fr-FR'));
// 01/01/2024 00:00:00

// Japanese
console.log(date.toLocaleString('ja-JP'));
// 2024/1/1 0:00:00

// Arabic
console.log(date.toLocaleString('ar-EG'));
// ١/١/٢٠٢٤, ١٢:٠٠:٠٠ ص

Notice how dates, times, and even numerals change by locale.

Timezone Handling in Formatting

JavaScript Intl API:

const timestamp = 1704067200;
const date = new Date(timestamp * 1000);

// Format with timezone info
const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  timeZoneName: 'short'
});

console.log(formatter.format(date));
// December 31, 2023, 07:00:00 PM EST
// (midnight UTC on Jan 1 is still 7 PM the day before in New York — see the FAQ on this below)

// Multiple timezones
const timezones = [
  'America/New_York',    // EST
  'Europe/London',       // GMT
  'Asia/Tokyo',          // JST
  'Australia/Sydney'     // AEDT
];

timezones.forEach(tz => {
  const fmt = new Intl.DateTimeFormat('en-US', {
    timeZone: tz,
    year: 'numeric',
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    timeZoneName: 'short'
  });
  console.log(`${tz}: ${fmt.format(date)}`);
});

// Output:
// America/New_York: Dec 31, 2023, 07:00 PM EST
// Europe/London: Jan 1, 2024, 12:00 AM GMT
// Asia/Tokyo: Jan 1, 2024, 09:00 AM JST
// Australia/Sydney: Jan 1, 2024, 11:00 AM AEDT
Advertisement

Python with pytz and Localization

pytz still works and is common in existing codebases, but Python 3.9+ ships a standard-library alternative: zoneinfo. It needs no separate install (pip install tzdata on Windows for the database itself) and is the approach the Python docs now recommend for new code. Both are shown below — swap pytz.timezone('America/New_York') for ZoneInfo('America/New_York') from the zoneinfo module and the rest of the code is unchanged.

from datetime import datetime
import pytz
from babel.dates import format_datetime

timestamp = 1704067200
dt_utc = datetime.fromtimestamp(timestamp, tz=pytz.UTC)

# Convert to different timezones
ny_tz = pytz.timezone('America/New_York')
tokyo_tz = pytz.timezone('Asia/Tokyo')

dt_ny = dt_utc.astimezone(ny_tz)
dt_tokyo = dt_utc.astimezone(tokyo_tz)

print(f"New York: {dt_ny.strftime('%B %d, %Y %I:%M %p %Z')}")
# New York: December 31, 2023 07:00 PM EST
# (same instant as UTC midnight Jan 1 — New York is 5 hours behind in January)

print(f"Tokyo: {dt_tokyo.strftime('%B %d, %Y %I:%M %p %Z')}")
# Tokyo: January 01, 2024 09:00 AM JST

# Using Babel for localized month/day names
print(format_datetime(dt_utc, locale='de_DE'))
# 1. Januar 2024 00:00:00

print(format_datetime(dt_utc, locale='ja_JP'))
# 2024年1月1日 0:00:00

(See the regional format conventions table above for the full breakdown by country.)

Custom Format Patterns

JavaScript with date-fns:

date-fns's format() has no timeZone option — it always formats in the machine's local system timezone, not UTC and not an arbitrary zone you pick. That's fine for a browser rendering a date for the user sitting in front of it, but wrong for a server. If you need a specific IANA zone regardless of where the code runs, use the companion date-fns-tz package's formatInTimeZone() instead.

import { format } from 'date-fns';
import { enUS, de, ja } from 'date-fns/locale';

const timestamp = 1704067200;
const date = new Date(timestamp * 1000);

// Formats in the local system timezone — output below assumes UTC
// US English
console.log(format(date, 'MMMM d, yyyy p', { locale: enUS }));
// January 1, 2024 12:00 AM

// German
console.log(format(date, 'dd. MMMM yyyy HH:mm', { locale: de }));
// 01. Januar 2024 00:00

// Japanese
console.log(format(date, 'yyyy年MM月dd日 HH:mm', { locale: ja }));
// 2024年01月01日 00:00

// With an explicit IANA timezone (date-fns-tz)
import { formatInTimeZone } from 'date-fns-tz';
console.log(formatInTimeZone(date, 'Asia/Tokyo', 'yyyy年MM月dd日 HH:mm', { locale: ja }));
// 2024年01月01日 09:00

Python with babel:

from babel import dates

timestamp = 1704067200
# Convert to datetime (in UTC)
from datetime import datetime
dt = datetime.utcfromtimestamp(timestamp)

# Format in different locales
formats = {
    'en_US': 'MMMM d, yyyy h:mm a',
    'de_DE': 'dd. MMMM yyyy HH:mm',
    'ja_JP': 'yyyy年MM月dd日 HH:mm',
}

for locale, fmt in formats.items():
    print(f"{locale}: {dates.format_datetime(dt, fmt, locale=locale)}")

Handling User Preferences

Storing User Preferences:

// User settings
const userPreferences = {
  locale: 'de-DE',           // German (Germany)
  timeZone: 'Europe/Berlin', // Central European Time
  dateFormat: 'dd.MM.yyyy',  // DD.MM.YYYY
  timeFormat: '24h',         // 24-hour format
  ampmPreference: false      // Don't show AM/PM
};

// Function to format timestamp per user preferences
function formatForUser(timestamp, preferences) {
  const date = new Date(timestamp * 1000);

  return new Intl.DateTimeFormat(preferences.locale, {
    timeZone: preferences.timeZone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: preferences.timeFormat === '12h'
  }).format(date);
}

console.log(formatForUser(1704067200, userPreferences));
// 01.01.2024, 01:00:00
// (Berlin is UTC+1 in January — CET, not CEST, so it's one hour ahead of the UTC instant)

Relative Time Formatting

Users often prefer relative times ("2 hours ago") instead of absolute times.

JavaScript:

const timestamp = 1704067200;

function getRelativeTime(timestamp) {
  const now = Math.floor(Date.now() / 1000);
  const diff = now - timestamp;

  const units = [
    { name: 'year', seconds: 31536000 },
    { name: 'month', seconds: 2592000 },
    { name: 'week', seconds: 604800 },
    { name: 'day', seconds: 86400 },
    { name: 'hour', seconds: 3600 },
    { name: 'minute', seconds: 60 },
    { name: 'second', seconds: 1 }
  ];

  for (const unit of units) {
    const count = Math.floor(diff / unit.seconds);
    if (count >= 1) {
      const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
      return rtf.format(-count, unit.name);
    }
  }

  return 'just now';
}

console.log(getRelativeTime(1704067200));
// Output depends on today's date, since it's relative to Date.now() —
// e.g. "2 years ago" if run in 2026. With numeric: 'auto', a difference of
// exactly one unit prints a word instead of a number: format(-1, 'month')
// returns "last month", not "1 month ago" — see below.

With locale support:

function getRelativeTimeLocale(timestamp, locale = 'en-US') {
  const now = Math.floor(Date.now() / 1000);
  const diff = now - timestamp;

  const units = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
  const seconds = [31536000, 2592000, 604800, 86400, 3600, 60, 1];

  for (let i = 0; i < units.length; i++) {
    const count = Math.floor(diff / seconds[i]);
    if (count >= 1) {
      const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
      return rtf.format(-count, units[i]);
    }
  }

  return new Intl.DateTimeFormat(locale).format(new Date(timestamp * 1000));
}

// numeric: 'auto' substitutes natural words for a difference of exactly 1 unit
console.log(new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' }).format(-1, 'month'));
// "last month"
console.log(new Intl.RelativeTimeFormat('de-DE', { numeric: 'auto' }).format(-1, 'month'));
// "letzten Monat"
console.log(new Intl.RelativeTimeFormat('fr-FR', { numeric: 'auto' }).format(-1, 'month'));
// "le mois dernier"

// For larger differences it falls back to numeric phrasing:
console.log(new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' }).format(-3, 'month'));
// "3 months ago"

Server-Side Formatting

Node.js/Express:

app.get('/api/timestamp/:ts', (req, res) => {
  const timestamp = parseInt(req.params.ts);
  const locale = req.query.locale || 'en-US';
  const timeZone = req.query.timezone || 'UTC';

  const date = new Date(timestamp * 1000);

  const formatter = new Intl.DateTimeFormat(locale, {
    timeZone,
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short'
  });

  res.json({
    timestamp,
    formatted: formatter.format(date),
    iso: date.toISOString(),
    locale,
    timeZone
  });
});

// Usage
// /api/timestamp/1704067200?locale=de-DE&timezone=Europe/Berlin
// Returns: "1. Januar 2024 um 01:00:00 MEZ"

Best Practices

  1. Store in UTC - Always store timestamps in UTC/Unix format
  2. Format on display - Convert to local time only when displaying
  3. Respect user preferences - Allow users to set locale/timezone
  4. Validate input - Ensure valid timestamps before formatting
  5. Use built-in APIs first - Intl.DateTimeFormat (JavaScript) and zoneinfo/Babel (Python) cover almost every formatting need without a dependency. Reach for date-fns or Luxon only when you need parsing, arithmetic, or duration math on top of formatting. Avoid Moment.js for new code — its own maintainers declared it a legacy project in 2020 and recommend date-fns or Luxon instead.
  6. Test globally - Test with various locales and timezones, and specifically around midnight UTC and DST transitions, where the local calendar day or offset can flip
  7. ISO for APIs - Use ISO 8601 for APIs/data interchange

The Bottom Line

Formatting a Unix timestamp for a region is a two-part decision, not one: pick the timezone (which controls what the local wall-clock time actually is) and pick the locale (which controls how that wall-clock time gets written down). Confusing the two is where most bugs come from — a formatter with the wrong timezone shows the right-looking format at the wrong hour, and a formatter with the wrong locale shows the right hour in a format your users will misread. Let Intl.DateTimeFormat (or your language's equivalent) own both decisions instead of hand-building strings, store everything in UTC, and format only at the point of display. Verify your own conversions against the Unix Timestamp Converter before you ship them.

Frequently Asked Questions

How do I format a date for a specific locale in JavaScript?

Build a Date object from your timestamp, then pass a BCP 47 locale code into Intl.DateTimeFormat or toLocaleString() — for example new Intl.DateTimeFormat('de-DE').format(date) for German conventions or 'ja-JP' for Japanese. Don't hand-build the string with getMonth()/getDate() concatenation; Intl.DateTimeFormat already knows the correct date order, separators, and digit style for every supported locale and updates automatically as locale data changes.

What is ISO 8601 and why should APIs use it?

ISO 8601 is the international standard date-time format: YYYY-MM-DDTHH:mm:ssZ, for example 2024-01-01T00:00:00.000Z. It's unambiguous (no MM/DD vs DD/MM guessing), sorts correctly as plain text, and every mainstream language and database parses it without extra libraries. APIs and data interchange should always use ISO 8601 (or the equivalent Unix timestamp) and leave locale formatting to the presentation layer — never send a pre-localized string like "1/1/2024" between systems.

Does Intl.DateTimeFormat handle timezone conversion?

Yes — pass an IANA timezone identifier (like America/New_York or Asia/Tokyo) in the timeZone option and Intl.DateTimeFormat converts the underlying instant to that zone's local wall-clock time before formatting it, including daylight saving adjustments. The Date object itself always represents a single UTC instant; timeZone only affects how that instant is displayed, not what instant it is.

What's the difference between toLocaleDateString() and Intl.DateTimeFormat?

They use the same underlying locale engine, but Intl.DateTimeFormat is reusable and faster in a loop — you build the formatter once with new Intl.DateTimeFormat(locale, options) and call .format() on many dates, while toLocaleDateString() re-parses its options object on every single call. For formatting more than a handful of dates (a table, a log viewer, a report), construct one Intl.DateTimeFormat instance and reuse it.

How do I convert a Unix timestamp to a specific timezone in Python?

Use the standard library zoneinfo module (Python 3.9+): datetime.fromtimestamp(ts, tz=ZoneInfo("Asia/Tokyo")) converts directly to that zone's local time without any third-party dependency. pytz still works and is common in older codebases, but zoneinfo is now the recommended approach since it ships with Python and stays current with the IANA timezone database via tzdata.

Why does my date show the wrong day when I convert to another timezone?

Because the same UTC instant can fall on different calendar days in different zones. Midnight UTC on January 1 is still 7:00 PM on December 31 in New York (EST, UTC-5) and 9:00 AM on January 1 in Tokyo (JST, UTC+9). This is expected and correct — if your app assumes "the date" is the same everywhere, you'll silently drop or duplicate events near midnight for users far from UTC.

Should I format dates on the server or the client?

Format on the client whenever the viewer's locale and timezone matter, since the server usually doesn't know either reliably (an IP-based guess is not the same as the user's actual OS/browser setting). Send the raw timestamp or ISO 8601 string from the server and let the client's Intl.DateTimeFormat render it. Format on the server only when you control the timezone deliberately — audit logs, invoices, or reports that must show one canonical timezone regardless of who's viewing.

What timezone should I use to store timestamps in a database?

UTC, always. Store every timestamp as a Unix timestamp or a UTC-based type (TIMESTAMPTZ in Postgres, for example), then convert to the viewer's local timezone only at the moment of display. Storing "local time" without a timezone attached is a common source of bugs, because the same stored value becomes ambiguous the instant you have users — or servers — in more than one zone.

How do I format relative time like '2 hours ago' in different languages?

Use Intl.RelativeTimeFormat, which localizes both the wording and the pluralization: new Intl.RelativeTimeFormat('de-DE', { numeric: 'auto' }).format(-1, 'month') returns "letzten Monat" ("last month"), while the French locale returns "le mois dernier". The numeric: 'auto' option substitutes natural words like "yesterday" or "last month" for -1/0/1 and falls back to numeric phrasing ("3 months ago") for larger differences.

Can I use hour12 to control 12-hour vs 24-hour format?

Yes — pass hour12: true or hour12: false in the Intl.DateTimeFormat options object to force 12-hour (with AM/PM) or 24-hour display regardless of what the locale would default to. This is useful when you want a locale's date order and language but a specific, user-chosen time format — for example en-GB dates with a 12-hour clock for a user who prefers it.

localizationinternationalizationunix-timestampformattingregional