Astro-App-Satpass2
view release on metacpan or search on metacpan
lib/Astro/App/Satpass2/ParseTime/ISO8601.pm view on Meta::CPAN
package Astro::App::Satpass2::ParseTime::ISO8601;
use strict;
use warnings;
use Astro::App::Satpass2::Utils qw{
back_end __back_end_class_name_of_record __parse_class_and_args
HAVE_DATETIME
@CARP_NOT
};
use Astro::Coord::ECI::Utils 0.112 qw{
looks_like_number SECSPERDAY greg_time_gm greg_time_local };
use parent qw{ Astro::App::Satpass2::ParseTime };
our $VERSION = '0.057';
my $package = __PACKAGE__;
sub attribute_names {
my ( $self ) = @_;
return ( $self->SUPER::attribute_names(), qw{ back_end } );
}
sub class_name_of_record {
my ( $self ) = @_;
return $self->__back_end_class_name_of_record(
$self->SUPER::class_name_of_record() );
}
my $zone_re = qr{ (?i: ( Z | UT | GMT ) |
( [+-] ) ( [0-9]{1,2} ) :? ( [0-9]{1,2} )? ) }smx;
sub delegate {
return __PACKAGE__;
}
{
my %special_day_offset = (
yesterday => -SECSPERDAY(),
today => 0,
tomorrow => SECSPERDAY(),
);
my $era_ad = sub { return $_[0] };
my $era_bc = sub { return 1 - $_[0] };
my %era_cvt = (
AD => $era_ad,
BC => $era_bc,
BCE => $era_bc,
CE => $era_ad,
);
# Note that we have to reverse sort the keys because otherwise 'BC'
# gets matched before we have a chance to try 'BCE'.
my $era_re = qr< (?: @{[
join ' | ', reverse sort keys %era_cvt
]} ) >smxi;
my $make_epoch = HAVE_DATETIME ? sub {
my ( $self, $zone, $offset, @date ) = @_;
$zone ||= 'local';
if ( defined( my $special = $special_day_offset{$date[0]} ) ) {
my $dt = DateTime->today(
time_zone => $zone,
);
splice @date, 0, 3, $dt->year(), $dt->month(), $dt->day();
$offset += $special;
}
my %dt_arg;
@dt_arg{ qw<
year month day hour minute second nanosecond
> } = @date;
$dt_arg{nanosecond} *= 1_000_000_000;
$dt_arg{time_zone} = $zone;
$self->{_back_end}
and return $self->{_back_end}{class}->new(
%dt_arg,
@{ $self->{_back_end}{arg} },
)->epoch() + $offset;
return DateTime->new( %dt_arg )->epoch() + $offset;
} : sub {
my ( undef, $zone, $offset, @date ) = @_;
if ( defined( my $special = $special_day_offset{$date[0]} )
) {
my @today = $zone ? gmtime : localtime;
splice @date, 0, 3, @today[ 5, 4, 3 ];
lib/Astro/App/Satpass2/ParseTime/ISO8601.pm view on Meta::CPAN
)?
>smxgc ) {
push @date, $1, $2 || 0, $3 || 0, $4 ? ".$4" : 0;
$special_only = 0;
} else {
push @date, ( 0 ) x 4;
}
# We might have gobbled part of the zone.
not $special_only
and $string =~ m/ \G (?<= [^0-9] ) /smxgc
and pos $string -= 1;
my ( $zone ) = $string =~ m/ \G ( .* ) /smxgc;
my ( $z, $offset ) = $self->_interpret_zone( $zone );
defined $offset
or return;
return $make_epoch->( $self, $z, $offset, @date );
}
}
sub _interpret_zone {
my ( $self, $zone, $fatal ) = @_;
defined $zone
and $zone =~ s/ \A \s+ //smx;
$zone
or return ( @{ $self->{$package}{tz} || [ undef, 0 ] } );
if ( $zone =~ m/ \A $zone_re \z /smxo ) {
$1
and return ( UTC => 0 );
my $offset = ( ( $3 || 0 ) * 60 + ( $4 || 0 ) ) * 60;
$2
and '-' eq $2
or $offset = - $offset;
return ( UTC => $offset );
} else {
HAVE_DATETIME
or return ( $zone => 0 ); # On the user's head be it.
DateTime::TimeZone->is_valid_name( $zone )
and return ( $zone => 0 );
$fatal
and $self->wail( "Invalid time zone '$zone'" );
return;
}
}
sub tz {
my ( $self, @args ) = @_;
if ( @args ) {
if ( defined $args[0] && $args[0] ne '' ) {
$self->{$package}{tz} = [
$self->_interpret_zone( $args[0], 1 ) ];
} else {
delete $self->{$package}{tz};
}
}
return $self->SUPER::tz( @args );
}
sub __back_end_default {
my ( undef, $cls ) = @_; # Invocant ($self) unused
defined $cls
and return $cls;
return 'DateTime';
}
sub __back_end_validate {
my ( undef, $cls, @arg ) = @_; # Invocant ($self) unused
$cls->now( @arg );
return;
}
1;
=head1 NAME
Astro::App::Satpass2::ParseTime::ISO8601 - Astro::App::Satpass2 minimal ISO-8601 parser
=head1 SYNOPSIS
No user-serviceable parts inside.
=head1 DETAILS
This class parses ISO-8601 dates. It does not do ordinal days or weeks,
but it is rather permissive on punctuation, and permits the convenience
dates C<'yesterday'>, C<'today'>, and C<'tomorrow'>.
This class understands ISO-8601 time zone specifications of the form
'Z', 'UT', 'GMT' and C<[+-]\d{1,2}:?\d{,2}>, but it knows nothing about
shifts for summer time. So C<2009/7/1 12:00:00 -5> is 5:00 PM GMT, not
4:00 PM. Other zones will be accepted, but may not do what you want. See
below.
As an extension to the ISO-8601 standard, years can be followed by an
era specification, which is one of C<'AD'>, C<'BC'>, C<'BCE'>, or
C<'CE'> without regard to case. The era indicator may be separated from
the year by white space, and be followed by a non-digit separator
character.
Unless the era is specified, years less than C<70> will have C<2000>
added, and years at least equal to C<70> but less than C<100> will have
C<1900> added.
If L<DateTime|DateTime> can be loaded, it will be used to get an epoch
from the parsed date, including zone.
If L<DateTime|DateTime> can B<not> be loaded, L<Time::Local|Time::Local>
will be used to get an epoch from the parsed date.
L<Time::Local|Time::Local> has its own quirks when it sees a year in the
distant past. Zones other than C<UTC>, C<GMT>, C<Z>, and numeric offsets
will be handled by setting C<$ENV{TZ}> to the specified zone before
converting from local time to epoch. If this works for you, fine. If
not, tough. B<You have been warned!>
=head1 METHODS
( run in 0.888 second using v1.01-cache-2.11-cpan-98e64b0badf )