File Coverage

blib/lib/DateTime/Event/Easter.pm
Criterion Covered Total %
statement 243 245 99.1
branch 144 148 97.3
condition 109 112 97.3
subroutine 32 33 96.9
pod 19 19 100.0
total 547 557 98.2


line stmt bran cond sub pod time code
1             # -*- encoding: utf-8; indent-tabs-mode: nil -*-
2             #
3             # Perl DateTime extension for computing the dates for Easter and related feasts
4             # Copyright © 2003-2004, 2015, 2019 Rick Measham and Jean Forget, all rights reserved
5             #
6             # See the license in the embedded documentation below.
7             #
8             package DateTime::Event::Easter;
9              
10 20     20   2166692 use utf8;
  20         398  
  20         99  
11 20     20   14286 use DateTime;
  20         8317560  
  20         818  
12 20     20   11141 use DateTime::Set;
  20         789182  
  20         553  
13 20     20   166 use Carp;
  20         39  
  20         1202  
14 20     20   141 use Params::Validate qw( validate SCALAR BOOLEAN OBJECT );
  20         41  
  20         962  
15              
16 20     20   100 use strict;
  20         42  
  20         344  
17 20     20   87 use warnings;
  20         38  
  20         630  
18 20         63676 use vars qw(
19             $VERSION @ISA @EXPORT @EXPORT_OK
20 20     20   95 );
  20         36  
21              
22             require Exporter;
23              
24             @ISA = qw(Exporter);
25              
26             @EXPORT_OK = qw(easter golden_number western_epact western_sunday_letter western_sunday_number
27             eastern_epact eastern_sunday_letter eastern_sunday_number);
28             $VERSION = '1.09';
29              
30             sub new {
31 54     54 1 34592 my $class = shift;
32 54         1590 my %args = validate( @_,
33             { easter => { type => SCALAR, default => 'western', optional => 1, regex => qr/^(western|eastern)$/i },
34             day => { type => SCALAR, default => 'sunday' , optional => 1 },
35             as => { type => SCALAR, default => 'point' , optional => 1 },
36             }
37             );
38            
39 54         602 my %self;
40             my $offset;
41 54 100       705 if ($args{day} =~ /^fat/i) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
42 1         3 $offset = -47;
43             }
44             elsif ($args{day} =~ /^ash/i) {
45             # First day of lent. Lent lasts for 40 days, excluding sundays.
46             # This translates to a 46-day duration, including sundays.
47 1         3 $offset = -46;
48             }
49             elsif ($args{day} =~ /^ascension/i) {
50 1         2 $offset = 39;
51             }
52             elsif ($args{day} =~ /^pentecost/i) {
53 1         2 $offset = 49;
54             }
55             elsif ($args{day} =~ /^trinity/i) {
56 1         3 $offset = 56;
57             }
58             elsif ($args{day} =~ /^palm/i) {
59 3         5 $offset = -7;
60             } elsif ($args{day} =~ /saturday/i) {
61 3         6 $offset = -1;
62             } elsif ($args{day} =~ /friday/i) {
63 3         6 $offset = -2;
64             } elsif ($args{day} =~ /thursday/i) {
65 3         6 $offset = -3;
66             } elsif ($args{day} =~ /^\-?\d+$/i) {
67 5         11 $offset = $args{day};
68             } else {
69 32         63 $offset = 0;
70             }
71 54         278 $self{offset} = DateTime::Duration->new(days=>$offset);
72 54         5691 $self{easter} = lc $args{easter};
73 54         242 $self{day} = $args{day};
74            
75 54 100       179 if ($self{easter} eq 'eastern') {
76 8         2665 require DateTime::Calendar::Julian;
77             }
78              
79             # Set to return points or spans
80 54 100       3805 die("Argument 'as' must be 'point' or 'span'.") unless $args{as} =~ /^(point|span)s?$/i;
81 53         169 $self{as} = lc $1;
82              
83 53         206 return bless \%self, $class;
84             }
85              
86             sub following {
87 378     378 1 282839 my $self = shift;
88 378         506 my $dt = shift;
89 378 100 100     4777 croak ("Dates need to be datetime objects")
      100        
      100        
      100        
      100        
90             unless defined($dt)
91             && ref($dt) ne ''
92             && ref($dt) ne 'SCALAR'
93             && ref($dt) ne 'HASH'
94             && ref($dt) ne 'ARRAY'
95             && $dt->can('utc_rd_values');
96 364         807 my $result = $self->_following_point($dt);
97 364 100       1372 return ($self->{as} eq 'span')
98             ? _tospan($result)
99             : $result;
100             }
101              
102             sub _following_point {
103 589     589   1029 my ($self, $event_start_dt) = @_;
104              
105             # How does _following_point work with a long offset?
106             #
107             # Let us suppose that the $self objet has an offset of 1000 days (about 2 years
108             # and 9 months) and the event starting point $event_start_dt is 2018-10-03.
109             #
110             # The following point will be in late 2018 or early 2019, which corresponds to an
111             # Easter sunday in 2016. So we compute a starting point for Easter sunday by
112             # subtracting the offset from the event starting point. 2018-10-03 - 1000 days
113             # gives $easter_start_dt = 2016-01-07.
114             #
115             # Next, we extract the year value and we compute the Easter sunday date. This
116             # gives $easter_sunday = 2016-03-27.
117             #
118             # Then we add back the offset, which gives the final result 2018-12-22.
119             #
120             # Now, suppose that the event starting point $event_start_dt is 2019-01-10. The Easter sunday
121             # starting point $easter_start_dt is 2016-04-15.
122             #
123             # Extracting the year value gives 2016, for an Easter sunday $easter_sunday = 2016-03-27, which is
124             # before the Easter starting point 2016-04-15. So we try the next year, 2017,
125             # for an Easter sunday $easter_sunday = 2017-04-16 and an event 1000 days later $event = 2020-01-11.
126             #
127             # Lastly, suppose that the event starting point $event_start_dt is 2019-09-01. The Easter sunday
128             # starting point $easter_start_dt is 2016-12-05. Since Easter cannot occur after 04-25 in any
129             # year, so we do not bother to compute Easter for 2016, we directly compute the 2017 Easter sunday.
130             # As above, we obtain an Easter sunday $easter_sunday = 2017-04-16 and an event 1000 days later
131             # $event = 2020-01-11.
132              
133 589         1117 my $class = ref($event_start_dt);
134 589 100 100     2030 if ($self->{easter} eq 'eastern' && $class ne 'DateTime::Calendar::Julian') {
    100          
135 319         824 $event_start_dt = DateTime::Calendar::Julian->from_object(object => $event_start_dt);
136             }
137             elsif ($class ne 'DateTime') {
138 11         32 $event_start_dt = DateTime->from_object(object => $event_start_dt);
139             }
140              
141 589         173666 my $easter_start_dt = $event_start_dt - $self->{offset};
142 589         158426 my $start_mmdd = $easter_start_dt->strftime("%m-%d");
143 589         20782 my $latest_mmdd = '04-25';
144 589         772 my $easter_sunday;
145 589 100       1341 if ($start_mmdd le $latest_mmdd) {
146 312         735 $easter_sunday = $self->_easter($easter_start_dt->year);
147             }
148 589 100 100     79965 if ($start_mmdd gt $latest_mmdd or $easter_sunday <= $easter_start_dt) {
149             # 2016-03-27 is not good for 2016-04-15, so let us switch to 2017-04-16
150             # or: 2016-03-27 has not been calculated for 2016-12-05, so let us choose 2017-04-16
151 564         19564 $easter_sunday = $self->_easter($easter_start_dt->year + 1);
152             }
153              
154 589         168954 my $event = $easter_sunday + $self->{offset};
155              
156 589 100       95697 $event = $class->from_object(object => $event) if (ref($event) ne $class);
157 589         148238 return $event;
158             }
159              
160             sub previous {
161 63     63 1 29733 my $self = shift;
162 63         89 my $dt = shift;
163 63 100 100     1592 croak ("Dates need to be datetime objects")
      100        
      100        
      100        
      100        
164             unless defined($dt)
165             && ref($dt) ne ''
166             && ref($dt) ne 'SCALAR'
167             && ref($dt) ne 'HASH'
168             && ref($dt) ne 'ARRAY'
169             && $dt->can('utc_rd_values');
170 49         145 my $result = $self->_previous_point($dt);
171 49 100       236 return ($self->{as} eq 'span')
172             ? _tospan($result)
173             : $result;
174             }
175              
176             sub _previous_point {
177 106     106   214 my ($self, $event_start_dt) = @_;
178              
179 106         186 my $class = ref($event_start_dt);
180 106 100 100     432 if ($self->{easter} eq 'eastern' && $class ne 'DateTime::Calendar::Julian') {
    100          
181 15         42 $event_start_dt = DateTime::Calendar::Julian->from_object(object => $event_start_dt);
182             }
183             elsif ($class ne 'DateTime') {
184 11         31 $event_start_dt = DateTime->from_object(object => $event_start_dt);
185             }
186              
187 106         27876 my $easter_start_dt = $event_start_dt - $self->{offset};
188 106         63521 my $start_mmdd = $easter_start_dt->strftime("%m-%d");
189 106         3954 my $earliest_mmdd = '03-21';
190 106         149 my $easter_sunday;
191 106 100       827 if ($start_mmdd ge $earliest_mmdd) {
192 101         249 $easter_sunday = $self->_easter($easter_start_dt->year);
193             }
194 106 100 100     24823 if ($start_mmdd lt $earliest_mmdd or $easter_sunday >= $easter_start_dt) {
195 67         4118 $easter_sunday = $self->_easter($easter_start_dt->year - 1);
196             }
197              
198 106         18687 my $event = $easter_sunday + $self->{offset};
199              
200 106 100       50445 $event = $class->from_object(object => $event) if (ref($event) ne $class);
201 106         12403 return $event;
202             }
203              
204             sub closest {
205 26     26 1 18358 my ($self, $dt) = @_;
206 26 100 100     1205 croak ("Dates need to be datetime objects")
      100        
      100        
      100        
      100        
207             unless defined($dt)
208             && ref($dt) ne ''
209             && ref($dt) ne 'SCALAR'
210             && ref($dt) ne 'HASH'
211             && ref($dt) ne 'ARRAY'
212             && $dt->can('utc_rd_values');
213              
214 12         27 my $class = ref($dt);
215              
216 12 100       32 if ($self->is($dt)) {
217 4         106 my $easter = $dt->clone->truncate(to => 'day');
218             # I do not see how $easter can be anything else than a $class,
219             # so the conversion below should be unnecessary.
220             # Yet, "if it ain't broke, don't fix it". So it remains.
221 4 50       1141 $easter = $class->from_object(object => $easter) if (ref($easter) ne $class);
222 4 100       22 return ($self->{as} eq 'span')
223             ? _tospan($easter)
224             : $easter;
225             }
226 8         202 my $following_easter = $self->_following_point($dt);
227 8         24 my $following_delta = $following_easter - $dt;
228 8         2100 my $previous_easter = $self->_previous_point($dt);
229            
230 8 100       22 my $easter = ($previous_easter + $following_delta < $dt)
231             ? $following_easter
232             : $previous_easter;
233             # Same remark as above.
234 8 50       7453 $easter = $class->from_object(object => $easter) if (ref($easter) ne $class);
235 8 100       73 return ($self->{as} eq 'span')
236             ? _tospan($easter)
237             : $easter;
238             }
239              
240             sub is {
241 36     36 1 10817 my ($self, $dt) = @_;
242 36 100 100     1276 croak ("Dates need to be datetime objects")
      100        
      100        
      100        
      100        
243             unless defined($dt)
244             && ref($dt) ne ''
245             && ref($dt) ne 'SCALAR'
246             && ref($dt) ne 'HASH'
247             && ref($dt) ne 'ARRAY'
248             && $dt->can('utc_rd_values');
249              
250 22         45 my $class = ref($dt);
251 22 100 100     76 if ($self->{easter} eq 'western' && $class ne 'DateTime') {
252 2         7 $dt = DateTime->from_object(object => $dt);
253             }
254 22 100 100     993 if ($self->{easter} eq 'eastern' && $class ne 'DateTime::Calendar::Julian') {
255 6         18 $dt = DateTime::Calendar::Julian->from_object(object => $dt)
256             }
257              
258 22         3100 my $easter_start = $dt - $self->{offset};
259 22         4986 my $easter_this_year = $self->_easter($easter_start->year) + $self->{offset};
260              
261 22 100       8317 return ($easter_this_year->ymd eq $dt->ymd) ? 1 : 0;
262             }
263              
264             sub as_list {
265 7     7 1 2216 my $self = shift;
266 7         164 my %args = validate( @_,
267             { from => { type => OBJECT },
268             to => { type => OBJECT },
269             inclusive => { type => SCALAR, default => 0 },
270             }
271             );
272            
273             # Make sure our args are in the right order
274 7         55 ($args{from}, $args{to}) = sort ($args{from}, $args{to});
275            
276 7         620 my @set = ();
277            
278 7 100       22 if ($args{inclusive}) {
279 3 50       13 if ($self->is($args{from})) {
280             push @set, ($self->{as} eq 'span')
281             ? _tospan($args{from})
282 3 100       96 : $args{from};
283             }
284 3 50       3584 if ($self->is($args{to})) {
285             push @set, ($self->{as} eq 'span')
286             ? _tospan($args{to})
287 3 100       91 : $args{to};
288             }
289             }
290            
291 7         3589 my $checkdate = $args{from};
292              
293 7         22 while ($checkdate < $args{to}) {
294 46         4933 my $check_obj = $self->following($checkdate);
295 46 100       28557 $checkdate = ($self->{as} eq 'span')
296             ? $check_obj->start
297             : $check_obj;
298 46 100       647 push(@set, $check_obj) if ($checkdate < $args{to});
299             }
300            
301             return ($self->{as} eq 'span')
302 7 100       856 ? sort { $a->start cmp $b->start} @set
  13         854  
303             : sort @set;
304             }
305              
306             sub as_old_set {
307 0     0 1 0 my $self = shift;
308 0         0 return DateTime::Set->from_datetimes( dates => [ $self->as_list(@_) ] );
309             }
310             sub as_set {
311 27     27 1 80664 my $self = shift;
312 27         97 my %args = @_;
313 27         61 my %args_1 = @_;
314 27 100 66     116 if (exists $args{inclusive}) {
    100          
315             croak("You must specify both a 'from' and a 'to' datetime")
316             unless ref($args{to}) =~ /DateTime/
317 10 100 100     439 and ref($args{from}) =~ /DateTime/;
318 6 100       20 if ($self->{as} eq 'point') {
319 4 100       16 if ($args{inclusive}) {
320 3         9 $args{start} = delete $args{from};
321 3         9 $args{end} = delete $args{to};
322             } else {
323 1         3 $args{after} = delete $args{from};
324 1         3 $args{before} = delete $args{to};
325             }
326 4         19 delete $args{inclusive};
327             }
328             }
329             elsif (exists $args{from} or exists $args{to}) {
330             croak("You must specify both a 'from' and a 'to' datetime")
331             unless ref($args{to}) =~ /DateTime/
332 9 100 100     351 and ref($args{from}) =~ /DateTime/;
333 5 100       40 if ($self->{as} eq 'point') {
334 3         9 $args{after} = delete $args{from};
335 3         54 $args{before} = delete $args{to};
336             }
337             }
338 19 100       61 if ($self->{as} eq 'span') {
339             # Should be ... // 'easter sunday', but that would lose the compatibility with 5.6.1 and 5.8.x
340             # Anyhow, the only problem occurs with a "day => 0" parameter and actually, "day => 0"
341             # and "day => 'easter sunday' are synonymous. So the problem is not a problem.
342             my $easter_point = DateTime::Event::Easter->new(day => $self->{day} || 'easter sunday'
343 7   50     45 , easter => $self->{easter} || 'western'
      50        
344             , as => 'point');
345 7         32 my $set_of_points = $easter_point->as_set(%args_1);
346 7         750 return DateTime::SpanSet->from_set_and_duration(set => $set_of_points, hours => 24);
347             }
348             else {
349             return DateTime::Set->from_recurrence(
350 293 100   293   103068 next => sub { return $_[0] if $_[0]->is_infinite; $self->_following_point( $_[0] ) },
  217         939  
351 123 100   123   76786 previous => sub { return $_[0] if $_[0]->is_infinite; $self->_previous_point( $_[0] ) },
  49         251  
352 12         125 %args
353             );
354             }
355             }
356              
357             sub as_span {
358 2     2 1 758 my $self = shift;
359 2         5 $self->{as} = 'span';
360 2         4 return $self;
361             }
362              
363             sub as_point {
364 1     1 1 5562 my $self = shift;
365 1         2 $self->{as} = 'point';
366 1         3 return $self;
367             }
368              
369             sub _tospan {
370 23     23   91 return DateTime::Span->from_datetime_and_duration(
371             start => $_[0],
372             hours => 24,
373             );
374             }
375              
376             sub _easter {
377 1066     1066   5258 my $self = shift;
378 1066         1294 my $year = shift;
379 1066 100       2725 return ($self->{easter} eq 'eastern')
380             ? eastern_easter($year)
381             : western_easter($year);
382             }
383              
384             sub western_easter {
385 824     824 1 155148 my ($year) = @_;
386 824   100     1513 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
387 824 100       3643 croak "Year value '$year' should be numeric."
388             if $year !~ /^\-?\d+$/;
389              
390 820         1453 my $epact_1 = western_epact($year);
391 820         1254 my $epact_2 = $epact_1;
392 820 100       1852 if ($epact_1 eq '25*') {
    100          
393             # adjust 25* → 26
394 21         106 $epact_2 = 26;
395             }
396             elsif ($epact_1 == 24) {
397             # adjust 24 → 25
398 61         84 $epact_2 = 25;
399             }
400 820 100       1360 if ($epact_2 > 24) {
401 235         307 $epact_2 -= 30;
402             }
403 820         1345 my $day = 45 - $epact_2 + ($epact_2 + western_sunday_number($year) + 1) % 7;
404 820         1127 my $month = 3;
405 820 100       1310 if ($day > 31) {
406 629         755 $day -= 31;
407 629         802 $month = 4;
408             }
409            
410 820         2054 return DateTime->new(year => $year, month => $month, day => $day);
411             }
412             *easter = \&western_easter; #alias so people can call 'easter($year)' externally
413              
414             sub eastern_easter {
415 426     426 1 1835 my $year = shift;
416 426   100     778 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
417 426 100       1663 croak "Year value '$year' should be numeric."
418             if $year !~ /^\-?\d+$/;
419            
420 424         759 my $epact_1 = eastern_epact($year);
421 424         583 my $epact_2;
422              
423 424         544 $epact_2 = $epact_1;
424 424 100       679 if ($epact_2 >= 24) {
425 61         88 $epact_2 -= 30;
426             }
427              
428 424         790 my $day = 45 - $epact_2 + ($epact_2 + eastern_sunday_number($year) + 1) % 7;
429 424         637 my $month = 3;
430 424 100       668 if ($day > 31) {
431 324         417 $day -= 31;
432 324         454 $month = 4;
433             }
434              
435 424         1056 return DateTime::Calendar::Julian->new(year => $year, month => $month, day => $day);
436             }
437              
438             sub golden_number {
439 1290     1290 1 9819 my ($year) = @_;
440 1290   100     2030 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
441 1290 100       2950 croak "Year value '$year' should be numeric."
442             if $year !~ /^\-?\d+$/;
443 1288         2909 return $year % 19 + 1;
444             }
445              
446             #
447             # La saga des calendriers page 145 (and page 142)
448             #
449             sub western_epact {
450 826     826 1 3448 my ($year) = @_;
451 826   100     1360 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
452 826 100       2033 croak "Year value '$year' should be numeric."
453             if $year !~ /^\-?\d+$/;
454             # centu is not the century, but nearly so
455 824         1835 my $centu = int($year / 100);
456 824         1385 my $metemptose = $centu - int($centu / 4);
457 824         1377 my $proemptose = int((8 * $centu + 13) / 25);
458 824         1332 my $epact = (11 * golden_number($year) - 3 - $metemptose + $proemptose) % 30;
459 824 100 100     1894 if ($epact == 25 && golden_number($year) > 11) {
460 22         42 $epact = '25*';
461             }
462 824         1292 return $epact;
463             }
464              
465             #
466             # La saga des calendriers page 146
467             #
468             sub western_sunday_letter {
469 6     6 1 1399 my ($year) = @_;
470 6   100     22 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
471 6 100       412 croak "Year value '$year' should be numeric."
472             if $year !~ /^\-?\d+$/;
473 4         8 my $prec = $year - 1;
474 4         15 my $n1 = 7 - ($year + int($prec / 4) - int($prec / 100) + int($prec / 400) + 6) % 7;
475 4         10 my $n2 = 7 - ($year + int($year / 4) - int($year / 100) + int($year / 400) + 6) % 7;
476 4         5 my $ref = '#ABCDEFG';
477 4         8 my $c1 = substr($ref, $n1, 1);
478 4         5 my $c2 = substr($ref, $n2, 1);
479 4 100       10 if ($c1 eq $c2) {
480 2         9 return $c1;
481             }
482             else {
483 2         9 return "$c1$c2";
484             }
485             }
486             sub western_sunday_number {
487 826     826 1 2784 my ($year) = @_;
488 826   100     1291 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
489 826 100       2205 croak "Year value '$year' should be numeric."
490             if $year !~ /^\-?\d+$/;
491 824         2125 return 7 - ($year + int($year / 4) - int($year / 100) + int($year / 400) + 6) % 7;
492             }
493              
494             #
495             # La saga des calendriers page 138
496             # Erratum for page 136 : for a golden number 19, epact is 26, not 6
497             #
498             sub eastern_epact {
499 430     430 1 7210 my ($year) = @_;
500 430   100     736 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
501 430 100       1183 croak "Year value '$year' should be numeric."
502             if $year !~ /^\-?\d+$/;
503 428         656 return (11 * golden_number($year) + 27) % 30;
504             }
505              
506             #
507             # La saga des calendriers pages 137-138
508             #
509             sub eastern_sunday_letter {
510 6     6 1 3862 my ($year) = @_;
511 6   100     23 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
512 6 100       863 croak "Year value '$year' should be numeric."
513             if $year !~ /^\-?\d+$/;
514 4         7 my $prec = $year - 1;
515 4         10 my $n1 = 7 - ($year + int($prec / 4) - 3) % 7;
516 4         9 my $n2 = 7 - ($year + int($year / 4) - 3) % 7;
517 4         5 my $ref = '#ABCDEFG';
518 4         9 my $c1 = substr($ref, $n1, 1);
519 4         5 my $c2 = substr($ref, $n2, 1);
520 4 100       9 if ($c1 eq $c2) {
521 2         9 return $c1;
522             }
523             else {
524 2         10 return "$c1$c2";
525             }
526             }
527             sub eastern_sunday_number {
528 430     430 1 7840 my ($year) = @_;
529 430   100     702 $year ||= ''; # should be //= in 5.10.0 or later, but we keep the compatibility with 5.6.1
530 430 100       1225 croak "Year value '$year' should be numeric."
531             if $year !~ /^\-?\d+$/;
532 428         1081 return 7 - ($year + int($year / 4) - 3) % 7;
533             }
534              
535              
536             # Ending a module with an unspecified number, which could be zero, is wrong.
537             # Therefore the custom of ending a module with a boring "1".
538             # Instead of that, end it with some verse.
539             q{
540             Il reviendra z-à Pâques, mironton mironton mirontaine,
541             Il reviendra z-à Pâques
542             Ou à la Trinité.
543             Ou à la Trinité...
544             };
545             __END__
546              
547             =encoding utf-8
548              
549             =head1 NAME
550              
551             DateTime::Event::Easter - Returns Easter events for DateTime objects
552              
553             =head1 SYNOPSIS
554              
555             use DateTime::Event::Easter;
556            
557             $dt = DateTime->new( year => 2002,
558             month => 3,
559             day => 31,
560             );
561            
562            
563             $easter_sunday = DateTime::Event::Easter->new();
564              
565             $previous_easter_sunday = $easter_sunday->previous($dt);
566             # Sun, 15 Apr 2001 00:00:00 UTC
567            
568             $following_easter_sunday = $easter_sunday->following($dt);
569             # Sun, 20 Apr 2003 00:00:00 UTC
570            
571             $closest_easter_sunday = $easter_sunday->closest($dt);
572             # Sun, 31 Mar 2002 00:00:00 UTC
573            
574             $is_easter_sunday = $easter_sunday->is($dt);
575             # 1
576            
577             $palm_sunday = DateTime::Event::Easter->new(day => 'Palm Sunday');
578              
579              
580             $dt2 = DateTime->new( year => 2006,
581             month => 4,
582             day => 30,
583             );
584            
585             $set = $palm_sunday->as_set (from => $dt, to => $dt2, inclusive => 1);
586             @list = $palm_sunday->as_list(from => $dt, to => $dt2, inclusive => 1);
587             # Sun, 13 Apr 2003 00:00:00 UTC
588             # Sun, 04 Apr 2004 00:00:00 UTC
589             # Sun, 20 Mar 2005 00:00:00 UTC
590             # Sun, 09 Apr 2006 00:00:00 UTC
591            
592             $datetime_set = $palm_sunday->as_set;
593             # A set of every Palm Sunday ever. See DateTime::Set for more information.
594              
595             =head1 DESCRIPTION
596              
597             The DateTime::Event::Easter module returns Easter events for DateTime
598             objects. From a given datetime, it can tell you the previous, the
599             following and the closest Easter event. The 'is' method will tell you if
600             the given DateTime is an Easter Event.
601              
602             Easter Events can be Fat Tuesday, Ash Wednesday, Palm Sunday, Maundy
603             Thursday, Good Friday, Black Saturday, Easter Sunday, Ascension,
604             Pentecost and Trinity Sunday. If that's not enough, the module will
605             also accept an offset so you can get the date for Quasimodo (the next
606             sunday after Easter Sunday) by passing 7.
607              
608             =head1 BACKGROUND
609              
610             Easter Sunday is the Sunday following the first full moon on or
611             following the Official Vernal Equinox. The Official Vernal Equinox is
612             March 21st. Easter Sunday is never on the full moon. Thus the earliest
613             Easter can be is March 22nd.
614              
615             In the orthodox world, although they now use the Gregorian Calendar
616             rather than the Julian, they still take the first full moon on or after the
617             Julian March 21st. As the Julian calendar is slowly getting further and
618             further out of sync with the Gregorian, the first full moon after this
619             date can be a completely different one than for the western Easter. This
620             is why the Orthodox churches celebrate Easter later than western
621             churches.
622              
623             =head1 CONSTRUCTOR
624              
625             =head2 C<new> constructor
626              
627             This class accepts the following options to its C<new> constructor:
628              
629             =over 4
630              
631             =item * easter => ([western]|eastern)
632              
633             DateTime::Event::Easter understands two calculations for Easter. For
634             simplicity we've called them 'western' and 'eastern'.
635              
636             Western Easter is the day celebrated by the Catholic and Protestant
637             churches. It falls on the first Sunday after the first Full
638             Moon on or after March 21st.
639              
640             Eastern Easter, as celebrated by the Eastern Orthodox Churches similarly
641             falls on the first Sunday after the first Full Moon on or after
642             March 21st. However Eastern Easter uses March 21st in the Julian
643             Calendar.
644              
645             By default this module uses the Western Easter. Even if you pass a
646             Julian DateTime to the module, you'll get back Western Easter unless you
647             specifically ask for Eastern.
648              
649             If this parameter is not supplied, the western Easter will be used.
650              
651             =item * day => ([Easter Sunday]|Palm Sunday|Maundy Thursday|Good Friday|Black
652             Saturday|Fat Tuesday|Ash Wednesday|Ascension|Pentecost|Trinity Sunday|I<n>)
653              
654             When constructed with a day parameter, the method can return associated
655             Easter days other than Easter Sunday. The constructor also allows an
656             integer to be passed here as an offset. For example, Maundy Thursday is
657             the same as an offset of -3 (Three days before Easter Sunday)
658              
659             When constructed without a day parameter, the method uses the date for
660             Easter Sunday (which is the churches' official day for 'Easter', think
661             of it a 'Easter Day' if you want)
662              
663             This parameter also allows the following abreviations: day =>
664             ([Sunday]|Palm|Thursday|Friday|Saturday|Fat|Ash|Ascension|Pentecost|Trinity)
665              
666             =item * as => ([point]|span)
667              
668             By default, all returns are single points in time. Namely they are the
669             moment of midnight for the day in question. If you want Easter 2003 then
670             you actually get back midnight of April 20th 2003. If you specify
671             C<< as => 'span' >> in your constructor, you'll now receive 24 hour spans
672             rather than moments (or 'points'). I<See also the C<as_span> and C<as_point>
673             methods below>
674              
675             =back
676              
677             =head1 METHODS
678              
679             For all these methods, unless otherwise noted, C<$dt> is a plain vanilla
680             DateTime object or a DateTime object from any DateTime::Calendar module
681             that can handle calls to C<from_object> and C<utc_rd_values> (which should be
682             all of them, but there's nothing stopping someone making a bad egg).
683              
684             This class offers the following methods.
685              
686             =over 4
687              
688             =item * following($dt)
689              
690             Returns the DateTime object for the Easter Event after C<$dt>. This will
691             not return C<$dt>.
692              
693             =item * previous($dt)
694              
695             Returns the DateTime object for the Easter Event before C<$dt>. This will
696             not return C<$dt>.
697              
698             =item * closest($dt)
699              
700             Returns the DateTime object for the Easter Event closest to C<$dt>. This
701             will return midnight of C<$dt> if C<$dt> is the Easter Event.
702              
703             =item * is($dt)
704              
705             Return true (1) if C<$dt> is the Easter Event, otherwise returns false
706             (0)
707              
708             =item * as_list(from => $dt, to => $dt2, inclusive => I<([0]|1)>)
709              
710             Returns a list of Easter Events between I<from> and I<to>.
711              
712             If the optional I<inclusive> parameter is true (non-zero), the to and
713             from dates will be included if they are the Easter Event.
714              
715             If you do not include an I<inclusive> parameter, we assume you do not
716             want to include these dates (the same behaviour as supplying a false
717             value)
718              
719              
720             =item * as_set()
721              
722             Returns a DateTime::Set of Easter Events.
723              
724             In the past this method used the same syntax as 'as_list' above.
725             However we now allow both the above syntax as well as the full options
726             allowable when creating sets with C<DateTime::Set>. This means you can
727             call C<< $datetime_set = $palm_sunday->as_set; >> and it will return a
728             C<DateTime::Set> of all Palm Sundays. See L<DateTime::Set> for more
729             information.
730              
731              
732             =item * as_span()
733              
734             This method switches output to spans rather than points. See the 'as' attribute
735             of the constructor for more information. The method returns the object for easy
736             chaining.
737              
738             =item * as_point()
739              
740             This method switches output to points rather than spans. See the 'as' attribute
741             of the constructor for more information. The method returns the object for easy
742             chaining.
743              
744             =item * as_old_set()
745              
746             Deprecated method.
747              
748             In the next version (1.10) or in October 2021 (two years after the
749             v1.08 initial announcement), whichever comes last, this method will
750             emit a warning. And within another two years / one version, this
751             method will be removed.
752              
753             =back
754              
755             =head1 SUBROUTINES
756              
757             The module provides a few subroutines giving the elements used to
758             compute the Easter date.
759              
760             These elements can be found in various sources, including what is
761             known in France as I<l'Almanach du Facteur> (the postman's almanach).
762             These values are printed at the bottom of the February frame, which is
763             a convenient way to ensure this frame has the same height as the
764             frames for 31-day months.
765              
766             These subroutines are not exported by default.
767              
768             =over 4
769              
770             =item * golden_number($year)
771              
772             Gives the position of the year in the Metonic cycle. This is a 1..19
773             number.
774              
775             This subroutine applies to both western and eastern computs.
776              
777             =item * western_epact($year)
778              
779             In the Gregorian comput, the epact is the age of the ecclesiastical
780             Moon on the 1st January of the given year. The C<western> part of the
781             subroutine name accounts for the fact that Gregorian and Julian
782             calendars do not use the same formula.
783              
784             The epact is a 0..29 number. The "0" value is shown as "*" in some
785             sources. This subroutine does not convert "0" to "*", the result is
786             always a pure number.
787              
788             Actually, the western epact is a little more than a number. As
789             explained by Paul Couderc (page 86) and Jean Lefort (page 142), there
790             is a special case for 25, which should be considered as two values,
791             "basic 25" and "alternate 25". "Basic 25" is printed as a plain number
792             C<25>, while "alternate 25" is printed in a way that distinguishes it
793             from the other numbers. Jean Lefort mentions C<XXV> or using italics
794             or bold digits, such as B<C<25>>. This module prints the "alternate
795             25" as "C<25*>".
796              
797             =item * eastern_epact($year)
798              
799             In the Julian comput, the epact is the age of the ecclesiastical Moon
800             on 22nd March. The C<eastern> part of the subroutine name accounts for
801             the fact that Gregorian and Julian calendars do not use the same
802             formula.
803              
804             The epact is a 0..29 number. The "0" value is shown as "*" in some
805             sources. This subroutine does not convert "0" to "*", the result is
806             always a pure number. There is no other special case, for 25 as for
807             any other number.
808              
809             The formula given by Reingold and Dershowitz is a "shifted epact" and
810             gives different results from the values printed in Lefort's and
811             Couderc's books. The module follows Couderc and Lefort.
812              
813             =item * western_sunday_letter($year), eastern_sunday_letter($year)
814              
815             On normal years (that is, excluding leap years), the Sunday letter is
816             determined by tagging 1st January with "A", 2nd January with "B", and
817             so on and looking at the first sunday of the year. The letter found at
818             this sunday if the sunday letter for the year.
819              
820             The sunday letter governs all conversions from (mm, dd) to
821             day-of-week. For example, if the letter is "F", then 1st January, 12th
822             February, 2nd July and 1st October, among others, are tuesdays, while
823             6th January, 24th February, 14th July and 6th October are sundays.
824              
825             On leap years, there are two sunday letters. The first one is
826             determined as above, the second one is determined by tagging 2nd
827             January, not 1st, with "A". The first sunday letter governs all
828             conversions from (mm, dd) to day-of-week for January and February
829             only, while the second sunday letter governs the conversions from (mm,
830             dd) to day-of-week for March and after.
831              
832             So, if the sunday letters are "FE", 1st January and 12th February are
833             still tuesdays, but 2nd July and 1st October are wednesdays. At the
834             same time, 6th January and 24th February are still sundays, while 14th
835             July and 6th October are mondays.
836              
837             C<western_sunday_letter> applies only to Gregorian years, while
838             C<eastern_sunday_letter> applies only to Julian years.
839              
840             =item * western_sunday_number($year), eastern_sunday_number($year)
841              
842             Letters (standalone or in pairs) are not convenient for numerical
843             calculations. So the I<xxx>C<_sunday_number> subroutine is used
844             instead of I<xxx>C<_sunday_letter>.
845              
846             In case of leap years, the I<xxx>C<_sunday_number> subroutine gives
847             the numerical value for the second sunday letter, because Easter never
848             falls in January or February.
849              
850             =item * easter($year)
851              
852             Given a Gregorian year, this subroutine will return a DateTime object
853             for Western Easter Sunday in that year.
854              
855             =item * western_easter($year)
856              
857             Given a Gregorian year, this subroutine will return a DateTime object
858             for Western Easter Sunday in that year. But unlike the previous
859             subroutine, C<western_easter> cannot be imported. You must use its
860             fully qualified name:
861              
862             my $date = DateTime::Event::Easter::western_easter($year);
863              
864             =item * eastern_easter($year)
865              
866             Given a Julian year, this subroutine will return a DateTime ::
867             Calendar :: Julian object for Eastern Easter Sunday in that year. And
868             like C<western_easter>, C<eastern_easter> cannot be imported. You must
869             use its fully qualified name:
870              
871             my $date = DateTime::Event::Easter::eastern_easter($year);
872              
873             =back
874              
875             =head1 BUGS AND PROBLEMS FOR SPANS
876              
877             =head2 Inclusion and exclusion of C<from> and C<to> dates in lists and sets
878              
879             If you build a list or a set of spans and if the C<from> or C<to> limits
880             coincide with the requested Easter event, the result may be different
881             from what you expect. For example, you ask for Easter sundays between
882             2017-04-16T21:43:00 and 2020-04-12T12:34:00.
883              
884             The inclusive list or set will be:
885              
886             2017-04-16T00:00:00 to 2017-04-16T23:59:59
887             2018-04-01T00:00:00 to 2018-04-01T23:59:59
888             2019-04-21T00:00:00 to 2019-04-21T23:59:59
889             2020-04-12T00:00:00 to 2020-04-12T23:59:59
890              
891             and not:
892              
893             2017-04-16T21:43:00 to 2017-04-16T23:59:59
894             2018-04-01T00:00:00 to 2018-04-01T23:59:59
895             2019-04-21T00:00:00 to 2019-04-21T23:59:59
896             2020-04-12T00:00:00 to 2020-04-12T12:34:00
897              
898             The exclusive list or set will be:
899              
900             2018-04-01T00:00:00 to 2018-04-01T23:59:59
901             2019-04-21T00:00:00 to 2019-04-21T23:59:59
902              
903             and not:
904              
905             2017-04-16T21:43:01 to 2017-04-16T23:59:59
906             2018-04-01T00:00:00 to 2018-04-01T23:59:59
907             2019-04-21T00:00:00 to 2019-04-21T23:59:59
908             2020-04-12T00:00:00 to 2020-04-12T12:35:59
909              
910             Remarks and patches welcome.
911              
912             Note for pedants: the hour C<21:43:01> should actually be
913             21 hours, 43 minutes, zero seconds and 1 nanosecond.
914             Likewise, all the times above ending with C<:59> include
915             999_999_999 nanoseconds.
916              
917             =head2 Interaction of spans with timezones
918              
919             It may happen that Palm sunday or Easter sunday coincide
920             with DST "spring forward" day (for Northern countries). I have not
921             checked what happens in this case for spans: a bit more than one day
922             for exactly 24 hours or exactly one day which gives 23 hours?
923             A similar question exists for DST "fall backward" day in the Southern
924             countries.
925              
926             Also, since you can use a numeric C<day> offset beyond the Trinity
927             sunday, you can reach the Northern "fall backwards" and the Southern
928             "spring forward" days, where the same problem will happen in reverse.
929              
930             =head1 THE SMALL PRINT
931              
932             =head2 REFERENCES
933              
934             =over 4
935              
936             =item * L<https://github.com/houseabsolute/DateTime.pm/wiki> - The official wiki
937             of the DateTime project
938              
939             =item * L<https://www.tondering.dk/claus/calendar.html> - Claus Tøndering's
940             calendar FAQ, especially the page L<https://www.tondering.dk/claus/cal/easter.php>.
941              
942             =item * I<Calendrical Calculations> (Third or Fourth Edition) by Nachum Dershowitz and
943             Edward M. Reingold, Cambridge University Press, see
944             L<http://www.calendarists.com>
945             or L<https://www.cambridge.org/us/academic/subjects/computer-science/computing-general-interest/calendrical-calculations-ultimate-edition-4th-edition?format=PB&isbn=9781107683167>,
946             ISBN 978-0-521-70238-6 for the third edition.
947              
948             =item * I<La saga des calendriers>, by Jean Lefort, published by I<Belin> (I<Pour la Science>), ISBN 2-90929-003-5
949             See L<https://www.belin-editeur.com/la-saga-des-calendriers>
950              
951             =item * I<Le Calendrier>, by Paul Couderc, published by I<Presses universitaires de France> (I<Que sais-je ?>), ISBN 2-13-036266-4
952             See L<https://catalogue.bnf.fr/ark:/12148/cb329699661>.
953              
954             =back
955              
956             =head2 SUPPORT
957              
958             Support for this module, and for all DateTime modules will be given
959             through the DateTime mailing list - datetime@perl.org.
960             See L<https://lists.perl.org/list/datetime.html>.
961              
962             Bugs should be reported through rt.cpan.org. See
963             L<https://rt.cpan.org/Public/Dist/Display.html?Name=DateTime-Event-Easter>.
964              
965             Or you can try to submit a pull request to
966             L<https://github.com/jforget/DateTime-Event-Easter>.
967              
968             =head2 AUTHOR
969              
970             Rick Measham <rickm@cpan.org>
971              
972             Co-maintainer Jean Forget <jforget@cpan.org>
973              
974             =head2 CREDITS
975              
976             Much help from the DateTime mailing list, especially from:
977              
978             B<Eugene van der Pijll> - who pointed out flaws causing errors on
979             gregorian years with no eastern easter (like 35000) and who came up with
980             a patch to make the module accept any calendar's DateTime object
981              
982             B<Dave Rolsky> - who picked nits, designed DateTime itself and leads the project
983              
984             B<Martin Hasch> - who pointed out the posibility of memory leak with an early beta
985              
986             B<Joe Orost> and B<Andreas König> - for RT tickets about the POD documentation
987              
988             B<Frank Wiegand> and B<Slaven Rezić> - for patches fixing the POD problems
989              
990             B<Tom Wyant> - for a constructive dialog about the relations between
991             L<DateTime::Calendar::Julian> (versions 0.101 and 0.102) and
992             L<DateTime::Event::Easter> (versions 1.08 and 1.09)
993              
994             B<Andreas König> (again) - for a message a long time ago (December
995             2010) in which he told me about his web site
996             L<http://analysis.cpantesters.org/> which was very useful nine years
997             later for debugging DT::E::Easter version 1.08.
998              
999             =head2 COPYRIGHT
1000              
1001             © Copyright 2003, 2004, 2015, 2019 Rick Measham and Jean Forget. All
1002             rights reserved. This program is free software; you can redistribute
1003             it and/or modify it under the same terms as Perl itself: GNU
1004             Public License version 1 or later and Perl Artistic License.
1005              
1006             The full text of the license can be found in the F<LICENSE> file
1007             included with this module or at
1008             L<https://dev.perl.org/licenses/artistic.html>
1009             and L<https://www.gnu.org/licenses/gpl-1.0.html>.
1010              
1011             Here is the summary of GPL:
1012              
1013             This program is free software; you can redistribute it and/or modify
1014             it under the terms of the GNU General Public License as published by
1015             the Free Software Foundation; either version 1, or (at your option)
1016             any later version.
1017              
1018             This program is distributed in the hope that it will be useful, but
1019             WITHOUT ANY WARRANTY; without even the implied warranty of
1020             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1021             General Public License for more details.
1022              
1023             You should have received a copy of the GNU General Public License
1024             along with this program; if not, see <https://www.gnu.org/licenses/> or
1025             write to the Free Software Foundation, Inc., L<https://fsf.org>.
1026              
1027             =head2 SEE ALSO
1028              
1029             L<DateTime>, L<DateTime::Calendar::Julian>, perl(1)
1030              
1031             L<https://metacpan.org/search?q=easter> which gives L<Date::Easter>, L<Date::Calc> and L<Date::Pcalc>
1032              
1033             L<https://github.com/houseabsolute/DateTime.pm/wiki>