date
Print, format and calculate with dates
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
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.
Read a file's modification time
date -r site/index.html
The same time ls -l shows in a column and stat prints as %y, in date's own default format.
Show output
Mon Jun 1 09:00:00 UTC 2026
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