File Coverage

blib/lib/Calendar/Japanese/Holiday.pm
Criterion Covered Total %
statement 163 176 92.6
branch 104 136 76.4
condition 60 105 57.1
subroutine 16 16 100.0
pod 2 11 18.1
total 345 444 77.7


line stmt bran cond sub pod time code
1             package Calendar::Japanese::Holiday;
2              
3 1     1   72903 use 5.008001;
  1         4  
4 1     1   7 use strict;
  1         2  
  1         33  
5 1     1   6 use warnings;
  1         2  
  1         26  
6              
7 1     1   5 use utf8;
  1         1  
  1         6  
8 1     1   558 use Time::Local;
  1         2439  
  1         2339  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13              
14             our @EXPORT = qw(getHolidays isHoliday);
15              
16             our $VERSION = '0.07';
17              
18              
19             our $FurikaeStr = '振替';
20              
21             my @StaticHoliday = (
22             # 山の日を8/11に戻す
23             {'start' => 2022, '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             # 2021年は山の日が8/8に移動
37             {'start' => 2021, 'end' => 2021,
38             'days' => {1 => { 1 => '元日'},
39             2 => {11 => '建国記念の日',
40             23 => '天皇誕生日'},
41             4 => {29 => '昭和の日'},
42             5 => { 3 => '憲法記念日',
43             4 => 'みどりの日',
44             5 => 'こどもの日'},
45             8 => { 8 => '山の日'},
46             11 => { 3 => '文化の日',
47             23 => '勤労感謝の日'},
48             },
49             },
50             # 2020年は山の日が8/10に移動
51             {'start' => 2020, 'end' => 2020,
52             'days' => {1 => { 1 => '元日'},
53             2 => {11 => '建国記念の日',
54             23 => '天皇誕生日'},
55             4 => {29 => '昭和の日'},
56             5 => { 3 => '憲法記念日',
57             4 => 'みどりの日',
58             5 => 'こどもの日'},
59             8 => {10 => '山の日'},
60             11 => { 3 => '文化の日',
61             23 => '勤労感謝の日'},
62             },
63             },
64             # 天皇誕生日削除(2019年のみ)
65             {'start' => 2019, 'end' => 2019,
66             'days' => {1 => { 1 => '元日'},
67             2 => {11 => '建国記念の日'},
68             4 => {29 => '昭和の日'},
69             5 => { 3 => '憲法記念日',
70             4 => 'みどりの日',
71             5 => 'こどもの日'},
72             8 => {11 => '山の日'},
73             11 => { 3 => '文化の日',
74             23 => '勤労感謝の日'},
75             },
76             },
77             # 山の日を追加
78             {'start' => 2016, 'end' => 2018,
79             'days' => {1 => { 1 => '元日'},
80             2 => {11 => '建国記念の日'},
81             4 => {29 => '昭和の日'},
82             5 => { 3 => '憲法記念日',
83             4 => 'みどりの日',
84             5 => 'こどもの日'},
85             8 => {11 => '山の日'},
86             11 => { 3 => '文化の日',
87             23 => '勤労感謝の日'},
88             12 => {23 => '天皇誕生日'},
89             },
90             },
91             # 4/29 みどりの日 => 昭和の日 変更
92             # みどりの日は5/4に移行
93             {'start' => 2007, 'end' => 2015,
94             'days' => {1 => { 1 => '元日'},
95             2 => {11 => '建国記念の日'},
96             4 => {29 => '昭和の日'},
97             5 => { 3 => '憲法記念日',
98             4 => 'みどりの日',
99             5 => 'こどもの日'},
100             11 => { 3 => '文化の日',
101             23 => '勤労感謝の日'},
102             12 => {23 => '天皇誕生日'},
103             },
104             },
105             # 海の日,敬老の日がHappy Mondayに
106             {'start' => 2003, 'end' => 2006,
107             'days' => {1 => { 1 => '元日'},
108             2 => {11 => '建国記念の日'},
109             4 => {29 => 'みどりの日'},
110             5 => { 3 => '憲法記念日',
111             5 => 'こどもの日'},
112             11 => { 3 => '文化の日',
113             23 => '勤労感謝の日'},
114             12 => {23 => '天皇誕生日'},
115             },
116             },
117             # 成人の日,体育の日がHappy Mondayに
118             {'start' => 2000, 'end' => 2002,
119             'days' => {1 => { 1 => '元日'},
120             2 => {11 => '建国記念の日'},
121             4 => {29 => 'みどりの日'},
122             5 => { 3 => '憲法記念日',
123             5 => 'こどもの日'},
124             7 => {20 => '海の日'},
125             9 => {15 => '敬老の日'},
126             11 => { 3 => '文化の日',
127             23 => '勤労感謝の日'},
128             12 => {23 => '天皇誕生日'},
129             },
130             },
131             # 海の日追加
132             {'start' => 1996, 'end' => 1999,
133             'days' => {1 => { 1 => '元日',
134             15 => '成人の日'},
135             2 => {11 => '建国記念の日'},
136             4 => {29 => 'みどりの日'},
137             5 => { 3 => '憲法記念日',
138             5 => 'こどもの日'},
139             7 => {20 => '海の日'},
140             9 => {15 => '敬老の日'},
141             10 => {10 => '体育の日'},
142             11 => { 3 => '文化の日',
143             23 => '勤労感謝の日'},
144             12 => {23 => '天皇誕生日'},
145             },
146             },
147             # 天皇誕生日変更 4/29 => 12/23
148             # 旧天皇誕生日をみどりの日に変更
149             {'start' => 1989, 'end' => 1995,
150             'days' => {1 => { 1 => '元日',
151             15 => '成人の日'},
152             2 => {11 => '建国記念の日'},
153             4 => {29 => 'みどりの日'},
154             5 => { 3 => '憲法記念日',
155             5 => 'こどもの日'},
156             9 => {15 => '敬老の日'},
157             10 => {10 => '体育の日'},
158             11 => { 3 => '文化の日',
159             23 => '勤労感謝の日'},
160             12 => {23 => '天皇誕生日'},
161             },
162             },
163             # 建国記念の日追加
164             {'start' => 1967, 'end' => 1988,
165             'days' => {1 => { 1 => '元日',
166             15 => '成人の日'},
167             2 => {11 => '建国記念の日'},
168             4 => {29 => '天皇誕生日'},
169             5 => { 3 => '憲法記念日',
170             5 => 'こどもの日'},
171             9 => {15 => '敬老の日'},
172             10 => {10 => '体育の日'},
173             11 => { 3 => '文化の日',
174             23 => '勤労感謝の日'},
175             },
176             },
177             # 敬老の日,体育の日追加
178             {'start' => 1966, 'end' => 1966,
179             'days' => {1 => { 1 => '元日',
180             15 => '成人の日'},
181             4 => {29 => '天皇誕生日'},
182             5 => { 3 => '憲法記念日',
183             5 => 'こどもの日'},
184             9 => {15 => '敬老の日'},
185             10 => {10 => '体育の日'},
186             11 => { 3 => '文化の日',
187             23 => '勤労感謝の日'},
188             },
189             },
190             # 国民の祝日に関する法律に定められた祝日のうち7/20以前のものを追加
191             {'start' => 1949, 'end' => 1965,
192             'days' => {1 => { 1 => '元日',
193             15 => '成人の日'},
194             4 => {29 => '天皇誕生日'},
195             5 => { 3 => '憲法記念日',
196             5 => 'こどもの日'},
197             11 => { 3 => '文化の日',
198             23 => '勤労感謝の日'},
199             },
200             },
201             # 国民の祝日に関する法律 1948/7/20制定
202             {'start' => 1948, 'end' => 1948,
203             'days' => {11 => { 3 => '文化の日',
204             23 => '勤労感謝の日'},
205             },
206             },
207             );
208              
209             my %ExceptionalHoliday = (
210             195904 => {10 => '皇太子明仁親王の結婚の儀'},
211             198902 => {24 => '昭和天皇の大喪の礼'},
212             199011 => {12 => '即位礼正殿の儀'},
213             199306 => { 9 => '皇太子徳仁親王の結婚の儀'},
214             201905 => { 1 => '天皇の即位の日'},
215             201910 => {22 => '即位礼正殿の儀の行われる日'},
216             );
217              
218             my @daysInMonth = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
219              
220             sub days_in_month {
221 3746     3746 0 7094 my ($year, $mon) = @_;
222              
223 3746         6846 my $days = $daysInMonth[$mon - 1];
224              
225 3746 100 100     8628 if ($mon == 2 && $year % 4 == 0) {
226 76 100       170 if ($year % 100 == 0) {
227 6 50       19 return $days + 1 if $year % 400 == 0;
228 0         0 return $days;
229             }
230 70         157 return $days + 1;
231             }
232              
233 3670         6621 return $days;
234             }
235              
236             # 指定曜日の日付一覧を配列で返す
237             sub weekdays {
238 2444     2444 0 4572 my ($year, $mon, $wday) = @_;
239              
240 2444         3769 my @week_days;
241              
242 2444         6260 my $wd = (localtime(timelocal(0, 0, 0, 1, $mon - 1, $year)))[6];
243              
244             # 指定曜日の最初の日付(カレンダー的に空欄の場合は0以下の値となる)
245 2444         177234 my $start = 1 - $wd + $wday;
246              
247 2444         6139 my $last_day = days_in_month($year, $mon);
248              
249 2444         5844 for (my $day = $start ; $day <= $last_day ; $day += 7) {
250 12358 100       30029 push @week_days, $day if $day > 0;
251             }
252              
253 2444         6628 return @week_days;
254             }
255              
256             sub lookup_holiday_table {
257 2444     2444 0 4352 my ($year) = @_;
258              
259 2444         4551 foreach my $tbl (@StaticHoliday) {
260             return $tbl->{days}
261 17772 100 66     42080 if ($tbl->{start} <= $year && $year <= $tbl->{end});
262             }
263 0         0 return;
264             }
265              
266             # 春分の日
267             # Ref to.
268             # http://www.nao.ac.jp/QA/faq/a0301.html
269             # http://ja.wikipedia.org/wiki/%E6%98%A5%E5%88%86%E3%81%AE%E6%97%A5
270             sub shunbun_day {
271 141     141 0 261 my ($year) = @_;
272              
273 141         200 my $day;
274              
275 141         258 my $mod = $year % 4;
276 141 100       420 if ($mod == 0) {
    100          
    100          
    50          
277 33 50 33     187 if (1900 <= $year && $year <= 1956) {$day = 21;}
  0 50 33     0  
    0 0        
278 33         59 elsif (1960 <= $year && $year <= 2088) {$day = 20;}
279 0         0 elsif (2092 <= $year && $year <= 2096) {$day = 19;}
280             } elsif ($mod == 1) {
281 34 100 66     177 if (1901 <= $year && $year <= 1989) {$day = 21;}
  3 50 33     6  
282 31         48 elsif (1993 <= $year && $year <= 2097) {$day = 20;}
283             } elsif ($mod == 2) {
284 35 100 66     201 if (1902 <= $year && $year <= 2022) {$day = 21;}
  27 50 33     44  
285 8         14 elsif (2026 <= $year && $year <= 2098) {$day = 20;}
286             } elsif ($mod == 3) {
287 39 50 33     216 if (1903 <= $year && $year <= 1923) {$day = 22;}
  0 50 33     0  
    0 0        
288 39         63 elsif (1927 <= $year && $year <= 2055) {$day = 21;}
289 0         0 elsif (2059 <= $year && $year <= 2099) {$day = 20;}
290             }
291              
292 141         504 return $day;
293             }
294              
295             # 秋分の日
296             sub shuubun_day {
297 167     167 0 309 my ($year) = @_;
298              
299 167         263 my $day;
300              
301 167         266 my $mod = $year % 4;
302 167 100       438 if ($mod == 0) {
    100          
    100          
    50          
303 38 50 66     191 if ($year == 1900) {$day = 23;}
  0 100 33     0  
    50          
304 28         50 elsif (1904 <= $year && $year <= 2008) {$day = 23;}
305 10         44 elsif (2012 <= $year && $year <= 2096) {$day = 22;}
306             } elsif ($mod == 1) {
307 38 50 33     228 if (1901 <= $year && $year <= 1917) {$day = 24;}
  0 50 33     0  
    0 0        
308 38         64 elsif (1921 <= $year && $year <= 2041) {$day = 23;}
309 0         0 elsif (2045 <= $year && $year <= 2097) {$day = 22;}
310             } elsif ($mod == 2) {
311 40 50 33     201 if (1902 <= $year && $year <= 1946) {$day = 24;}
  0 50 33     0  
    0 0        
312 40         60 elsif (1950 <= $year && $year <= 2074) {$day = 23;}
313 0         0 elsif (2078 <= $year && $year <= 2098) {$day = 22;}
314             } elsif ($mod == 3) {
315 51 100 66     236 if (1903 <= $year && $year <= 1979) {$day = 24;}
  8 50 33     16  
316 43         73 elsif (1983 <= $year && $year <= 2099) {$day = 23;}
317             }
318              
319 167         472 return $day;
320             }
321              
322             sub furikae_days {
323 1107     1107 0 2041 my ($year, $mon, $holidays_tbl) = @_;
324              
325 1107         1680 my %days;
326              
327 1107 100       2363 return \%days if $year < 1973;
328              
329 786         2389 while (my ($h_day, $name) = each %$holidays_tbl) {
330             # 祝日が日曜日かチェック
331 1341         4084 my $wday = (localtime(timelocal(0, 0, 0, $h_day, $mon - 1, $year)))[6];
332              
333 1341 100       93410 if ($wday == 0) {
334 178         433 my $furikae_day = $h_day + 1;
335 178 100       392 if ($year >= 2007) {
336             # 振り替えた先も祝日ならさらに進める
337 88         263 $furikae_day++ while (exists $holidays_tbl->{$furikae_day});
338 88         435 $days{$furikae_day} = $name;
339             } else {
340             $days{$furikae_day} = $name
341 90 50       549 if (!exists $holidays_tbl->{$furikae_day});
342             }
343             }
344             }
345              
346 786         1929 return \%days;
347             }
348              
349             # 指定年月の休日一覧を取得(国民の休日、振替休日を処理する前)
350             sub get_holidays {
351 2444     2444 0 4368 my ($year, $mon) = @_;
352              
353 2444         3435 my $holiday_tbl;
354              
355 2444 50       4583 return if !($holiday_tbl = lookup_holiday_table($year));
356              
357 2444         4034 my %holidays;
358 2444 100       5060 if (exists $holiday_tbl->{$mon}) {
359 1599         2215 %holidays = %{$holiday_tbl->{$mon}}; # Copy
  1599         5499  
360             }
361              
362             # Happy Monday (成人の日、海の日、敬老の日、体育の日)
363 2444         5426 my @mondays = weekdays($year, $mon, 1); # 月曜日の一覧
364              
365 2444 100 100     7993 if ($year >= 2000 && $mon == 1) {$holidays{$mondays[1]} = '成人の日';}
  134         437  
366              
367             # 体育の日/スポーツの日(2020年以降)
368 2444 100 100     14667 if ($year >= 2000 && $year <= 2019 && $mon == 10) {
    100 100        
    100 100        
    100 100        
      100        
369 83         308 $holidays{$mondays[1]} = '体育の日';
370             } elsif ($year == 2020 && $mon == 7) {
371             # 2020年はオリンピックにあわせて変更となりHappy Mondayではない
372 3         18 $holidays{24} = 'スポーツの日';
373             } elsif ($year == 2021 && $mon == 7) {
374             # 2021年もオリンピックにあわせて変更となりHappy Mondayではない
375 4         24 $holidays{23} = 'スポーツの日';
376             } elsif ($year >= 2022 && $mon == 10) {
377             # 2022年以降は第二月曜に戻る
378 28         138 $holidays{$mondays[1]} = 'スポーツの日';
379             }
380              
381             # 海の日追加
382 2444 100 100     6384 if ($year >= 2003 && $mon == 7) {
383 44 100       111 if ($year == 2020) {$holidays{23} = '海の日';} # 2020年は7/23に変更
  3 100       9  
384 4         11 elsif ($year == 2021) {$holidays{22} = '海の日';} # 2021年は7/22に変更
385             else {
386 37         139 $holidays{$mondays[2]} = '海の日';
387             }
388             }
389              
390 2444 100 100     6132 if ($year >= 2003 && $mon == 9) {$holidays{$mondays[2]} = '敬老の日';}
  75         265  
391              
392             # 不定なもの
393 2444 100       4426 if ($mon == 3) {$holidays{shunbun_day($year)} = '春分の日';}
  141         307  
394 2444 100       4378 if ($mon == 9) {$holidays{shuubun_day($year)} = '秋分の日';}
  167         362  
395              
396             # 例外的なもの
397 2444         8850 my $yymm = sprintf("%04d%02d", $year, $mon);
398 2444 100       5423 if (exists $ExceptionalHoliday{$yymm}) {
399 39         71 while (my ($day, $name) = each %{$ExceptionalHoliday{$yymm}}) {
  78         279  
400 39         119 $holidays{$day} = $name;
401             }
402             }
403              
404 2444         6513 return \%holidays;
405             }
406              
407             sub next_year_mon {
408 651     651 0 1178 my ($year, $mon) = @_;
409              
410 651         933 $mon++;
411 651 100       1258 if ($mon > 12) {
412 33         55 $year++;
413 33         55 $mon = 1;
414             }
415 651         1254 return ($year, $mon);
416             }
417              
418             sub prev_year_mon {
419 1302     1302 0 2217 my ($year, $mon) = @_;
420              
421 1302         1859 $mon--;
422 1302 100       2393 if ($mon < 1) {
423 192         266 $year--;
424 192         319 $mon = 12;
425             }
426 1302         2445 return ($year, $mon);
427             }
428              
429             sub getHolidays {
430 1142     1142 1 12241 my ($year, $mon, $furikae) = @_;
431              
432 1142         1621 $year = int($year);
433 1142         1518 $mon = int($mon);
434 1142 50 33     3545 if ($mon < 1 || $mon > 12) {
435 0         0 die('$mon argument is out of range.');
436             }
437              
438 1142         2317 my $holidays = get_holidays($year, $mon);
439              
440 1142 50       2477 return if not defined $holidays;
441              
442             # 国民の休日
443 1142 100       2285 if ($year >= 1986) {
444             # 祝日に挟まれた平日を探す (祝日A - 平日B - 祝日C)
445              
446             # 休日検索用テーブル
447             # 祝日Aと祝日Cが月をまたぐケースもあるので、前後の月の情報も結合する
448 651         2550 my %holidays_search_table = %$holidays;
449 651         1560 my $next_holidays = get_holidays(next_year_mon($year, $mon));
450 651 50       1864 if ($next_holidays) {
451 651         1212 my $offset = days_in_month($year, $mon);
452 651         2573 while (my ($d, $name) = each %$next_holidays) {
453 682         3119 $holidays_search_table{$d + $offset} = $name;
454             }
455             }
456 651         1577 my $prev_holidays = get_holidays(prev_year_mon($year, $mon));
457 651 50       1400 if ($prev_holidays) {
458 651         1402 my $offset = -days_in_month(prev_year_mon($year, $mon));
459 651         2712 while (my ($d, $name) = each %$prev_holidays) {
460 679         3213 $holidays_search_table{$d + $offset} = $name;
461             }
462             }
463              
464 651         1904 foreach my $day (keys %$holidays) {
465 1099 100 100     4377 if ( exists $holidays_search_table{$day + 2} &&
466             !exists $holidays_search_table{$day + 1}) {
467 64         203 my $wday = (localtime(timelocal(0, 0, 0,
468             $day, $mon - 1, $year)))[6];
469             # 祝日Aの時は平日Bはただの振り替え休日
470 64 100       4470 next if $wday == 0;
471              
472             # 平日Bが日曜の場合も国民の休日とはならない
473 56 100       136 next if $wday == 6;
474              
475 48         202 $holidays->{$day + 1} = '国民の休日';
476             }
477             }
478             }
479              
480             # 振り替え休日も含める
481 1142 100       2461 if ($furikae) {
482 1107         2428 my $furikae_days = furikae_days($year, $mon, $holidays);
483              
484 1107         4184 while (my ($val, $name) = each %$furikae_days) {
485 178         721 $holidays->{$val} = $FurikaeStr;
486             }
487             }
488              
489 1142         2532 return $holidays;
490             }
491              
492             my $Cache_holidays_Year = 0;
493             my $Cache_holidays_Month = 0;
494             my $Cache_holidays;
495              
496             sub isHoliday {
497 1100     1100 1 27478 my ($year, $mon, $day, $furikae) = @_;
498              
499 1100         1722 $year = int($year);
500 1100         1481 $mon = int($mon);
501 1100         1692 $day = int($day);
502              
503 1100 50 33     3611 if ($mon < 1 || $mon > 12) {
504 0         0 die('$mon argument is out of range.');
505             }
506              
507 1100         1565 my $holidays;
508              
509 1100 100 100     2424 if ($year == $Cache_holidays_Year &&
510             $mon == $Cache_holidays_Month) {
511 2         4 $holidays = $Cache_holidays; # From Cache
512             } else {
513 1098         2145 $holidays = getHolidays($year, $mon, 1);
514 1098 50       2275 return if not defined $holidays;
515             # Cache
516 1098         2084 $Cache_holidays = $holidays;
517 1098         1634 $Cache_holidays_Year = $year;
518 1098         1537 $Cache_holidays_Month = $mon;
519             }
520              
521 1100 100       2756 return if !exists $holidays->{$day};
522              
523 856 100 100     3474 return if (!$furikae && $holidays->{$day} eq $FurikaeStr);
524              
525 853         2395 return $holidays->{$day};
526             }
527              
528             1;
529             __END__