15

Is there any way I can get the current 15 minute interval using the date command or similar ?

e.g. something like date %Y.%m.%d %H:%M will give me 2011.02.22 10:19 , I need something that yields 2011.02.22 10:15 in the time span from 10:15 to 10:29.

7 Answers 7

17

You can get the current unix timestamp with date "+%s", find the current quarter-hour with some simple math (my example is in bash) and print it back with a better format with date :

curdate=`date "+%s"`
curquarter=$(($curdate - ($curdate % (15 * 60))))
date -d"@$curquarter"

The @ syntax to set the current date and time from a timestamp is a GNU extention to date, if it don't works on your OS, you can do the same like this (don't forget to specify UTC, otherwise, it won't work) :

date -d"1970-01-01 $curquarter seconds UTC"
5

The following methods make it unnecessary to call date twice.
A system call's overhead can make a "simple" command 100 times slower than bash doing the same thing in its own local environment.

UPDATE Just a quick mention about my above comment: "100 times slower". It can now read "500 times slower"... I recently fell (no, walked blindly) into this very issue. here is the link: Fast way to build a test file

eval $(date +Y=%Y\;m=%m\;d=%d\;H=%H\;M=%M)
[[ "$M" < "15" ]] && M=00 # cater for octal clash
M=$(((M/15)*15))
((M==0)) && M=00 # the math returns 0, so make it 00  
echo $Y.$m.$d $H:$M  

or

eval $(date +Y=%Y\;m=%m\;d=%d\;H=%H\;M=%M)
if   [[ "$M" < "15" ]] ; then M=00
elif [[ "$M" < "30" ]] ; then M=15
elif [[ "$M" < "45" ]] ; then M=30
else M=45
fi
echo $Y.$m.$d $H:$M

Both versions will return only

2011.02.23 01:00
2011.02.23 01:15
2011.02.23 01:30
2011.02.23 01:45   

Here is the first one with a TEST loop for all 60 values {00..59}

for X in {00..59} ;                         ###### TEST
do                                          ###### TEST 
  eval $(date +Y=%Y\;m=%m\;d=%d\;H=%H\;M=%M)
  M=$X                                      ###### TEST 
  [[ "$M" < "15" ]] && M=00 # cater for octal clash
  M=$(((M/15)*15))
  ((M==0)) && M=00 # the math returns 0, so make it 00  
  echo $Y.$m.$d $H:$M 
done                                        ###### TEST 
2
  • 1
    Just for clarification — by "system call", you mean "fork/exec/wait, like the system() call", not system call in general. (I mention this because making system calls also has an overhead within a program, because of the user/kernel switch. That's not the particular issue here.)
    – mattdm
    Mar 6, 2011 at 6:03
  • mattdm... Thanks.. I'm working from only 6 months Linux experience, so I've quite likely used the wrong phrase, but I keep reading about the overheads incurred by unnecessary calls, and in my own experience of testing my own scripts, I've seen many examples where calling printf or sed and also that "unnecessary" cat vs <file does make a dramatic actual run-time difference. when looping, as in my "500 times slower" example.. So it seems that using the shell wherever possible, and allowing a program such as date to batch process is what I mean.. eg. Gilles' Left-Pad-0 example, vs printf
    – Peter.O
    Mar 6, 2011 at 11:09
3

Here's a way to work on dates in the shell. First call date to get the components, and fill the positional parameters ($1, $2, etc) with the components (note that this is one of these rare cases where you do want to use $(…) outside of double quotes, to break the string into words). Then perform arithmetics, tests, or whatever you need to do on the components. Finally assemble the components.

The arithmetic part can be a little tricky because shells treat 0 as an octal prefix; for example here $(($5/15)) would fail at 8 or 9 minutes past the hour. Since there's at most one leading 0, ${5#0} is safe for arithmetic. Adding 100 and subsequently stripping the 1 is a way to get a fixed number of digits in the output.

set $(date "+%Y %m %d %H %M")
m=$((100+15*(${5#0}/15)))
last_quarter_hour="$1.$2.$3 $4:${m#1}"
1
  • "The tricks of the trade" ...a good one. :)
    – Peter.O
    Mar 6, 2011 at 6:05
2

Maybe it doesn't matter anymore but you could try my very own dateutils. Rounding (down) to minutes is done with dround and a negative argument:

dround now /-15m
=>
  2012-07-11T13:00:00

Or to adhere to your format:

dround now /-15m -f '%Y.%m.%d %H:%M'
=>
  2012.07.11 13:00
3
  • is that actually how your utils work anymore? using -15m I get the input rewound to the nearest 15 minutes past an hour: dateutils.dround '2018-11-12 13:55' -15m produces 2018-11-12T13:15:00 not 2018-11-12T13:45:00.
    – user12810
    Nov 12, 2018 at 15:34
  • 1
    @JackDouglas The tools have evolved, what you want is now: /-15m, i.e. round down to the next multiple of 15 minutes in the hour. This feature got the more cumbersome syntax because it accepts less values, /-13m isn't possible for instance because 13 is not a divisor of 60 (minutes in the hour)
    – hroptatyr
    Nov 13, 2018 at 7:46
  • nice, I get what I need now with dateutils.dround '2018-11-12 12:52:31' /450s /-15m, thanks!
    – user12810
    Nov 13, 2018 at 11:27
2

Some shells are able to do the work without calling an external date command:

a=$(printf '%(%s)T\n'); printf '%(%Y.%m.%d %H:%M)T\n' "#$((a-a%(15*60)))"
a=$(printf '%(%s)T\n'); printf '%(%Y.%m.%d %H:%M)T\n' "$((a-a%(15*60)))"
zmodload zsh/datetime; a=$EPOCHSECONDS; strftime '%Y-%m-%d %H:%M' $((a-a%(15*60)))

The three above provide local (not UTC) time. Use a leading TZ=UTC0 if needed.

The ksh and bash syntax is almost identical (except for the required # in ksh). The zsh require to load a module (included with the zsh distribution).

It is also possible to do it with (GNU) awk:

awk 'BEGIN{t=systime();print strftime("%Y.%m.%d %H:%M",t-t%(15*60),1)}'

This provide an UTC result (change the last 1 to 0) if local is needed. But loading an external awk or an external zsh module might be as slow as calling date itself:

a=$(date +%s); date -ud "@$((a-a%(15*60)))" +'%Y.%m.%d %H:%M'

A small executable like busybox could provide similar results:

a=$(busybox date +%s);busybox date -ud "@$((a-a%(15*60)))" +'%Y.%m.%d %H:%M'

Note that the leading busybox word may be omitted if busybox is linked to those names in a directory of the PATH.

Both date and busybox date above will print UTC times. Remove the -u option for local times.


If your OS has a more limited version of date (and that is what you must use) then try:

a=$(date +%s); a=$(( a-a%(15*60) ))
date -d"1970-01-01 $a seconds UTC" +'%Y.%m.%d %H:%M'

Or, if in FreeBSD, try:

a=$(date +%s); a=$(( a-a%(15*60) ))
date -r "$a" +'%Y.%m.%d %H:%M'
1

If you can live with calling date two times, this one works in bash on Solaris:

date +"%Y.%m.%d %H:$(( $(date +'%M') / 15 * 15 ))"

Edited on behalf of the comment to:

date +"%Y.%m.%d %H:$(( $(date +'%M') / 15 * 15 ))" | sed 's/:0$/:00/'
3
  • 1
    In the first qurarter of the hour, this will produce a minutes output with only a single 0 instead of 00
    – Peter.O
    Feb 22, 2011 at 14:40
  • Corrected, it is a simple sed after the original command.
    – ddeimeke
    Feb 23, 2011 at 15:53
  • One more bug: it doesn't work between H:08 and H:10. See my answer for why and a fix. Feb 23, 2011 at 21:33
-1

Am not sure about your exact requirement. However, if you want to generate the time after 15 min, then you can use something like date -d '+15 min'. Output is shown below:

$ date; date  -d '+15 min'
Tue Feb 22 18:12:39 IST 2011
Tue Feb 22 18:27:39 IST 2011
1
  • 1
    You misunderstood the question. The point is to round the date (down) to a multiple of 15 minutes. Feb 22, 2011 at 19:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .