Date-Lectionary-Daily

 view release on metacpan or  search on metacpan

lib/Date/Lectionary/Daily.pm  view on Meta::CPAN

use v5.22;
use strict;
use warnings;

use Moose;
use Carp;
use Try::Catch;
use XML::LibXML;
use File::Share ':all';
use Time::Piece;
use Date::Lectionary::Time qw(isSunday prevSunday);
use Date::Lectionary::Day;
use namespace::autoclean;
use Moose::Util::TypeConstraints;
use MooseX::StrictConstructor;

use version; our $VERSION = version->declare("v1.20200102");

=encoding utf8
=head1 NAME

Date::Lectionary::Daily - Daily Readings for the Christian Lectionary

=head1 VERSION

Version 1.20200102

=cut

=head1 SYNOPSIS

    use Time::Piece;
    use Date::Lectionary::Daily;

    #Using the old ACNA Liturgical Daily Lectionary
    my $dailyReading = Date::Lectionary::Daily->new(
        'date' => Time::Piece->strptime("2017-12-24", "%Y-%m-%d"),
        'lectionary' => 'acna-xian'
    );
    say $dailyReading->readings->{evening}->{1}; #First lesson for evening prayer, Isaiah 51

    #Using the new ACNA Secular/Civil Daily Lectionary
    my $dailyNewReading = Date::Lectionary::Daily->new(
        'date' => Time::Piece->strptime( "2018-03-12", "%Y-%m-%d" ),
        'lectionary' => 'acna-sec'
    );
    say $dailyNewReading->readings->{morning}->{2}; #Second lesson for morning prayer, Matthew 5

=head1 DESCRIPTION

Date::Lectionary::Daily takes a Time::Piece date and returns readings for morning and evening prayer for that date.

=head2 CONSTRUCTOR ATTRIBUTES

=head3 date

The Time::Piece object date of the day you woudl like the lessons for.

=head3 lectionary

One of two choices `acna-sec` for the new secular calendar based ACNA daily lectionary or `acna-xian` for the previous liturgically-based ACNA daily lectionary.

If lectionary is not given at construction, the ACNA secular daily lectionary — `acna-sec` — will be used.

=head2 ATTRIBUTES

=head3 week

The name of the liturgical week in the lectionary; e.g. `The First Sunday in Lent`.

=head3 day

The name of the day of the week; e.g. `Sunday`.

=head3 tradition

Presently only returns `acna`.  Future version of the module may include daily lectionary from other traditions.

=head3 type

Returns `secular` for daily lectionaries based on the secular/civil calendar and `liturgical` for daily lectionaries based on the liturgical calendar.

=head3 readings

A hasref of the readings for the day.

=cut

enum 'DailyLectionary', [qw(acna-sec acna-xian)];
enum 'Tradition',       [qw(acna)];
enum 'LectionaryType',  [qw(secular liturgical)];
no Moose::Util::TypeConstraints;

=head1 SUBROUTINES/METHODS

=cut

has 'date' => (
    is       => 'ro',
    isa      => 'Time::Piece',
    required => 1,
);

has 'week' => (
    is       => 'ro',
    isa      => 'Str',
    writer   => '_setWeek',
    init_arg => undef,
);

has 'day' => (
    is       => 'ro',
    isa      => 'Str',
    writer   => '_setDay',
    init_arg => undef,
);

has 'lectionary' => (
    is      => 'ro',
    isa     => 'DailyLectionary',
    default => 'acna-sec',
);

has 'tradition' => (
    is       => 'ro',
    isa      => 'Tradition',
    writer   => '_setTradition',
    init_arg => undef,
);

has 'type' => (
    is       => 'ro',
    isa      => 'LectionaryType',
    writer   => '_setType',
    init_arg => undef,
);

has 'readings' => (
    is       => 'ro',
    isa      => 'HashRef',
    writer   => '_setReadings',
    init_arg => undef,
);

=head2 BUILD

Constructor for the Date::Lectionary object.  Takes a Time::Piect object, C<date>, to create the object.

=cut

sub BUILD {
    my $self = shift;

    $self->_setTradition( _buildTradition( $self->lectionary ) );
    $self->_setType( _buildType( $self->lectionary ) );

    my $sunday;
    if ( isSunday( $self->date ) ) {
        $sunday = $self->date;
    }
    else {
        $sunday = prevSunday( $self->date );
    }

    my $fixedHolyDay = 0;
    if ( $self->lectionary eq 'acna-xian' && ( $self->date->mon == 1 || $self->date->mon == 12 ) ) {
        $fixedHolyDay = _checkFixed( $self->date, $self->lectionary );
    }

    $self->_setWeek(
        Date::Lectionary::Day->new(
            'date'          => $sunday,
            'lectionary'    => $self->tradition,
            'includeFeasts' => 'no',
        )->name
    );

    if ( $self->type eq 'liturgical' ) {
        if ($fixedHolyDay) {
            $self->_setReadings( _buildReadingsLiturgical( "Fixed Holy Days", $self->date->fullmonth . " " . $self->date->mday, $self->lectionary ) );
        }
        else {
            $self->_setReadings( _buildReadingsLiturgical( $self->week, $self->date->fullday, $self->lectionary ) );
        }
    }
    elsif ( $self->type eq 'secular' ) {
        $self->_setReadings( _buildReadingsSecular( $self->week, $self->date, $self->lectionary ) );
    }

}

=head2 _buildType

Private method to determine if the daily lectionary follows the secular calendar or the liturgical calendar.

=cut

sub _buildType {
    my $lectionary = shift;

    if ( $lectionary eq 'acna-xian' ) {
        return 'liturgical';
    }
    if ( $lectionary eq 'acna-sec' ) {
        return 'secular';
    }

    return undef;
}

=head2 _buildTradition

Private method to determine the Sunday lectionary tradition of the daily lectionary selected. Used to determine the liturgical week the day falls within.

=cut

sub _buildTradition {
    my $lectionary = shift;

    if ( $lectionary eq 'acna-xian' || $lectionary eq 'acna-sec' ) {
        return 'acna';
    }

    return undef;
}

=head2 _parseLectDB

Private method to open and parse the lectionary XML to be used by other methods to XPATH queries.

=cut

sub _parseLectDB {
    my $lectionary = shift;

    my $parser = XML::LibXML->new();
    my $lectDB;

    try {
        my $data_location = dist_file( 'Date-Lectionary-Daily', $lectionary . '_lect_daily.xml' );
        $lectDB = $parser->parse_file($data_location);
    }
    catch {
        carp "The readings database for the $lectionary daily lectionary could not be found or parsed.";
    };

    return $lectDB;
}

=head2 _checkFixed

Private method to determine if the day given is a fixed holiday rather than a standard day.

=cut

sub _checkFixed {
    my $date       = shift;
    my $lectionary = shift;

    my $searchDate = $date->fullmonth . " " . $date->mday;

    my $lectDB = _parseLectDB($lectionary);

    my $fixed_xpath;

    try {
        $fixed_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"Fixed Holy Days\"]/day[\@name=\"$searchDate\"]/lesson");
    }
    catch {
        carp "Could not compile the XPath Expression for $searchDate in the $lectionary lectionary.";
    };

    if ( $lectDB->exists($fixed_xpath) ) {
        return 1;
    }

    return 0;
}

=head2 _buildReadingsLiturgical

Private method that returns an ArrayRef of strings for the lectionary readings associated with the date according to the liturgical calendar.

=cut

sub _buildReadingsLiturgical {
    my $weekName   = shift;
    my $weekDay    = shift;
    my $lectionary = shift;

    my $readings = _parseLectDB($lectionary);

    my $morn1_xpath;
    my $morn2_xpath;
    my $eve1_xpath;
    my $eve2_xpath;
    try {
        $morn1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"morning\" and \@order=\"1\"]");
        $morn2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"morning\" and \@order=\"2\"]");
        $eve1_xpath  = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"evening\" and \@order=\"1\"]");
        $eve2_xpath  = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"evening\" and \@order=\"2\"]");
    }
    catch {
        carp "Could not compile the XPath Expression for $weekDay in $weekName in the $lectionary lectionary.";
    };

    my %readings;
    try {
        %readings = (
            morning => {
                1 => $readings->find($morn1_xpath)->string_value(),
                2 => $readings->find($morn2_xpath)->string_value()
            },
            evening => {
                1 => $readings->find($eve1_xpath)->string_value(),
                2 => $readings->find($eve2_xpath)->string_value()
            }
        );
    }
    catch {
        carp "The readings for $weekDay in $weekName in the $lectionary lectionary could not be found.";
    };

    return \%readings;
}

=head2 _buildReadingsSecular

Private method that returns an ArrayRef of strings for the lectionary readings associated with the date according to the secular calendar.

=cut

sub _buildReadingsSecular {
    my $weekName   = shift;
    my $date       = shift;
    my $lectionary = shift;

    my $readings = _parseLectDB($lectionary);

    my $seekDate = substr( $date->ymd, 5, 5 );

    my $morn1_xpath;
    my $morn2_xpath;
    my $eve1_xpath;
    my $eve2_xpath;
    try {
        $morn1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"morning\" and \@order=\"1\"]");
        $morn2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"morning\" and \@order=\"2\"]");
        $eve1_xpath  = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"evening\" and \@order=\"1\"]");
        $eve2_xpath  = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"evening\" and \@order=\"2\"]");
    }
    catch {
        carp "Could not compile the XPath Expression for $seekDate in $weekName in the $lectionary lectionary.";
    };

    my %readings;
    try {
        %readings = (
            morning => {
                1 => $readings->find($morn1_xpath)->string_value(),
                2 => $readings->find($morn2_xpath)->string_value()
            },
            evening => {
                1 => $readings->find($eve1_xpath)->string_value(),
                2 => $readings->find($eve2_xpath)->string_value()
            }
        );
    }
    catch {
        carp "The readings for $seekDate in $weekName in the $lectionary lectionary could not be found.";
    };

    return \%readings;
}

=head1 AUTHOR

Michael Wayne Arnold, C<< <michael at rnold.info> >>

=head1 BUGS

=for html <a href="https://travis-ci.org/marmanold/Date-Lectionary-Daily"><img src="https://travis-ci.org/marmanold/Date-Lectionary-Daily.svg?branch=master"></a>

=for html <a href='https://coveralls.io/github/marmanold/Date-Lectionary-Daily?branch=master'><img src='https://coveralls.io/repos/github/marmanold/Date-Lectionary-Daily/badge.svg?branch=master' alt='Coverage Status' /></a>

Please report any bugs or feature requests to C<bug-date-lectionary-daily at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Date-Lectionary-Daily>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.



( run in 0.870 second using v1.01-cache-2.11-cpan-ceb78f64989 )