Dates and Timezones
Problem statement
Convert Unix timestamps from a log into readable UTC and India times, turn a date back into a timestamp, work out the minutes between two events, and build a dated file name. Logs, APIs and databases store time in many forms, and during an incident you must line up events from servers in different time zones.
events.log (Unix timestamps: seconds since 1970-01-01 UTC)
1790850600 deploy started1790851500 errors began1790853300 rollback done- Print each event's time in UTC and in India time (IST).
- Turn
2026-10-01 10:30:00 UTCinto a timestamp. - Print how many minutes passed from the first event to the last.
- Print the first event in ISO 8601 format, and a backup file name that contains its date.
Expected output:
== each event in UTC and India time ==2026-10-01 10:30:00 UTC (16:00:00 IST) deploy started2026-10-01 10:45:00 UTC (16:15:00 IST) errors began2026-10-01 11:15:00 UTC (16:45:00 IST) rollback done== a date back to a timestamp ==1790850600== minutes from first to last event ==45 minutes== ISO 8601 and a dated file name ==2026-10-01T10:30:00+00:00backup-2026-10-01.tar.gzHints
date -u -d @1790850600 '+%F %T %Z' prints a timestamp in UTC. Put TZ=Asia/Kolkata in front of date to see the same moment in India time.Approach
Optimal: date -d with TZ and formats
Covers: Unix time (epoch), date -d @N, date -u, TZ=Zone, format codes %F %T %Z %s, date -Iseconds, arithmetic $(( )), while read.
A timestamp is just a count of seconds. Unix time counts the seconds since 1970-01-01 00:00:00 UTC. 1790850600 is one exact moment, the same everywhere on Earth. Only the way you show it changes with the time zone:
one moment"]):::purple E --> U["UTC
2026-10-01 10:30"]:::blue E --> I["India, UTC+5:30
16:00 IST"]:::green E --> N["New York, UTC-4
06:30 EDT"]:::yellow classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px
That is why servers should store and log time in UTC or as timestamps: no confusion, no daylight saving jumps. Convert to a local zone only when showing it to people.
date in both directions.
| Command | Does |
|---|---|
date -u -d @1790850600 |
timestamp to a date, shown in UTC |
TZ=Asia/Kolkata date -d @1790850600 |
the same moment in India time |
date -u -d '2026-10-01 10:30:00' +%s |
a date (read as UTC) to a timestamp |
date +%s |
the current timestamp |
date -u -Iseconds -d @N |
ISO 8601, like 2026-10-01T10:30:00+00:00 |
-d (date) tells date which moment to use instead of now, and the @ means "this is a timestamp". -u means UTC. TZ=Asia/Kolkata sets the time zone for that one command; zone names come from /usr/share/zoneinfo.
Format codes. Anything after + is a pattern:
| Code | Prints | Example |
|---|---|---|
%F |
date | 2026-10-01 |
%T |
time | 10:30:00 |
%Z |
zone name | UTC, IST |
%s |
timestamp | 1790850600 |
%H%M |
hour and minute | 1030 |
%F is the best format for file names, because YYYY-MM-DD sorts in time order.
Walking through the code. The # Setup: lines only create the sample log, so skip past them.
while read -r ts whatreads each line into the timestamp and the rest of the text, then prints the event in both zones.date -u -d ... +%sprints the timestamp for 10:30 UTC, which is the first event.headandtailtake the first and last timestamps, and$(( (last - first) / 60 ))gives 45 minutes.-Isecondsprints ISO 8601, and%Fbuilds the file name.
Edge cases. date -d is GNU only; on macOS use date -r 1790850600 or install gdate. Without -u, a date string is read in the machine's own zone, which is a classic source of off-by-hours bugs. Timestamps in milliseconds (13 digits, common in JavaScript and Java logs) must be divided by 1000 first.
# Setup: create the sample log in a fresh temporary folder
cd "$(mktemp -d)"
cat > events.log << 'LOG'
1790850600 deploy started
1790851500 errors began
1790853300 rollback done
LOG
echo "== each event in UTC and India time =="
while read -r ts what; do
utc=$(date -u -d "@$ts" '+%F %T %Z')
ist=$(TZ=Asia/Kolkata date -d "@$ts" '+%T %Z')
echo "$utc ($ist) $what"
done < events.log
echo "== a date back to a timestamp =="
date -u -d '2026-10-01 10:30:00' +%s
echo "== minutes from first to last event =="
first=$(head -n 1 events.log | cut -d' ' -f1)
last=$(tail -n 1 events.log | cut -d' ' -f1)
echo "$(( (last - first) / 60 )) minutes"
echo "== ISO 8601 and a dated file name =="
date -u -Iseconds -d "@$first"
echo "backup-$(date -u -d "@$first" +%F).tar.gz"Interview follow-ups
Print how long ago each event happened, in minutes, compared with now.
Get the current timestamp once with
now=$(date +%s), then for each line compute$(( (now - ts) / 60 )). Doing the maths on timestamps avoids any time zone or daylight saving mistakes. For a readable form, split the seconds into hours and minutes with/ 3600and% 3600 / 60. The output changes every run, which is why this page uses fixed timestamps instead.
Frequently asked questions
Usually because one side used local time and the other UTC, or because of daylight saving: zones like Europe/London shift by an hour twice a year, so the same local time can happen twice in autumn. India does not use daylight saving, so IST is always UTC + 5:30. Set servers to UTC (timedatectl set-timezone UTC) and keep local zones for display only, and the problem disappears.
For milliseconds, drop the last three digits first: date -u -d "@$((ms / 1000))". For ISO strings, GNU date reads them directly: date -u -d '2026-10-01T10:30:00Z' +%s. A trailing Z means UTC, and an offset like +05:30 is understood too. For many lines at once, awk or a small script is faster than calling date per line.
timedatectl shows the current zone, whether the clock is synced, and the UTC and local times together. timedatectl list-timezones lists valid names, and sudo timedatectl set-timezone UTC changes it. On minimal systems without systemd, the zone is the link /etc/localtime. The TZ variable overrides it for one command or one shell, as this page does.