chinese_format/gregorian/date/
mod.rs

1mod day;
2mod errors;
3mod month;
4mod pattern;
5mod styled_week_day;
6mod week_day;
7mod week_format;
8mod year;
9
10pub use self::pattern::*;
11pub use self::week_day::*;
12pub use self::week_format::*;
13pub use errors::*;
14
15use self::{day::Day, month::Month, styled_week_day::StyledWeekDay, year::Year};
16use crate::GenericResult;
17use crate::{chinese_vec, Chinese, ChineseFormat, EmptyPlaceholder, Variant};
18
19/// Provides a configurable way to build [Date] instances.
20///
21/// The date can be incrementally set up by providing its individual
22/// components via a chain of the `with_` methods.
23///
24/// Upon [build](Self::build), the builder ensures the validity and the
25/// existence of the date - e.g.: 29th February only in leap years; however,
26/// consistency is **not** checked for week days, which can therefore be arbitrary.
27///
28/// ```
29/// use chinese_format::{*, gregorian::*};
30///
31/// # fn main() -> GenericResult<()> {
32/// let date = DateBuilder::new()
33///     .with_year(1998)
34///     .with_month(6)
35///     .with_day(13)
36///     .with_week_day(WeekDay::Saturday)
37///     .with_week_format(WeekFormat::LiBai)
38///     .with_formal(false)
39///     .build()?;
40///
41/// assert_eq!(date.to_chinese(Variant::Simplified), Chinese {
42///     logograms: "一九九八年六月十三日礼拜六".to_string(),
43///     omissible: false
44/// });
45///
46/// assert_eq!(date.to_chinese(Variant::Traditional), Chinese {
47///     logograms: "一九九八年六月十三日禮拜六".to_string(),
48///     omissible: false
49/// });
50///
51/// # Ok(())
52/// # }
53/// ```
54///
55/// The builder can also build single date components,
56/// as well as a range of reasonable patterns:
57///
58/// ```
59/// use chinese_format::{*, gregorian::*};
60///
61/// # fn main() -> GenericResult<()> {
62/// let single_year = DateBuilder::new()
63///     .with_year(1998)
64///     .build()?;
65/// assert_eq!(single_year.to_chinese(Variant::Simplified), "一九九八年");
66/// assert_eq!(single_year.to_chinese(Variant::Traditional), "一九九八年");
67///
68/// let single_month = DateBuilder::new()
69///     .with_month(4)
70///     .build()?;
71/// assert_eq!(single_month.to_chinese(Variant::Simplified), "四月");
72/// assert_eq!(single_month.to_chinese(Variant::Traditional), "四月");
73///
74/// let single_day = DateBuilder::new()
75///     .with_day(29)
76///     .with_formal(true)
77///     .build()?;
78/// assert_eq!(single_day.to_chinese(Variant::Simplified), "二十九号");
79/// assert_eq!(single_day.to_chinese(Variant::Traditional), "二十九號");
80///
81/// let single_week_day = DateBuilder::new()
82///     .with_week_day(WeekDay::Sunday)
83///     .with_week_format(WeekFormat::Zhou)
84///     .build()?;
85/// assert_eq!(single_week_day.to_chinese(Variant::Simplified), "周日");
86/// assert_eq!(single_week_day.to_chinese(Variant::Traditional), "周日");
87///
88/// let year_month = DateBuilder::new()
89///     .with_year(1996)
90///     .with_month(2)
91///     .build()?;
92/// assert_eq!(year_month.to_chinese(Variant::Simplified), "一九九六年二月");
93/// assert_eq!(year_month.to_chinese(Variant::Traditional), "一九九六年二月");
94///  
95/// let year_month_day = DateBuilder::new()
96///     .with_year(2014)
97///     .with_month(12)
98///     .with_day(25)
99///     .with_formal(false)
100///     .build()?;
101/// assert_eq!(year_month_day.to_chinese(Variant::Simplified), "二零一四年十二月二十五日");
102/// assert_eq!(year_month_day.to_chinese(Variant::Traditional), "二零一四年十二月二十五日");
103///  
104/// let month_day = DateBuilder::new()
105///     .with_month(4)
106///     .with_day(29)
107///     .with_formal(true)
108///     .build()?;
109/// assert_eq!(month_day.to_chinese(Variant::Simplified), "四月二十九号");
110/// assert_eq!(month_day.to_chinese(Variant::Traditional), "四月二十九號");
111///
112/// let month_day_week_day = DateBuilder::new()
113///     .with_month(10)
114///     .with_day(17)
115///     .with_formal(false)
116///     .with_week_format(WeekFormat::XingQi)
117///     .with_week_day(WeekDay::Monday)
118///     .build()?;
119/// assert_eq!(month_day_week_day.to_chinese(Variant::Simplified), "十月十七日星期一");
120/// assert_eq!(month_day_week_day.to_chinese(Variant::Traditional), "十月十七日星期一");
121///
122/// let day_week_day = DateBuilder::new()
123///     .with_day(16)
124///     .with_formal(false)
125///     .with_week_format(WeekFormat::Zhou)
126///     .with_week_day(WeekDay::Tuesday)
127///     .build()?;
128/// assert_eq!(day_week_day.to_chinese(Variant::Simplified), "十六日周二");
129/// assert_eq!(day_week_day.to_chinese(Variant::Traditional), "十六日周二");
130///
131/// # Ok(())
132/// # }
133/// ```
134///
135/// Invalid patterns (such as <year; week day>) and inexisting dates
136/// are not allowed: in these cases, building a [Date] returns the
137/// most suitable error.
138///
139/// ```
140/// use chinese_format::{*, gregorian::*};
141/// use dyn_error::*;
142///
143/// # fn main() -> GenericResult<()> {
144/// let absurd_builder = DateBuilder::new()
145///     .with_month(4)
146///     .with_day(31);
147/// assert_err_box!(absurd_builder.build(), InvalidDate {
148///     year: None,
149///     month: 4,
150///     day: 31,
151/// });
152///  
153/// let inexisting_builder = DateBuilder::new()
154///     .with_year(2023)
155///     .with_month(2)
156///     .with_day(29);
157/// assert_err_box!(inexisting_builder.build(), InvalidDate {
158///     year: Some(2023),
159///     month: 2,
160///     day: 29,
161/// });
162///
163/// let still_allowed_builder = DateBuilder::new()
164///     .with_month(2)
165///     .with_day(29);
166/// assert!(still_allowed_builder.build().is_ok());
167///
168/// let day_out_of_range_builder = DateBuilder::new()
169///     .with_year(2023)
170///     .with_month(2)
171///     .with_day(90);
172/// assert_err_box!(day_out_of_range_builder.build(), DayOutOfRange(90));
173///
174/// let month_out_of_range_builder = DateBuilder::new()
175///     .with_year(2023)
176///     .with_month(67)
177///     .with_day(9);
178/// assert_err_box!(month_out_of_range_builder.build(), MonthOutOfRange(67));
179///
180/// let invalid_pattern_builder = DateBuilder::new()
181///     .with_year(2023)
182///     .with_day(9);
183/// assert_err_box!(invalid_pattern_builder.build(), InvalidDatePattern("yd".to_string()));
184///
185/// # Ok(())
186/// # }
187/// ```
188///
189/// As an exception, the consistency of week day is not ensured:
190///
191/// ```
192/// use chinese_format::{*, gregorian::*};
193///
194/// # fn main() -> GenericResult<()> {
195/// let one_day_as_monday = DateBuilder::new()
196///     .with_month(5)
197///     .with_day(13)
198///     .with_formal(false)
199///     .with_week_format(WeekFormat::Zhou)
200///     .with_week_day(WeekDay::Tuesday)
201///     .build();
202/// assert!(one_day_as_monday.is_ok());
203///
204/// let same_day_week_as_saturday = DateBuilder::new()
205///     .with_month(5)
206///     .with_day(13)
207///     .with_formal(false)
208///     .with_week_format(WeekFormat::Zhou)
209///     .with_week_day(WeekDay::Saturday)
210///     .build();
211/// assert!(same_day_week_as_saturday.is_ok());
212///
213/// # Ok(())
214/// # }
215/// ```
216pub struct DateBuilder {
217    year: Option<u16>,
218    month: Option<u8>,
219    day: Option<u8>,
220    week_day: Option<WeekDay>,
221    formal: bool,
222    week_format: WeekFormat,
223}
224
225impl DateBuilder {
226    /// Creates the default instance of the builder.
227    pub fn new() -> Self {
228        Self::default()
229    }
230
231    /// Sets the year - a positive value.
232    pub fn with_year(mut self, year: u16) -> Self {
233        self.year = Some(year);
234        self
235    }
236
237    /// Sets the month - between 1 and 12.
238    pub fn with_month(mut self, month: u8) -> Self {
239        self.month = Some(month);
240        self
241    }
242
243    /// Sets the day - between 1 and 31.
244    pub fn with_day(mut self, day: u8) -> Self {
245        self.day = Some(day);
246        self
247    }
248
249    /// Sets the week day.
250    pub fn with_week_day(mut self, week_day: WeekDay) -> Self {
251        self.week_day = Some(week_day);
252        self
253    }
254
255    /// Sets whether the register is formal.
256    pub fn with_formal(mut self, formal: bool) -> Self {
257        self.formal = formal;
258        self
259    }
260
261    /// Sets the word used to express a week.
262    pub fn with_week_format(mut self, week_format: WeekFormat) -> Self {
263        self.week_format = week_format;
264        self
265    }
266
267    fn validate_consistency(&self, year: Option<&Year>) -> Result<(), InvalidDate> {
268        let is_leap_year = year.map(Year::is_leap).unwrap_or(true);
269
270        if let Some(month_ordinal) = self.month {
271            if let Some(day_ordinal) = self.day {
272                let day_is_valid = match month_ordinal {
273                    4 | 6 | 9 | 11 => day_ordinal <= 30,
274                    2 => {
275                        let max_day = if is_leap_year { 29 } else { 28 };
276                        day_ordinal <= max_day
277                    }
278                    _ => true,
279                };
280
281                if !day_is_valid {
282                    return Err(InvalidDate {
283                        year: self.year,
284                        month: month_ordinal,
285                        day: day_ordinal,
286                    });
287                }
288            }
289        }
290
291        Ok(())
292    }
293
294    /// Creates a [Date] instance based on the current parameters,
295    /// after performing validation.
296    pub fn build(&self) -> GenericResult<Date> {
297        DatePattern::validate(DatePatternFlags {
298            year: self.year.is_some(),
299            month: self.month.is_some(),
300            day: self.day.is_some(),
301            week_day: self.week_day.is_some(),
302        })?;
303
304        let year: Option<Year> = self.year.map(|year| year.into());
305
306        let month: Option<Month> = self
307            .month
308            .map(|month_ordinal| month_ordinal.try_into())
309            .transpose()?;
310
311        let day: Option<Day> = self
312            .day
313            .map(|day_ordinal| {
314                if self.formal {
315                    Day::try_new_formal(day_ordinal)
316                } else {
317                    Day::try_new_informal(day_ordinal)
318                }
319            })
320            .transpose()?;
321
322        self.validate_consistency(year.as_ref())?;
323
324        let week_day = self.week_day.map(|week_day| StyledWeekDay {
325            week_format: self.week_format,
326            week_day,
327        });
328
329        Ok(Date {
330            year,
331            month,
332            day,
333            week_day,
334        })
335    }
336}
337
338/// The default instance for [DateBuilder].
339impl Default for DateBuilder {
340    fn default() -> Self {
341        Self {
342            year: None,
343            month: None,
344            day: None,
345            week_day: None,
346            formal: true,
347            week_format: WeekFormat::default(),
348        }
349    }
350}
351
352/// Naïve date based on the Gregorian calendar.
353///
354/// It can be built using the related [DateBuilder], which also
355/// ensures its consistency and existence.
356#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357pub struct Date {
358    year: Option<Year>,
359    month: Option<Month>,
360    day: Option<Day>,
361    week_day: Option<StyledWeekDay>,
362}
363
364impl ChineseFormat for Date {
365    fn to_chinese(&self, variant: Variant) -> Chinese {
366        chinese_vec!(
367            variant,
368            [
369                EmptyPlaceholder::new(&self.year),
370                EmptyPlaceholder::new(&self.month),
371                EmptyPlaceholder::new(&self.day),
372                EmptyPlaceholder::new(&self.week_day)
373            ]
374        )
375        .trim_end()
376        .collect()
377    }
378}