File Coverage

blib/lib/Date/Holidays/RU.pm
Criterion Covered Total %
statement 66 67 98.5
branch 27 34 79.4
condition 7 15 46.6
subroutine 16 16 100.0
pod 5 5 100.0
total 121 137 88.3


line stmt bran cond sub pod time code
1             package Date::Holidays::RU;
2             $Date::Holidays::RU::VERSION = '1.2023.1';
3             # ABSTRACT: Determine Russian Federation official holidays and business days.
4              
5              
6 4     4   277768 use warnings;
  4         51  
  4         127  
7 4     4   24 use strict;
  4         7  
  4         100  
8 4     4   21 use utf8;
  4         5  
  4         25  
9 4     4   92 use base 'Exporter';
  4         8  
  4         484  
10              
11             our @EXPORT_OK = qw(
12             is_holiday
13             is_ru_holiday
14             holidays
15             is_business_day
16             is_short_business_day
17             );
18              
19 4     4   36 use Carp;
  4         21  
  4         271  
20 4     4   2361 use Time::Piece;
  4         51366  
  4         15  
21 4     4   329 use List::Util qw/ first /;
  4         8  
  4         5242  
22              
23              
24             my $HOLIDAYS_VALID_SINCE = 1991;
25             #my $BUSINESS_DAYS_VALID_SINCE = 2004;
26              
27             # sources:
28             # http://www.consultant.ru/law/ref/calendar/proizvodstvennye/
29             # http://ru.wikipedia.org/wiki/История_праздников_России
30             # http://www.consultant.ru/popular/kzot/54_6.html#p530
31             # http://www.consultant.ru/document/cons_doc_LAW_127924/?frame=17#p1681
32              
33             my @REGULAR_HOLIDAYS = (
34             {
35             name => {
36             1948 => 'Новый год',
37             2005 => 'Новогодние каникулы',
38             },
39             days => {
40             1948 => '0101',
41             1992 => [ qw( 0101 0102 ) ],
42             2005 => [ qw( 0101 0102 0103 0104 0105 ) ],
43             2013 => [ qw( 0101 0102 0103 0104 0105 0106 0108 ) ],
44             },
45             },
46             {
47             name => 'Рождество Христово',
48             days => {
49             1991 => '0107', # maybe 1992
50             },
51             },
52             {
53             name => 'День защитника Отечества',
54             days => {
55             2002 => '0223',
56             },
57             },
58             {
59             name => 'Международный женский день',
60             days => {
61             1966 => '0308',
62             }
63             },
64             {
65             name => {
66             1965 => 'День международной солидарности трудящихся',
67             1992 => 'Праздник Весны и Труда',
68             },
69             days => {
70             1965 => [ qw( 0501 0502 ) ],
71             2005 => '0501',
72             },
73             },
74             {
75             name => 'День Победы',
76             days => {
77             1965 => '0509',
78             },
79             },
80             {
81             name => {
82             1992 => 'День принятия декларации о государственном суверенитете Российской Федерации',
83             2002 => 'День России',
84             },
85             days => {
86             1992 => '0612',
87             },
88             },
89             {
90             name => 'День народного единства',
91             days => {
92             2005 => '1104',
93             },
94             },
95             {
96             name => {
97             1965 => 'Годовщина Великой Октябрьской социалистической революции',
98             1996 => 'День согласия и примирения',
99             },
100             days => {
101             1928 => [ qw( 1107 1108 ) ],
102             1992 => '1107',
103             2005 => undef,
104             },
105             },
106             {
107             name => 'День Конституции Российской Федерации',
108             days => {
109             1994 => '1212',
110             2005 => undef,
111             },
112             },
113             );
114              
115             my %HOLIDAYS_SPECIAL = (
116             2004 => [ qw( 0503 0504 0510 0614 1108 1213 ) ],
117             2005 => [ qw( 0106 0110 0307 0502 0613 ) ],
118             2006 => [ qw( 0106 0109 0224 0508 1106 ) ],
119             2007 => [ qw( 0108 0430 0611 1105 1231 ) ],
120             2008 => [ qw( 0108 0225 0310 0502 0613 1103 ) ],
121             2009 => [ qw( 0106 0108 0109 0309 0511 ) ],
122             2010 => [ qw( 0106 0108 0222 0503 0510 0614 1105 ) ],
123             2011 => [ qw( 0106 0110 0307 0502 0613 ) ],
124             2012 => [ qw( 0106 0109 0309 0430 0507 0508 0611 1105 1231 ) ],
125             2013 => [ qw( 0502 0503 0510 ) ],
126             2014 => [ qw( 0310 0502 0613 1103 ) ],
127             2015 => [ qw( 0109 0309 0504 0511 ) ],
128             2016 => [ qw( 0222 0307 0502 0503 0613 ) ],
129             2017 => [ qw( 0224 0508 1106 ) ],
130             2018 => [ qw( 0309 0430 0502 0611 1105 1231 ) ],
131             2019 => [ qw( 0502 0503 0510 ) ],
132             2020 => [ qw( 0224 0309 0504 0505 ) ],
133             2021 => [ qw( 0222 0503 0510 0614 1105 1231 ) ],
134             2022 => [ qw( 0307 0503 0510 0613 ) ],
135             2023 => [ qw( 0224 0508 1106 ) ],
136             );
137              
138             my %BUSINESS_DAYS_ON_WEEKENDS = (
139             2005 => [ qw( 0305 ) ],
140             2006 => [ qw( 0226 0506 ) ],
141             2007 => [ qw( 0428 0609 1229 ) ],
142             2008 => [ qw( 0504 0607 1101 ) ],
143             2009 => [ qw( 0111 ) ],
144             2010 => [ qw( 0227 1113 ) ],
145             2011 => [ qw( 0305 ) ],
146             2012 => [ qw( 0311 0428 0505 0512 0609 1229 ) ],
147             2016 => [ qw( 0220 ) ],
148             2018 => [ qw( 0428 0609 1229 ) ],
149             2021 => [ qw( 0220 ) ],
150             2022 => [ qw( 0305 ) ],
151             );
152              
153             my %SHORT_BUSINESS_DAYS = (
154             2004 => [ qw( 0106 0430 0611 1231 ) ],
155             2005 => [ qw( 0222 0305 1103 ) ],
156             2006 => [ qw( 0222 0307 0506 1103 ) ],
157             2007 => [ qw( 0222 0307 0428 0508 0609 ) ],
158             2008 => [ qw( 0222 0307 0430 0508 0611 1101 1231 ) ],
159             2009 => [ qw( 0430 0508 0611 1103 1231 ) ],
160             2010 => [ qw( 0227 0430 0611 1103 1231 ) ],
161             2011 => [ qw( 0222 0305 1103 ) ],
162             2012 => [ qw( 0222 0307 0428 0512 0609 1229 ) ],
163             2013 => [ qw( 0222 0307 0430 0508 0611 1231 ) ],
164             2014 => [ qw( 0224 0307 0430 0508 0611 1231 ) ],
165             2015 => [ qw( 0430 0508 0611 1103 1231 ) ],
166             2016 => [ qw( 0220 1103 ) ],
167             2017 => [ qw( 0222 0307 1103 ) ],
168             2018 => [ qw( 0222 0307 0428 0508 0609 1229 ) ],
169             2019 => [ qw( 0222 0307 0430 0508 0611 1231 ) ],
170             2020 => [ qw( 0430 0508 0611 1103 1231 ) ],
171             2021 => [ qw( 0220 0430 0611 1103 ) ],
172             2022 => [ qw( 0222 0305 1103 ) ],
173             2023 => [ qw( 0222 0307 1103 ) ],
174             );
175              
176              
177              
178             sub is_holiday {
179 17     17 1 1600 my ( $year, $month, $day ) = @_;
180              
181 17 50 33     89 croak 'Bad params' unless $year && $month && $day;
      33        
182              
183 17         43 return holidays( $year )->{ _get_date_key($month, $day) };
184             }
185              
186              
187             sub is_ru_holiday {
188 1     1 1 4 goto &is_holiday;
189             }
190              
191              
192             my %cache;
193             sub holidays {
194 18 50   18 1 131 my $year = shift or croak 'Bad year';
195              
196 18 100       94 return $cache{ $year } if $cache{ $year };
197              
198 11         29 my $holidays = _get_regular_holidays_by_year($year);
199              
200 10 100       32 if ( my $spec = $HOLIDAYS_SPECIAL{ $year } ) {
201 6         41 $holidays->{ $_ } = 'Перенос праздничного дня' for @$spec;
202             }
203              
204 10         33 return $cache{ $year } = $holidays;
205             }
206              
207             sub _get_regular_holidays_by_year {
208 11     11   32 my ($year) = @_;
209 11 100       63 croak "RU holidays is not valid before $HOLIDAYS_VALID_SINCE" if $year < $HOLIDAYS_VALID_SINCE;
210              
211 10         16 my %day;
212 10         31 for my $holiday (@REGULAR_HOLIDAYS) {
213 100         200 my $days = _resolve_yhash_value($holiday->{days}, $year);
214 100 100       190 next if !$days;
215 80 100       172 $days = [$days] if !ref $days;
216 80 50       137 next if !@$days;
217              
218 80         131 my $name = _resolve_yhash_value($holiday->{name}, $year);
219 80 50       138 croak "Name is not defined" if !$name; # assertion
220              
221 80         256 $day{$_} = $name for @$days;
222             }
223              
224 10         22 return \%day;
225             }
226              
227             sub _resolve_yhash_value {
228 180     180   305 my ($value, $year) = @_;
229 180 100       376 return $value if ref $value ne 'HASH';
230              
231 133     155   555 my $ykey = first {$year >= $_} reverse sort keys %$value;
  155         333  
232 133 100       354 return if !$ykey;
233 127         293 return $value->{$ykey};
234             }
235              
236              
237              
238             sub is_business_day {
239 5     5 1 100 my ( $year, $month, $day ) = @_;
240              
241 5 50 33     32 croak 'Bad params' unless $year && $month && $day;
      33        
242              
243 5 100       15 return 0 if is_holiday( $year, $month, $day );
244              
245             # check if date is a weekend
246 4         24 my $t = Time::Piece->strptime( "$year-$month-$day", '%Y-%m-%d' );
247 4         295 my $wday = $t->day;
248 4 100 100     94 return 1 unless $wday eq 'Sat' || $wday eq 'Sun';
249              
250             # check if date is a business day on weekend
251 3 100       25 my $ref = $BUSINESS_DAYS_ON_WEEKENDS{ $year } or return 0;
252              
253 1         4 my $md = _get_date_key($month, $day);
254 1         5 for ( @$ref ) {
255 1 50       7 return 1 if $_ eq $md;
256             }
257              
258 0         0 return 0;
259             }
260              
261              
262             sub is_short_business_day {
263 3     3 1 94 my ( $year, $month, $day ) = @_;
264              
265 3 50       13 my $short_days_ref = $SHORT_BUSINESS_DAYS{ $year } or return 0;
266              
267 3         11 my $date_key = _get_date_key($month, $day);
268 3         9 return !!grep { $_ eq $date_key } @$short_days_ref;
  15         51  
269             }
270              
271              
272             sub _get_date_key {
273 20     20   48 my ($month, $day) = @_;
274 20         137 return sprintf '%02d%02d', $month, $day;
275             }
276              
277              
278             1;
279              
280             __END__