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:
Zfor UTC,+HH:MMor-HH:MMfor 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
| Option | Values | Example |
|---|---|---|
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” |
hour12 | true, false | false → 24-hour clock |
timeZone | IANA 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
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2026 |
%m | Month (zero-padded) | 03 |
%d | Day (zero-padded) | 15 |
%H | Hour 24h (zero-padded) | 14 |
%M | Minute (zero-padded) | 30 |
%S | Second (zero-padded) | 00 |
%I | Hour 12h (zero-padded) | 02 |
%p | AM/PM | PM |
%A | Weekday full name | Sunday |
%B | Month full name | March |
%Z | Timezone name | UTC |
%z | UTC 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
| Output | JavaScript (Intl) | Python (strftime) | PostgreSQL |
|---|---|---|---|
2026-03-15 | { dateStyle: 'short' } (locale-dependent) | %Y-%m-%d | YYYY-MM-DD |
March 15, 2026 | { dateStyle: 'long' } | %B %d, %Y | Month DD, YYYY |
14:30:00 | { timeStyle: 'medium', hour12: false } | %H:%M:%S | HH24:MI:SS |
2:30 PM | { timeStyle: 'short' } | %I:%M %p | HH12:MI PM |
| ISO 8601 | .toISOString() | .isoformat() | YYYY-MM-DD"T"HH24:MI:SSOF |