koyomi/
holiday.rs

1//! # 祝祭日定義
2//!
3//! 日本の法律で祝日・祭日となる日と、
4//! 指定日が祝祭日にあたるかどうかを判定する
5//! 関数を定義する。
6use crate::KoyomiResult;
7use crate::{Date, Weekday};
8
9/// 指定日が祝祭日にあたるかどうかを判定する
10///
11/// # Examples
12///
13/// ```rust
14/// use koyomi;
15///
16/// let date = koyomi::Date::from_ymd(2018, 1, 1).unwrap();
17/// let holiday = koyomi::holiday(&date);
18/// assert_eq!(holiday.unwrap(), "元日");
19///
20/// let date = koyomi::Date::from_ymd(2018, 1, 2).unwrap();
21/// let holiday = koyomi::holiday(&date);
22/// assert_eq!(holiday, None);
23/// ```
24pub fn holiday(date: &Date) -> Option<String> {
25    // 規定の祝祭日
26    defined_holiday(date)
27        // 振替休日(前日が日曜で祝日)
28        .or(substitute_holiday(date))
29        // 成人の日(1月第2月曜)
30        .or(variable_holiday(1, date, is_second_week))
31        // 海の日(7月第3月曜)
32        .or(variable_holiday(9, date, is_third_week))
33        // 敬老の日(9月第3月曜)
34        .or(variable_holiday(11, date, is_third_week))
35        // 体育の日(10月第2月曜)
36        .or(variable_holiday(12, date, is_second_week))
37        // 春分の日
38        .or(vernal_equinox_day(date))
39        // 秋分の日
40        .or(autumnal_equinox_day(date))
41        // 国民の休日(前後が祝日の平日)
42        .or(national_holiday(date))
43        // 年度ごとに発生するスポットの祝日
44        .or(spot_holiday(date))
45}
46
47/// 秋分日
48/// @see https://ja.wikipedia.org/wiki/秋分の日
49const AUTUMNAL_EQUINOX_DAYS: [[u32; 4]; 7] = [
50    [23, 24, 24, 24], // 1900-1919
51    [23, 23, 24, 24], // 1920-1947
52    [23, 23, 23, 24], // 1948-1979
53    [23, 23, 23, 23], // 1980-2011
54    [22, 23, 23, 23], // 2012-2043
55    [22, 22, 23, 23], // 2044-2075
56    [22, 22, 22, 23], // 2076-2099
57];
58
59/// 国民の祝日
60/// @see https://ja.wikipedia.org/wiki/国民の祝日
61const HOLIDAYS: [(&str, i32, u32, u32, Option<i32>); 16] = [
62    ("元日", 1948, 1, 1, None),
63    ("成人の日", 1948, 1, 15, Some(1999)),
64    ("建国記念日", 1967, 2, 11, None),
65    ("天皇誕生日", 1948, 4, 29, Some(1988)),
66    ("みどりの日", 1989, 4, 29, Some(2006)),
67    ("昭和の日", 2007, 4, 29, None),
68    ("憲法記念日", 1948, 5, 3, None),
69    ("みどりの日", 2007, 5, 4, None),
70    ("こどもの日", 1948, 5, 5, None),
71    ("海の日", 1996, 7, 20, Some(2002)),
72    ("山の日", 2016, 8, 11, None),
73    ("敬老の日", 1966, 9, 15, Some(2002)),
74    ("体育の日", 1966, 10, 10, Some(1999)),
75    ("文化の日", 1948, 11, 3, None),
76    ("勤労感謝の日", 1948, 11, 23, None),
77    ("天皇誕生日", 1989, 12, 23, Some(2018)),
78];
79
80/// 特定年のみ制定されるスポットの祝日
81const SPOT: [(&str, i32, u32, u32); 4] = [
82    ("国民の休日", 2019, 4, 30),
83    ("新天皇即位日", 2019, 5, 1),
84    ("国民の休日", 2019, 5, 2),
85    ("即位礼正殿の儀", 2019, 10, 22),
86];
87
88/// 春分日
89/// @see https://ja.wikipedia.org/wiki/春分の日
90const VERNAL_EQUINOX_DAYS: [[u32; 4]; 7] = [
91    [21, 21, 21, 22], // 1900-1923
92    [21, 21, 21, 21], // 1924-1959
93    [20, 21, 21, 21], // 1960-1991
94    [20, 20, 21, 21], // 1992-2023
95    [20, 20, 20, 21], // 2024-2055
96    [20, 20, 20, 20], // 2056-2091
97    [19, 20, 20, 20], // 2092-2099
98];
99
100/// 国民の祝日に関する法律が施行された年
101const HOLIDAY_FROM: i32 = 1948;
102
103/// 国民の休日に関する法律が施行された年
104const NATION_FROM: i32 = 1986;
105
106/// 1週間は何日か?(指定日が何週目にあたるかの判定で利用する)
107const ONE_WEEK: u32 = 7;
108
109/// 振替休日に関する法律が施行された年
110const SUBSTITUTE_FROM: i32 = 1973;
111
112/// 指定日が秋分の日かどうかを判定する
113fn autumnal_equinox_day(date: &Date) -> Option<String> {
114    if date.year() < HOLIDAY_FROM {
115        return None;
116    }
117
118    if date.month() != 9 {
119        return None;
120    }
121
122    let index = match date.year() {
123        1900..=1919 => Some(0),
124        1920..=1947 => Some(1),
125        1948..=1979 => Some(2),
126        1980..=2011 => Some(3),
127        2012..=2043 => Some(4),
128        2044..=2075 => Some(5),
129        2076..=2099 => Some(6),
130        _ => None,
131    };
132
133    match index {
134        None => None,
135        Some(i) => {
136            let m = date.year() % 4;
137            let d = AUTUMNAL_EQUINOX_DAYS[i as usize][m as usize];
138            if date.day() == d {
139                Some("秋分の日".into())
140            } else {
141                None
142            }
143        }
144    }
145}
146
147/// 指定日が定義された祝祭日かどうかを判定する
148fn defined_holiday(date: &Date) -> Option<String> {
149    if date.year() < HOLIDAY_FROM {
150        return None;
151    }
152
153    HOLIDAYS
154        .iter()
155        .filter(|h| match h.4 {
156            Some(until) => h.1 <= date.year() && date.year() <= until,
157            None => h.1 <= date.year(),
158        })
159        .filter(|h| date.month() == h.2 && date.day() == h.3)
160        .nth(0)
161        .map(|h| h.0.into())
162}
163
164/// 指定日が第2週にあたるかどうかを判定する
165/// 第2週の月曜日となった、成人の日・体育の日を判定するために利用する
166fn is_second_week(day: u32) -> bool {
167    (day / ONE_WEEK == 2 && day % ONE_WEEK == 0) || (day / ONE_WEEK == 1 && day % ONE_WEEK > 0)
168}
169
170/// 指定日が第3週にあたるかどうかを判定する
171/// 第3週の月曜日となった、海の日・敬老の日を判定するために利用する
172fn is_third_week(day: u32) -> bool {
173    (day / ONE_WEEK == 3 && day % ONE_WEEK == 0) || (day / ONE_WEEK == 2 && day % ONE_WEEK >= 1)
174}
175
176/// 指定日が国民の休日にあたるかどうかを判定する
177fn national_holiday(date: &Date) -> Option<String> {
178    if date.year() < NATION_FROM {
179        return None;
180    }
181
182    if date.weekday() == &Weekday::Sunday {
183        return None;
184    }
185
186    if defined_holiday(&date).is_some() {
187        return None;
188    }
189
190    // シルバーウィークで、敬老の日(可変)を考慮する必要がある
191    let yesterday = date.yesterday().ok()?;
192    let between = defined_holiday(&yesterday).or(variable_holiday(11, &yesterday, is_third_week));
193    if between.is_none() {
194        return None;
195    }
196
197    // シルバーウィークで、秋分の日を考慮する必要がある
198    let tomorrow = date.tomorrow().ok()?;
199    let between = defined_holiday(&tomorrow).or(autumnal_equinox_day(&tomorrow));
200    if between.is_none() {
201        return None;
202    }
203
204    Some("国民の休日".into())
205}
206
207/// 指定日がスポットの休日かどうかを判定する
208/// 祝日には、特定の年度だけ制定されるものもある
209fn spot_holiday(date: &Date) -> Option<String> {
210    SPOT.iter()
211        .filter(|t| t.1 == date.year() && t.2 == date.month() && t.3 == date.day())
212        .nth(0)
213        .map(|h| h.0.into())
214}
215
216/// 指定日が振替休日かどうかを判定する
217///
218/// 日曜が祝日の場合は、その次の平日が振替休日となるため、
219/// 遡って判定する必要がある(月曜日とは限らない)
220fn substitute(yesterday: KoyomiResult<Date>) -> Option<String> {
221    match yesterday {
222        Err(_) => None,
223        Ok(y) => {
224            let holiday = defined_holiday(&y)
225                .or(vernal_equinox_day(&y))
226                .or(autumnal_equinox_day(&y));
227            match holiday {
228                None => None,
229                Some(_) if y.weekday() == &Weekday::Sunday => Some("振替休日".into()),
230                Some(_) => substitute(y.yesterday()),
231            }
232        }
233    }
234}
235
236/// 指定日が振替休日かどうかを判定する
237fn substitute_holiday(date: &Date) -> Option<String> {
238    if date.year() < SUBSTITUTE_FROM {
239        None
240    } else {
241        substitute(date.yesterday())
242    }
243}
244
245/// 指定日が年ごとに変動する祝日かどうかを判定する
246fn variable_holiday(index: usize, date: &Date, week: impl Fn(u32) -> bool) -> Option<String> {
247    if date.month() != HOLIDAYS[index].2 {
248        return None;
249    }
250
251    if date.weekday() != &Weekday::Monday {
252        return None;
253    }
254
255    if date.year() <= HOLIDAYS[index].4.unwrap() {
256        return None;
257    }
258
259    if !week(date.day()) {
260        return None;
261    }
262
263    Some(HOLIDAYS[index].0.into())
264}
265
266/// 指定日が春分の日かどうかを判定する
267fn vernal_equinox_day(date: &Date) -> Option<String> {
268    if date.year() <= HOLIDAY_FROM {
269        return None;
270    }
271
272    if date.month() != 3 {
273        return None;
274    }
275
276    let index = match date.year() {
277        1900..=1923 => Some(0),
278        1924..=1959 => Some(1),
279        1960..=1991 => Some(2),
280        1992..=2023 => Some(3),
281        2024..=2055 => Some(4),
282        2056..=2091 => Some(5),
283        2092..=2099 => Some(6),
284        _ => None,
285    };
286
287    match index {
288        None => None,
289        Some(i) => {
290            let m = date.year() % 4;
291            let d = VERNAL_EQUINOX_DAYS[i as usize][m as usize];
292            if date.day() == d {
293                Some("春分の日".into())
294            } else {
295                None
296            }
297        }
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn new_years_day() {
307        let name = "元日";
308
309        let date = Date::from_ymd(1948, 1, 1).unwrap();
310        assert_eq!(holiday(&date).unwrap(), name);
311
312        let date = Date::from_ymd(1947, 1, 1).unwrap();
313        assert!(holiday(&date).is_none());
314    }
315
316    #[test]
317    fn fixed_coming_of_age() {
318        let name = "成人の日";
319
320        let date = Date::from_ymd(1948, 1, 15).unwrap();
321        assert_eq!(holiday(&date).unwrap(), name);
322
323        let date = Date::from_ymd(1999, 1, 15).unwrap();
324        assert_eq!(holiday(&date).unwrap(), name);
325
326        let date = Date::from_ymd(1947, 1, 15).unwrap();
327        assert!(holiday(&date).is_none());
328
329        let date = Date::from_ymd(2000, 1, 15).unwrap();
330        assert!(holiday(&date).is_none());
331    }
332
333    #[test]
334    fn variable_coming_of_age() {
335        let name = "成人の日";
336
337        let date = Date::from_ymd(2000, 1, 10).unwrap();
338        assert_eq!(holiday(&date).unwrap(), name);
339
340        let date = Date::from_ymd(1999, 1, 11).unwrap();
341        assert!(holiday(&date).is_none());
342
343        let date = Date::from_ymd(2018, 1, 9).unwrap();
344        assert!(holiday(&date).is_none());
345
346        let date = Date::from_ymd(2018, 3, 12).unwrap();
347        assert!(holiday(&date).is_none());
348    }
349
350    #[test]
351    fn national_foundation_day() {
352        let name = "建国記念日";
353
354        let date = Date::from_ymd(1967, 2, 11).unwrap();
355        assert_eq!(holiday(&date).unwrap(), name);
356
357        let date = Date::from_ymd(1966, 2, 11).unwrap();
358        assert!(holiday(&date).is_none());
359    }
360
361    #[test]
362    fn birthday_of_showa_emperor() {
363        let name = "天皇誕生日";
364
365        let date = Date::from_ymd(1947, 4, 29).unwrap();
366        assert!(holiday(&date).is_none());
367
368        let date = Date::from_ymd(1948, 4, 29).unwrap();
369        assert_eq!(holiday(&date).unwrap(), name);
370
371        let date = Date::from_ymd(1988, 4, 29).unwrap();
372        assert_eq!(holiday(&date).unwrap(), name);
373
374        let date = Date::from_ymd(1989, 4, 29).unwrap();
375        assert_ne!(holiday(&date).unwrap(), name);
376    }
377
378    #[test]
379    fn green_day() {
380        let name = "みどりの日";
381
382        let date = Date::from_ymd(1989, 4, 29).unwrap();
383        assert_eq!(holiday(&date).unwrap(), name);
384
385        let date = Date::from_ymd(2006, 4, 29).unwrap();
386        assert_eq!(holiday(&date).unwrap(), name);
387
388        let date = Date::from_ymd(2007, 5, 4).unwrap();
389        assert_eq!(holiday(&date).unwrap(), name);
390
391        let date = Date::from_ymd(2006, 5, 4).unwrap();
392        assert_ne!(holiday(&date).unwrap(), name);
393    }
394
395    #[test]
396    fn showa_day() {
397        let name = "昭和の日";
398
399        let date = Date::from_ymd(2007, 4, 29).unwrap();
400        assert_eq!(holiday(&date).unwrap(), name);
401    }
402
403    #[test]
404    fn constitution_day() {
405        let name = "憲法記念日";
406
407        let date = Date::from_ymd(1948, 5, 3).unwrap();
408        assert_eq!(holiday(&date).unwrap(), name);
409
410        let date = Date::from_ymd(1947, 5, 3).unwrap();
411        assert!(holiday(&date).is_none());
412    }
413
414    #[test]
415    fn childrens_day() {
416        let name = "こどもの日";
417
418        let date = Date::from_ymd(1948, 5, 5).unwrap();
419        assert_eq!(holiday(&date).unwrap(), name);
420
421        let date = Date::from_ymd(1947, 5, 5).unwrap();
422        assert!(holiday(&date).is_none());
423    }
424
425    #[test]
426    fn fixed_marine_day() {
427        let name = "海の日";
428
429        let date = Date::from_ymd(1996, 7, 20).unwrap();
430        assert_eq!(holiday(&date).unwrap(), name);
431
432        let date = Date::from_ymd(2002, 7, 20).unwrap();
433        assert_eq!(holiday(&date).unwrap(), name);
434
435        let date = Date::from_ymd(1995, 7, 20).unwrap();
436        assert!(holiday(&date).is_none());
437
438        let date = Date::from_ymd(2003, 7, 20).unwrap();
439        assert!(holiday(&date).is_none());
440    }
441
442    #[test]
443    fn variable_marine_day() {
444        let name = "海の日";
445
446        let date = Date::from_ymd(2003, 7, 21).unwrap();
447        assert_eq!(holiday(&date).unwrap(), name);
448
449        let date = Date::from_ymd(2002, 7, 15).unwrap();
450        assert!(holiday(&date).is_none());
451
452        let date = Date::from_ymd(2018, 7, 17).unwrap();
453        assert!(holiday(&date).is_none());
454
455        let date = Date::from_ymd(2018, 8, 20).unwrap();
456        assert!(holiday(&date).is_none());
457    }
458
459    #[test]
460    fn mountain_day() {
461        let name = "山の日";
462
463        let date = Date::from_ymd(2016, 8, 11).unwrap();
464        assert_eq!(holiday(&date).unwrap(), name);
465
466        let date = Date::from_ymd(2015, 8, 11).unwrap();
467        assert!(holiday(&date).is_none());
468    }
469
470    #[test]
471    fn fixed_respect_for_the_aged_day() {
472        let name = "敬老の日";
473
474        let date = Date::from_ymd(1966, 9, 15).unwrap();
475        assert_eq!(holiday(&date).unwrap(), name);
476
477        let date = Date::from_ymd(2002, 9, 15).unwrap();
478        assert_eq!(holiday(&date).unwrap(), name);
479
480        let date = Date::from_ymd(1965, 9, 15).unwrap();
481        assert!(holiday(&date).is_none());
482
483        let date = Date::from_ymd(2004, 9, 15).unwrap();
484        assert!(holiday(&date).is_none());
485    }
486
487    #[test]
488    fn variable_respect_for_the_aged_day() {
489        let name = "敬老の日";
490
491        let date = Date::from_ymd(2003, 9, 15).unwrap();
492        assert_eq!(holiday(&date).unwrap(), name);
493
494        let date = Date::from_ymd(2002, 9, 16).unwrap();
495        assert_ne!(holiday(&date).unwrap(), name);
496
497        let date = Date::from_ymd(2018, 9, 18).unwrap();
498        assert!(holiday(&date).is_none());
499
500        let date = Date::from_ymd(2018, 6, 18).unwrap();
501        assert!(holiday(&date).is_none());
502    }
503
504    #[test]
505    fn fixed_sports_day() {
506        let name = "体育の日";
507
508        let date = Date::from_ymd(1966, 10, 10).unwrap();
509        assert_eq!(holiday(&date).unwrap(), name);
510
511        let date = Date::from_ymd(1999, 10, 10).unwrap();
512        assert_eq!(holiday(&date).unwrap(), name);
513
514        let date = Date::from_ymd(1965, 10, 10).unwrap();
515        assert!(holiday(&date).is_none());
516
517        let date = Date::from_ymd(2000, 10, 10).unwrap();
518        assert!(holiday(&date).is_none());
519    }
520
521    #[test]
522    fn variable_sports_day() {
523        let name = "体育の日";
524
525        let date = Date::from_ymd(2000, 10, 9).unwrap();
526        assert_eq!(holiday(&date).unwrap(), name);
527
528        let date = Date::from_ymd(1999, 10, 11).unwrap();
529        assert_ne!(holiday(&date).unwrap(), name);
530
531        let date = Date::from_ymd(2018, 10, 9).unwrap();
532        assert!(holiday(&date).is_none());
533
534        let date = Date::from_ymd(2018, 11, 12).unwrap();
535        assert!(holiday(&date).is_none());
536    }
537
538    #[test]
539    fn culture_day() {
540        let name = "文化の日";
541
542        let date = Date::from_ymd(1948, 11, 3).unwrap();
543        assert_eq!(holiday(&date).unwrap(), name);
544
545        let date = Date::from_ymd(1947, 11, 3).unwrap();
546        assert!(holiday(&date).is_none());
547    }
548
549    #[test]
550    fn labor_thanksgiving_day() {
551        let name = "勤労感謝の日";
552
553        let date = Date::from_ymd(1948, 11, 23).unwrap();
554        assert_eq!(holiday(&date).unwrap(), name);
555
556        let date = Date::from_ymd(1947, 11, 23).unwrap();
557        assert!(holiday(&date).is_none());
558    }
559
560    #[test]
561    fn birthday_of_heisei_emperor() {
562        let name = "天皇誕生日";
563
564        let date = Date::from_ymd(1989, 12, 23).unwrap();
565        assert_eq!(holiday(&date).unwrap(), name);
566
567        let date = Date::from_ymd(1988, 12, 23).unwrap();
568        assert!(holiday(&date).is_none());
569
570        let date = Date::from_ymd(2018, 12, 23).unwrap();
571        assert_eq!(holiday(&date).unwrap(), name);
572
573        let date = Date::from_ymd(2019, 12, 23).unwrap();
574        assert!(holiday(&date).is_none());
575    }
576
577    #[test]
578    fn substitute_holiday() {
579        let name = "振替休日";
580
581        let date = Date::from_ymd(2018, 4, 30).unwrap();
582        assert_eq!(holiday(&date).unwrap(), name);
583
584        let date = Date::from_ymd(2018, 4, 23).unwrap();
585        assert!(holiday(&date).is_none());
586    }
587
588    #[test]
589    fn vernal_equinox_day() {
590        let name = "春分の日";
591
592        let date = Date::from_ymd(2018, 3, 21).unwrap();
593        assert_eq!(holiday(&date).unwrap(), name);
594
595        let date = Date::from_ymd(2018, 2, 21).unwrap();
596        assert!(holiday(&date).is_none());
597
598        let date = Date::from_ymd(1899, 3, 20).unwrap();
599        assert!(holiday(&date).is_none());
600
601        let date = Date::from_ymd(2300, 3, 20).unwrap();
602        assert!(holiday(&date).is_none());
603    }
604
605    #[test]
606    fn autumnal_equinox_day() {
607        let name = "秋分の日";
608
609        let date = Date::from_ymd(2018, 9, 23).unwrap();
610        assert_eq!(holiday(&date).unwrap(), name);
611
612        let date = Date::from_ymd(2018, 8, 23).unwrap();
613        assert!(holiday(&date).is_none());
614
615        let date = Date::from_ymd(1899, 9, 23).unwrap();
616        assert!(holiday(&date).is_none());
617
618        let date = Date::from_ymd(2300, 9, 23).unwrap();
619        assert!(holiday(&date).is_none());
620    }
621
622    #[test]
623    fn national_holiday() {
624        let name = "国民の休日";
625
626        let date = Date::from_ymd(1988, 5, 4).unwrap();
627        assert_eq!(holiday(&date).unwrap(), name);
628
629        let date = Date::from_ymd(1986, 5, 4).unwrap();
630        assert!(holiday(&date).is_none());
631
632        let date = Date::from_ymd(1987, 5, 4).unwrap();
633        assert_ne!(holiday(&date).unwrap(), name);
634    }
635
636    #[test]
637    fn special_holiday() {
638        let name = "新天皇即位日";
639
640        let date = Date::from_ymd(2019, 5, 1).unwrap();
641        assert_eq!(holiday(&date).unwrap(), name);
642
643        let date = Date::from_ymd(2018, 5, 1).unwrap();
644        assert!(holiday(&date).is_none());
645    }
646}