Astro-MoonPhase-Simple

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for Astro-MoonPhase-Simple

0.01    2024-07-14/17:00
        First version, released on an unsuspecting world.
0.02    2024-07-15/19:00
        The calculation of the "localepoch" for specified timezone
        is now accurate, hopefully.
        'location' can now be a nameplace as well as lat/lon coordinates.
        Pod additions.
0.03    2024-07-17/12:00
        Hopefully fixed a bug in testing where I did not take into
        account different timezones in host machines.
        In tests, gotten and expected results will not be compared
        using is_deeply()
        if perl was compiled with uselongdouble flag because the
        extra accuracy fails the comparisons. All other checks will be made.

README  view on Meta::CPAN

    Alternatively, specify the location, as a HASHref of {lon, lat}, the
    moon is observed from and this will deduce the timezone, albeit not
    always as accurately as with specifying a "timezone" explicitly.

    Warning: if the caller does not specify a timezone or location then the
    specified time will be assumed to be UTC time and not at the local
    timezone of the host.

    Astro::MoonPhase calculates the moon phase given an epoch. Which is the
    number of seconds since 1970-01-01 on a UTC timezone. This epoch is
    corrected to a "localepoch" by adding to it the specific timezone
    offset. For example, if you specified the timezone to be
    "China/Beijing" and the local time (at the specified timezone) to be
    23:00. It means UTC time is 15:00. The epoch will be calculated on UTC
    time. However, we add 23:00-15:00=8:00 hours to that epoch to make it
    "localepoch" and this is what we pass on to Astro::MoonPhase to
    calculate the moon phase.

    On failure it returns undef. On success it returns a HASHref with keys:

      * MoonPhase : the moon phase (terminator phase angle) as a number
      between 0 and 1. New Moon (dark) being 0 and Full Moon (bright) being
      1.

      * MoonPhase% : the above as a percentage.

README.md  view on Meta::CPAN

will deduce the timezone, albeit not always as accurately
as with specifying a "timezone" explicitly.

Warning: if the caller does not specify a `timezone` or `location`
then the specified `time` will be assumed to be **UTC time** and not
at the local timezone of the host.

[Astro::MoonPhase](https://metacpan.org/pod/Astro%3A%3AMoonPhase) calculates the moon phase
given an _epoch_. Which is the number of seconds
since 1970-01-01 **on a UTC timezone**. This epoch
is corrected to a "_localepoch_" by adding to it
the specific timezone offset. For example, if you
specified the timezone to be "China/Beijing" and
the local time (at the specified timezone) to be 23:00.
It means UTC time is 15:00. The epoch will be calculated
on UTC time. However, we add `23:00-15:00=8:00` hours to
that epoch to make it "_localepoch_" and this is
what we pass on to [Astro::MoonPhase](https://metacpan.org/pod/Astro%3A%3AMoonPhase) to calculate
the moon phase.

On failure it returns `undef`.
On success it returns a HASHref with keys:

- `MoonPhase` : the moon phase (terminator phase angle) as a number between 0 and 1. New Moon (dark) being 0 and Full Moon (bright) being 1.
- `MoonPhase%` : the above as a percentage.
- `MoonIllum` : the illuminated fraction of the moon's disc as a number between 0 and 1. New Moon (dark) being 0 and Full Moon (bright) being 1.
- `MoonIllum%` : the above as a percentage.

lib/Astro/MoonPhase/Simple.pm  view on Meta::CPAN

			print STDERR "$whoami (via $parent) : parameter 'location' is not a string of a location name (e.g. London) or it is not a HASHref which contains the two keys 'lon' and 'lat' : ".perl2dump(\$params->{'location'})."\n";
			return undef
		}
	}
	if( exists($params->{'localtimezone'}) && defined($params->{'localtimezone'}) ){
		print STDOUT "$whoami (via $parent) : found parameter 'localtimezone' : '".$params->{'localtimezone'}."'.\n" if $verbosity > 1;
	}

	my $parsed_results = _parse_event($params);
	if( ! defined $parsed_results ){ print STDERR perl2dump($params)."$whoami (via $parent) : error, call to ".'_parse_event()'." has failed for above parameters.\n"; return undef }
	my $epoch = $parsed_results->{'localepoch'};

	print STDOUT _event2str($params)."\n$whoami (via $parent) : deduced epoch as '$epoch' for above parameters, now calling Astro::MoonPhase::phase() ...\n"
	 if $verbosity > 0;

	my (	$MoonPhase,
		$MoonIllum,
		$MoonAge,
		$MoonDist,
		$MoonAng,
		$SunDist,
		$SunAng
	) = Astro::MoonPhase::phase($epoch);

	# the phases are unix epoch for each of the below moon phase names
	# we are printing the date via DateTime on that epoch and adjusting for the timezone
	# the user asked or UTC/or-local-see-below if none was specified.
	# localtime() uses locale timezone or envvar TZ
	# the DateTime as came from $parsed_results{'datetime'} knows the used timezone
	# so we will use that same timezone.
	#'New Moon' => scalar localtime($phases[0]), #<<< don't use this
	my @phases = phasehunt($epoch);
	my @phases_names = ('New Moon', 'First quarter', 'Full moon', 'Last quarter', 'Next New Moon');
	my %phases = map { $phases_names[$_] => DateTime->from_epoch(epoch => $phases[$_], time_zone => $parsed_results->{'datetime'}->time_zone())->strftime('%a %b %d %T %Y') } 0..$#phases;

	my $outstr = "Moon age: $MoonAge days\n";
	$outstr .= "Moon phase: " . sprintf("%.1f", 100.0*$MoonPhase) . " % of cycle (birth-to-death)\n";
	$outstr .= "Moon's illuminated fraction: " . sprintf("%.1f", 100.0*$MoonIllum) . " % of full disc\n";

lib/Astro/MoonPhase/Simple.pm  view on Meta::CPAN

#   time : optional time in hh:mm:ss
#   localtimezone : optional LOCAL timezone for making the corrections to the UTC-based epoch
#   
#   timezone : optional timezone as a TZ identifier e.g. Africa/Abidjan
#   location : optionally deduce timezone from location if above timezone is absent,
#              it is a nameplace string, e.g. 'Abidjan'
#        OR    it is a HASHref with keys 'lat' and 'lon'
#   verbosity: optionally specify a positive integer to increase verbosity, default is zero for no verbose messages (only errors and warnings)
# On failure it returns undef
# On success it returns the input parameters HASH complemented with
# various calculated things, most useful of which is 'localepoch' (based on timezone and time, date of the input params)
# and also the DateTime object for calculating the above 'localepoch' under key: 'datetime'
sub _parse_event {
	my $_params = shift;

	# we are returning our input plus some more ...
	my $params = { %$_params };

	my $verbosity = exists($params->{'verbosity'}) && defined($params->{'verbosity'}) ? $params->{'verbosity'} : 0;

	if( ! exists $params->{date} ){ print STDERR "_parse_event() : 'date' field is missing from params.\n"; return undef }

lib/Astro/MoonPhase/Simple.pm  view on Meta::CPAN

		} elsif( (ref($loc) eq 'HASH') && (exists $loc->{lat}) && (exists $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 ){ print STDERR "_parse_event() : timezone lookup from location coordinates lat:".$loc->{lat}.", lon:".$loc->{lon}." has failed.\n"; return undef }
			print STDOUT "_parse_event(): setting timezone via 'location' coordinates lat:".$loc->{lat}.", lon:".$loc->{lon}." ...\n"
				if $verbosity > 0
		}
	}
	my $localepoch = $dt->epoch; # this is UTC-based but the specified date+time has perhaps a timezone ...

	if( defined $tzstr ){
		# the DateTime object now has the specified timezone
		# but its epoch will still be UTC-based (as always) ...
		# our "localepoch" is adjusted though, see below,
		$dt->set_time_zone($tzstr);

		# we have a timezone, find the offset and add it to the epoch
		print "_parse_event(): deduced timezone to '$tzstr' and adjusting epoch to it ...\n"
			if $verbosity > 0;
		my $tzobj = eval { DateTime::TimeZone->new( name => $tzstr ) };
		if( ! defined($tzobj) || $@ ){
			print STDERR "_parse_event(): failed to set the timezone '$tzstr', is it valid? : $@\n";
			return undef;
		}
		my $offset = $tzobj->offset_for_datetime($dt);
		# we should not change the DateTime, just our own 'localepoch' 
		#$dt->add(seconds => $offset);
		$localepoch += $offset;
		print STDOUT "_parse_event(): adjusted epoch for timezone '$tzstr' to $localepoch (offset of $offset).\n"
			if $verbosity > 0;
	}
	print STDOUT "DateTime: $dt\n(timezone: ".$dt->time_zone().")\n_parse_event(): above is the DateTime object for the moon phase calculations, with adjusted timezone.\n"
		if $verbosity > 0;
	$params->{localepoch} = $dt->epoch;
	# this is our input hash with added fields, the most important is 'localepoch'
	# of the input date/time/timezone
	return $params
}



=pod

=head1 NAME

lib/Astro/MoonPhase/Simple.pm  view on Meta::CPAN

will deduce the timezone, albeit not always as accurately
as with specifying a "timezone" explicitly.

Warning: if the caller does not specify a C<timezone> or C<location>
then the specified C<time> will be assumed to be B<UTC time> and not
at the local timezone of the host.

L<Astro::MoonPhase> calculates the moon phase
given an I<epoch>. Which is the number of seconds
since 1970-01-01 B<on a UTC timezone>. This epoch
is corrected to a "I<localepoch>" by adding to it
the specific timezone offset. For example, if you
specified the timezone to be "China/Beijing" and
the local time (at the specified timezone) to be 23:00.
It means UTC time is 15:00. The epoch will be calculated
on UTC time. However, we add C<23:00-15:00=8:00> hours to
that epoch to make it "I<localepoch>" and this is
what we pass on to L<Astro::MoonPhase> to calculate
the moon phase.

On failure it returns C<undef>.
On success it returns a HASHref with keys:

=over 2

=item * C<MoonPhase> : the moon phase (terminator phase angle) as a number between 0 and 1. New Moon (dark) being 0 and Full Moon (bright) being 1.



( run in 0.834 second using v1.01-cache-2.11-cpan-5a3173703d6 )