Date Formatting in Python, JavaScript & Java
How to parse, format, and display dates across timezones in Python, JavaScript, and Java, with a per-language directive cheat sheet.
Overview
Formatting turns a Date, datetime, or Instant into something a person can read; parsing does the reverse. Both look trivial and both break in the same two places: ambiguous local formats (02/03/2026 means different days in the US and in Europe) and timezone handling.
The rule that prevents most production bugs: store and transmit dates as UTC instants (ISO 8601 or epoch), and convert to the user’s locale and timezone only at the presentation layer.
When to Use
Use this recipe when you need to:
- Serialize dates to JSON or XML for an REST API, where ISO 8601 is the de facto standard.
- Render dates in the UI so each reader sees their own format:
10/22/2026for US users,22/10/2026for Spanish ones. - Parse dates entered by users or loaded from files. See Data Validation for sanitizing that input.
- Convert one instant between timezones, such as showing a deploy time in each teammate’s zone.
- Write precise timestamps to logs and audit trails. See Logging for structured patterns.
Skip manual string formatting when you need relative times (“3 hours ago”) or calendar math (next business day, every second Tuesday). Dedicated libraries handle those better; see Variants.
Solution
Python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# Current instant, stored in UTC
now_utc = datetime.now(timezone.utc)
print(now_utc.isoformat()) # 2026-06-10T14:30:00+00:00
# Format for display
print(now_utc.strftime("%Y-%m-%d %H:%M:%S")) # 2026-06-10 14:30:00
print(now_utc.strftime("%A, %B %d, %Y")) # Wednesday, June 10, 2026
# Parse an ISO 8601 string and convert zone
dt = datetime.fromisoformat("2026-06-10T14:30:00+00:00")
madrid = dt.astimezone(ZoneInfo("Europe/Madrid"))
print(madrid.strftime("%Y-%m-%d %H:%M %Z")) # 2026-06-10 16:30 CEST
# Parse a local format explicitly
dt2 = datetime.strptime("10/06/2026", "%d/%m/%Y") # June 10, not Oct 6
JavaScript
const now = new Date();
// ISO 8601, always UTC
console.log(now.toISOString()); // 2026-06-10T14:30:00.000Z
// Locale-aware formatting
console.log(now.toLocaleString('en-US', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'America/New_York',
})); // Wednesday, June 10, 2026 at 10:30 AM
// Intl.DateTimeFormat, created once and reused
const fmt = new Intl.DateTimeFormat('de-DE', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit',
timeZone: 'Europe/Berlin',
});
console.log(fmt.format(now)); // 10.06.2026, 16:30
// Parse an ISO string
const parsed = new Date('2026-06-10T14:30:00Z');
console.log(parsed.toISOString());
Java
import java.time.*;
import java.time.format.DateTimeFormatter;
ZonedDateTime nowUtc = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println(nowUtc.format(DateTimeFormatter.ISO_INSTANT));
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
System.out.println(nowUtc.format(formatter)); // 2026-06-10 14:30:00 UTC
ZonedDateTime parsed = ZonedDateTime.parse("2026-06-10T14:30:00Z");
ZonedDateTime tokyo = parsed.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyo.format(formatter)); // 2026-06-10 23:30:00 JST
How It Works
An instant is one fixed point on the global timeline. Wall time is different: it’s whatever a clock shows in a particular zone, and it drifts with daylight saving. 2026-06-10T14:30:00Z names an instant without ambiguity; 14:30 alone could sit anywhere on the timeline.
The other distinction that trips people up is offset versus timezone. +02:00 is only a fixed distance from UTC, while Europe/Madrid is a named zone in the IANA database that carries rules of its own, including daylight saving: UTC+1 in winter, UTC+2 in summer. Freeze the offset into your data and you’re off by an hour for half the year.
DST is where the traps live. At spring-forward, a local time is skipped and 02:30 never happens that day. At fall-back, a local time happens twice, so 01:30 names no single instant. UTC sidesteps both problems.
Format Pattern Cheat Sheet
Pattern letters aren’t portable between languages, and case means different things in each. In Java, MM means month while mm means minutes; Python keeps the same letters in a different convention: %m for month, %M for minutes. Swapping them produces silently wrong output.
Python strftime / strptime
| Directive | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2026 |
%m | Month (01-12) | 06 |
%d | Day of month (01-31) | 10 |
%A | Full weekday name | Wednesday |
%B | Full month name | June |
%H | Hour 24h (00-23) | 14 |
%I | Hour 12h (01-12) | 02 |
%M | Minute | 30 |
%S | Second | 00 |
%p | AM or PM | PM |
%z | UTC offset | +0000 |
%Z | Timezone name | UTC |
%j | Day of year | 161 |
Java DateTimeFormatter
| Pattern | Meaning | Example |
|---|---|---|
yyyy | Year | 2026 |
MM | Month number | 06 |
MMM | Short month name | Jun |
MMMM | Full month name | June |
dd | Day of month | 10 |
EEEE | Full weekday | Wednesday |
HH | Hour 0-23 | 14 |
mm | Minute | 30 |
ss | Second | 00 |
XXX | ISO 8601 offset | Z or +02:00 |
VV | IANA timezone ID | Europe/Madrid |
z | Short timezone name | CEST |
JavaScript Intl.DateTimeFormat
| Option | Values | Example output |
|---|---|---|
dateStyle | full, long, medium, short | Wednesday, June 10, 2026 |
timeStyle | full, long, medium, short | 2:30 PM |
year | numeric, 2-digit | 2026 or 26 |
month | numeric, 2-digit, long, short, narrow | 6, 06, June, Jun, J |
day | numeric, 2-digit | 10 |
weekday | long, short, narrow | Wednesday, Wed, W |
hour, minute, second | numeric, 2-digit | 14, 30, 00 |
hour12 | true, false | 2:30 PM vs 14:30 |
timeZone | IANA ID | Europe/Madrid |
timeZoneName | short, long | CEST |
The same output in each language
| Output | Python | Java | JavaScript |
|---|---|---|---|
2026-06-10 | %Y-%m-%d | yyyy-MM-dd | dateStyle: 'short' (locale dependent) |
2026-06-10T14:30:00Z | isoformat() | ISO_INSTANT | toISOString() |
Wednesday, June 10, 2026 | %A, %B %d, %Y | EEEE, MMMM d, yyyy | dateStyle: 'full' in en-US |
14:30 | %H:%M | HH:mm | timeStyle: 'short' in en-GB |
2:30 PM | %I:%M %p | h:mm a | hour12: true in en-US |
Variants and Alternatives
-
Python: the stdlib
datetimepluszoneinfo(Python 3.9+) covers most cases.python-dateutilhandles fuzzy parsing like “next Friday”. Arrow and Pendulum wrap the same engine in a friendlier API. -
JavaScript:
Intl.DateTimeFormatships in every modern runtime, costs 0 KB, and knows every IANA zone. Reach fordate-fnsorDay.jswhen you need parsing and arithmetic, andLuxonwhen the app is timezone-heavy. Avoidmomentin new code: it’s mutable, weighs ~70 KB minified, and sits in maintenance mode. -
Java: always
java.time.java.util.DateandSimpleDateFormatare mutable and not thread-safe. -
Performance note: constructing a formatter is the expensive part.
DateTimeFormatterinstances are immutable and thread-safe, so cache and reuse them. The same applies toIntl.DateTimeFormatinside a hot loop. -
Runnable companion: three working scripts (Python, JavaScript, Java) in the companion repo with the examples from this page.
Best Practices
- Store every date as a UTC instant and convert to local time only when rendering for the user.
- On the wire, always send ISO 8601 rather than a locale-specific string. A value such as
2026-06-10T14:30:00Zmeans the same instant to a Python backend, a Node API, and a Java service. - Send the offset or timezone in API responses so clients can render local time correctly.
- Prefer IANA IDs (
America/New_York) over abbreviations (EST), which ignore DST and are ambiguous (CSTnames three different zones). - Never build or parse dates by concatenating strings or with regular expressions; use the platform parser.
Common Mistakes
- Storing wall time with no zone: a
2026-06-10 14:30row in the database can’t be reconstructed once servers or users move across zones. - Parsing non-ISO strings with
Date.parse(): each engine treats10/06/2026differently, so pass an explicit format or use a parsing library. MMvsmmconfusion: month versus minutes is the most common silent bug in JavaDateTimeFormatterpatterns.- Formatting in the server’s timezone: the API responds in whatever zone the server runs in, and the user usually reads another. Format at the edge.
- Assuming every local time exists: scheduling something at
02:30fails on spring-forward days in zones that observe DST.
See Also
Frequently Asked Questions
Should I store timestamps or formatted strings in my database?
Store instants in a native TIMESTAMP WITH TIME ZONE column (or epoch seconds), never formatted strings. Strings lose the instant and sort wrong across zones. See Database Transactions for related integrity patterns.
How do I handle daylight saving time?
Everything stays in UTC internally — convert with IANA IDs (Europe/Madrid) at display time. Baking +02:00 into stored data or code breaks the moment DST flips.
What is the difference between a timezone and a UTC offset?
+02:00 is an offset: a fixed distance from UTC that never changes. Europe/Madrid is a timezone: a named region whose offset jumps between +1 and +2 as DST rules kick in. Store instants and format with timezones rather than bare offsets.
What is the difference between toISOString() and toUTCString()?
JSON APIs and databases expect ISO 8601 — and toISOString() produces exactly that (2026-06-10T14:30:00.000Z). toUTCString() returns the older RFC 7231 string (Wed, 10 Jun 2026 14:30:00 GMT) that appears in HTTP headers.
How do I format a date in the user's locale without a library?
new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }).format(date) picks up the runtime's locale automatically. If the user chose a zone in settings, pass it through as an explicit timeZone.
Related Resources
Parse JSON in Python, JavaScript and Java
Parse JSON strings into native objects in Python, JavaScript and Java — with error handling, streaming for large files and schema validation.
RecipeCall a REST API: Python, JavaScript, Java & Go Examples
How to make HTTP requests to a REST API and handle the JSON response in Python, JavaScript, Java, and Go.
RecipeValidate and Sanitize User Input Data
How to validate, sanitize, and constrain user input data at the application boundary using schemas, type checking, and validation libraries.
RecipeCron Jobs: Schedule Tasks in Linux, Python & Node.js
How to schedule and manage recurring tasks using cron syntax across Linux, Python, Node.js, and Kubernetes.
RecipeBatch Processing Patterns
Design reliable batch processing pipelines for large datasets with retry logic, idempotency, and observability.
GuideTime-Series Databases — InfluxDB, TimescaleDB, ClickHouse
Practical guide to time-series databases: when to use a TSDB, data model, retention policies, and choosing between InfluxDB, TimescaleDB, and ClickHouse.