Skip to content
Linux Basics: Normalize Timestamps and Measure Intervals with GNU date

Linux Basics: Normalize Timestamps and Measure Intervals with GNU date

October 25, 2026

A log timestamp is easier to compare when its time zone is explicit. GNU date can parse a supplied timestamp, display the same instant in UTC, and convert it to epoch seconds for a small interval calculation.

These examples were tested on Linux with GNU Bash 5.2.37 and GNU coreutils 9.7. Every script uses fixed input and LC_ALL=C; none changes the system clock. The --date option selects input to display. Do not replace it with --set or -s, which request a clock change.

Prepare a scratch directory

You need Bash, GNU date, a text editor, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. The guards stop the shell if setup fails. Only after setup succeeds, save each named script here and run it in order. No administrator access is needed.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Normalize an offset timestamp and round-trip its epoch value

Save this as date-normalize.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
instant='2026-10-25T09:00:00+09:00'
date --utc --date="$instant" '+UTC: %Y-%m-%dT%H:%M:%SZ'
seconds=$(date --utc --date="$instant" '+%s')
printf 'Epoch seconds: %s\n' "$seconds"
date --utc --date="@$seconds" '+Round trip: %Y-%m-%dT%H:%M:%SZ'

Run it:

bash date-normalize.sh

Output:

UTC: 2026-10-25T00:00:00Z
Epoch seconds: 1792886400
Round trip: 2026-10-25T00:00:00Z

The input’s +09:00 identifies its offset from UTC. --utc selects the display zone; it does not erase the input offset before parsing. %s produces seconds since the Unix epoch, while @ tells the input parser to interpret a value as epoch seconds.

The final Z is literal text. It is truthful here because --utc explicitly selected UTC. Do not append Z to a local-time display and assume the result has been converted. The GNU date manual packaged by Debian documents these input and output options.

2. Measure an interval across midnight

Save this as date-elapsed.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
started='2026-10-24T23:58:30Z'
finished='2026-10-25T00:02:10Z'
start_seconds=$(date --utc --date="$started" '+%s')
end_seconds=$(date --utc --date="$finished" '+%s')
elapsed=$((end_seconds - start_seconds))
if ((elapsed < 0)); then
  printf 'End precedes start\n' >&2
  exit 1
fi
printf 'Elapsed seconds: %d\n' "$elapsed"
printf 'Elapsed minutes and seconds: %d:%02d\n' \
  "$((elapsed / 60))" "$((elapsed % 60))"

Run it:

bash date-elapsed.sh

Output:

Elapsed seconds: 220
Elapsed minutes and seconds: 3:40

Converting both instants before subtraction handles the date boundary without special midnight logic. The second display is a duration built from integer division and remainder, not a timestamp formatted as a time of day. Formatting a duration through date would introduce calendar and time-zone behavior that is irrelevant here.

This calculation uses whole seconds and ordinary Unix timestamp semantics. For benchmarking a live process, use a monotonic timing facility rather than wall-clock timestamps, which can jump when the clock is adjusted. Calendar intervals such as “one month” also need a separately defined rule; they are not a fixed number of seconds.

3. Reject an invalid calendar date before using its result

Save this as date-reject-invalid.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
if parsed=$(date --utc --date='2026-02-30T12:00:00Z' '+%s' \
  2> invalid-date-error.txt); then
  printf 'Unexpected success: %s\n' "$parsed" >&2
  exit 1
else
  status=$?
  test -s invalid-date-error.txt
  printf 'Invalid date rejected: status %s\n' "$status"
fi
valid=$(date --utc --date='2028-02-29T12:00:00Z' '+%F')
printf 'Leap-year date: %s\n' "$valid"

Run it:

bash date-reject-invalid.sh

Output:

Invalid date rejected: status 1
Leap-year date: 2028-02-29

The failed conversion is inside an if, so the script can inspect its status and retain the diagnostic separately. The invalid value is never passed to arithmetic. The valid leap-day fixture shows that the rejection is about the calendar date rather than a blanket rule against February 29.

GNU date accepts many human-readable forms, including relative expressions. A successful parse is therefore not strict validation of an application’s timestamp format. Enforce your chosen input grammar separately when values must have an exact shape, and reject empty input rather than relying on the parser’s defaults.

Keep in mind

Use explicit offsets or UTC for machine-generated timestamps. Ambiguous abbreviations and local times near daylight-saving transitions need more context. LC_ALL=C stabilizes locale-sensitive output, while --utc controls the time zone; those settings solve different problems.

These options are GNU-specific. A system with a different date implementation may have different parsing flags and behavior. Check its manual before adapting the scripts.

Clean up

Remove only the practice files, return to your original directory, and remove the empty scratch directory:

rm -- date-normalize.sh date-elapsed.sh date-reject-invalid.sh \
  invalid-date-error.txt
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on