File Coverage

blib/lib/Calendar/Japanese/Holiday.pm
Criterion Covered Total %
statement 161 174 92.5
branch 100 132 75.7
condition 57 102 55.8
subroutine 16 16 100.0
pod 2 11 18.1
total 336 435 77.2


line stmt bran cond sub pod time code
1             package Calendar::Japanese::Holiday;
2              
3 1     1   67309 use 5.008001;
  1         5  
4 1     1   5 use strict;
  1         2  
  1         30  
5 1     1   6 use warnings;
  1         2  
  1         25  
6              
7 1     1   6 use utf8;
  1         2  
  1         6  
8 1     1   521 use Time::Local;
  1         2262  
  1         2106  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13              
14             our @EXPORT = qw(getHolidays isHoliday);
15              
16             our $VERSION = '0.06';
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 3731     3731 0 7084 my ($year, $mon) = @_;
208              
209 3731         6815 my $days = $daysInMonth[$mon - 1];
210              
211 3731 100 100     8727 if ($mon == 2 && $year % 4 == 0) {
212 76 100       162 if ($year % 100 == 0) {
213 6 50       21 return $days + 1 if $year % 400 == 0;
214 0         0 return $days;
215             }
216 70         160 return $days + 1;
217             }
218              
219 3655         6379 return $days;
220             }
221              
222             # 指定曜日の日付一覧を配列で返す
223             sub weekdays {
224 2435     2435 0 4733 my ($year, $mon, $wday) = @_;
225              
226 2435         3384 my @week_days;
227              
228 2435         6759 my $wd = (localtime(timelocal(0, 0, 0, 1, $mon - 1, $year)))[6];
229              
230             # 指定曜日の最初の日付(カレンダー的に空欄の場合は0以下の値となる)
231 2435         164817 my $start = 1 - $wd + $wday;
232              
233 2435         5548 my $last_day = days_in_month($year, $mon);
234              
235 2435         5977 for (my $day = $start ; $day <= $last_day ; $day += 7) {
236 12312 100       28266 push @week_days, $day if $day > 0;
237             }
238              
239 2435         6057 return @week_days;
240             }
241              
242             sub lookup_holiday_table {
243 2435     2435 0 3855 my ($year) = @_;
244              
245 2435         4544 foreach my $tbl (@StaticHoliday) {
246             return $tbl->{days}
247 15674 100 66     38403 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 141     141 0 234 my ($year) = @_;
258              
259 141         209 my $day;
260              
261 141         245 my $mod = $year % 4;
262 141 100       382 if ($mod == 0) {
    100          
    100          
    50          
263 33 50 33     153 if (1900 <= $year && $year <= 1956) {$day = 21;}
  0 50 33     0  
    0 0        
264 33         47 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     185 if (1901 <= $year && $year <= 1989) {$day = 21;}
  3 50 33     6  
268 31         50 elsif (1993 <= $year && $year <= 2097) {$day = 20;}
269             } elsif ($mod == 2) {
270 35 100 66     133 if (1902 <= $year && $year <= 2022) {$day = 21;}
  27 50 33     46  
271 8         14 elsif (2026 <= $year && $year <= 2098) {$day = 20;}
272             } elsif ($mod == 3) {
273 39 50 33     186 if (1903 <= $year && $year <= 1923) {$day = 22;}
  0 50 33     0  
    0 0        
274 39         61 elsif (1927 <= $year && $year <= 2055) {$day = 21;}
275 0         0 elsif (2059 <= $year && $year <= 2099) {$day = 20;}
276             }
277              
278 141         475 return $day;
279             }
280              
281             # 秋分の日
282             sub shuubun_day {
283 166     166 0 266 my ($year) = @_;
284              
285 166         275 my $day;
286              
287 166         264 my $mod = $year % 4;
288 166 100       456 if ($mod == 0) {
    100          
    100          
    50          
289 38 50 66     174 if ($year == 1900) {$day = 23;}
  0 100 33     0  
    50          
290 28         47 elsif (1904 <= $year && $year <= 2008) {$day = 23;}
291 10         28 elsif (2012 <= $year && $year <= 2096) {$day = 22;}
292             } elsif ($mod == 1) {
293 38 50 33     201 if (1901 <= $year && $year <= 1917) {$day = 24;}
  0 50 33     0  
    0 0        
294 38         60 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     194 if (1902 <= $year && $year <= 1946) {$day = 24;}
  0 50 33     0  
    0 0        
298 39         59 elsif (1950 <= $year && $year <= 2074) {$day = 23;}
299 0         0 elsif (2078 <= $year && $year <= 2098) {$day = 22;}
300             } elsif ($mod == 3) {
301 51 100 66     242 if (1903 <= $year && $year <= 1979) {$day = 24;}
  8 50 33     16  
302 43         64 elsif (1983 <= $year && $year <= 2099) {$day = 23;}
303             }
304              
305 166         458 return $day;
306             }
307              
308             sub furikae_days {
309 1104     1104 0 1984 my ($year, $mon, $holidays_tbl) = @_;
310              
311 1104         1524 my %days;
312              
313 1104 100       2218 return \%days if $year < 1973;
314              
315 783         2185 while (my ($h_day, $name) = each %$holidays_tbl) {
316             # 祝日が日曜日かチェック
317 1336         3927 my $wday = (localtime(timelocal(0, 0, 0, $h_day, $mon - 1, $year)))[6];
318              
319 1336 100       86067 if ($wday == 0) {
320 176         345 my $furikae_day = $h_day + 1;
321 176 100       362 if ($year >= 2007) {
322             # 振り替えた先も祝日ならさらに進める
323 86         257 $furikae_day++ while (exists $holidays_tbl->{$furikae_day});
324 86         399 $days{$furikae_day} = $name;
325             } else {
326             $days{$furikae_day} = $name
327 90 50       521 if (!exists $holidays_tbl->{$furikae_day});
328             }
329             }
330             }
331              
332 783         1916 return \%days;
333             }
334              
335             # 指定年月の休日一覧を取得(国民の休日、振替休日を処理する前)
336             sub get_holidays {
337 2435     2435 0 4031 my ($year, $mon) = @_;
338              
339 2435         3448 my $holiday_tbl;
340              
341 2435 50       4461 return if !($holiday_tbl = lookup_holiday_table($year));
342              
343 2435         4147 my %holidays;
344 2435 100       5376 if (exists $holiday_tbl->{$mon}) {
345 1596         2352 %holidays = %{$holiday_tbl->{$mon}}; # Copy
  1596         5408  
346             }
347              
348             # Happy Monday (成人の日、海の日、敬老の日、体育の日)
349 2435         5495 my @mondays = weekdays($year, $mon, 1); # 月曜日の一覧
350              
351 2435 100 100     7303 if ($year >= 2000 && $mon == 1) {$holidays{$mondays[1]} = '成人の日';}
  134         416  
352              
353             # 体育の日/スポーツの日(2020年以降)
354 2435 100 100     12287 if ($year >= 2000 && $year <= 2019 && $mon == 10) {
    100 100        
    100 100        
      100        
355 83         286 $holidays{$mondays[1]} = '体育の日';
356             } elsif ($year == 2020 && $mon == 7) {
357             # 2020年はオリンピックにあわせて変更となりHappy Mondayではない
358 3         11 $holidays{24} = 'スポーツの日';
359             } elsif ($year >= 2021 && $mon == 10) {
360             # 2021年以降は第二月曜に戻る
361 31         126 $holidays{$mondays[1]} = 'スポーツの日';
362             }
363              
364             # 海の日追加
365 2435 100 100     6066 if ($year >= 2003 && $mon == 7) {
366 41 100       86 if ($year != 2020) {
367 38         123 $holidays{$mondays[2]} = '海の日';
368             } else {
369 3         7 $holidays{23} = '海の日'; # 2020年は7/23に変更される
370             }
371             }
372              
373 2435 100 100     5918 if ($year >= 2003 && $mon == 9) {$holidays{$mondays[2]} = '敬老の日';}
  74         241  
374              
375             # 不定なもの
376 2435 100       4460 if ($mon == 3) {$holidays{shunbun_day($year)} = '春分の日';}
  141         290  
377 2435 100       4552 if ($mon == 9) {$holidays{shuubun_day($year)} = '秋分の日';}
  166         379  
378              
379             # 例外的なもの
380 2435         8385 my $yymm = sprintf("%04d%02d", $year, $mon);
381 2435 100       5358 if (exists $ExceptionalHoliday{$yymm}) {
382 39         83 while (my ($day, $name) = each %{$ExceptionalHoliday{$yymm}}) {
  78         257  
383 39         113 $holidays{$day} = $name;
384             }
385             }
386              
387 2435         6043 return \%holidays;
388             }
389              
390             sub next_year_mon {
391 648     648 0 1098 my ($year, $mon) = @_;
392              
393 648         945 $mon++;
394 648 100       1193 if ($mon > 12) {
395 33         61 $year++;
396 33         51 $mon = 1;
397             }
398 648         1369 return ($year, $mon);
399             }
400              
401             sub prev_year_mon {
402 1296     1296 0 2316 my ($year, $mon) = @_;
403              
404 1296         1885 $mon--;
405 1296 100       2533 if ($mon < 1) {
406 192         305 $year--;
407 192         275 $mon = 12;
408             }
409 1296         2512 return ($year, $mon);
410             }
411              
412             sub getHolidays {
413 1139     1139 1 11837 my ($year, $mon, $furikae) = @_;
414              
415 1139         1612 $year = int($year);
416 1139         1681 $mon = int($mon);
417 1139 50 33     3558 if ($mon < 1 || $mon > 12) {
418 0         0 die('$mon argument is out of range.');
419             }
420              
421 1139         2208 my $holidays = get_holidays($year, $mon);
422              
423 1139 50       2396 return if not defined $holidays;
424              
425             # 国民の休日
426 1139 100       2123 if ($year >= 1986) {
427             # 祝日に挟まれた平日を探す (祝日A - 平日B - 祝日C)
428              
429             # 休日検索用テーブル
430             # 祝日Aと祝日Cが月をまたぐケースもあるので、前後の月の情報も結合する
431 648         2670 my %holidays_search_table = %$holidays;
432 648         1637 my $next_holidays = get_holidays(next_year_mon($year, $mon));
433 648 50       1435 if ($next_holidays) {
434 648         1348 my $offset = days_in_month($year, $mon);
435 648         2471 while (my ($d, $name) = each %$next_holidays) {
436 679         2819 $holidays_search_table{$d + $offset} = $name;
437             }
438             }
439 648         1360 my $prev_holidays = get_holidays(prev_year_mon($year, $mon));
440 648 50       1362 if ($prev_holidays) {
441 648         1572 my $offset = -days_in_month(prev_year_mon($year, $mon));
442 648         2481 while (my ($d, $name) = each %$prev_holidays) {
443 678         2953 $holidays_search_table{$d + $offset} = $name;
444             }
445             }
446              
447 648         1784 foreach my $day (keys %$holidays) {
448 1094 100 100     4112 if ( exists $holidays_search_table{$day + 2} &&
449             !exists $holidays_search_table{$day + 1}) {
450 64         218 my $wday = (localtime(timelocal(0, 0, 0,
451             $day, $mon - 1, $year)))[6];
452             # 祝日Aの時は平日Bはただの振り替え休日
453 64 100       4488 next if $wday == 0;
454              
455             # 平日Bが日曜の場合も国民の休日とはならない
456 56 100       132 next if $wday == 6;
457              
458 48         204 $holidays->{$day + 1} = '国民の休日';
459             }
460             }
461             }
462              
463             # 振り替え休日も含める
464 1139 100       2341 if ($furikae) {
465 1104         2415 my $furikae_days = furikae_days($year, $mon, $holidays);
466              
467 1104         4041 while (my ($val, $name) = each %$furikae_days) {
468 176         703 $holidays->{$val} = $FurikaeStr;
469             }
470             }
471              
472 1139         2341 return $holidays;
473             }
474              
475             my $Cache_holidays_Year = 0;
476             my $Cache_holidays_Month = 0;
477             my $Cache_holidays;
478              
479             sub isHoliday {
480 1097     1097 1 25245 my ($year, $mon, $day, $furikae) = @_;
481              
482 1097         1723 $year = int($year);
483 1097         1530 $mon = int($mon);
484 1097         1524 $day = int($day);
485              
486 1097 50 33     3751 if ($mon < 1 || $mon > 12) {
487 0         0 die('$mon argument is out of range.');
488             }
489              
490 1097         1663 my $holidays;
491              
492 1097 100 100     2418 if ($year == $Cache_holidays_Year &&
493             $mon == $Cache_holidays_Month) {
494 2         20 $holidays = $Cache_holidays; # From Cache
495             } else {
496 1095         2035 $holidays = getHolidays($year, $mon, 1);
497 1095 50       2368 return if not defined $holidays;
498             # Cache
499 1095         2035 $Cache_holidays = $holidays;
500 1095         1666 $Cache_holidays_Year = $year;
501 1095         1577 $Cache_holidays_Month = $mon;
502             }
503              
504 1097 100       2735 return if !exists $holidays->{$day};
505              
506 853 100 100     3484 return if (!$furikae && $holidays->{$day} eq $FurikaeStr);
507              
508 850         2226 return $holidays->{$day};
509             }
510              
511             1;
512             __END__