date

Print, format and calculate with dates

Updated 2026-09-07

date prints the current time, and that is the least of what it does. -d hands it a date to work on instead of now, + describes the output down to the character, and the two together turn it into a calculator: a date read in one format and printed in another, a month added, a file's timestamp converted to seconds and back.

Everything after the + is printed as written except the % specifiers, so date "+%F %T" gives 2026-06-01 09:00:00 and date "+backup-%F.tar.gz" gives a filename. %F and %T cover most of what anyone needs. %s is seconds since 1970, and it is the form to use whenever two times have to be compared or subtracted, since a shell can do arithmetic on it and cannot do arithmetic on Mon Jun 1 09:00:00 UTC 2026.

Parsing is looser than it looks and is worth testing before trusting. date -d "01/06/2026" reads the American way round and returns 6 January, and date -d "2026-01-31 + 1 month" returns 3 March, because adding a month to the 31st gives 31 February and the result is normalised. --debug prints what it decided and warns about both.

Setting the clock is a different job. date -s exists and needs privileges, and on a machine running systemd-timesyncd or ntpd anything it sets is corrected again shortly afterwards. timedatectl is the interface that sticks, and it is part of the same systemd tooling.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

site the tree the cp, touch and stat pages use as well; date -r reads a modification time, so the two files below carry times set deliberately rather than whatever a checkout stamped on them

site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user   36 Jun  1 09:00 index.html
lrwxrwxrwx 1 user user    9 Jun 18 09:00 latest.css -> style.css
-rw-r--r-- 1 user user   33 Jun 20 09:00 style.css

site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg

site/backups:
total 0
48 outputs, collapsed by default

The time now

With no arguments date prints the current time in the system's own zone, in a format nothing should ever parse. Every other form on this page states what it wants.

Print the current date and time

date

The default layout comes from the C locale, and it changes with LC_TIME. Anything reading this output is depending on a setting that belongs to whoever runs it.

Show output

Your output will differ: the time is when the example ran, and the zone abbreviation is the machine's

Mon Sep  7 11:08:28 UTC 2026

Print it in a format that sorts

date -Is

-Is is --iso-8601=seconds. Dates in this form sort correctly as plain text, which is why log files and filenames want it.

Show output

Your output will differ: the time is when the example ran

2026-09-07T11:09:42+00:00

Print seconds since 1970

date +%s

The Unix epoch as one integer. A shell can subtract these, and no other form date prints can be subtracted at all.

Show output

Your output will differ: the number grows by one every second

1788779335

Choosing the output format

Each example below is given a fixed date with -d, so the format string is the only thing changing. The specifiers are the ones strftime(3) documents, and date --help lists them all.

Print the date and time as numbers

date -d "2026-06-01 09:00" "+%F %T"

%F is %Y-%m-%d and %T is %H:%M:%S. These two cover most of what a script wants and are worth knowing by name.

Show output
2026-06-01 09:00:00

Write the date out in words

date -d "2026-06-01" "+%A %-d %B %Y"

%A is the weekday and %B the month, both in the current locale. %-d drops the padding that %d would add.

Show output
Monday 1 June 2026

See the three ways a day number can be padded

date -d "2026-06-01" "+%d %-d %e|"

%d pads with a zero, %-d does not pad at all, and %e pads with a space. The trailing | is there to make the space visible.

Show output
01 1  1|

Use the twelve-hour clock

date -d "2026-06-01 15:30" "+%I:%M %p"

%I is the hour from 01 to 12 and %p gives AM or PM. In some locales %p is empty, so a format built on it can lose the only thing distinguishing morning from afternoon.

Show output
03:30 PM

Print an ISO 8601 timestamp with the offset

date -d "2026-06-01 09:00" "+%Y-%m-%dT%H:%M:%S%z"

%z is the numeric offset from UTC. Writing it out by hand gives control that -I does not, which matters when something downstream expects no colon in the offset.

Show output
2026-06-01T09:00:00+0000

Ask for ISO 8601 without spelling it out

date -d "2026-06-01 09:00" --iso-8601=seconds

--iso-8601 takes date, hours, minutes, seconds or ns, and puts a colon in the offset. -I on its own means --iso-8601=date.

Show output
2026-06-01T09:00:00+00:00

Print the format email and HTTP use

date -d "2026-06-01 09:00" --rfc-email

RFC 5322, which is what a Date: header holds. -R is the short form.

Show output
Mon, 01 Jun 2026 09:00:00 +0000

Get the day of the year and the week number

date -d "2026-06-01 09:00" "+%j %V %u"

%j is the day of the year, %V the ISO week and %u the weekday as 1 to 7 starting on Monday. %U counts weeks from Sunday and gives 22 for this same date.

Show output
152 23 1

See where the ISO week year disagrees with the calendar

date -d "2026-01-01" "+%V %G"

1 January 2026 falls in ISO week 1 of 2026, and some years it falls in week 52 or 53 of the year before. %G is the year that goes with %V, and pairing %V with %Y is how off-by-one bugs get into weekly reports.

Show output
01 2026

Print fractions of a second

date -d "2026-06-01 09:00:00.123456789" "+%T.%N"

%N is nanoseconds, and the input has to carry them for there to be anything to print. date +%s%N gives an epoch timestamp in nanoseconds, which is the usual way of getting a unique-enough id out of a shell.

Show output
09:00:00.123456789

Print a two-digit year, and see why not to

date -d "2026-06-01" "+%y %C %G"

%y is the year within the century and %C the century itself, so the two together make the year %Y would have given on its own. A log file written with %y sorts 1999 after 2026.

Show output
26 20 2026

Build a filename from the date

echo "backup-$(date -d "2026-06-01" +%F).tar.gz"

The format string is ordinary text with specifiers in it, so the whole filename can be built inside it: date -d "2026-06-01" "+backup-%F.tar.gz" gives the same thing.

Show output
backup-2026-06-01.tar.gz

Stamp a line of output

echo "$(date -d "2026-06-01 09:00:00" "+%Y-%m-%d %H:%M:%S") deploy finished"

A script logging its own progress wants a fixed format rather than the default, so that later greps and sorts have something predictable to work on.

Show output
2026-06-01 09:00:00 deploy finished

Reading a date that is not now

-d (--date) takes a string and works out what date it names. What it accepts is broad, and two of the readings below are not what a British reader expects.

Give date a date to work on

date -d "2026-06-01 09:00:00"

The rest of this page is this command with a format string on the end. Given no format, -d prints in the same layout date uses for now.

Show output
Mon Jun  1 09:00:00 UTC 2026

Read an ISO 8601 timestamp

date -d "2026-06-01T09:00:00Z" -u "+%F %T"

The T and the trailing Z are both understood, Z meaning UTC. This is the shape a JSON API or a log file usually hands you.

Show output
2026-06-01 09:00:00

Watch a slash-separated date come back wrong

date -d "01/06/2026" +%F

Read as 6 January, because date takes month first when the parts are separated by slashes. Nothing warns, and the two readings only differ for a fortnight in each month, so this survives testing and fails in production.

Show output
2026-01-06

Hand it something it cannot read

date -d "not a date"; echo "exit $?"

The message goes to stderr and the status is 1, so a script can test for a bad date rather than carrying on with an empty variable.

Show output
date: invalid date 'not a date'
exit 1

Ask for a day that does not exist

date -d "2026-02-29" +%F; echo "exit $?"

2026 is not a leap year, so there is no 29 February to return and date refuses rather than rounding to the 28th or the 1st. date -d "2028-02-29" answers normally.

Show output
date: invalid date '2026-02-29'
exit 1

Turn epoch seconds back into a date

date -u -d @1780304400

@ marks the argument as seconds since 1970. -u prints the result in UTC, which is what the number means, and without it the answer is shifted into the machine's zone.

Show output
Mon Jun  1 09:00:00 UTC 2026

See where the epoch starts

date -u -d @0

Midnight on 1 January 1970 UTC, and the instant every %s value counts from.

Show output
Thu Jan  1 00:00:00 UTC 1970

Convert a date to epoch seconds

date -d "2026-06-01 09:00:00 UTC" +%s

Naming the zone in the string removes the dependency on the machine's own. Without UTC here the answer would differ for every reader.

Show output
1780304400

Convert a list of dates at once

printf "2026-06-01 09:00\n2026-06-20 17:30\n2026-01-31 + 1 month\n" > dates.txt; date -f dates.txt +%F

-f (--file) reads one date per line and applies the same format to each, which saves starting a process per line in a loop. date -f - reads them from a pipe.

Show output
2026-06-01
2026-06-20
2026-03-03

Arithmetic on dates

A relative phrase in the -d string is applied to the date in front of it. Adding days is predictable. Adding months is not, and --debug prints the reasoning.

Add days to a date

date -d "2026-06-01 + 30 days" +%F

day, week, month and year all work, singular or plural. Days and weeks are the units with no surprises in them.

Show output
2026-07-01

Go back a month

date -d "2026-03-31 - 1 month" +%F

February has no 31st, so subtracting a month from 31 March gives 31 February, which normalises to 3 March. The answer is later than the date you started from.

Show output
2026-03-03

Add a month to the end of January

date -d "2026-01-31 + 1 month" +%F

The same normalisation, in the direction it is usually met first. A monthly job that adds a month to today runs on 3 March once a year and is otherwise correct.

Show output
2026-03-03

Ask date to explain itself

date --debug -d "2026-01-31 + 1 month" +%F 2>&1 | grep -A2 "shifted dates"

--debug prints about fifteen lines to stderr describing every step it took, and warns wherever the answer moved. These three lines are the warning behind the 3 March.

Show output
date: warning: month/year adjustment resulted in shifted dates:
date:      adjusted Y M D: 2026 02 31
date:    normalized Y M D: 2026 03 03

Step a day either side

date -d "2026-06-01 yesterday" +%F; date -d "2026-06-01 tomorrow" +%F

yesterday, today and tomorrow are read relative to the date given rather than to now, which makes them usable on a date from a file.

Show output
2026-05-31
2026-06-02

Find the last day of a month

date -d "2026-06-01 + 1 month - 1 day" +%F

Going forward to the first of next month and back one day avoids having to know which months have 31 days. Starting from the first keeps it clear of the normalisation above.

Show output
2026-06-30

Find the next Friday from today

date -d "next friday" +%F

Weekday names are read relative to now. Combined with an explicit date in the same string they are ignored: date -d "2026-06-01 friday" hands back 1 June, which is a Monday.

Show output

Your output will differ: the answer moves with the day the example runs

2026-09-11

A file's timestamp

-r (--reference) takes the modification time off a file instead of parsing a string, and formats it exactly as -d would.

Format a file's timestamp

date -r site/style.css "+%F"

A format string applies to -r exactly as it does to -d, so this is the short way to get a file's date into a variable.

Show output
2026-06-20

Date several files in a loop

for f in site/index.html site/style.css; do echo "$(date -r "$f" +%F) $f"; done

-r takes one file, so more than one needs a loop. stat accepts a list and is the better tool once there are many.

Show output
2026-06-01 site/index.html
2026-06-20 site/style.css

Convert a timestamp stat gave you

date -d "@$(stat -c %Y site/index.html)" "+%F %T"

stat -c %Y is the modification time as epoch seconds, and @ reads it back. Worth knowing because stat can produce the number for anything on the filesystem while date -r is limited to a modification time.

Show output
2026-06-01 09:00:00

Time zones

TZ in the environment decides which zone date reads and prints in, for that one command. The zone names come from tzdata and live under /usr/share/zoneinfo.

Print one instant in two zones

TZ=America/New_York date -d "2026-06-01 09:00:00 UTC"; TZ=Asia/Tokyo date -d "2026-06-01 09:00:00 UTC"

The same moment in time, named twice. Setting TZ in front of the command affects only that command, so nothing else in the shell changes.

Show output
Mon Jun  1 05:00:00 EDT 2026
Mon Jun  1 18:00:00 JST 2026

Watch the same zone change name in summer

TZ=Europe/London date -d "2026-01-15 12:00:00 UTC"; TZ=Europe/London date -d "2026-06-15 12:00:00 UTC"

GMT in January and BST in June, one hour apart, from the same TZ. A server storing local times rather than UTC has two readings for one hour every autumn.

Show output
Thu Jan 15 12:00:00 GMT 2026
Mon Jun 15 13:00:00 BST 2026

Print the zone name and its offset

TZ=Europe/London date -d "2026-06-15 12:00" "+%Z %z"

%Z is the abbreviation and %z the numeric offset. Only %z is unambiguous: abbreviations are not unique across the world's zones, so nothing in BST says which one it is.

Show output
BST +0100

See that -u changes the reading as well as the printing

TZ=Asia/Tokyo date -d "2026-06-01 09:00:00" +%s; TZ=Asia/Tokyo date -u -d "2026-06-01 09:00:00" +%s

The same string, nine hours apart. -u sets the zone for the whole operation, so the input is taken as UTC too, and a -u added to make the output tidy has quietly changed the answer.

Show output
1780272000
1780304400

Put the zone in the string instead

date -d "2026-06-01 09:00:00 JST" -u "+%F %T"

Naming the zone in the date says what the input means, and leaves -u to do nothing but choose the output. This is the version to write when both ends matter.

Show output
2026-06-01 00:00:00

Fix the zone for a whole script

export TZ=Asia/Tokyo; date -d "2026-06-01 09:00:00 UTC" "+%F %T %Z"

Exporting TZ once at the top of a script makes every date in it agree, and TZ=UTC is the usual choice: it has no daylight saving, so no hour is ever repeated or skipped.

Show output
2026-06-01 18:00:00 JST

Give it a zone that does not exist

TZ=Europe/Nowhere date -d "2026-06-01 12:00" "+%F %T %Z"

No error, no warning. An unknown TZ falls back to UTC and %Z prints whatever it made of the name, so a typo in a cron file shifts every timestamp and says nothing.

Show output
2026-06-01 12:00:00 Europe

Durations, and the clock itself

Subtracting two %s values gives seconds, and date can format that count back into something readable as long as it stays under a day.

Measure the gap between two times

start=$(date -d "2026-06-01 09:00" +%s); end=$(date -d "2026-06-01 17:30" +%s); echo $(( (end - start) / 3600 )) hours

Epoch seconds are integers, so the shell's own arithmetic handles them. Dividing by 3600 truncates, which is why this says 8 rather than 8.5.

Show output
8 hours

Format a duration as a clock time

date -u -d @$(( $(date -d "2026-06-01 17:30" +%s) - $(date -d "2026-06-01 09:00" +%s) )) +%H:%M:%S

Treating the count of seconds as a time since the epoch and printing it in UTC gives hours, minutes and seconds. It works only below 24 hours, since anything longer rolls into a day the format does not print.

Show output
08:30:00

Handle a duration longer than a day

secs=$(( $(date -d "2026-06-03 11:15" +%s) - $(date -d "2026-06-01 09:00" +%s) )); echo "$((secs/86400))d $(date -u -d @$((secs%86400)) +%H:%M)"

Days come out of the division and the remainder goes through the same trick as above. Anything more elaborate than this wants a real language.

Show output
2d 02:15

Compare two dates

a=$(date -d "2026-06-01" +%s); b=$(date -d "2026-06-20" +%s); [ "$a" -lt "$b" ] && echo "the first date is earlier"

Converting both to %s first gives -lt two integers to compare. Comparing the printed forms as strings works for %F and fails for every other format.

Show output
the first date is earlier

Try to set the system clock

date -s "2026-06-01 09:00:00" >/dev/null; echo "exit $?"

Refused without the CAP_SYS_TIME capability, which an ordinary account does not have. As root on a machine syncing time it succeeds and is then undone by the next sync, so timedatectl is the command that lasts.

Show output
date: cannot set date: Operation not permitted
exit 1