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}