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