File Coverage

blib/lib/Calendar/Japanese/Holiday.pm
Criterion Covered Total %
statement 154 165 93.3
branch 98 128 76.5
condition 55 96 57.2
subroutine 16 16 100.0
pod 2 11 18.1
total 325 416 78.1


line stmt bran cond sub pod time code
1             package Calendar::Japanese::Holiday;
2              
3 1     1   68837 use 5.008001;
  1         4  
4 1     1   13 use strict;
  1         2  
  1         30  
5 1     1   7 use warnings;
  1         2  
  1         25  
6              
7 1     1   4 use utf8;
  1         2  
  1         6  
8 1     1   614 use Time::Local;
  1         2508  
  1         2186  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13              
14             our @EXPORT = qw(getHolidays isHoliday);
15              
16             our $VERSION = '0.05';
17              
18              
19             our $FurikaeStr = '振替';
20              
21             my @StaticHoliday = (
22             # 山の日を8/11に戻す
23             {'start' => 2021, 'end' => 2999,
24             'days' => {1 => { 1 => '元日'},
25             2 => {11 => '建国記念の日',
26             23 => '天皇誕生日'},
27             4 => {29 => '昭和の日'},
28             5 => { 3 => '憲法記念日',
29             4 => 'みどりの日',
30             5 => 'こどもの日'},
31             8 => {11 => '山の日'},
32             11 => { 3 => '文化の日',
33             23 => '勤労感謝の日'},
34             },
35             },
36             # 2020年は山の日が8/10に移動
37             {'start' => 2020, 'end' => 2020,
38             'days' => {1 => { 1 => '元日'},
39             2 => {11 => '建国記念の日',
40             23 => '天皇誕生日'},
41             4 => {29 => '昭和の日'},
42             5 => { 3 => '憲法記念日',
43             4 => 'みどりの日',
44             5 => 'こどもの日'},
45             8 => {10 => '山の日'},
46             11 => { 3 => '文化の日',
47             23 => '勤労感謝の日'},
48             },
49             },
50             # 天皇誕生日削除(2019年のみ)
51             {'start' => 2019, 'end' => 2019,
52             'days' => {1 => { 1 => '元日'},
53             2 => {11 => '建国記念の日'},
54             4 => {29 => '昭和の日'},
55             5 => { 3 => '憲法記念日',
56             4 => 'みどりの日',
57             5 => 'こどもの日'},
58             8 => {11 => '山の日'},
59             11 => { 3 => '文化の日',
60             23 => '勤労感謝の日'},
61             },
62             },
63             # 山の日を追加
64             {'start' => 2016, 'end' => 2018,
65             'days' => {1 => { 1 => '元日'},
66             2 => {11 => '建国記念の日'},
67             4 => {29 => '昭和の日'},
68             5 => { 3 => '憲法記念日',
69             4 => 'みどりの日',
70             5 => 'こどもの日'},
71             8 => {11 => '山の日'},
72             11 => { 3 => '文化の日',
73             23 => '勤労感謝の日'},
74             12 => {23 => '天皇誕生日'},
75             },
76             },
77             # 4/29 みどりの日 => 昭和の日 変更
78             # みどりの日は5/4に移行
79             {'start' => 2007, 'end' => 2015,
80             'days' => {1 => { 1 => '元日'},
81             2 => {11 => '建国記念の日'},
82             4 => {29 => '昭和の日'},
83             5 => { 3 => '憲法記念日',
84             4 => 'みどりの日',
85             5 => 'こどもの日'},
86             11 => { 3 => '文化の日',
87             23 => '勤労感謝の日'},
88             12 => {23 => '天皇誕生日'},
89             },
90             },
91             # 海の日,敬老の日がHappy Mondayに
92             {'start' => 2003, 'end' => 2006,
93             'days' => {1 => { 1 => '元日'},
94             2 => {11 => '建国記念の日'},
95             4 => {29 => 'みどりの日'},
96             5 => { 3 => '憲法記念日',
97             5 => 'こどもの日'},
98             11 => { 3 => '文化の日',
99             23 => '勤労感謝の日'},
100             12 => {23 => '天皇誕生日'},
101             },
102             },
103             # 成人の日,体育の日がHappy Mondayに
104             {'start' => 2000, 'end' => 2002,
105             'days' => {1 => { 1 => '元日'},
106             2 => {11 => '建国記念の日'},
107             4 => {29 => 'みどりの日'},
108             5 => { 3 => '憲法記念日',
109             5 => 'こどもの日'},
110             7 => {20 => '海の日'},
111             9 => {15 => '敬老の日'},
112             11 => { 3 => '文化の日',
113             23 => '勤労感謝の日'},
114             12 => {23 => '天皇誕生日'},
115             },
116             },
117             # 海の日追加
118             {'start' => 1996, 'end' => 1999,
119             'days' => {1 => { 1 => '元日',
120             15 => '成人の日'},
121             2 => {11 => '建国記念の日'},
122             4 => {29 => 'みどりの日'},
123             5 => { 3 => '憲法記念日',
124             5 => 'こどもの日'},
125             7 => {20 => '海の日'},
126             9 => {15 => '敬老の日'},
127             10 => {10 => '体育の日'},
128             11 => { 3 => '文化の日',
129             23 => '勤労感謝の日'},
130             12 => {23 => '天皇誕生日'},
131             },
132             },
133             # 天皇誕生日変更 4/29 => 12/23
134             # 旧天皇誕生日をみどりの日に変更
135             {'start' => 1989, 'end' => 1995,
136             'days' => {1 => { 1 => '元日',
137             15 => '成人の日'},
138             2 => {11 => '建国記念の日'},
139             4 => {29 => 'みどりの日'},
140             5 => { 3 => '憲法記念日',
141             5 => 'こどもの日'},
142             9 => {15 => '敬老の日'},
143             10 => {10 => '体育の日'},
144             11 => { 3 => '文化の日',
145             23 => '勤労感謝の日'},
146             12 => {23 => '天皇誕生日'},
147             },
148             },
149             # 建国記念の日追加
150             {'start' => 1967, 'end' => 1988,
151             'days' => {1 => { 1 => '元日',
152             15 => '成人の日'},
153             2 => {11 => '建国記念の日'},
154             4 => {29 => '天皇誕生日'},
155             5 => { 3 => '憲法記念日',
156             5 => 'こどもの日'},
157             9 => {15 => '敬老の日'},
158             10 => {10 => '体育の日'},
159             11 => { 3 => '文化の日',
160             23 => '勤労感謝の日'},
161             },
162             },
163             # 敬老の日,体育の日追加
164             {'start' => 1966, 'end' => 1966,
165             'days' => {1 => { 1 => '元日',
166             15 => '成人の日'},
167             4 => {29 => '天皇誕生日'},
168             5 => { 3 => '憲法記念日',
169             5 => 'こどもの日'},
170             9 => {15 => '敬老の日'},
171             10 => {10 => '体育の日'},
172             11 => { 3 => '文化の日',
173             23 => '勤労感謝の日'},
174             },
175             },
176             # 国民の祝日に関する法律に定められた祝日のうち7/20以前のものを追加
177             {'start' => 1949, 'end' => 1965,
178             'days' => {1 => { 1 => '元日',
179             15 => '成人の日'},
180             4 => {29 => '天皇誕生日'},
181             5 => { 3 => '憲法記念日',
182             5 => 'こどもの日'},
183             11 => { 3 => '文化の日',
184             23 => '勤労感謝の日'},
185             },
186             },
187             # 国民の祝日に関する法律 1948/7/20制定
188             {'start' => 1948, 'end' => 1948,
189             'days' => {11 => { 3 => '文化の日',
190             23 => '勤労感謝の日'},
191             },
192             },
193             );
194              
195             my %ExceptionalHoliday = (
196             195904 => {10 => '皇太子明仁親王の結婚の儀'},
197             198902 => {24 => '昭和天皇の大喪の礼'},
198             199011 => {12 => '即位礼正殿の儀'},
199             199306 => { 9 => '皇太子徳仁親王の結婚の儀'},
200             201905 => { 1 => '天皇の即位の日'},
201             201910 => {22 => '即位礼正殿の儀の行われる日'},
202             );
203              
204             my @daysInMonth = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
205              
206             sub days_in_month {
207 3606     3606 0 6753 my ($year, $mon) = @_;
208              
209 3606         6160 my $days = $daysInMonth[$mon - 1];
210              
211 3606 100 100     8298 if ($mon == 2 && $year % 4 == 0) {
212 76 100       179 if ($year % 100 == 0) {
213 6 50       26 return $days + 1 if $year % 400 == 0;
214 0         0 return $days;
215             }
216 70         163 return $days + 1;
217             }
218              
219 3530         6069 return $days;
220             }
221              
222             # 指定曜日の日付一覧を配列で返す
223             sub weekdays {
224 2360     2360 0 4559 my ($year, $mon, $wday) = @_;
225              
226 2360         3655 my @week_days;
227              
228 2360         5588 my $wd = (localtime(timelocal(0, 0, 0, 1, $mon - 1, $year)))[6];
229              
230             # 指定曜日の最初の日付(カレンダー的に空欄の場合は0以下の値となる)
231 2360         157642 my $start = 1 - $wd + $wday;
232              
233 2360         5562 my $last_day = days_in_month($year, $mon);
234              
235 2360         5559 for (my $day = $start ; $day <= $last_day ; $day += 7) {
236 11934 100       27684 push @week_days, $day if $day > 0;
237             }
238              
239 2360         5822 return @week_days;
240             }
241              
242             sub lookup_holiday_table {
243 2360     2360 0 3754 my ($year) = @_;
244              
245 2360         4449 foreach my $tbl (@StaticHoliday) {
246             return $tbl->{days}
247 15448 100 66     36608 if ($tbl->{start} <= $year && $year <= $tbl->{end});
248             }
249 0         0 return;
250             }
251              
252             # 春分の日
253             # Ref to.
254             # http://www.nao.ac.jp/QA/faq/a0301.html
255             # http://ja.wikipedia.org/wiki/%E6%98%A5%E5%88%86%E3%81%AE%E6%97%A5
256             sub shunbun_day {
257 135     135 0 243 my ($year) = @_;
258              
259 135         183 my $day;
260              
261 135         233 my $mod = $year % 4;
262 135 100       392 if ($mod == 0) {
    100          
    100          
    50          
263 33 50 33     167 if (1900 <= $year && $year <= 1956) {$day = 21;}
  0 50 33     0  
    0 0        
264 33         57 elsif (1960 <= $year && $year <= 2088) {$day = 20;}
265 0         0 elsif (2092 <= $year && $year <= 2096) {$day = 19;}
266             } elsif ($mod == 1) {
267 34 100 66     175 if (1901 <= $year && $year <= 1989) {$day = 21;}
  3 50 33     7  
268 31         52 elsif (1993 <= $year && $year <= 2097) {$day = 20;}
269             } elsif ($mod == 2) {
270 35 100 66     137 if (1902 <= $year && $year <= 2022) {$day = 21;}
  27 50 33     48  
271 8         14 elsif (2026 <= $year && $year <= 2098) {$day = 20;}
272             } elsif ($mod == 3) {
273 33 50 33     185 if (1903 <= $year && $year <= 1923) {$day = 22;}
  0 50 33     0  
    0 0        
274 33         50 elsif (1927 <= $year && $year <= 2055) {$day = 21;}
275 0         0 elsif (2059 <= $year && $year <= 2099) {$day = 20;}
276             }
277              
278 135         447 return $day;
279             }
280              
281             # 秋分の日
282             sub shuubun_day {
283 160     160 0 328 my ($year) = @_;
284              
285 160         208 my $day;
286              
287 160         284 my $mod = $year % 4;
288 160 100       426 if ($mod == 0) {
    100          
    100          
    50          
289 38 50 66     187 if ($year == 1900) {$day = 23;}
  0 100 33     0  
    50          
290 28         45 elsif (1904 <= $year && $year <= 2008) {$day = 23;}
291 10         17 elsif (2012 <= $year && $year <= 2096) {$day = 22;}
292             } elsif ($mod == 1) {
293 38 50 33     225 if (1901 <= $year && $year <= 1917) {$day = 24;}
  0 50 33     0  
    0 0        
294 38         70 elsif (1921 <= $year && $year <= 2041) {$day = 23;}
295 0         0 elsif (2045 <= $year && $year <= 2097) {$day = 22;}
296             } elsif ($mod == 2) {
297 39 50 33     198 if (1902 <= $year && $year <= 1946) {$day = 24;}
  0 50 33     0  
    0 0        
298 39         65 elsif (1950 <= $year && $year <= 2074) {$day = 23;}
299 0         0 elsif (2078 <= $year && $year <= 2098) {$day = 22;}
300             } elsif ($mod == 3) {
301 45 100 66     215 if (1903 <= $year && $year <= 1979) {$day = 24;}
  8 50 33     15  
302 37         57 elsif (1983 <= $year && $year <= 2099) {$day = 23;}
303             }
304              
305 160         429 return $day;
306             }
307              
308             sub furikae_days {
309 1103     1103 0 2003 my ($year, $mon, $holidays_tbl) = @_;
310              
311 1103         1521 my %days;
312              
313 1103 100       2206 return \%days if $year < 1973;
314              
315 782         2164 while (my ($h_day, $name) = each %$holidays_tbl) {
316             # 祝日が日曜日かチェック
317 1334         3621 my $wday = (localtime(timelocal(0, 0, 0, $h_day, $mon - 1, $year)))[6];
318              
319 1334 100       84163 if ($wday == 0) {
320 176         349 my $furikae_day = $h_day + 1;
321 176 100       680 if ($year >= 2007) {
322             # 振り替えた先も祝日ならさらに進める
323 86         262 $furikae_day++ while (exists $holidays_tbl->{$furikae_day});
324 86         395 $days{$furikae_day} = $name;
325             } else {
326             $days{$furikae_day} = $name
327 90 50       544 if (!exists $holidays_tbl->{$furikae_day});
328             }
329             }
330             }
331              
332 782         1899 return \%days;
333             }
334              
335             # 指定年月の休日一覧を取得(国民の休日、振替休日を処理する前)
336             sub get_holidays {
337 2360     2360 0 3866 my ($year, $mon) = @_;
338              
339 2360         3294 my $holiday_tbl;
340              
341 2360 50       4956 return if !($holiday_tbl = lookup_holiday_table($year));
342              
343 2360         4025 my %holidays;
344 2360 100       4748 if (exists $holiday_tbl->{$mon}) {
345 1555         2289 %holidays = %{$holiday_tbl->{$mon}}; # Copy
  1555         5083  
346             }
347              
348             # Happy Monday (成人の日、海の日、敬老の日、体育の日)
349 2360         5154 my @mondays = weekdays($year, $mon, 1); # 月曜日の一覧
350              
351 2360 100 100     7302 if ($year >= 2000 && $mon == 1) {$holidays{$mondays[1]} = '成人の日';}
  127         367  
352              
353             # 体育の日/スポーツの日(2020年以降)
354 2360 100 100     11840 if ($year >= 2000 && $year <= 2019 && $mon == 10) {
    100 100        
    100 100        
      100        
355 77         264 $holidays{$mondays[1]} = '体育の日';
356             } elsif ($year == 2020 && $mon == 7) {
357             # 2020年はオリンピックにあわせて変更となりHappy Mondayではない
358 3         14 $holidays{24} = 'スポーツの日';
359             } elsif ($year >= 2021 && $mon == 10) {
360             # 2021年以降は第二月曜に戻る
361 31         99 $holidays{$mondays[1]} = 'スポーツの日';
362             }
363              
364             # 海の日追加
365 2360 100 100     5509 if ($year >= 2003 && $mon == 7) {
366 35 100       81 if ($year != 2020) {
367 32         114 $holidays{$mondays[2]} = '海の日';
368             } else {
369 3         12 $holidays{23} = '海の日'; # 2020年は7/23に変更される
370             }
371             }
372              
373 2360 100 100     5642 if ($year >= 2003 && $mon == 9) {$holidays{$mondays[2]} = '敬老の日';}
  68         221  
374              
375             # 不定なもの
376 2360 100       4249 if ($mon == 3) {$holidays{shunbun_day($year)} = '春分の日';}
  135         316  
377 2360 100       4493 if ($mon == 9) {$holidays{shuubun_day($year)} = '秋分の日';}
  160         322  
378              
379             # 例外的なもの
380 2360         8249 my $yymm = sprintf("%04d%02d", $year, $mon);
381 2360 100       5089 if (exists $ExceptionalHoliday{$yymm}) {
382 27         45 while (my ($day, $name) = each %{$ExceptionalHoliday{$yymm}}) {
  54         186  
383 27         85 $holidays{$day} = $name;
384             }
385             }
386              
387 2360         5900 return \%holidays;
388             }
389              
390             sub next_year_mon {
391 623     623 0 1122 my ($year, $mon) = @_;
392              
393 623         848 $mon++;
394 623 100       1176 if ($mon > 12) {
395 31         42 $year++;
396 31         47 $mon = 1;
397             }
398 623         1276 return ($year, $mon);
399             }
400              
401             sub prev_year_mon {
402 1246     1246 0 2022 my ($year, $mon) = @_;
403              
404 1246         1846 $mon--;
405 1246 100       2296 if ($mon < 1) {
406 186         248 $year--;
407 186         282 $mon = 12;
408             }
409 1246         2335 return ($year, $mon);
410             }
411              
412             sub getHolidays {
413 1114     1114 1 8443 my ($year, $mon, $furikae) = @_;
414              
415 1114         2148 my $holidays = get_holidays($year, $mon);
416              
417 1114 50       2363 return if not defined $holidays;
418              
419             # 国民の休日
420 1114 100       2079 if ($year >= 1986) {
421             # 祝日に挟まれた平日を探す (祝日A - 平日B - 祝日C)
422              
423             # 休日検索用テーブル
424             # 祝日Aと祝日Cが月をまたぐケースもあるので、前後の月の情報も結合する
425 623         2304 my %holidays_search_table = %$holidays;
426 623         1553 my $next_holidays = get_holidays(next_year_mon($year, $mon));
427 623 50       1316 if ($next_holidays) {
428 623         1156 my $offset = days_in_month($year, $mon);
429 623         2237 while (my ($d, $name) = each %$next_holidays) {
430 644         2708 $holidays_search_table{$d + $offset} = $name;
431             }
432             }
433 623         1271 my $prev_holidays = get_holidays(prev_year_mon($year, $mon));
434 623 50       1301 if ($prev_holidays) {
435 623         1215 my $offset = -days_in_month(prev_year_mon($year, $mon));
436 623         2447 while (my ($d, $name) = each %$prev_holidays) {
437 641         2866 $holidays_search_table{$d + $offset} = $name;
438             }
439             }
440              
441 623         1727 foreach my $day (keys %$holidays) {
442 1058 100 100     4159 if ( exists $holidays_search_table{$day + 2} &&
443             !exists $holidays_search_table{$day + 1}) {
444 60         178 my $wday = (localtime(timelocal(0, 0, 0,
445             $day, $mon - 1, $year)))[6];
446             # 祝日Aの時は平日Bはただの振り替え休日
447 60 100       3823 next if $wday == 0;
448              
449             # 平日Bが日曜の場合も国民の休日とはならない
450 52 100       122 next if $wday == 6;
451              
452 44         181 $holidays->{$day + 1} = '国民の休日';
453             }
454             }
455             }
456              
457             # 振り替え休日も含める
458 1114 100       2434 if ($furikae) {
459 1103         2270 my $furikae_days = furikae_days($year, $mon, $holidays);
460              
461 1103         4357 while (my ($val, $name) = each %$furikae_days) {
462 176         684 $holidays->{$val} = $FurikaeStr;
463             }
464             }
465              
466 1114         2214 return $holidays;
467             }
468              
469             my $Cache_holidays_Year = 0;
470             my $Cache_holidays_Month = 0;
471             my $Cache_holidays;
472              
473             sub isHoliday {
474 1095     1095 1 25006 my ($year, $mon, $day, $furikae) = @_;
475              
476 1095         1522 my $holidays;
477              
478 1095 100 100     2722 if ($year == $Cache_holidays_Year &&
479             $mon == $Cache_holidays_Month) {
480 1         3 $holidays = $Cache_holidays; # From Cache
481             } else {
482 1094         2189 $holidays = getHolidays($year, $mon, 1);
483 1094 50       2421 return if not defined $holidays;
484             # Cache
485 1094         2060 $Cache_holidays = $holidays;
486 1094         1674 $Cache_holidays_Year = $year;
487 1094         1736 $Cache_holidays_Month = $mon;
488             }
489              
490 1095 100       2580 return if !exists $holidays->{$day};
491              
492 851 100 100     3194 return if (!$furikae && $holidays->{$day} eq $FurikaeStr);
493              
494 848         2225 return $holidays->{$day};
495             }
496              
497             1;
498             __END__