Dave finalizes his phase of the moon script.
I don't know about you, but I'm still thinking extra-planetary thoughts as we go through the tail end of this particularly contentious election season and its aftermath. Maybe life on other planets is easier? Ah, maybe not.
In any case, I completed the Martian lander and now am enmeshed in a phase of the moon script. In my last article, I talked about the complications of calculating the phase of the moon and decided simply to scrape the same website that Google uses: www.moongiant.com.
That site provides the current moon illumination level, which lets you break it down into the phases of new moon, crescent, quarter, gibbous and full. Amateur astronomers know that the fun part of tracking the moon's phase is to understand whether it's “waxing” (growing more illuminated) or “waning” (growing less illuminated).
Although at any given moment the moon is illuminated based on its location, and your location, relative to the sun, the full cycle of a moon phase starts and ends with a new (0% illuminated) moon, and the full moon (100% illuminated) is the mid-point of the journey.
Therefore, to ascertain waxing or waning, all you need to do is know the moon's illumination level today and either yesterday or tomorrow. Fortunately, the Moon Giant website obligingly has the ability for you to ascertain the illumination level for a specific date.
A quick visit to the site with a regular web browser reveals that it works using a date-based URL format like this: http://www.moongiant.com/phase/11/6/2016.
So, you can build the date URL for the day before today with a call to the date program. If you've got the GNU version of date, it's easy to back up a day:
$ date Mon Nov 7 11:40:31 MST 2016 $ date -v -1d Sun Nov 6 11:40:15 MST 2016
It turns out that you also can specify that you want to back up 24 hours, although, of course, the net result is the same:
$ date -v -24H Sun Nov 6 11:40:24 MST 2016
More important, you can pass date a format string that you then can evaluate with the eval function, so you can set month, day and year for yesterday in one easy step:
$ eval $( date -v -1d +"mon=%m day=%d year=%Y" ) $ echo month = $mon, day = $day and year $year month = 11, day = 06 and year 2016
It's quite a handy trick when you need to work with extracting specific elements from date and 10x that when it also involves date math.
But, what if your version of date doesn't include the -v flag and doesn't have all these fancy features? Then, my friend, you are facing a definite challenge. Date math is pretty easy, except for the edge cases.
That is, it's easy to extract the current month, day and year from even the most rudimentary Linux version of date, and it's obviously easy to subtract one from the day, but what if it's the first of the month? Or the first of the year?
That's doable too, but it's just a bit more work. Notably, you'll also want to know about leap years, because one day prior to March 1, 2016, might be February 28, or it might be February 29, depending on whether 2016 was a leap year.
Now a sneaky way to do it simply would be to sidestep the issue. If the day number of the month is greater than 1, subtract one to get yesterday's date. If it is the first, however, add one and reverse the logic of the waxing/waning test.
Fortunately, I do have the more sophisticated date program, so I'm going to do that most frustrating of things and leave this particular facet as the proverbial exercise for the reader.
Knowing the format of the Moon Giant URL when you specify a date, and knowing how to use eval and date to get the numeric month, day and year values for yesterday, here's some code to put that all together:
url_ago="http://www.moongiant.com/phase" eval $( date -v -1d +"mon=%m day=%d year=%Y" ) ydayurl="$url_ago/$mon/$day/$year"
The good news is that the resultant web page has the same format as the page for today's illumination level too, so the same curl|grep sequence explored in my last article can be reused for this task:
yillumlevel="$( curl -s "$ydayurl" | grep "$pattern" | tr ',' '\ ' | grep "$pattern" | sed 's/[^0-9]//g')"
In fact, let's add a debugging statement that displays both today's lunar illumination level and yesterday's level:
echo today\'s illumination level = $illumlevel and \ yesterday was $yillumlevel
Running it on November 7, 2016, here's what the script and the Moon Giant website report:
today's illumination level = 47 and yesterday was 37
Now it's a simple test: is today's level greater or less than yesterday's level?
But wait, “waxing” and “waning” applies only to crescent and gibbous moon phases. If the moon is new, quarter or full, neither word applies in common astronomical parlance.
Seriously, who came up with these rules? Talk about complicated!
Here's how this all fits together:
if [ $illumlevel -gt $yillumlevel ] ; then # we're waxing if it's getting brighter waxwane="waxing" else waxwane="waning" fi if [ $illumlevel -lt 5 ] ; then phasename="new" elif [ $illumlevel -lt 45 ] ; then phasename="$waxwane crescent" elif [ $illumlevel -lt 55 ] ; then phasename="quarter" elif [ $illumlevel -lt 95 ] ; then phasename="$waxwane gibbous" else phasename="full" fi echo "The moon is currently $phasename with \ $illumlevel% illuminated."
That's just about the entire script. If I run it on November 7, 2016, the moon is 47% illuminated, which makes it a quarter moon (45%–55%), so the output is:
The moon is currently quarter with 47% illuminated.
A few days later, on November 10, the output is what you would hope:
The moon is currently waxing gibbous with 78% illuminated.
Done. Nice and easy.
You could do plenty of things with this script to improve it and make it more powerful and flexible. The easiest would be simply to rewrite that output line so it's less grammatically awkward:
echo "It's a $phasename moon that's $illumlevel% illuminated."
Now the output will make a bit more sense as the script reports that “It's a waning gibbous moon that's 72% illuminated.”
The bigger task is to allow users to specify a date and calculate the values for that particular date (including the day prior to the date specified). I would do this using the same basic date -v approach, but first parse users' input and break it down into month, day and year. For simplicity's sake, constrain their input to a MM/DD/YYYY format, and there's surprisingly little involved in the task.
For huge bonus points, of course, a graphical display would be nice. But that's hard to do with a shell script, right?
That's it for space. Next month, I'm planning to turn back to games and explore how to write a rock, paper, scissors game. You might want to study the game first so you're ready!