File Coverage

blib/lib/Date/Lectionary/Daily.pm
Criterion Covered Total %
statement 121 129 93.8
branch 15 18 83.3
condition 8 9 88.8
subroutine 29 35 82.8
pod 1 1 100.0
total 174 192 90.6


line stmt bran cond sub pod time code
1             package Date::Lectionary::Daily;
2              
3 5     5   578319 use v5.22;
  5         31  
4 5     5   25 use strict;
  5         9  
  5         97  
5 5     5   22 use warnings;
  5         10  
  5         115  
6              
7 5     5   2748 use Moose;
  5         2290216  
  5         36  
8 5     5   36703 use Carp;
  5         11  
  5         430  
9 5     5   3074 use Try::Catch;
  5         3962  
  5         301  
10 5     5   3030 use XML::LibXML;
  5         284209  
  5         41  
11 5     5   2826 use File::Share ':all';
  5         104112  
  5         777  
12 5     5   596 use Time::Piece;
  5         11695  
  5         45  
13 5     5   2768 use Date::Lectionary::Time qw(isSunday prevSunday);
  5         33510  
  5         324  
14 5     5   2680 use Date::Lectionary::Day;
  5         1573650  
  5         357  
15 5     5   49 use namespace::autoclean;
  5         14  
  5         51  
16 5     5   480 use Moose::Util::TypeConstraints;
  5         11  
  5         57  
17 5     5   12573 use MooseX::StrictConstructor;
  5         12  
  5         43  
18              
19 5     5   42793 use version; our $VERSION = version->declare("v1.20200102");
  5         12  
  5         46  
20              
21             =encoding utf8
22             =head1 NAME
23              
24             Date::Lectionary::Daily - Daily Readings for the Christian Lectionary
25              
26             =head1 VERSION
27              
28             Version 1.20200102
29              
30             =cut
31              
32             =head1 SYNOPSIS
33              
34             use Time::Piece;
35             use Date::Lectionary::Daily;
36              
37             #Using the old ACNA Liturgical Daily Lectionary
38             my $dailyReading = Date::Lectionary::Daily->new(
39             'date' => Time::Piece->strptime("2017-12-24", "%Y-%m-%d"),
40             'lectionary' => 'acna-xian'
41             );
42             say $dailyReading->readings->{evening}->{1}; #First lesson for evening prayer, Isaiah 51
43              
44             #Using the new ACNA Secular/Civil Daily Lectionary
45             my $dailyNewReading = Date::Lectionary::Daily->new(
46             'date' => Time::Piece->strptime( "2018-03-12", "%Y-%m-%d" ),
47             'lectionary' => 'acna-sec'
48             );
49             say $dailyNewReading->readings->{morning}->{2}; #Second lesson for morning prayer, Matthew 5
50              
51             =head1 DESCRIPTION
52              
53             Date::Lectionary::Daily takes a Time::Piece date and returns readings for morning and evening prayer for that date.
54              
55             =head2 CONSTRUCTOR ATTRIBUTES
56              
57             =head3 date
58              
59             The Time::Piece object date of the day you woudl like the lessons for.
60              
61             =head3 lectionary
62              
63             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.
64              
65             If lectionary is not given at construction, the ACNA secular daily lectionary — `acna-sec` — will be used.
66              
67             =head2 ATTRIBUTES
68              
69             =head3 week
70              
71             The name of the liturgical week in the lectionary; e.g. `The First Sunday in Lent`.
72              
73             =head3 day
74              
75             The name of the day of the week; e.g. `Sunday`.
76              
77             =head3 tradition
78              
79             Presently only returns `acna`. Future version of the module may include daily lectionary from other traditions.
80              
81             =head3 type
82              
83             Returns `secular` for daily lectionaries based on the secular/civil calendar and `liturgical` for daily lectionaries based on the liturgical calendar.
84              
85             =head3 readings
86              
87             A hasref of the readings for the day.
88              
89             =cut
90              
91             enum 'DailyLectionary', [qw(acna-sec acna-xian)];
92             enum 'Tradition', [qw(acna)];
93             enum 'LectionaryType', [qw(secular liturgical)];
94 5     5   918 no Moose::Util::TypeConstraints;
  5         14  
  5         37  
95              
96             =head1 SUBROUTINES/METHODS
97              
98             =cut
99              
100             has 'date' => (
101             is => 'ro',
102             isa => 'Time::Piece',
103             required => 1,
104             );
105              
106             has 'week' => (
107             is => 'ro',
108             isa => 'Str',
109             writer => '_setWeek',
110             init_arg => undef,
111             );
112              
113             has 'day' => (
114             is => 'ro',
115             isa => 'Str',
116             writer => '_setDay',
117             init_arg => undef,
118             );
119              
120             has 'lectionary' => (
121             is => 'ro',
122             isa => 'DailyLectionary',
123             default => 'acna-sec',
124             );
125              
126             has 'tradition' => (
127             is => 'ro',
128             isa => 'Tradition',
129             writer => '_setTradition',
130             init_arg => undef,
131             );
132              
133             has 'type' => (
134             is => 'ro',
135             isa => 'LectionaryType',
136             writer => '_setType',
137             init_arg => undef,
138             );
139              
140             has 'readings' => (
141             is => 'ro',
142             isa => 'HashRef',
143             writer => '_setReadings',
144             init_arg => undef,
145             );
146              
147             =head2 BUILD
148              
149             Constructor for the Date::Lectionary object. Takes a Time::Piect object, C<date>, to create the object.
150              
151             =cut
152              
153             sub BUILD {
154 68     68 1 170 my $self = shift;
155              
156 68         2314 $self->_setTradition( _buildTradition( $self->lectionary ) );
157 68         2105 $self->_setType( _buildType( $self->lectionary ) );
158              
159 68         148 my $sunday;
160 68 100       2025 if ( isSunday( $self->date ) ) {
161 11         1314 $sunday = $self->date;
162             }
163             else {
164 57         6965 $sunday = prevSunday( $self->date );
165             }
166              
167 68         8566 my $fixedHolyDay = 0;
168 68 100 100     2401 if ( $self->lectionary eq 'acna-xian' && ( $self->date->mon == 1 || $self->date->mon == 12 ) ) {
      100        
169 13         423 $fixedHolyDay = _checkFixed( $self->date, $self->lectionary );
170             }
171              
172             $self->_setWeek(
173 68         2629 Date::Lectionary::Day->new(
174             'date' => $sunday,
175             'lectionary' => $self->tradition,
176             'includeFeasts' => 'no',
177             )->name
178             );
179              
180 68 100       1916 if ( $self->type eq 'liturgical' ) {
    50          
181 58 100       209 if ($fixedHolyDay) {
182 5         128 $self->_setReadings( _buildReadingsLiturgical( "Fixed Holy Days", $self->date->fullmonth . " " . $self->date->mday, $self->lectionary ) );
183             }
184             else {
185 53         1584 $self->_setReadings( _buildReadingsLiturgical( $self->week, $self->date->fullday, $self->lectionary ) );
186             }
187             }
188             elsif ( $self->type eq 'secular' ) {
189 10         255 $self->_setReadings( _buildReadingsSecular( $self->week, $self->date, $self->lectionary ) );
190             }
191              
192             }
193              
194             =head2 _buildType
195              
196             Private method to determine if the daily lectionary follows the secular calendar or the liturgical calendar.
197              
198             =cut
199              
200             sub _buildType {
201 68     68   139 my $lectionary = shift;
202              
203 68 100       225 if ( $lectionary eq 'acna-xian' ) {
204 58         2065 return 'liturgical';
205             }
206 10 50       24 if ( $lectionary eq 'acna-sec' ) {
207 10         308 return 'secular';
208             }
209              
210 0         0 return undef;
211             }
212              
213             =head2 _buildTradition
214              
215             Private method to determine the Sunday lectionary tradition of the daily lectionary selected. Used to determine the liturgical week the day falls within.
216              
217             =cut
218              
219             sub _buildTradition {
220 68     68   159 my $lectionary = shift;
221              
222 68 50 66     258 if ( $lectionary eq 'acna-xian' || $lectionary eq 'acna-sec' ) {
223 68         2755 return 'acna';
224             }
225              
226 0         0 return undef;
227             }
228              
229             =head2 _parseLectDB
230              
231             Private method to open and parse the lectionary XML to be used by other methods to XPATH queries.
232              
233             =cut
234              
235             sub _parseLectDB {
236 81     81   179 my $lectionary = shift;
237              
238 81         333 my $parser = XML::LibXML->new();
239 81         1063 my $lectDB;
240              
241             try {
242 81     81   2225 my $data_location = dist_file( 'Date-Lectionary-Daily', $lectionary . '_lect_daily.xml' );
243 81         19561 $lectDB = $parser->parse_file($data_location);
244             }
245             catch {
246 0     0   0 carp "The readings database for the $lectionary daily lectionary could not be found or parsed.";
247 81         738 };
248              
249 81         276881 return $lectDB;
250             }
251              
252             =head2 _checkFixed
253              
254             Private method to determine if the day given is a fixed holiday rather than a standard day.
255              
256             =cut
257              
258             sub _checkFixed {
259 13     13   32 my $date = shift;
260 13         33 my $lectionary = shift;
261              
262 13         50 my $searchDate = $date->fullmonth . " " . $date->mday;
263              
264 13         195 my $lectDB = _parseLectDB($lectionary);
265              
266 13         38 my $fixed_xpath;
267              
268             try {
269 13     13   500 $fixed_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"Fixed Holy Days\"]/day[\@name=\"$searchDate\"]/lesson");
270             }
271             catch {
272 0     0   0 carp "Could not compile the XPath Expression for $searchDate in the $lectionary lectionary.";
273 13         105 };
274              
275 13 100       225 if ( $lectDB->exists($fixed_xpath) ) {
276 5         3458 return 1;
277             }
278              
279 8         6512 return 0;
280             }
281              
282             =head2 _buildReadingsLiturgical
283              
284             Private method that returns an ArrayRef of strings for the lectionary readings associated with the date according to the liturgical calendar.
285              
286             =cut
287              
288             sub _buildReadingsLiturgical {
289 58     58   160 my $weekName = shift;
290 58         128 my $weekDay = shift;
291 58         117 my $lectionary = shift;
292              
293 58         207 my $readings = _parseLectDB($lectionary);
294              
295 58         290 my $morn1_xpath;
296             my $morn2_xpath;
297 58         0 my $eve1_xpath;
298 58         0 my $eve2_xpath;
299             try {
300 58     58   2422 $morn1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"morning\" and \@order=\"1\"]");
301 58         878 $morn2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"morning\" and \@order=\"2\"]");
302 58         767 $eve1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"evening\" and \@order=\"1\"]");
303 58         740 $eve2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/week[\@name=\"$weekName\"]/day[\@name=\"$weekDay\"]/lesson[\@service=\"evening\" and \@order=\"2\"]");
304             }
305             catch {
306 0     0   0 carp "Could not compile the XPath Expression for $weekDay in $weekName in the $lectionary lectionary.";
307 58         637 };
308              
309 58         1121 my %readings;
310             try {
311 58     58   1385 %readings = (
312             morning => {
313             1 => $readings->find($morn1_xpath)->string_value(),
314             2 => $readings->find($morn2_xpath)->string_value()
315             },
316             evening => {
317             1 => $readings->find($eve1_xpath)->string_value(),
318             2 => $readings->find($eve2_xpath)->string_value()
319             }
320             );
321             }
322             catch {
323 0     0   0 carp "The readings for $weekDay in $weekName in the $lectionary lectionary could not be found.";
324 58         456 };
325              
326 58         72475 return \%readings;
327             }
328              
329             =head2 _buildReadingsSecular
330              
331             Private method that returns an ArrayRef of strings for the lectionary readings associated with the date according to the secular calendar.
332              
333             =cut
334              
335             sub _buildReadingsSecular {
336 10     10   22 my $weekName = shift;
337 10         18 my $date = shift;
338 10         19 my $lectionary = shift;
339              
340 10         27 my $readings = _parseLectDB($lectionary);
341              
342 10         53 my $seekDate = substr( $date->ymd, 5, 5 );
343              
344 10         241 my $morn1_xpath;
345             my $morn2_xpath;
346 10         0 my $eve1_xpath;
347 10         0 my $eve2_xpath;
348             try {
349 10     10   346 $morn1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"morning\" and \@order=\"1\"]");
350 10         96 $morn2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"morning\" and \@order=\"2\"]");
351 10         103 $eve1_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"evening\" and \@order=\"1\"]");
352 10         100 $eve2_xpath = XML::LibXML::XPathExpression->new("/daily-lectionary/day[\@date=\"$seekDate\"]/lesson[\@service=\"evening\" and \@order=\"2\"]");
353             }
354             catch {
355 0     0   0 carp "Could not compile the XPath Expression for $seekDate in $weekName in the $lectionary lectionary.";
356 10         85 };
357              
358 10         169 my %readings;
359             try {
360 10     10   201 %readings = (
361             morning => {
362             1 => $readings->find($morn1_xpath)->string_value(),
363             2 => $readings->find($morn2_xpath)->string_value()
364             },
365             evening => {
366             1 => $readings->find($eve1_xpath)->string_value(),
367             2 => $readings->find($eve2_xpath)->string_value()
368             }
369             );
370             }
371             catch {
372 0     0   0 carp "The readings for $seekDate in $weekName in the $lectionary lectionary could not be found.";
373 10         68 };
374              
375 10         18330 return \%readings;
376             }
377              
378             =head1 AUTHOR
379              
380             Michael Wayne Arnold, C<< <michael at rnold.info> >>
381              
382             =head1 BUGS
383              
384             =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>
385              
386             =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>
387              
388             Please report any bugs or feature requests to C<bug-date-lectionary-daily at rt.cpan.org>, or through
389             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Date-Lectionary-Daily>. I will be notified, and then you'll
390             automatically be notified of progress on your bug as I make changes.
391              
392             =head1 SUPPORT
393              
394             You can find documentation for this module with the perldoc command.
395              
396             perldoc Date::Lectionary::Daily
397              
398              
399             You can also look for information at:
400              
401             =over 4
402              
403             =item * RT: CPAN's request tracker (report bugs here)
404              
405             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Date-Lectionary-Daily>
406              
407             =item * AnnoCPAN: Annotated CPAN documentation
408              
409             L<http://annocpan.org/dist/Date-Lectionary-Daily>
410              
411             =item * CPAN Ratings
412              
413             L<http://cpanratings.perl.org/d/Date-Lectionary-Daily>
414              
415             =item * Search CPAN
416              
417             L<http://search.cpan.org/dist/Date-Lectionary-Daily/>
418              
419             =back
420              
421              
422             =head1 ACKNOWLEDGEMENTS
423              
424             Many thanks to my beautiful wife, Jennifer, my amazing daughter, Rosemary, and my sweet son, Oliver. But, above all, SOLI DEO GLORIA!
425              
426             =head1 LICENSE
427              
428             Copyright 2017-2020 MICHAEL WAYNE ARNOLD
429              
430             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
431              
432             1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
433              
434             2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
435              
436             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
437              
438              
439             =cut
440              
441             __PACKAGE__->meta->make_immutable;
442              
443             1; # End of Date::Lectionary::Daily