henning glatter-götz

Looping Through Dates in a Bash Script on OSX

UPDATE (May 21st 2015):

In the comment by hk0i you will find a simpler way of doing this by installing the coreutils via homebrew

A little while ago I came across a problem in PHP where I had to process large amounts of data from Google Analytics. If I choose a date range that was too large, PHP would run out of memory. I was not able to resolve the memory leak issue caused by parsing very large XML documents so I settled on writing a shell script that would process the data one day at a time by essentially calling the PHP script over and over instead of having it process all data during a single run. I got as far as confirming that this was not a case of simply not having enough memory to do the job, rather there appeared to be a memory leak in a library that I used to process the GA data. Anyway, that memory leak issue is a problem that still needs to be solved, but I did not have the necessary time when I needed this to work and that is not the topic here anyway.

The bash script loops from a start date to an end date, incrementing one day at a time and calls the PHP script on each iteration, passing the date to the script as a parameter.

On Linux it was not a big deal to get it to work:

1
2
3
4
5
6
7
8
9
#!/bin/bash
currentdate=$1
loopenddate=$(/bin/date --date "$2 1 day" +%Y-%m-%d)

until [ "$currentdate" == "$loopenddate" ]
do
  echo $currentdate
  currentdate=$(/bin/date --date "$currentdate 1 day" +%Y-%m-%d)
done

Now execute the script, assuming you created a file called dateloop.sh and assigned it execute permissions:

1
./dateloop.sh 2011-01-01 2011-01-05

And you should get this

1
2
3
4
5
2011-01-01
2011-01-02
2011-01-03
2011-01-04
2011-01-05

On OSX it is not quite as straight forward (to me), because the date command does not support the --date option. Here you have to jump through a few hoops to get it to work. I was able to figure this out with the help of SiegeX on stackoverflow.com.

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
currentDateTs=$(date -j -f "%Y-%m-%d" $1 "+%s")
endDateTs=$(date -j -f "%Y-%m-%d" $2 "+%s")
offset=86400

while [ "$currentDateTs" -le "$endDateTs" ]
do
  date=$(date -j -f "%s" $currentDateTs "+%Y-%m-%d")
  echo $date
  currentDateTs=$(($currentDateTs+$offset))
done

The solution here was to convert both the start and end dates to seconds (epoch) using

1
date -j -f "%Y-%m-%d" $1 "+%s" // Lines 2 and 3 above

and then add the number of seconds for a day (86400) and convert it back to the format I wanted

1
date -j -f "%s" $currentDateTs "+%Y-%m-%d" // line 8 above

Not that hard after all, as with everything else, once you understand it.

Comments