Sample output
$ TZ=America/New_York date -d @1788373391 +'%F %T %Z'
2026-09-02 14:23:11 EDT
$ TZ=Asia/Tokyo date -d @1788373391 +'%F %T %Z'
2026-09-03 03:23:11 JST
One number, two answers, and they do not fall on the same calendar day. The epoch value is a fixed point that never changes; the date you read off it depends entirely on where you agree to be standing. That gap is why an incident timeline assembled from raw timestamps and one assembled from locally rendered log output can disagree about which event came first.
When you would reach for it
A log line, a JSON field, or a database column hands you a bare number like 1788373391 and you need to know when that was, in the zone the person you are talking to actually lives in. It also settles the recurring argument during an incident review, where one engineer is reading UTC off a dashboard and another is reading local time off a terminal, and the two accounts of the outage do not line up. Converting explicitly, with the zone printed alongside, replaces the mental arithmetic that people reliably get wrong at exactly the moment it matters most.
Gotchas
- On macOS the same flag means something else entirely. BSD
dateuses-rfor epoch input (date -r 1788373391), while GNU-ris--reference=FILEand reports a file’s modification time. BSD-dis not an input option at all, it sets the kernel’s daylight saving value, so a Linux snippet pasted onto a Mac errors instead of converting. Going the other way is worse: a Mac snippet on Linux returnsdate: 1788373391: No such file or directory, which sends people hunting for a missing file that was never involved.brew install coreutilsgives yougdate, which takes the GNU form. - A 13-digit timestamp fails without telling you. JavaScript’s
Date.now()and most Java and JSON APIs emit milliseconds, anddate -d @1788373391123returns+58641-04-19 02:25:23and exits 0. There is no error to catch, only a date in the year 58641 sitting in your output. Check the digit count before you convert: 10 digits are seconds, 13 are milliseconds. - A misspelled zone name also fails silently.
TZ=America/Nowhere date -d @1788373391 +'%F %T %Z'prints2026-09-02 18:23:11 Americaand exits 0, having quietly fallen back to UTC and used your typo as the zone abbreviation. Keeping%Zin the format string is what catches this: a zone abbreviation you do not recognise means the lookup missed. - Zone abbreviations are not unique and are not safe to compute with.
EDTandESTare the same location four months apart, andISTis claimed by India, Ireland, and Israel. Use fullArea/Citynames from/usr/share/zoneinfofor input, and treat%Zin the output as a label for humans rather than something to parse.
Variants
$ date -u -d @1788373391 +'%Y-%m-%dT%H:%M:%SZ'
ISO 8601 in UTC, the format to use when the timestamp is going into a file, a ticket, or anywhere another program will read it
$ MS=1788373391123; date -u -d "@${MS:0:10}.${MS:10}" +'%F %T.%3N'
Convert a millisecond timestamp without losing the milliseconds. Bash substring expansion splits the number at the tenth digit and rebuilds it as a decimal, which GNU date accepts directly, and %3N prints the fraction back out. Dividing by 1000 in arithmetic would truncate it away
$ awk '{ $1 = strftime("%F %T", $1); print }' epoch.log
Rewrite an entire column of epoch values in place while leaving the rest of each line alone. Both gawk and mawk provide strftime, and it honours TZ the same way date does, so prefixing the command converts the whole file into any zone at once