How to Convert Between Time Formats
Time formats vary across systems, APIs, and countries. Unix timestamps in API responses, ISO 8601 in databases, 12-hour clocks in the US, 24-hour clocks in Europe, converting between them is a constant need for developers and anyone working with international data. Understanding why each format exists, where it shines, and where it bites makes conversion a quick reflex rather than a recurring source of subtle bugs.
A short history of time formats
Standardising time is older than the internet. In 1884 the International Meridian Conference established Greenwich Mean Time and the prime meridian, which gave the world a common reference for the first time. The 24-hour clock spread through aviation and the military for the same reason: it removes the AM/PM ambiguity that turns a midnight booking into a noon flight.
Unix time arrived with the original Unix kernel in 1971, counting seconds since 1 January 1970 UTC (the "epoch"). The choice was pragmatic: a 32-bit integer could cover decades, sorting timestamps meant sorting numbers, and computing intervals was a subtraction. ISO 8601 came in 1988 to standardise the human-readable side: year-month-day in big-endian order, T as the date/time separator, and Z for UTC. RFC 3339 (2002) is a more restrictive profile of ISO 8601 designed for internet protocols. RFC 2822 (2001) defines the email-style format with day names and offsets. Each format solves a real problem; none of them replaces the others entirely.
Common time formats
Six formats cover almost everything you will encounter in the wild. The first column is the name you will see in documentation; the example column shows what 14:30 on 7 April 2024 UTC looks like.
| Format | Example | Used by |
|---|---|---|
| Unix timestamp (seconds) | 1712502600 | APIs, databases, JWT tokens, log lines |
| Unix timestamp (ms) | 1712502600000 | JavaScript, Java, Android |
| Unix timestamp (microseconds) | 1712502600000000 | Postgres, Kafka, OpenTelemetry |
| ISO 8601 / RFC 3339 | 2024-04-07T14:30:00Z | JSON APIs, databases, logs |
| RFC 2822 | Sun, 07 Apr 2024 14:30:00 +0000 | Email headers, HTTP headers |
| 24-hour | 14:30 | Europe, military, aviation |
| 12-hour | 2:30 PM | US, casual use |
| Day of year (Julian) | 2024-098 | Avionics, scientific data |
| Week date | 2024-W14-7 | Industrial planning, banking |
Most teams settle on Unix milliseconds for storage and ISO 8601 for transit, with display formats chosen per locale. The choice matters less than consistency: pick a primary format, document it, and convert at the edges.
Quick conversion reference
12-hour to 24-hour
The 12-hour clock has two oddities: 12 AM is midnight (the start of the day) and 12 PM is noon. The table below covers the cases that trip people up.
| 12-hour | 24-hour |
|---|---|
| 12:00 AM (midnight) | 00:00 |
| 1:00 AM | 01:00 |
| 11:59 AM | 11:59 |
| 12:00 PM (noon) | 12:00 |
| 12:01 PM | 12:01 |
| 1:00 PM | 13:00 |
| 6:00 PM | 18:00 |
| 11:59 PM | 23:59 |
Timestamps to dates
The epoch converter translates Unix timestamps to human-readable dates and back instantly. The converter detects both seconds (10 digits) and milliseconds (13 digits) formats automatically, so you can paste a value from any source without worrying about the unit.
A useful sanity check: divide by 86400 (seconds in a day) then add 1970 days to mentally locate any timestamp. 1712502600 / 86400 ~ 19820 days, which is about 54 years after 1970, putting it in 2024. Not precise but enough to catch off-by-1000 errors at a glance.
Conversion examples in code
Most developers eventually need to convert timestamps in code. The core APIs are short:
- JavaScript,
new Date(1712502600 * 1000).toISOString()returns2024-04-07T14:30:00.000Z. Remember to multiply by 1000 because JavaScriptDateexpects milliseconds. Going the other way:Math.floor(Date.now() / 1000)gives a seconds timestamp. - Python,
datetime.fromtimestamp(1712502600, tz=timezone.utc).isoformat()returns2024-04-07T14:30:00+00:00. Avoidutcfromtimestamp(); it returns a naive datetime that the rest of your code will mistakenly treat as local time. - Go,
time.Unix(1712502600, 0).UTC().Format(time.RFC3339)returns2024-04-07T14:30:00Z. Go's time package is unusual but consistent once you internalise the reference layoutMon Jan 2 15:04:05 MST 2006. - Shell,
date -u -d @1712502600 +"%Y-%m-%dT%H:%M:%SZ"on GNU/Linux, ordate -u -r 1712502600 +"%Y-%m-%dT%H:%M:%SZ"on BSD/macOS. The flag difference is one of the most-Googled cross-platform footguns. - Rust,
chrono::DateTime::<chrono::Utc>::from_timestamp(1712502600, 0).unwrap().to_rfc3339()returns the same ISO string. The standard library now hasSystemTimebut chrono is still the practical choice for formatting. - SQL (Postgres),
SELECT to_timestamp(1712502600) AT TIME ZONE 'UTC';returns atimestamptz. MySQL usesFROM_UNIXTIME(); SQLite usesdatetime(1712502600, 'unixepoch').
A converter tool is faster than any of these one-liners when you only need to translate a single value, which is most of the time during debugging.
Why ISO 8601 wins for storage
ISO 8601 (and the RFC 3339 profile of it) sorts correctly as a plain string. 2024-04-07T14:30:00Z is alphabetically before 2024-04-07T15:00:00Z, which is before 2024-04-08T00:00:00Z. That property lets you sort timestamps without parsing them, group log lines with sort | uniq -c, and reason about ranges with simple string comparisons.
It is also unambiguous in a way the regional formats are not. 04/07/2024 could be 7 April (USA) or 4 July (most of Europe); 2024-04-07 is the same in every locale. For machine-to-machine exchange that lack of ambiguity is worth more than any other formatting consideration.
Common pitfalls to avoid
- Forgetting the timezone,
2026-04-07 14:30is ambiguous without a zone. Always includeZor a+00:00offset when storing ISO strings, and never rely on the reader's default zone. - Mixing seconds and milliseconds, a 10-digit Unix timestamp is seconds, a 13-digit one is milliseconds. Mixing them produces dates either in 1970 or far in the future. Microseconds (16 digits) and nanoseconds (19 digits) are creeping in, so anticipate them too.
- Relying on local time in servers, if your server is in UTC and your user is in Tokyo, store UTC and convert on display. Letting the server infer locale almost always leads to bugs when users travel or daylight saving changes.
- Parsing with string math, do not slice and concatenate date strings. Use
Date.parse(),dateutil.parser,chrono, or your language's standard library. Manual parsing breaks on extreme dates, leap years, and timezone offsets. - Trusting
new Date("2024-04-07"), JavaScript parses date-only strings as UTC midnight, but date-time strings without a zone as local midnight. The behaviour differs across browsers and Node versions. Always include an explicitZor offset. - Two-digit years,
04/07/24could be 1924, 2024, or 2124 depending on the implementation. Use four-digit years everywhere. - Daylight saving transitions, the local clock skips an hour in spring and repeats one in autumn. A naive scheduler can run a job twice or not at all on those days. Use UTC for scheduling logic and convert only for display.
- Leap seconds, Unix time pretends they do not exist by smearing or repeating the affected second. Code that compares "elapsed seconds" can briefly disagree with a wall clock that respects leap seconds.
- The Year 2038 problem, 32-bit signed
time_toverflows on 19 January 2038. Most modern systems use 64-bit time, but legacy embedded devices and old database columns can still hit it. Audit anywhere you seeINT4orint32used to store timestamps. - Locale-dependent formatting,
toLocaleString()in JavaScript and equivalent functions in other languages produce different output on different machines. For machine-readable timestamps, usetoISOString()or its equivalent; for display, format explicitly.
Alternative tools and libraries
A converter handles the one-off case; for production code you will reach for a library.
| Tool / library | Language | Strength | Watch out for |
|---|---|---|---|
| Web converter | Browser | Instant, no install, handles common ambiguities | One value at a time |
dateutil |
Python | Tolerant parser, timezone-aware | Slower than datetime for bulk work |
arrow |
Python | Friendly API, ISO defaults | Extra dependency for small projects |
date-fns |
JavaScript | Tree-shakable, immutable | Each function is its own import |
dayjs |
JavaScript | Tiny, moment-compatible API | Plugin model for extras |
luxon |
JavaScript | First-class IANA timezones | Larger bundle |
chrono |
Rust | Rich types, RFC 3339 native | Maintenance pace varies |
Joda-Time / java.time |
Java | The reference design that influenced others | Avoid legacy Date and Calendar |
NodaTime |
C#/.NET | Cleanly separated instants, durations, calendars | Migration from DateTime is non-trivial |
GNU date |
Linux shell | Flexible parsing with -d |
BSD/macOS uses incompatible flags |
The right library depends on your stack; the right discipline is the same everywhere: store UTC, transmit ISO 8601, format only for display, and never trust a timestamp without an explicit zone.
Privacy and the converter
The time format converter runs entirely in your browser. The timestamps you paste in and the dates you generate never leave the page. There is no server log of which values were converted, no analytics on which formats are popular, and no way for anyone to link a request to your IP. Time conversions are not personal data in themselves, but the timestamps in your logs (login times, transaction times, schedule windows) often are, and feeding them through a third-party converter could leak operational details. Doing the work client-side keeps that information on your machine where it belongs. For a task as routine as flipping between seconds and ISO 8601, the privacy default should be no transmission, no logging, no third party in the loop.
Frequently Asked Questions
What is ISO 8601 format?
ISO 8601 is the international standard for date and time representation. It looks like 2026-04-07T14:30:00Z, where T separates date and time, and Z indicates UTC. It is unambiguous regardless of locale.
Why do APIs use Unix timestamps instead of readable dates?
Unix timestamps are a single number, making them easy to store, sort, and compare. They are timezone-neutral (always UTC) and take up less space than formatted date strings. The tradeoff is that they are not human-readable.
What does the Z mean at the end of a timestamp?
The Z stands for "Zulu time," which is another name for UTC (Coordinated Universal Time). A timestamp ending in Z is in UTC, not local time.
How do I convert 24-hour time to 12-hour?
For hours 1-12, the time stays the same (add AM for 0-11, PM for 12). For hours 13-23, subtract 12 and add PM. 00:00 is 12:00 AM (midnight). 12:00 is 12:00 PM (noon).
What is the Year 2038 problem?
32-bit signed Unix timestamps overflow on 19 January 2038 at 03:14:07 UTC, after which they wrap around to negative values that look like dates in 1901. Most modern systems use 64-bit timestamps and are unaffected, but legacy embedded devices, old databases, and 32-bit time_t in some C code still need to be audited.
How are leap seconds handled in Unix time?
Unix time pretends leap seconds do not exist. The clock simply repeats or stretches the affected second, so a Unix timestamp is not a strict count of elapsed SI seconds. For most applications this is fine, but precision-timing code (astronomy, GPS, high-frequency trading) needs TAI or UTC with explicit leap-second handling.