File Coverage

blib/lib/Date/Holidays/UA.pm
Criterion Covered Total %
statement 122 122 100.0
branch 46 60 76.6
condition 14 16 87.5
subroutine 19 19 100.0
pod 7 7 100.0
total 208 224 92.8


line stmt bran cond sub pod time code
1             package Date::Holidays::UA;
2              
3 2     2   718705 use 5.006;
  2         19  
4 2     2   16 use strict;
  2         8  
  2         43  
5 2     2   13 use warnings;
  2         3  
  2         51  
6 2     2   12 use Carp;
  2         4  
  2         148  
7 2     2   1072 use DateTime;
  2         528436  
  2         74  
8 2     2   1506 use DateTime::Event::Easter;
  2         111342  
  2         127  
9              
10 2     2   20 use Exporter qw(import);
  2         6  
  2         346  
11              
12             our %EXPORT_TAGS = ( 'all' => [ qw(
13             is_holiday
14             is_ua_holiday
15             is_holiday_dt
16             holidays
17             ua_holidays
18             holidays_dt
19             ) ] );
20              
21             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
22              
23             use constant {
24 2         2887 DEFAULT_LANG => 'UA',
25             WEEKEND_MAP => {
26             6 => 1,
27             7 => 1
28             },
29             HOLIDAY_RULES => [
30             {name => 'New Year', local_name => 'Новий рік', month => 1, day => 1},
31             {name => 'Labour Day', local_name => 'День праці', month => 5, day => 1},
32             {name => 'Labour Day', local_name => 'День праці', month => 5, day => 2, end_year => 2018},
33             {name => 'Defender Of Ukraine day', local_name => 'День захисника України', month => 10, day => 14, start_year => 2015},
34             {name => 'Catholic Christmas day', local_name => 'Різдво Христове(католицьке)', month => 12, day => 25, start_year => 2017},
35             {name => 'Orthodox Christmas day', local_name => 'Різдво Христове', month => 1, day => 7},
36             {name => 'Women Day', local_name => 'Міжнародний жіночий день', month => 3, day => 8},
37             {name => 'Victory Day', local_name => 'День перемоги над нацизмом у Другій світовій війні', month => 5, day => 9},
38             {name => 'Constitution Day', local_name => 'День Конституції України', month => 6, day => 28},
39             {name => 'Independence Day', local_name => 'День незалежності України', month => 8, day => 24},
40             {name => 'Orthodox Easter Day', local_name => 'Великдень', is_easter_depend => 1, easter_offset_day => 0},
41             {name => 'Orthodox Pentecost Day', local_name => 'Трійця', is_easter_depend => 1, easter_offset_day => 49},
42             ]
43 2     2   25 };
  2         3  
44              
45             =head1 NAME
46              
47             Date::Holidays::UA - Holidays module for Ukraine
48              
49             =head1 VERSION
50              
51             Version 0.02
52              
53             =cut
54              
55             our $VERSION = '0.02';
56              
57              
58             =head1 SYNOPSIS
59              
60             # procedural approach
61              
62             use Date::Holidays::UA qw(:all);
63              
64             my ($year, $month, $day) = (localtime)[5, 4, 3];
65             $year += 1900;
66             $month += 1;
67              
68             print 'Holiday!' if is_holiday($year, $month, $day);
69              
70             my $calendar = holidays($year, {language => 'en'});
71             print $calendar->{'0824'};
72              
73              
74             # object-oriented approach
75              
76             use DateTime;
77             use Date::Holidays::UA;
78              
79             my $ua = Date::Holidays::UA->new({ language => 'en' });
80             print 'Holiday!' if $ua->is_holiday_dt(DateTime->today);
81              
82             my $calendar = $ua->holidays(DateTime->today->year);
83             print join("\n", value(%$calendar)); # list of holiday names for Ukraine
84              
85             =head1 SUBROUTINES/METHODS
86              
87             =head2 new()
88              
89             Create a new Date::Holidays::UA object. Parameters should be given as
90             a hashref of key-value pairs.
91              
92             my $ua = Date::Holidays::UA->new();
93              
94             my $ua = Date::Holidays::UA->new({
95             language => 'en'
96             });
97              
98             One parameters can be specified: B<language>.
99              
100             =cut
101              
102             sub new {
103 150     150 1 451 my $class = shift;
104 150   100     706 my $args_ref = shift || {};
105              
106 150 50       454 croak('Wrong language parameter') if (!ref($args_ref));
107              
108             return bless {
109 150   100     834 language => $args_ref->{language} || DEFAULT_LANG
110             }, $class;
111             }
112              
113             =head2 is_holiday()
114              
115             For a given year, month (1-12) and day (1-31), return 1 if the given
116             day is a holiday; 0 if not. When using procedural calling style, an
117             additional hashref of options can be specified.
118              
119             $holiday_p = is_holiday($year, $month, $day);
120              
121             $holiday_p = is_holiday($year, $month, $day, {
122             language => 'en'
123             });
124              
125             $holiday_p = $ua->is_holiday($year, $month, $day);
126              
127             =cut
128              
129             sub is_holiday {
130 198 100   198 1 35420 return (is_ua_holiday(@_) ? 1 : 0);
131             }
132              
133             =head2 is_holiday_dt()
134              
135             As is_holiday, but accepts a DateTime object in place of a numeric year,
136             month, and day.
137              
138             $holiday_p = is_holiday_dt($dt, {language => 'en'});
139              
140             $holiday_p = $ua->is_holiday_dt($dt);
141              
142             =cut
143              
144             sub is_holiday_dt {
145             my @args = map {
146 99 100   99 1 339 ref $_ eq 'DateTime' ? ($_->year, $_->month, $_->day) : $_
  165         775  
147             } @_;
148              
149 99         1539 return is_holiday(@args);
150             }
151              
152             =head2 is_ua_holiday()
153              
154             Similar to C<is_holiday>. Return the name of the holiday occurring on
155             the specified date if there is one; C<undef> if there isn't.
156              
157             print $ua->is_ua_holiday(2020, 1, 1); # "New Year"
158              
159             =cut
160              
161             sub is_ua_holiday {
162 330     330 1 618 my $self;
163 330 100       1010 $self = shift if (ref $_[0]);
164              
165 330         568 my $year = shift;
166 330         592 my $month = shift;
167 330         506 my $day = shift;
168 330         511 my $opt = shift;
169              
170 330         958 _assert_valid_date($year, $month, $day);
171              
172 330 100       100079 if (!defined($self)) {
173 132         511 $self = __PACKAGE__->new($opt);
174             }
175              
176 330         663 my $holiday_name = undef;
177 330         859 my $calendar = $self->_generate_calendar($year);
178              
179 330 50       532 for my $holiday(@{$calendar || []}) {
  330         1009  
180 2730         14587 my $holiday_dt = $holiday->{dt};
181              
182 2730 100 100     4900 if (($holiday_dt->month == $month) && ($holiday_dt->day == $day)) {
183 290         3333 $holiday_name = $self->_get_holiday_name($holiday);
184 290         580 last;
185             }
186             }
187              
188 330         10259 return $holiday_name;
189             }
190              
191             =head2 holidays()
192              
193             For the given year, return a hashref containing all the holidays for
194             that year. The keys are the date of the holiday in C<mmdd> format
195             (eg '1225' for December 25); the values are the holiday names.
196              
197             my $calendar = holidays($year, {language => 'en'});
198             print $calendar->{'0824'}; # "Independence Day"
199              
200             my $calendar = $ua->holidays($year);
201             print $calendar->{'0628'}; # "Constitution Day"
202              
203             =cut
204              
205             sub holidays {
206 28     28 1 3026 my $self;
207 28 100       96 $self = shift if (ref $_[0]);
208              
209 28         64 my $year = shift;
210 28         46 my $args_ref = shift;
211              
212 28 100       78 unless (defined $self) {
213 12         47 $self = __PACKAGE__->new($args_ref);
214             }
215              
216 28         80 my $calendar = $self->_generate_calendar($year);
217             my %holidays = map {
218 406         877 $_->{dt}->strftime('%m%d') => $self->_get_holiday_name($_)
219 28 50       56 }@{$calendar || []};
  28         81  
220              
221 28         920 return \%holidays;
222             }
223              
224             =head2 ua_holidays()
225              
226             Same as C<holidays()>.
227              
228             =cut
229              
230             sub ua_holidays {
231 16     16 1 4415 return holidays(@_);
232             }
233              
234             =head2 holidays_dt()
235              
236             Similar to C<holidays()>, The keys are the date of the holiday in C<mmdd> format
237             (eg '1225' for December 25); and DateTime objects as the values.
238              
239             my $calendar = $ua->holidays_dt($year);
240              
241             =cut
242              
243             sub holidays_dt {
244 4     4 1 1699 my $self;
245 4 50       14 $self = shift if (ref $_[0]);
246              
247 4         11 my $year = shift;
248 4         9 my $args_ref = shift;
249              
250 4 50       9 unless (defined $self) {
251 4         17 $self = __PACKAGE__->new($args_ref);
252             }
253              
254 4         16 my $calendar = $self->_generate_calendar($year);
255             my %holidays = map {
256             $_->{dt}->strftime('%m%d') => $_->{dt}
257 4 50       10 }@{$calendar || []};
  58         1858  
  4         15  
258              
259 4         202 return \%holidays;
260             }
261              
262             # _get_holiday_name
263             #
264             # accepts: holiday item
265             # returns: holiday name
266             #
267             # generate a holiday calendar for the specified year
268             sub _get_holiday_name {
269 696     696   14740 my $self = shift;
270 696         1010 my $holiday = shift;
271              
272 696 50 33     1504 croak('Missing or wrong holiday item') if (!$holiday || !keys(%{$holiday || {}}));
  696 50       2707  
273              
274 696 100       2185 my $holiday_name = (lc($self->{language}) eq lc(DEFAULT_LANG)) ? $holiday->{local_name} : $holiday->{name};
275 696         1530 return $holiday_name;
276             }
277              
278             # _generate_calendar
279             #
280             # accepts: numeric year
281             # returns: arrayref of hashref
282             #
283             # generate a holiday calendar for the specified year
284             sub _generate_calendar {
285 362     362   621 my $self = shift;
286 362         582 my $year = shift;
287 362         695 my $calendar = [];
288              
289 362 50       836 croak('Missing year parameter') if (!$year);
290              
291 362         522 for my $holiday_rule(@{${\HOLIDAY_RULES}}) {
  362         518  
  362         896  
292 4344 100 100     759056 next if ($holiday_rule->{start_year} && ($year <= $holiday_rule->{start_year}));
293 4042 100 100     9117 next if ($holiday_rule->{end_year} && ($year >= $holiday_rule->{end_year}));
294              
295 3836 100       7598 if ($holiday_rule->{is_easter_depend}) {
296 724         1993 my $dt = DateTime->new(year => $year);
297 724         203990 my $easter = DateTime::Event::Easter->new(easter => "eastern");
298 724         126880 my $easter_offset_day = $holiday_rule->{easter_offset_day};
299              
300 724         2480 push @{$calendar}, {
301             name => $holiday_rule->{name},
302             local_name => $holiday_rule->{local_name},
303 724         1162 dt => $easter->following($dt)->add(days => $easter_offset_day)
304             };
305             }
306             else {
307             my $dt = DateTime->new(
308             year => $year,
309             month => $holiday_rule->{month},
310             day => $holiday_rule->{day}
311 3112         8892 );
312 3112         858507 push @{$calendar}, {name => $holiday_rule->{name}, local_name => $holiday_rule->{local_name}, dt => $dt};
  3112         14316  
313             }
314             }
315              
316 362         1032827 return _spread_on_weekend($calendar);
317             }
318              
319             # _spread_on_weekend
320             #
321             # accepts: calendar of holidays
322             # returns: arrayref of hashref
323             #
324             # spread weekend holidays on other non-weekend days
325              
326             sub _spread_on_weekend {
327 362     362   827 my $calendar = shift;
328              
329 362 50       531 croak('Missing calendar') if (!scalar(@{$calendar || []}));
  362 50       1454  
330 362         628 my $calc = [];
331              
332 362 50       589 for my $holiday(@{$calendar || []}) {
  362         1044  
333 3836 50       10653 next if (!$holiday->{dt});
334              
335 3836         18616 push(@{$calc}, $holiday);
  3836         6569  
336              
337 3836         8058 my $dt = $holiday->{dt}->clone();
338 3836 100       38117 my $is_weekend = WEEKEND_MAP->{$dt->day_of_week()} ? 1 : 0;
339              
340 3836 100       21002 if ($is_weekend) {
341 1418         3553 for (my $offset_day = 1; $offset_day <= 2; $offset_day++) {
342 1876         7097 my $dt_next = $dt->clone()->add(days => $offset_day);
343 1876 100       1849166 next if (WEEKEND_MAP->{$dt_next->day_of_week()});
344              
345 1514 50       8657 my $is_holiday = scalar(grep{ DateTime->compare($dt_next, $_->{dt}) == 0 }@{$calc || []}) ? 1 : 0;
  13542 100       742966  
  1514         4326  
346              
347 1514 100       94270 if (!$is_holiday) {
348 1418         2308 push(@{$calc}, {name => $holiday->{name}, local_name => $holiday->{local_name}, dt => $dt_next});
  1418         6011  
349 1418         6289 last;
350             }
351             }
352             }
353             }
354              
355 362         1209 return $calc;
356             }
357              
358             # _assert_valid_date
359             #
360             # accepts: numeric year, month, day
361             # returns: nothing
362             #
363             # throw an exception on invalid dates; otherwise, do nothing.
364              
365             sub _assert_valid_date {
366 330     330   821 my ($year, $month, $day) = @_;
367              
368             # DateTime does date validation when a DT object is created.
369 330         1257 my $dt = DateTime->new(
370             year => $year, month => $month, day => $day,
371             );
372             }
373              
374             =head1 AUTHOR
375              
376             Denis Boyun, C<< <denisboyun at gmail.com> >>
377              
378             =head1 BUGS
379              
380             Please report any bugs or feature requests to C<bug-date-holidays-ua at rt.cpan.org>, or through
381             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Date-Holidays-UA>. I will be notified, and then you'll
382             automatically be notified of progress on your bug as I make changes.
383              
384              
385              
386              
387             =head1 SUPPORT
388              
389             You can find documentation for this module with the perldoc command.
390              
391             perldoc Date::Holidays::UA
392              
393              
394             You can also look for information at:
395              
396             =over 4
397              
398             =item * RT: CPAN's request tracker (report bugs here)
399              
400             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Date-Holidays-UA>
401              
402             =item * AnnoCPAN: Annotated CPAN documentation
403              
404             L<http://annocpan.org/dist/Date-Holidays-UA>
405              
406             =item * CPAN Ratings
407              
408             L<https://cpanratings.perl.org/d/Date-Holidays-UA>
409              
410             =item * Search CPAN
411              
412             L<https://metacpan.org/release/Date-Holidays-UA>
413              
414             =back
415              
416              
417             =head1 ACKNOWLEDGEMENTS
418              
419              
420             =head1 LICENSE AND COPYRIGHT
421              
422             This software is copyright (c) 2020 by Denis Boyun.
423              
424             This is free software; you can redistribute it and/or modify it under
425             the same terms as the Perl 5 programming language system itself.
426              
427              
428             =cut
429              
430             1; # End of Date::Holidays::UA