Dates are one of the most common sources of bugs in software. Timezone confusion, month zero-indexing, locale differences, and format string quirks trip up developers in every language. This guide covers practical date formatting across JavaScript, Python, Go, and PHP, with working code you can use directly.
Quick reference: common date format patterns
Before the code, here are the most common output formats you'll need and what they look like:
| Format | Example output | Use case |
|---|---|---|
| ISO 8601 (UTC) | 2026-07-13T14:30:00Z |
APIs, databases, logs |
| ISO 8601 (local) | 2026-07-13T16:30:00+02:00 |
Calendar events |
| Short date | 2026-07-13 |
File names, SQL, sorting |
| Human readable | July 13, 2026 |
UI display |
| Locale-aware | 13.07.2026 / 07/13/2026 |
Localised interfaces |
| Unix timestamp | 1752415800 |
Storage, math, APIs |
| Relative | 3 hours ago |
Social feeds, comments |
The ISO 8601 date format (YYYY-MM-DD) is the safe default for any data you store or transmit. Human-readable formats belong only in UI display — never in storage or sorting.
JavaScript
JavaScript's built-in Date object is notoriously awkward, but it has improved significantly with the Intl.DateTimeFormat API. For complex use cases, a library like date-fns or Temporal (a Stage 3 proposal) is worth adding.
Basic formatting
const now = new Date();
// ISO 8601 (always UTC)
console.log(now.toISOString());
// → "2026-07-13T14:30:00.000Z"
// Short date (local timezone)
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0'); // ← +1 because months are 0-indexed
const dd = String(now.getDate()).padStart(2, '0');
console.log(`${yyyy}-${mm}-${dd}`);
// → "2026-07-13"
The month gotcha: getMonth() returns 0–11, not 1–12. Forgetting the + 1 is one of the most common JavaScript date bugs.
Locale-aware formatting with Intl
const date = new Date('2026-07-13T14:30:00Z');
// US English
console.log(new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }).format(date));
// → "July 13, 2026"
// German
console.log(new Intl.DateTimeFormat('de-DE', { dateStyle: 'long' }).format(date));
// → "13. Juli 2026"
// With time, in a specific timezone
console.log(new Intl.DateTimeFormat('en-GB', {
dateStyle: 'short',
timeStyle: 'short',
timeZone: 'Europe/London',
}).format(date));
// → "13/07/2026, 15:30"
Intl.DateTimeFormat handles all locale and timezone complexity without any third-party library. It works in all modern browsers and Node.js 12+.
Unix timestamp conversion
// Date → Unix timestamp (seconds)
const ts = Math.floor(Date.now() / 1000);
console.log(ts); // → 1752415800
// Unix timestamp → Date string
const date = new Date(ts * 1000); // ← multiply by 1000: JS uses milliseconds
console.log(date.toISOString()); // → "2026-07-13T14:30:00.000Z"
Use Toolmingo's Unix Timestamp Converter to convert timestamps without writing code.
Using date-fns
import { format, parseISO, formatDistanceToNow } from 'date-fns';
const date = parseISO('2026-07-13T14:30:00');
console.log(format(date, 'MMMM d, yyyy')); // → "July 13, 2026"
console.log(format(date, 'yyyy-MM-dd HH:mm:ss')); // → "2026-07-13 14:30:00"
console.log(formatDistanceToNow(date, { addSuffix: true })); // → "3 hours ago"
Python
Python's datetime module uses strftime (string format time) and strptime (string parse time). The format codes are mostly POSIX-standard and portable.
Basic formatting with strftime
from datetime import datetime, timezone
now = datetime.now()
utc = datetime.now(timezone.utc)
# ISO 8601 (local, no timezone info)
print(now.strftime('%Y-%m-%dT%H:%M:%S'))
# → "2026-07-13T14:30:00"
# ISO 8601 (UTC, with Z suffix)
print(utc.strftime('%Y-%m-%dT%H:%M:%SZ'))
# → "2026-07-13T14:30:00Z"
# Or use isoformat() — the idiomatic Python way
print(utc.isoformat())
# → "2026-07-13T14:30:00+00:00"
# Short date
print(now.strftime('%Y-%m-%d'))
# → "2026-07-13"
# Human-readable
print(now.strftime('%B %d, %Y'))
# → "July 13, 2026"
Common strftime format codes
| Code | Meaning | Example |
|---|---|---|
%Y |
4-digit year | 2026 |
%m |
Month, zero-padded | 07 |
%d |
Day, zero-padded | 13 |
%H |
Hour (24-hour), zero-padded | 14 |
%M |
Minute, zero-padded | 30 |
%S |
Second, zero-padded | 00 |
%B |
Full month name | July |
%b |
Abbreviated month name | Jul |
%A |
Full weekday name | Monday |
%Z |
Timezone name | UTC |
Parsing a date string
from datetime import datetime
# strptime: string → datetime (format must match exactly)
s = "2026-07-13 14:30:00"
dt = datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
print(dt.year) # → 2026
# fromisoformat (Python 3.7+) — parses ISO 8601 strings
dt2 = datetime.fromisoformat('2026-07-13T14:30:00+02:00')
print(dt2) # → 2026-07-13 14:30:00+02:00
Unix timestamps in Python
from datetime import datetime, timezone
import time
# Current timestamp (seconds)
ts = int(time.time())
print(ts) # → 1752415800
# timestamp → datetime (UTC)
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt.isoformat()) # → "2026-07-13T14:30:00+00:00"
# datetime → timestamp
ts2 = int(dt.timestamp())
print(ts2) # → 1752415800
Timezone trap: datetime.fromtimestamp(ts) without tz=timezone.utc gives a naive datetime in your local timezone. Always pass tz=timezone.utc when working with Unix timestamps to avoid ambiguity.
Go
Go's time package uses a unique reference time — Mon Jan 2 15:04:05 MST 2006 — instead of format codes. You format a date by writing what you want the reference time itself to look like. This confuses almost everyone at first.
The reference time explained
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// The magic reference time: Mon Jan 2 15:04:05 MST 2006
// January = month 1, day 2, 15:04:05 = 3:04:05 PM, year 2006
fmt.Println(now.Format("2006-01-02")) // → "2026-07-13"
fmt.Println(now.Format("2006-01-02 15:04:05")) // → "2026-07-13 14:30:00"
fmt.Println(now.Format("January 2, 2006")) // → "July 13, 2026"
fmt.Println(now.Format(time.RFC3339)) // → "2026-07-13T14:30:00+02:00"
fmt.Println(now.UTC().Format(time.RFC3339)) // → "2026-07-13T12:30:00Z"
}
Predefined constants (use these for standard formats)
fmt.Println(now.Format(time.RFC3339)) // ISO 8601 with timezone
fmt.Println(now.Format(time.RFC3339Nano)) // with nanoseconds
fmt.Println(now.Format(time.DateOnly)) // "2006-01-02" (Go 1.20+)
fmt.Println(now.Format(time.TimeOnly)) // "15:04:05" (Go 1.20+)
fmt.Println(now.Format(time.DateTime)) // "2006-01-02 15:04:05" (Go 1.20+)
Parsing a date string in Go
const layout = "2006-01-02T15:04:05Z07:00"
t, err := time.Parse(layout, "2026-07-13T14:30:00+02:00")
if err != nil {
log.Fatal(err)
}
fmt.Println(t.UTC()) // → 2026-07-13 12:30:00 +0000 UTC
// Parse short date
t2, _ := time.Parse("2006-01-02", "2026-07-13")
fmt.Println(t2) // → 2026-07-13 00:00:00 +0000 UTC
Unix timestamps in Go
now := time.Now()
// time.Time → Unix timestamp
ts := now.Unix() // seconds
tsMs := now.UnixMilli() // milliseconds
// Unix timestamp → time.Time
t := time.Unix(ts, 0)
fmt.Println(t.UTC().Format(time.RFC3339)) // → "2026-07-13T14:30:00Z"
Gotcha: Go's time.Parse treats a string with no timezone as UTC. time.ParseInLocation lets you specify the timezone explicitly when the string has no offset.
PHP
PHP has two date APIs: the legacy procedural functions (date(), mktime()) and the modern object-oriented DateTime/DateTimeImmutable classes. Use DateTimeImmutable in new code — it avoids mutation bugs.
Basic formatting
<?php
$now = new DateTimeImmutable();
// ISO 8601 (local timezone)
echo $now->format('Y-m-d\TH:i:s');
// → "2026-07-13T14:30:00"
// ISO 8601 with timezone offset
echo $now->format(DateTimeInterface::ATOM);
// → "2026-07-13T14:30:00+02:00"
// Short date
echo $now->format('Y-m-d');
// → "2026-07-13"
// Human-readable
echo $now->format('F j, Y');
// → "July 13, 2026"
// Custom European format
echo $now->format('d.m.Y H:i');
// → "13.07.2026 14:30"
Common PHP date format characters
| Char | Meaning | Example |
|---|---|---|
Y |
4-digit year | 2026 |
m |
Month, zero-padded | 07 |
d |
Day, zero-padded | 13 |
H |
Hour (24-hour), zero-padded | 14 |
i |
Minute, zero-padded | 30 |
s |
Second, zero-padded | 00 |
F |
Full month name | July |
M |
Abbreviated month name | Jul |
l |
Full weekday name | Monday |
N |
ISO weekday number (1=Mon, 7=Sun) | 1 |
Parsing and converting
<?php
// Parse any date string
$dt = new DateTimeImmutable('2026-07-13 14:30:00');
// Parse with explicit format (safer for ambiguous strings)
$dt2 = DateTimeImmutable::createFromFormat('d/m/Y', '13/07/2026');
echo $dt2->format('Y-m-d'); // → "2026-07-13"
// Unix timestamp → DateTimeImmutable
$ts = 1752415800;
$dt3 = (new DateTimeImmutable())->setTimestamp($ts);
echo $dt3->setTimezone(new DateTimeZone('UTC'))->format(DateTimeInterface::ATOM);
// → "2026-07-13T14:30:00+00:00"
// DateTimeImmutable → Unix timestamp
echo $dt3->getTimestamp(); // → 1752415800
Timezone handling in PHP
<?php
$utc = new DateTimeZone('UTC');
$cet = new DateTimeZone('Europe/Berlin');
// Create UTC datetime
$dt = new DateTimeImmutable('2026-07-13 14:30:00', $utc);
// Convert to CET
$dtCet = $dt->setTimezone($cet);
echo $dtCet->format('Y-m-d H:i:s T');
// → "2026-07-13 16:30:00 CEST"
6 common date formatting mistakes
1. Storing human-readable formats in a database
-- Bad: cannot sort, cannot query ranges reliably
INSERT INTO events (created_at) VALUES ('July 13, 2026');
-- Good: ISO 8601, sortable lexicographically
INSERT INTO events (created_at) VALUES ('2026-07-13T14:30:00Z');
Always store dates as ISO 8601 strings or Unix timestamps in your database. Apply human-readable formatting only at display time.
2. Forgetting JavaScript's zero-indexed months
// Bad: month will be off by one
const month = new Date().getMonth(); // → 6 (for July)
const display = `Month: ${month}`; // → "Month: 6" ← wrong
// Good
const display = `Month: ${new Date().getMonth() + 1}`; // → "Month: 7"
3. Mixing up UTC and local time
A date object always represents a single moment in time. What changes is the display. If you format a Date/datetime/time.Time without specifying a timezone, you get local time — which differs between your development machine and production server.
Rule: Store and transmit UTC. Convert to local time only for display.
4. Python's datetime.utcnow() being naive
# Bad: naive datetime, no timezone info, misleads future code
from datetime import datetime
now = datetime.utcnow() # returns a naive datetime despite being "utc"
# Good: timezone-aware UTC datetime
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
datetime.utcnow() is deprecated in Python 3.12. Use datetime.now(timezone.utc) instead.
5. Go's confusing reference time
// Bad: this is a mistake — using current year and month in the layout
wrong := now.Format("2026-07") // won't format correctly
// Good: ALWAYS use the reference time numbers
correct := now.Format("2006-01") // "2026-07" ✓
The reference time values (2006, 01, 02, 15, 04, 05) are not magic constants you choose — they are the exact numbers from the reference time Mon Jan 2 15:04:05 MST 2006. Using the wrong numbers silently produces wrong output.
6. PHP's date() using server timezone
// Bad: depends on server's date.timezone ini setting
echo date('Y-m-d H:i:s');
// Good: always explicit
$dt = new DateTimeImmutable('now', new DateTimeZone('UTC'));
echo $dt->format('Y-m-d H:i:s');
Set date.timezone = UTC in php.ini or call date_default_timezone_set('UTC') at application boot to eliminate this risk.
FAQ
Q: What is the difference between ISO 8601 and RFC 3339?
RFC 3339 is a profile of ISO 8601 with tighter constraints — it requires a complete datetime (not just a date), requires the separator to be T (not a space), and requires a timezone offset. For internet protocols and APIs, use RFC 3339. Most ISO 8601 parsers will also accept RFC 3339 strings.
Q: Should I use Unix timestamps or ISO 8601 strings? Use Unix timestamps when you need to do arithmetic (difference between two dates, adding N days) or when storage size matters. Use ISO 8601 strings when you need human readability in logs or config files, and when you need unambiguous timezone information. Both can be parsed reliably — the choice depends on your use case.
Q: How do I find the number of days between two dates?
In JavaScript: Math.floor((dateB - dateA) / (1000 * 60 * 60 * 24)). In Python: (date_b - date_a).days. In Go: int(dateB.Sub(dateA).Hours() / 24). In PHP: $dateA->diff($dateB)->days. Use the Age Calculator for quick date difference calculations.
Q: Why does new Date('2026-07-13') give me a time of midnight UTC but new Date('2026-07-13T00:00:00') gives midnight local time?
This is a browser/Node.js quirk: date-only strings (YYYY-MM-DD) are parsed as UTC midnight by JavaScript, while datetime strings without a timezone offset are parsed as local time. To avoid this inconsistency, always include an explicit timezone offset: new Date('2026-07-13T00:00:00Z') for UTC or new Date('2026-07-13T00:00:00+00:00').
Q: How do I format a relative time like "3 hours ago"?
Use Intl.RelativeTimeFormat in JavaScript: new Intl.RelativeTimeFormat('en').format(-3, 'hour') → "3 hours ago". In Python, the humanize library provides humanize.naturaltime(delta). In Go, use github.com/dustin/go-humanize. In PHP, Carbon::now()->diffForHumans($date).
Q: What is the Y2K38 problem?
Unix timestamps stored as 32-bit signed integers will overflow on January 19, 2038 at 03:14:07 UTC. Modern systems use 64-bit integers, which won't overflow for billions of years. Verify that any legacy C code or embedded system you work with uses time_t as a 64-bit type.