27 September 2021

You can use the date utility to print, format and convert a timestamp. Historically the utility was also used to set the system time, though that is nowadays usually done via timedatectl.

Formatting dates

One of the first things you might notice when you run the date command is that the timestamp is a bit odd. This is the default format:

$ date
Thu 23 Sep 17:31:03 BST 2021

The month is shown as an abbreviated name, which presumably is because some countries put the month before the date (January 1st instead of 1st January). I don’t think there is a particular reason why the year appears after the timestamp. Luckily, you can format the timestamps any way you like.

The format string

To format a timestamp you specify a format string. The string start with a plus sign followed by one or more control characters (in quotes). For instance, you can print the current time in HH:MM:SS format using the following command:

$ date +'%H:%M:%S'

There are quite a control characters. As always, check the documentation (man 1 date) if you want a full list. For convenience, here are the most common format strings.

%aShort weekday nameMon, Tue etc.
%AFull weekday nameMonday, Tuesday etc.
%bShort month nameJan, Feb etc.
%BFull month nameJanuary, February etc.
%dDay or the month01 to 31
%HTwo digit hour (24 hour clock)00 to 23
%ITwo digit hour (12 hour clock)01 to 12
%jDay of the year001 to 365
%mTwo digit month01 to 12
%MTwo digit minutes00 to 59
%sSeconds since Unix epoch1632734437
%uDay of week (Monday is 1)1 to 7
%wDay of week (Sunday is 00 to 6
%WWeek number00 to 53
%ZShort time zoneBST, CET
%FShort for %Y-%m-%d2021-09-27
%TShort for %H:%M:%S10:26:58
%RShort for %H:%M10:26

So, the default timestamp is formatted as follows:

$ date '+%a %d %b %T %Z %Y'
Thu 23 Sep 17:56:56 BST 2021

And if you prefer something more intuitive then you can use a format string like this:

$ date '+%F %T %Z'
2021-09-23 17:57:39 BST

The Unix epoch

Operating systems and software applications often use a more or less random date from which it can measure time in seconds. Unix systems, including Linux, use the start of 1 January 1970 (UTC) as the epoch.

The Unix time is handy in lots of situations. For instance, when we look into website errors we often use a script that retrieves log entries in the last x minutes. To make that work the script converts timestamps to Unix times, and it then retrieves all entries in the last x seconds. Without converting timestamps to Unix times that isn’t possible.

The %s control character prints a Unix time. You can add the -d (--date) option to print a specific date as the number of seconds since 1 January 1970:

$ date '+%s' --date '2020-01-01 00:00:00 UTC'

$ date '+%s' --date '1970-01-01 UTC'

$ date '+%s' --date '1900-01-01 UTC'

Note that the time in the first command was redundant – if you don’t specify the time the command assumes you want to know the time at the start of the day. Also, dates prior to 1 January 1970 are always negative.

The Epochalypse?

As an aside, the Unix timestamp is stored as a 32-bit integer. That is a problem, as it means we can’t count beyond 19 January 2038 (03:14:07 to be precise). This is a bit like the year 2000 problem. The issue is being addressed, but there might be some smart doorbells that start misbehaving in early 2038.

Converting timestamps

The --date option is quite flexible. I typically specify dates in the format YYYY-MM-DD HH:MM:SS TZ, just because that makes sense to me. However, the utility is smart enough to understand and convert lots of different timestamps. For instance, user error logs on Apache servers typically contain timestamps using the format DD-MMM-YYYY HH:MM:SS TZ. Those timestamps can be converted on the fly:

$ date "+%s" --date '01-Jan-2020 00:00:00 UTC'

$ date --date @1577836800 +"%F %T %Z"
2020-01-01 00:00:00 GMT

$ date "+%s" --date '01-Jan-2020 00:00:00 CET'

$ date --date @1577833200 +"%F %T %Z"
2019-12-31 23:00:00 GMT

Similarly, entries in the global Apache error log are formatted as DDD MMM DD HH:MM:SS.MS YYYY. Again, the timestamps can be converted without performing magic:

$ date "+%s" --date 'Wed Jan 01 00:00:00.00000 2020'

$ date "+%s" --date 'Wed Jan 01 00:00:00.00000 2020 CET'

Other timestamps can’t be easily converted. For instance, the date utility doesn’t understand Apache access log timestamps:

$ date "+%s" --date '01/Jan/2020:00:00:00'
date: invalid date ‘01/Jan/2020:00:00:00’

There are more limitations. If you want to convert all timestamps in a log file then you need to use an alternative tool, such as python or awk.

Time zones

One thing to always bear in mind is that date uses the system’s timezone by default. As I write it is British Summer Time:

$ date
Fri 24 Sep 20:28:16 BST 2021

You can get more detailed information about the system time using timedatectl, which is part of systemd:

$ timedatectl
               Local time: Fri 2021-09-24 20:27:02 BST
           Universal time: Fri 2021-09-24 19:27:02 UTC
                 RTC time: Fri 2021-09-24 19:27:02
                Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

As already shown, you can always specify the time zone. You can even do things like this:

$ TZ='Europe/Amsterdam' date +'%H:%M'

The command prints the current time in another time zone. You can do this with any of the time zones listed in /usr/share/zoneinfo/.

Using dates in variables

When you edit a file it is usually a good idea to make a copy of the original file first. For instance, before you change your website’s .htaccess file you can make a copy, so that you can easily undo your changes if needed. Rather than naming the copy something like .htaccess.bak you can append the current date:

$ cp .htaccess .htaccess_$(date "+%Y%m%d%H%M%S")

$ ls -1AF .htaccess*

The $(...) syntax is called command substitution. In this case I put a date command inside the brackets, which appends the current timestamp. That way you can quickly see the when the copy was made.