http://qs1969.pair.com?node_id=11137299

While I was watching the moon at a place away from civilisation I wondered what phase was the moon on certain historical events. I am not into astrology. I was curious to see how military planners make use of the moon and its effect on illumination and the tides. Invasions primarily...

Thankfully Perl and the wonderful playground of CPAN provide all the tools one needs for an application which calculates the moon phase given date and, optionally, time and timezone or location. Thank you Astro::MoonPhase and DateTime and Geo::Location::TimeZone. All is needed is the date. Time defaults to the beginning of the day (00:00:01) and the timezone defaults to UTC. If the event's time is critical then supply it along with a timezone to make the conversion to unix-epoch-seconds (assumes UTC) accurate. In starting this I was not aware that Moon phase for a specific time is more-or-less the same for anywhere on the surface of our planet, irrespective of standpoint. With the caveat that our antipodean fellows will see it, well ... , antipodeanly.

It looks that some military invasions took advantage of the full moon (invasion of Libya/2011, invasion of Iraq/2003 which, btw, both happened on the same day 8 years apart) while others planned for a "dark" moon (Invasion of Bay of Pigs, Cuba/1961). Normandy Landing/1944 (D-Day) planners chose a full moon (in the output: illuminated fraction: 99.4 % of full disc and also Moon age: 14.0637595011504 days which is just about half the moon cycle, i.e. full-moon) because the way it affected the tide, although the illumination would have been unwanted.

The basic module used is Astro::MoonPhase and seems to work fine as I confirmed its output with an online calculator. Although its input is unix-epoch-seconds, it seems to handle well cases older than 1970, with a negative epoch.

A moon phase calculator has also been posted here some time in 2007, phoon - show the phase of the moon by jima, based on previous program by Jef Poskanzer.

Some results:

Normandy Landing on 1944-06-06T05:00:00 (-806965200 seconds unix-epoch +) timezone: Europe/Paris (lat: 49.18, lon: -0.37) Moon age: 14.0637595011504 days Moon phase: 47.6 % of cycle (birth-to-death) Moon's illuminated fraction: 99.4 % of full disc important moon phases around the event: New Moon = Mon May 22 08:14:24 1944 First quarter = Tue May 30 02:06:14 1944 Full moon = Tue Jun 6 20:59:37 1944 Last quarter = Tue Jun 13 17:57:49 1944 New Moon = Tue Jun 20 19:01:26 1944 end event. US Invasion of Cuba, Bay of Pigs on 1961-04-15T05:00:00 (-274975200 se +conds unix-epoch) timezone: America/Havana Moon age: 0.207041969010797 days Moon phase: 0.7 % of cycle (birth-to-death) Moon's illuminated fraction: 0.0 % of full disc end event. Invasion of Libya on 2011-03-19T05:00:00 (1300503600 seconds unix-epoc +h) timezone: Africa/Tripoli Moon age: 14.0213525003449 days Moon phase: 47.5 % of cycle (birth-to-death) Moon's illuminated fraction: 99.4 % of full disc end event. Invasion of Iraq on 2003-03-19T05:00:00 (1048039200 seconds unix-epoch +) timezone: Asia/Baghdad Moon age: 15.4878029842796 days Moon phase: 52.4 % of cycle (birth-to-death) Moon's illuminated fraction: 99.4 % of full disc end event.

Edit: caveat: the most reliable way to get the timezone right is to set it manually using a string that DateTime::TimeZone understands. Getting timezone from coordinates via Geo::Location::TimeZone is less reliable (case in point is Cuba's Bay of Pigs which gets the timezone of Pacific/Norfolk, I have edited the results to correct this.). Another edit: made example output terse and added readmore tags around the code. And added some more thanks to the existing modules my lame app relies on because they deserve it.

The driver script follows. The input is provided in-script by an event hash which specifies a name and date, optional are time, timezone, location coordinates. Enjoy:

use strict; use warnings; use DateTime; use DateTime::Format::ISO8601; use DateTime::TimeZone; use Data::Dumper; use Try::Tiny; use Astro::MoonPhase; ###### # Calculate the moon phase of an event in time. # Required is the date (time defaults to 00:00:01) of the event # The moon-phase calculator does not need a location # since moon phase is the same across the planet (more-or-less) # with the usual antipodean side-effects # However, by specifying a timezone or even coordinates of the # event conversion of event's time to UTC is more accurate # (else UTC time is assumed) # author: bliako # date: 2021-10-07 ###### my $debug = 0; my @events = ( # { # # example event specification # "name" => "a name for reference, required", # "date" => "date of the event in YYYY-MM-DD", # "time" => "optional time as hh:mm:ss, default is 00:00:01", # #specify optional location using one of these for calculating # #epoch which uses UTC time, # #otherwise, UTC timezone will be assumed for event's time: # "timezone" => "standard timezone name, e.g. America/Havana", # "location" => "cuba", # "location" => {lon=>-81.1376, lat=>22.17927}, # }, { "name" => "Normandy Landing", "date" => "1944-06-06", "time" => "05:00:00", #"timezone" => "Europe/Paris", "location" => {lat=>49.180000, lon=>-0.370000} }, { "name" => "US Invasion of Cuba, Bay of Pigs", "date" => "1961-04-15", "time" => "05:00:00", "timezone" => "America/Havana", #"location" => {lon=>-81.1376, lat=>22.17927} }, { "name" => "Invasion of Libya", "date" => "2011-03-19", "time" => "05:00:00", "timezone" => "Africa/Tripoli", }, { "name" => "Invasion of Iraq", "date" => "2003-03-19", "time" => "05:00:00", "timezone" => "Asia/Baghdad", }, ); for my $event (@events){ my $epoch = parse_event($event); print event2str($event) . "\n"; my ( $MoonPhase, $MoonIllum, $MoonAge, $MoonDist, $MoonAng, $SunDist, $SunAng ) = phase($epoch); print "Moon age: $MoonAge days\n"; print "Moon phase: ".sprintf("%.1f", 100.0*$MoonPhase)." % of cycl +e (birth-to-death)\n"; print "Moon's illuminated fraction: ",sprintf("%.1f", 100.0*$MoonI +llum)." % of full disc\n"; print "important moon phases around the event:\n"; my @phases = phasehunt($epoch); print " New Moon = ", scalar(localtime($phases[0])), "\n"; print " First quarter = ", scalar(localtime($phases[1])), "\n"; print " Full moon = ", scalar(localtime($phases[2])), "\n"; print " Last quarter = ", scalar(localtime($phases[3])), "\n"; print " New Moon = ", scalar(localtime($phases[4])), "\n"; print "end event.\n\n" } sub event2str { my $event = shift; if( ! exists $event->{_is_parsed} ){ warn "event has not been parsed, just dumping it..."; print Dump($event); } my $str = $event->{name} . " on ".$event->{datetime} . " (".$event->{datetime}->epoch." seconds unix-epoch)" . " timezone: ".$event->{datetime}->time_zone->name ; if( exists $event->{location} ){ if( ref($event->{location}) eq 'HASH' ){ $str .= " (lat: ".$event->{location}->{lat}.", lon: ".$eve +nt->{location}->{lon}.")" } else { $str .= "(".$event->{location}.")" } } return $str } sub parse_event { my $event = shift; if( ! exists $event->{date} ){ die "date field is missing from eve +nt." } my $datestr = $event->{date}; die "event does not have a 'name' field, please specify one, anyth +ing really." unless exists $event->{name}; my $timestr = "00:00:01"; if( exists $event->{time} ){ $timestr = $event->{time}; print "event2epoch(): setting time to '$timestr' ...\n" if $debug > 0; die "time '$timestr' is not valid, it must be in the form 'hh: +mm:ss'." unless $timestr =~ /^\d{2}:\d{2}:\d{2}$/; } else { $event->{time} = $timestr } my $isostr = $datestr . 'T' . $timestr; my $dt = DateTime::Format::ISO8601->parse_datetime($isostr); die "failed to parse date '$isostr', check date and time fields." unless defined $dt; $event->{datetime} = $dt; my $tzstr = 'UTC'; if( exists $event->{timezone} ){ $tzstr = $event->{timezone}; print "event2epoch(): found a timezone via 'timezone' field as + '$tzstr' (that does not mean it is valid) ...\n" if $debug > 0; } elsif( exists $event->{location} ){ my $loc = $event->{location}; if( (ref($loc) eq '') && ($loc =~ /^[a-zA-Z]$/) ){ # we have a location string my @alltzs = DateTime::TimeZone->all_names; my $tzstr; for (@alltzs){ if( $_ =~ /$loc/i ){ $tzstr = $_; last } } die "event's location can not be converted to a timezone, +consider specifying the 'timezone' directly or setting 'location' coo +rdinates with: \[lat,lon\]." unless $tzstr; print "event2epoch(): setting timezone via 'location' name + to '$timestr' ...\n" if $debug > 0; } elsif( (ref($loc) eq 'HASH') && (exists $loc->{lat}) && (exi +sts $loc->{lon}) ){ # we have a [lat,lon] array for location require Geo::Location::TimeZone; my $gltzobj = Geo::Location::TimeZone->new(); $tzstr = $gltzobj->lookup(lat => $loc->{lat}, lon => $loc- +>{lon}); if( ! $tzstr ){ die "timezone lookup from location coordin +ates lat:".$loc->{lat}.", lon:".$loc->{lon}." has failed." } print "event2epoch(): setting timezone via 'location' coor +dinates lat:".$loc->{lat}.", lon:".$loc->{lon}." ...\n" if $debug > 0 } } if( $tzstr ){ print "event2epoch(): deduced timezone to '$tzstr' and setting + it ...\n" if $debug > 0; try { $dt->set_time_zone($tzstr) } catch { die "$_\n failed to set the timezone '$tzstr', is it valid +?" } } $event->{_is_parsed} = 1; $event->{epoch} = $dt->epoch; return $event->{epoch} }

bw, bliako

Replies are listed 'Best First'.
Re: Moon phase on historical events
by Aldebaran (Curate) on Oct 09, 2021 at 08:15 UTC

    I found this understandable once I went through it with the debugger. A person can get caught up in the geometry of it. This comes at it with strings. I changed the events to my locality and then switched the location to a point somewhere in bliako's neighborhood:

    { "name" => "Current Moon in the 'Ho", "date" => "2021-10-09", "time" => "01:00:00", "timezone" => "America/Boise", }, { "name" => "Scylla and Charybdis", "date" => "2021-10-09", "time" => "01:00:00", "location" => {lon=>38.245833, lat=>15.6325}, },
    Current Moon in the 'Ho on 2021-10-09T01:00:00 (1633762800 seconds uni +x-epoch) timezone: America/Boise Moon age: 3.16929659167535 days Moon phase: 10.7 % of cycle (birth-to-death) Moon's illuminated fraction: 10.9 % of full disc important moon phases around the event: New Moon = Wed Oct 6 05:05:44 2021 First quarter = Tue Oct 12 21:27:35 2021 Full moon = Wed Oct 20 08:57:41 2021 Last quarter = Thu Oct 28 14:06:44 2021 New Moon = Thu Nov 4 15:15:26 2021 end event. Scylla and Charybdis on 2021-10-09T01:00:00 (1633730400 seconds unix-e +poch) timezone: Africa/Nairobi (lat: 15.6325, lon: 38.245833) Moon age: 2.74601783357238 days Moon phase: 9.3 % of cycle (birth-to-death) Moon's illuminated fraction: 8.3 % of full disc important moon phases around the event: New Moon = Wed Oct 6 05:05:44 2021 First quarter = Tue Oct 12 21:27:35 2021 Full moon = Wed Oct 20 08:57:41 2021 Last quarter = Thu Oct 28 14:06:44 2021 New Moon = Thu Nov 4 15:15:26 2021 end event.

    These data seem plausible. I don't know why I needed Geo::Location::TimeZone to get this done and also Math::Polygon, but it works as advertised.

    In starting this I was not aware that Moon phase for a specific time is more-or-less the same for anywhere on the surface of our planet, irrespective of standpoint. With the caveat that our antipodean fellows will see it, well ... , antipodeanly.

    Yeah, so if you freeze time, we all see the same moon, barring obstructions like walls, trees, mountains, clouds, or the earth itself. If you wonder what it was like for someone at one a.m. at the antipode, the answer will differ as the above. The comparison of phase to fraction illuminated is also interesting.

    Fun stuff. Thanks for posting.

      Thanks Aldebaran for doing that very insightful test of different place, "same" time. The unix-epoch seconds of the output show: 1633762800-1633730400=324000, a 9 hour difference. So, yes plausible results as you said.

      bw from atop the Scylla

        bw from atop the Scylla

        Likewise and always good to hear from you. I extended your script to attempt to determine what this moon might look like from the points we have defined and their antipodes. Any observation also needs a height which I have set at the height of Boise. I threw in the planets Venus, Saturn, and Jupiter too, as they seem to be part of the big celestial show for the next few days.

        I also use Log::Log4perl, because these data would overwhelm STDOUT. The .conf files are from the examples.

        My first question is how the azimuth is defined. Astro::Coord is suuuper complicated. Looks like there's even fortran in there. Physicists and mathematicians do things differently in a lot of settings. I can't tell if my results are wrong or untransformed.

        The same time is used for 4 different places. (Not that they're gleichzeitig.) I'll put the log and then the source between readmore tags:

        One thing I noticed is that these data are very sensitive to altitude of the defined observer. A person at 817 meters above the Scylla would have a much better view of rising and setting planets. Is there any way to figure out what a reasonable guesstimate is of altitude given latitude and longitude?

        How long does the moon's transit from Venus to Jupiter take?

        How can I determine that a priori? I know that I can bound the value in DateTime with output from the original post. Can one loop over this interval with the methods of DateTime adding the duration of a second and then checking values. A month isn't a lot of seconds for a computer.

Re: Moon phase on historical events
by cavac (Parson) on Oct 29, 2021 at 14:23 UTC

    Does this take into account historical changes of timezones and calendars?

    Asking, because this can get quite messy and facepalm'y. Things like countries skipping a day (or even a few weeks) are quite a normal thing. As are things like "we change how or if we use daylight savings time whenever we change government".

    Tom Scott has a video in Computerphile that shows how messy things can get.

    perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'

      Good point. The answer is I don't know, I assumed that DateTime would handle that. It seems there is something relevant here https://metacpan.org/pod/DateTime::TimeZone#$tz-%3Eoffset_for_datetime(-$dt-)

      Given a DateTime object, this method returns the offset in seconds for + the given datetime. This takes into account historical time zone inf +ormation, as well as Daylight Saving Time. The offset is determined b +y looking at the object's UTC Rata Die days and seconds.