NC Logo UseToolSuite
Time & Date

Date Formatting for Developers: ISO 8601, Intl API, and Cross-Language Patterns

A practical guide to formatting dates and times in JavaScript, Python, and SQL. Covers ISO 8601, the Intl.DateTimeFormat API, locale-aware formatting, and common pitfalls.

Necmeddin Cunedioglu Necmeddin Cunedioglu

Practice what you learn

Unix Timestamp Converter

Try it free →

Date Formatting for Developers: ISO 8601, Intl API, and Cross-Language Patterns

Date formatting looks simple until you ship to users in different locales. Format strings differ between languages, locale conventions vary dramatically across regions, and the same date can be ambiguous depending on the format. This guide covers the reliable, standards-based approaches.

Convert and inspect Unix timestamps with our Timestamp Converter.

ISO 8601 — The Unambiguous Format

For data exchange between systems, always use ISO 8601. It is unambiguous and machine-readable:

2026-03-15T14:30:00Z          ← UTC timestamp
2026-03-15T09:30:00-05:00     ← With explicit offset
2026-03-15                    ← Date only
14:30:00                      ← Time only
2026-W11                      ← Week date
P1Y2M3DT4H5M6S                ← Duration (1 year, 2 months, 3 days, 4h 5m 6s)

The key rules:

  • Year-Month-Day (not Day-Month-Year or Month-Day-Year)
  • Always include timezone: Z for UTC, +HH:MM or -HH:MM for offsets
  • Separate date and time with T

Never use formats like 03/15/26 or 15-Mar-2026 in APIs — they are locale-specific and ambiguous.

Date Formatting in JavaScript

The Intl.DateTimeFormat API

The built-in Intl.DateTimeFormat is the modern, locale-aware approach:

const date = new Date('2026-03-15T14:30:00Z');

// Short date in US English
new Intl.DateTimeFormat('en-US', { dateStyle: 'short' }).format(date);
// "3/15/26"

// Long date in British English
new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' }).format(date);
// "15 March 2026"

// Full date and time in French
new Intl.DateTimeFormat('fr-FR', {
  dateStyle: 'full',
  timeStyle: 'short',
  timeZone: 'Europe/Paris',
}).format(date);
// "dimanche 15 mars 2026 à 15:30"

// Custom format
new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'short',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  timeZone: 'America/New_York',
}).format(date);
// "Mar 15, 2026, 10:30 AM"

Intl.DateTimeFormat Options

OptionValuesExample
dateStyle'full', 'long', 'medium', 'short''long' → “March 15, 2026”
timeStyle'full', 'long', 'medium', 'short''short' → “2:30 PM”
year'numeric', '2-digit''numeric' → “2026”
month'numeric', '2-digit', 'long', 'short', 'narrow''short' → “Mar”
day'numeric', '2-digit''numeric' → “15”
hour12true, falsefalse → 24-hour clock
timeZoneIANA timezone string'America/Chicago'

toISOString() for Machine-Readable Output

const now = new Date();
now.toISOString();
// "2026-03-15T14:30:00.000Z" — always UTC, always ISO 8601

Use toISOString() whenever you need to store or transmit a date — it is unambiguous and parseable everywhere.

Relative Time with Intl.RelativeTimeFormat

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

rtf.format(-1, 'day');   // "yesterday"
rtf.format(2, 'day');    // "in 2 days"
rtf.format(-3, 'hour');  // "3 hours ago"
rtf.format(1, 'month');  // "next month"

Calculate the difference manually:

function timeAgo(dateString) {
  const diff = (new Date() - new Date(dateString)) / 1000; // seconds
  const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
  if (diff < 60) return rtf.format(-Math.round(diff), 'second');
  if (diff < 3600) return rtf.format(-Math.round(diff / 60), 'minute');
  if (diff < 86400) return rtf.format(-Math.round(diff / 3600), 'hour');
  return rtf.format(-Math.round(diff / 86400), 'day');
}

Date Formatting in Python

strftime Format Codes

CodeMeaningExample
%Y4-digit year2026
%mMonth (zero-padded)03
%dDay (zero-padded)15
%HHour 24h (zero-padded)14
%MMinute (zero-padded)30
%SSecond (zero-padded)00
%IHour 12h (zero-padded)02
%pAM/PMPM
%AWeekday full nameSunday
%BMonth full nameMarch
%ZTimezone nameUTC
%zUTC offset+0000
from datetime import datetime, timezone

now = datetime.now(timezone.utc)

# ISO 8601
now.isoformat()  # "2026-03-15T14:30:00+00:00"

# Custom format
now.strftime('%Y-%m-%d %H:%M:%S UTC')  # "2026-03-15 14:30:00 UTC"
now.strftime('%B %d, %Y')             # "March 15, 2026"
now.strftime('%m/%d/%Y')              # "03/15/2026"

Parsing Dates in Python

from datetime import datetime

# Parse ISO 8601 (Python 3.7+)
datetime.fromisoformat('2026-03-15T14:30:00+00:00')

# Parse with strptime
datetime.strptime('15/03/2026', '%d/%m/%Y')
datetime.strptime('March 15, 2026', '%B %d, %Y')

Timezone-Aware Datetimes

from datetime import datetime, timezone
from zoneinfo import ZoneInfo  # Python 3.9+

utc_now = datetime.now(timezone.utc)

# Convert to New York time
ny_time = utc_now.astimezone(ZoneInfo('America/New_York'))
ny_time.strftime('%Y-%m-%d %I:%M %p %Z')
# "2026-03-15 10:30 AM EDT"

Date Formatting in SQL

PostgreSQL

-- Format a timestamp
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS');
-- "2026-03-15 14:30:00"

SELECT TO_CHAR(NOW(), 'Month DD, YYYY');
-- "March     15, 2026"

-- Parse a string to timestamp
SELECT TO_TIMESTAMP('2026-03-15 14:30:00', 'YYYY-MM-DD HH24:MI:SS');

MySQL

-- Format a datetime
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s');
-- "2026-03-15 14:30:00"

SELECT DATE_FORMAT(NOW(), '%M %d, %Y');
-- "March 15, 2026"

-- Parse a string
SELECT STR_TO_DATE('15/03/2026', '%d/%m/%Y');

Common Formatting Mistakes

1. Month/Day ambiguity

03/04/2026 — is this March 4th (US) or April 3rd (UK)? Always use ISO 8601 or explicit month names in user-facing text.

2. Two-digit years

26 for 2026 is ambiguous near century boundaries. Always use 4-digit years in storage and data exchange.

3. 12-hour clock without AM/PM

“3:30” — is it 3:30 AM or 3:30 PM? Always include AM/PM for 12-hour clocks, or use 24-hour format.

4. No timezone on stored timestamps

Storing 2026-03-15 14:30:00 in a database without timezone context makes the value ambiguous. When read on a server in a different timezone, the interpretation changes.

5. Building format strings manually

// Fragile — breaks for single-digit months/days
const d = new Date();
const formatted = `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
// "3/5/2026" (missing leading zero)

// Robust — use Intl.DateTimeFormat
new Intl.DateTimeFormat('en-US').format(d);
// "3/5/2026" (consistent with locale expectations)

// Or padStart for custom formats
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
const iso = `${y}-${m}-${day}`;
// "2026-03-05"

Quick Reference: Format String Comparison

OutputJavaScript (Intl)Python (strftime)PostgreSQL
2026-03-15{ dateStyle: 'short' } (locale-dependent)%Y-%m-%dYYYY-MM-DD
March 15, 2026{ dateStyle: 'long' }%B %d, %YMonth DD, YYYY
14:30:00{ timeStyle: 'medium', hour12: false }%H:%M:%SHH24:MI:SS
2:30 PM{ timeStyle: 'short' }%I:%M %pHH12:MI PM
ISO 8601.toISOString().isoformat()YYYY-MM-DD"T"HH24:MI:SSOF
Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.