Skip to main content

jp_holidays_lib/
client.rs

1//! クレートのエントリーポイントである `Client` を定義しています。
2
3use chrono::{Datelike, NaiveDate};
4
5/// `jp_holidays_lib::client::Client::init()` にて初期化を行います。
6///
7/// ### 関連関数
8///
9/// - `init()`: クライアントを初期化します。
10///
11/// ### メソッド
12///
13/// - `get_holiday()`: `chrono::NaiveDate` を渡して祝日を取得します。
14/// - `get_holiday_ymd()`: 年月日を渡して祝日を取得します。
15/// - `is_holiday()`:  `chrono::NaiveDate` を渡して祝日かどうかを判定します。
16/// - `is_holiday_ymd()`:  年月日を渡して祝日かどうかを判定します。
17/// - `is_day_off()`: `chrono::NaiveDate` を渡して休日かどうかを判定します。
18/// - `is_day_off_ymd.()`: 年月日を渡して休日かどうかを判定します。
19/// - `list_holidays()`: 公開されている祝日をすべて取得します (`BTreeMap<NaiveDate, String>`)
20pub struct Client {
21    data: std::collections::BTreeMap<NaiveDate, String>,
22}
23
24impl Client {
25    /// クライアントを初期化します。
26    ///
27    /// ## 使用例
28    ///
29    /// ```
30    #[doc = include_str!("../examples/basic.rs")]
31    /// ```
32    ///
33    /// ## キャッシュの利用
34    ///
35    /// 非同期ランタイムに `tokio` を使用している場合、以下のようにキャッシュを活用できます。
36    /// 
37    /// ```
38    #[doc = include_str!("../examples/cache.rs")]
39    /// ```    
40    pub async fn init() -> Result<Self, crate::error::Error> {
41        let holiday_repository = std::sync::Arc::new(crate::repository::HolidayRepositoryImpl);
42        let holiday_service =
43            std::sync::Arc::new(crate::service::HolidayService { holiday_repository });
44        let shiftjis_bytes = holiday_service.fetch_shiftjis_csv_bytes().await?;
45        let csv = holiday_service.parse_csv(shiftjis_bytes).await?;
46        let data = holiday_service.deserialize_csv(&csv)?;
47        Ok(Self { data })
48    }
49
50    #[cfg(test)]
51    async fn init_stub() -> Result<Self, crate::error::Error> {
52        let holiday_repository = std::sync::Arc::new(crate::repository::HolidayRepositoryStub);
53        let holiday_service =
54            std::sync::Arc::new(crate::service::HolidayService { holiday_repository });
55        let shiftjis_bytes = holiday_service.fetch_shiftjis_csv_bytes().await?;
56        let csv = holiday_service.parse_csv(shiftjis_bytes).await?;
57        let data = holiday_service.deserialize_csv(&csv)?;
58        Ok(Self { data })
59    }
60
61    /// 現在内閣府から公開されている範囲の祝日一覧を取得します。
62    ///
63    /// ## 使用例
64    ///
65    /// ```
66    #[doc = include_str!("../examples/list_holidays.rs")]
67    /// ```
68    pub fn list_holidays(&self) -> &std::collections::BTreeMap<NaiveDate, String> {
69        &self.data
70    }
71
72    /// `chrono::NaiveDate` を渡して祝日を取得します。
73    ///
74    /// ## 使用例
75    ///
76    /// ```
77    #[doc = include_str!("../examples/get_holiday.rs")]
78    /// ```
79    pub fn get_holiday(&self, date: NaiveDate) -> Option<&str> {
80        self.data.get(&date).map(|s| s.as_str())
81    }
82
83    /// 年月日を渡して祝日を取得します。
84    ///
85    /// ## 使用例
86    /// ```
87    #[doc = include_str!("../examples/get_holiday_ymd.rs")]
88    /// ```
89    pub fn get_holiday_ymd(
90        &self,
91        year: i32,
92        month: u32,
93        day: u32,
94    ) -> Result<Option<&str>, crate::error::Error> {
95        let date =
96            NaiveDate::from_ymd_opt(year, month, day).ok_or(crate::error::Error::InvalidDate(
97                format!("不正な日付です: {}年 {}月 {}日", year, month, day),
98            ))?;
99        Ok(self.get_holiday(date))
100    }
101
102    /// `chrono::NaiveDate` を渡して祝日かどうか確認します。
103    ///
104    /// ## 使用例
105    ///
106    /// ```
107    #[doc = include_str!("../examples/is_holiday.rs")]
108    /// ```
109    pub fn is_holiday(&self, date: NaiveDate) -> bool {
110        self.data.contains_key(&date)
111    }
112
113    /// 年月日を渡して祝日かどうか確認します。
114    ///
115    /// ## 使用例
116    ///
117    /// ```
118    #[doc = include_str!("../examples/is_holiday_ymd.rs")]
119    /// ```
120    pub fn is_holiday_ymd(
121        &self,
122        year: i32,
123        month: u32,
124        day: u32,
125    ) -> Result<bool, crate::error::Error> {
126        let date =
127            NaiveDate::from_ymd_opt(year, month, day).ok_or(crate::error::Error::InvalidDate(
128                format!("不正な日付です: {}年 {}月 {}日", year, month, day),
129            ))?;
130        Ok(self.is_holiday(date))
131    }
132
133    /// `chrono::NaiveDate` を渡して**休日**(祝日+土日)かどうか確認します。
134    ///
135    /// ## 使用例
136    ///
137    /// ```
138    #[doc = include_str!("../examples/is_day_off.rs")]
139    /// ```
140    pub fn is_day_off(&self, date: NaiveDate) -> bool {
141        matches!(date.weekday(), chrono::Weekday::Sat | chrono::Weekday::Sun)
142            || self.is_holiday(date)
143    }
144
145    /// 年月日を渡して**休日**(祝日+土日)かどうか確認します。
146    ///
147    /// ## 使用例
148    ///
149    /// ```
150    #[doc = include_str!("../examples/is_day_off_ymd.rs")]
151    /// ```
152    pub fn is_day_off_ymd(
153        &self,
154        year: i32,
155        month: u32,
156        day: u32,
157    ) -> Result<bool, crate::error::Error> {
158        let date =
159            NaiveDate::from_ymd_opt(year, month, day).ok_or(crate::error::Error::InvalidDate(
160                format!("不正な日付です: {}年 {}月 {}日", year, month, day),
161            ))?;
162        Ok(self.is_day_off(date))
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[tokio::test]
171    async fn test_get_holiday_known_date() {
172        let client = Client::init_stub().await.unwrap();
173        let holiday = client.get_holiday_ymd(1955, 1, 1).unwrap();
174        assert_eq!(holiday, Some("元日"));
175    }
176
177    #[tokio::test]
178    async fn test_get_holiday_unknown_date() {
179        let client = Client::init_stub().await.unwrap();
180        let holiday = client.get_holiday_ymd(1955, 1, 2).unwrap();
181        assert_eq!(holiday, None);
182    }
183
184    #[tokio::test]
185    async fn test_is_holiday_true() {
186        let client = Client::init_stub().await.unwrap();
187        let is_holiday = client.is_holiday_ymd(1955, 5, 5).unwrap();
188        assert!(is_holiday);
189    }
190
191    #[tokio::test]
192    async fn test_is_holiday_false() {
193        let client = Client::init_stub().await.unwrap();
194        let is_holiday = client.is_holiday_ymd(1955, 5, 4).unwrap();
195        assert!(!is_holiday);
196    }
197
198    #[tokio::test]
199    async fn test_invalid_date() {
200        let client = Client::init_stub().await.unwrap();
201        let result = client.get_holiday_ymd(1955, 2, 30);
202        assert!(result.is_err());
203    }
204
205    #[tokio::test]
206    async fn test_is_day_off_holiday() {
207        let client = Client::init_stub().await.unwrap();
208        let is_day_off = client.is_day_off_ymd(1955, 1, 1).unwrap();
209        assert!(is_day_off);
210    }
211
212    #[tokio::test]
213    async fn test_is_day_off_weekend() {
214        let client = Client::init_stub().await.unwrap();
215        let is_day_off = client.is_day_off_ymd(1955, 1, 8).unwrap();
216        assert!(is_day_off);
217    }
218
219    #[tokio::test]
220    async fn test_is_day_off_weekday_non_holiday() {
221        let client = Client::init_stub().await.unwrap();
222        let is_day_off = client.is_day_off_ymd(1955, 1, 5).unwrap();
223        assert!(!is_day_off);
224    }
225}