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.2022.1';
3             # ABSTRACT: Determine Russian Federation official holidays and business days.
4              
5              
6 4     4   319038 use warnings;
  4         44  
  4         133  
7 4     4   21 use strict;
  4         8  
  4         77  
8 4     4   18 use utf8;
  4         7  
  4         19  
9 4     4   91 use base 'Exporter';
  4         7  
  4         475  
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   38 use Carp;
  4         28  
  4         270  
20 4     4   2214 use Time::Piece;
  4         54497  
  4         17  
21 4     4   334 use List::Util qw/ first /;
  4         8  
  4         5235  
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             );
136              
137             my %BUSINESS_DAYS_ON_WEEKENDS = (
138             2005 => [ qw( 0305 ) ],
139             2006 => [ qw( 0226 0506 ) ],
140             2007 => [ qw( 0428 0609 1229 ) ],
141             2008 => [ qw( 0504 0607 1101 ) ],
142             2009 => [ qw( 0111 ) ],
143             2010 => [ qw( 0227 1113 ) ],
144             2011 => [ qw( 0305 ) ],
145             2012 => [ qw( 0311 0428 0505 0512 0609 1229 ) ],
146             2016 => [ qw( 0220 ) ],
147             2018 => [ qw( 0428 0609 1229 ) ],
148             2021 => [ qw( 0220 ) ],
149             2022 => [ qw( 0305 ) ],
150             );
151              
152             my %SHORT_BUSINESS_DAYS = (
153             2004 => [ qw( 0106 0430 0611 1231 ) ],
154             2005 => [ qw( 0222 0305 1103 ) ],
155             2006 => [ qw( 0222 0307 0506 1103 ) ],
156             2007 => [ qw( 0222 0307 0428 0508 0609 ) ],
157             2008 => [ qw( 0222 0307 0430 0508 0611 1101 1231 ) ],
158             2009 => [ qw( 0430 0508 0611 1103 1231 ) ],
159             2010 => [ qw( 0227 0430 0611 1103 1231 ) ],
160             2011 => [ qw( 0222 0305 1103 ) ],
161             2012 => [ qw( 0222 0307 0428 0512 0609 1229 ) ],
162             2013 => [ qw( 0222 0307 0430 0508 0611 1231 ) ],
163             2014 => [ qw( 0224 0307 0430 0508 0611 1231 ) ],
164             2015 => [ qw( 0430 0508 0611 1103 1231 ) ],
165             2016 => [ qw( 0220 1103 ) ],
166             2017 => [ qw( 0222 0307 1103 ) ],
167             2018 => [ qw( 0222 0307 0428 0508 0609 1229 ) ],
168             2019 => [ qw( 0222 0307 0430 0508 0611 1231 ) ],
169             2020 => [ qw( 0430 0508 0611 1103 1231 ) ],
170             2021 => [ qw( 0220 0430 0611 1103 ) ],
171             2022 => [ qw( 0222 0305 1103 ) ],
172             );
173              
174              
175              
176             sub is_holiday {
177 17     17 1 1626 my ( $year, $month, $day ) = @_;
178              
179 17 50 33     86 croak 'Bad params' unless $year && $month && $day;
      33        
180              
181 17         35 return holidays( $year )->{ _get_date_key($month, $day) };
182             }
183              
184              
185             sub is_ru_holiday {
186 1     1 1 5 goto &is_holiday;
187             }
188              
189              
190             my %cache;
191             sub holidays {
192 18 50   18 1 134 my $year = shift or croak 'Bad year';
193              
194 18 100       54 return $cache{ $year } if $cache{ $year };
195              
196 11         28 my $holidays = _get_regular_holidays_by_year($year);
197              
198 10 100       29 if ( my $spec = $HOLIDAYS_SPECIAL{ $year } ) {
199 6         41 $holidays->{ $_ } = 'Перенос праздничного дня' for @$spec;
200             }
201              
202 10         34 return $cache{ $year } = $holidays;
203             }
204              
205             sub _get_regular_holidays_by_year {
206 11     11   40 my ($year) = @_;
207 11 100       53 croak "RU holidays is not valid before $HOLIDAYS_VALID_SINCE" if $year < $HOLIDAYS_VALID_SINCE;
208              
209 10         17 my %day;
210 10         41 for my $holiday (@REGULAR_HOLIDAYS) {
211 100         180 my $days = _resolve_yhash_value($holiday->{days}, $year);
212 100 100       181 next if !$days;
213 80 100       182 $days = [$days] if !ref $days;
214 80 50       142 next if !@$days;
215              
216 80         128 my $name = _resolve_yhash_value($holiday->{name}, $year);
217 80 50       141 croak "Name is not defined" if !$name; # assertion
218              
219 80         257 $day{$_} = $name for @$days;
220             }
221              
222 10         20 return \%day;
223             }
224              
225             sub _resolve_yhash_value {
226 180     180   298 my ($value, $year) = @_;
227 180 100       348 return $value if ref $value ne 'HASH';
228              
229 133     155   551 my $ykey = first {$year >= $_} reverse sort keys %$value;
  155         295  
230 133 100       383 return if !$ykey;
231 127         244 return $value->{$ykey};
232             }
233              
234              
235              
236             sub is_business_day {
237 5     5 1 95 my ( $year, $month, $day ) = @_;
238              
239 5 50 33     30 croak 'Bad params' unless $year && $month && $day;
      33        
240              
241 5 100       15 return 0 if is_holiday( $year, $month, $day );
242              
243             # check if date is a weekend
244 4         22 my $t = Time::Piece->strptime( "$year-$month-$day", '%Y-%m-%d' );
245 4         290 my $wday = $t->day;
246 4 100 100     88 return 1 unless $wday eq 'Sat' || $wday eq 'Sun';
247              
248             # check if date is a business day on weekend
249 3 100       17 my $ref = $BUSINESS_DAYS_ON_WEEKENDS{ $year } or return 0;
250              
251 1         16 my $md = _get_date_key($month, $day);
252 1         7 for ( @$ref ) {
253 1 50       9 return 1 if $_ eq $md;
254             }
255              
256 0         0 return 0;
257             }
258              
259              
260             sub is_short_business_day {
261 3     3 1 87 my ( $year, $month, $day ) = @_;
262              
263 3 50       12 my $short_days_ref = $SHORT_BUSINESS_DAYS{ $year } or return 0;
264              
265 3         11 my $date_key = _get_date_key($month, $day);
266 3         9 return !!grep { $_ eq $date_key } @$short_days_ref;
  15         46  
267             }
268              
269              
270             sub _get_date_key {
271 20     20   39 my ($month, $day) = @_;
272 20         145 return sprintf '%02d%02d', $month, $day;
273             }
274              
275              
276             1;
277              
278             __END__