Linux – Calculating relative dates

If you need to generate a relative date in a shell script the Linux date command will do the needful, though there are some platform differences you might need to be aware of

You won’t be around the Linux shell for very long before you’ve worked out how to get the current date at the command prompt or in a shell script:-

$ date
Sun Jun 8 13:56:44 BST 2014

While the syntax for formatting the returned date might seem a little bizarre you’ll probably have easily worked out how to apply a custom format as well (though if you’re like me you probably need a refresher from the man page from time to time on this one):-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 13:59:50

Stick around the Linux shell long enough though and you’ll probably hit a requirement for calculating a relative date, for example to get the date and time an hour ago in the above ISO 8601 style, handy for an awk or grep on a log file.

When you start to think about date arithmetic – all those rules for “carrying the remainder” – this can look like a bit of a nightmare. Like most common and somewhat gnarly tasks it also seems like one which will have a pre-canned solution for you to use too.

Fortunately the date command has your back on this one. Though depending on your flavour of Linux, not always in the same way.

BSD

Though to non-geeks it may sound more like a flavour of porn than a flavour of operating system, in the Linux and Unix world BSD stands for Berkley Software Distribution and underpins a number of current platforms, notably Apple’s OS X. If the usage information for your date command looks like this:-

usage: date [-jnu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
            [-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]

You’ve got a BSD derived date command, and if you poke around the man page you’ll discover the -v option which will apply an adjustment (perhaps easier to think of a v for variation) to the date it returns based on the same abbreviations you use in the custom format string. So if you want the date one hour ago, adding -v-1H to your command will do the business:-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:14:11
$ date -v-1H "+%Y-%m-%d %H:%M:%S"
2014-06-08 13:14:17

Or if you want to adjust by perhaps a configurable number of minutes:-

$ DELAY=75
$ DELAYED_DATE=`date -v-${DELAY}M "+%Y-%m-%d %H:%M:%S"`
$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:18:01
$ echo ${DELAYED_DATE}
2014-06-08 13:03:01

You can repeat the -v option to adjust multiple fields as well:-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:20:10
$ date -v-1d -v-1H "+%Y-%m-%d %H:%M:%S"
2014-06-07 13:20:13

GNU

If GNU makes you think of Rod Hull’s aggressive stuffed sidekick you’re not only a non-geek but you probably did way too much ecstasy in the 90s (that was emu).

GNU, stands for GNU’s Not Unix and its open source flavour of date command can be found on many Linux distros such as Red Hat and Fedora. You’ve a GNU date command if it responds something like this:-

$ date --version
date (GNU coreutils) 8.15
Copyright (C) 2012 Free Software Foundation, Inc.

There’s no -v option here but there is a -d or –date option which lets you feed in either an absolute or relative date for it to display back. The syntax for relative dates is quite expansive in an attempt to respond to natural language requests:-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:38:12
$ date -d "yesterday" "+%Y-%m-%d %H:%M:%S"
2014-06-07 14:38:14

It will respond to similar options to BSD’s -v, though you’ll need to expand on the component you’re rolling (instead of H for example you’ll need to say hour):-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:39:51
$ date -d "-1 hour" "+%Y-%m-%d %H:%M:%S"
2014-06-08 13:39:52

Unlike BSD’s -v though, you can’t supply multiple adjustments as multiple options, it’ll only apply the last one:-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:41:43
$ date -d "-1 hour" -d "-1 day" "+%Y-%m-%d %H:%M:%S"
2014-06-07 14:41:45

You can apply multiple adjustments in a single command but the “clever” syntax and limited examples in the info page can make finding the correct syntax a challenge:-

$ date "+%Y-%m-%d %H:%M:%S"
2014-06-08 14:54:14
$ date -d "yesterday 1 hour ago" "+%Y-%m-%d %H:%M:%S"
2014-06-07 13:54:19
$ date -d "1 day ago and 1 hour ago" "+%Y-%m-%d %H:%M:%S"
date: invalid date `1 day ago and 1 hour ago'
$ date -d "1 day ago, 1 hour ago" "+%Y-%m-%d %H:%M:%S"
date: invalid date `1 day ago, 1 hour ago'
$ date -d "1 day ago 1 hour ago" "+%Y-%m-%d %H:%M:%S"
2014-06-07 13:54:58
$ date -d "-1 day -1 hour" "+%Y-%m-%d %H:%M:%S"
2014-06-07 13:55:24

To be fair you can generally reduce a date adjustment to a common unit anyway (25 hours would work fine in the above example) and the GNU command does let you feed a date in to adjust if you do need to work out a more complex adjustment in multiple steps:-

$ date
Sun Jun 8 15:25:17 BST 2014
$ DATE1=`date -d "-1 hour"`
$ echo $DATE1
Sun Jun 8 14:25:17 BST 2014
$ DATE2=`date -d "${DATE1} -1 day"`
$ echo $DATE2
Sat Jun 7 14:25:17 BST 2014

You can explore the GNU date command options in the info page by typing info coreutils “date” in your shell.

Leave a Reply

Your email address will not be published. Required fields are marked *