1use std::cmp::Ordering;
12use std::fmt;
13
14use chrono::{Datelike, NaiveDate, Weekday as ChronoWeekday};
15
16use self::Weekday::*;
17use super::{KoyomiError, KoyomiResult};
18use crate::era;
19use crate::holiday;
20
21#[derive(Clone, Debug, Eq, PartialEq)]
28pub enum Weekday {
29 Monday,
30 Tuesday,
31 Wednesday,
32 Thursday,
33 Friday,
34 Saturday,
35 Sunday,
36}
37
38impl Weekday {
39 pub fn japanese(&self) -> char {
50 match *self {
51 Monday => '月',
52 Tuesday => '火',
53 Wednesday => '水',
54 Thursday => '木',
55 Friday => '金',
56 Saturday => '土',
57 Sunday => '日',
58 }
59 }
60}
61
62impl From<ChronoWeekday> for Weekday {
63 fn from(weekday: ChronoWeekday) -> Self {
67 match weekday {
68 ChronoWeekday::Mon => Monday,
69 ChronoWeekday::Tue => Tuesday,
70 ChronoWeekday::Wed => Wednesday,
71 ChronoWeekday::Thu => Thursday,
72 ChronoWeekday::Fri => Friday,
73 ChronoWeekday::Sat => Saturday,
74 ChronoWeekday::Sun => Sunday,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct Date {
85 year: i32,
86 month: u32,
87 day: u32,
88 weekday: Weekday,
89}
90
91impl Date {
92 pub fn parse(fmt: &str) -> KoyomiResult<Self> {
110 NaiveDate::parse_from_str(fmt, "%Y-%m-%d")
111 .or(NaiveDate::parse_from_str(fmt, "%Y/%m/%d"))
112 .map_err(|_| KoyomiError::InvalidFormat(fmt.into()))
113 .map(|d| Date::from(d))
114 }
115
116 pub fn from_ymd(year: i32, month: u32, day: u32) -> KoyomiResult<Self> {
130 let ymd = format!("{:<4}-{:<02}-{:<02}", year, month, day);
131 Date::parse(&ymd)
132 }
133
134 pub fn day(&self) -> u32 {
145 self.day
146 }
147
148 pub fn era(&self) -> Option<era::Era> {
159 era::era(self)
160 }
161
162 pub fn holiday(&self) -> Option<String> {
176 holiday::holiday(self)
177 }
178
179 pub fn month(&self) -> u32 {
190 self.month
191 }
192
193 pub fn num_days(&self, date: &Date) -> i64 {
210 let min = NaiveDate::from_ymd(self.year, self.month, self.day);
211 let sub = NaiveDate::from_ymd(date.year(), date.month(), date.day());
212 min.signed_duration_since(sub).num_days()
213 }
214
215 pub fn to_string(&self) -> String {
227 format!("{:<4}-{:<02}-{:<02}", self.year, self.month, self.day)
228 }
229
230 pub fn tomorrow(&self) -> KoyomiResult<Self> {
242 NaiveDate::from_ymd(self.year, self.month, self.day)
243 .succ_opt()
244 .ok_or(KoyomiError::NoTomorrow(self.year, self.month, self.day))
245 .map(|d| Date::from(d))
246 }
247
248 pub fn weekday(&self) -> &Weekday {
259 &self.weekday
260 }
261
262 pub fn year(&self) -> i32 {
273 self.year
274 }
275
276 pub fn yesterday(&self) -> KoyomiResult<Self> {
288 NaiveDate::from_ymd(self.year, self.month, self.day)
289 .pred_opt()
290 .ok_or(KoyomiError::NoYesterday(self.year, self.month, self.day))
291 .map(|d| Date::from(d))
292 }
293}
294
295impl From<NaiveDate> for Date {
296 fn from(date: NaiveDate) -> Self {
297 Date {
298 year: date.year(),
299 month: date.month(),
300 day: date.day(),
301 weekday: Weekday::from(date.weekday()),
302 }
303 }
304}
305
306impl fmt::Display for Date {
307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308 write!(f, "{}", self.to_string())
309 }
310}
311
312impl Ord for Date {
313 fn cmp(&self, other: &Date) -> Ordering {
315 match self.year.cmp(&other.year) {
316 Ordering::Equal => match self.month.cmp(&other.month) {
317 Ordering::Equal => self.day.cmp(&other.day),
318 ord => ord,
319 },
320 ord => ord,
321 }
322 }
323}
324
325impl PartialOrd for Date {
326 fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
328 Some(self.cmp(&other))
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use chrono::naive::{MAX_DATE, MIN_DATE};
336
337 #[test]
338 fn parse_hyphen_format() {
339 assert!(Date::parse("2018-01-01").is_ok());
340 }
341
342 #[test]
343 fn parse_slash_format() {
344 assert!(Date::parse("2018/01/01").is_ok());
345 }
346
347 #[test]
348 fn parse_invalid_format() {
349 assert!(Date::parse("2018 01 01").is_err());
350 }
351
352 #[test]
353 fn valid_ymd() {
354 assert!(Date::from_ymd(2018, 1, 1).is_ok());
355 }
356
357 #[test]
358 fn invalid_ymd() {
359 assert!(Date::from_ymd(2018, 13, 1).is_err());
360 }
361
362 #[test]
363 fn ymd_of_date() {
364 let date = Date::parse("2018-12-24").unwrap();
365 assert_eq!(date.year(), 2018);
366 assert_eq!(date.month(), 12);
367 assert_eq!(date.day(), 24);
368 }
369
370 #[test]
371 fn era_of_date() {
372 let date = Date::parse("2018-12-24").unwrap();
373 assert_eq!(date.era().unwrap().name(), "平成");
374 }
375
376 #[test]
377 fn holiday_of_date() {
378 let date = Date::parse("2018-12-23").unwrap();
379 assert_eq!(date.holiday().unwrap(), "天皇誕生日");
380 }
381
382 #[test]
383 fn monday_of_weekday() {
384 let weekday = Weekday::from(ChronoWeekday::Mon);
385 assert_eq!(weekday, Monday);
386 assert_eq!(weekday.japanese(), '月');
387 }
388
389 #[test]
390 fn tuesday_of_weekday() {
391 let weekday = Weekday::from(ChronoWeekday::Tue);
392 assert_eq!(weekday, Tuesday);
393 assert_eq!(weekday.japanese(), '火');
394 }
395
396 #[test]
397 fn wednesday_of_weekday() {
398 let weekday = Weekday::from(ChronoWeekday::Wed);
399 assert_eq!(weekday, Wednesday);
400 assert_eq!(weekday.japanese(), '水');
401 }
402
403 #[test]
404 fn thursday_of_weekday() {
405 let weekday = Weekday::from(ChronoWeekday::Thu);
406 assert_eq!(weekday, Thursday);
407 assert_eq!(weekday.japanese(), '木');
408 }
409
410 #[test]
411 fn friday_of_weekday() {
412 let weekday = Weekday::from(ChronoWeekday::Fri);
413 assert_eq!(weekday, Friday);
414 assert_eq!(weekday.japanese(), '金');
415 }
416
417 #[test]
418 fn saturday_of_weekday() {
419 let weekday = Weekday::from(ChronoWeekday::Sat);
420 assert_eq!(weekday, Saturday);
421 assert_eq!(weekday.japanese(), '土');
422 }
423
424 #[test]
425 fn sunday_of_weekday() {
426 let weekday = Weekday::from(ChronoWeekday::Sun);
427 assert_eq!(weekday, Sunday);
428 assert_eq!(weekday.japanese(), '日');
429 }
430
431 #[test]
432 fn valid_tomorrow() {
433 let date = Date::parse("2018-12-24").unwrap();
434 assert!(date.tomorrow().is_ok());
435 }
436
437 #[test]
438 fn invalid_tomorrow() {
439 let date = Date::parse(&MAX_DATE.format("%Y-%m-%d").to_string()).unwrap();
440 assert!(date.tomorrow().is_err());
441 }
442
443 #[test]
444 fn valid_yesterday() {
445 let date = Date::parse("2018-12-24").unwrap();
446 assert!(date.yesterday().is_ok());
447 }
448
449 #[test]
450 fn invalid_yesterday() {
451 let date = Date::parse(&MIN_DATE.format("%Y-%m-%d").to_string()).unwrap();
452 assert!(date.yesterday().is_err());
453 }
454
455 #[test]
456 fn num_days() {
457 let date = Date::parse("2018-04-01").unwrap();
458
459 let sub = Date::parse("2018-03-01").unwrap();
460 assert_eq!(date.num_days(&sub), 31);
461
462 let sub = Date::parse("2018-05-01").unwrap();
463 assert_eq!(date.num_days(&sub), -30);
464
465 let sub = Date::parse("2018-04-01").unwrap();
466 assert_eq!(date.num_days(&sub), 0);
467 }
468
469 #[test]
470 fn date_to_string() {
471 let format = "2018-01-01";
472 let date = Date::parse(format).unwrap();
473 assert_eq!(date.to_string(), format);
474 }
475
476 #[test]
477 fn date_display() {
478 let format = "2018-01-01";
479 let date = Date::parse(format).unwrap();
480 assert_eq!(format!("{}", date), format);
481 }
482
483 #[test]
484 fn date_equal() {
485 let d1 = Date::parse("2018-04-01").unwrap();
486 let d2 = Date::parse("2018-04-01").unwrap();
487 assert!(d1 == d2);
488 }
489
490 #[test]
491 fn date_greater() {
492 let d1 = Date::parse("2018-04-10").unwrap();
493 let d2 = Date::parse("2017-04-10").unwrap();
494 assert!(d1 > d2);
495
496 let d2 = Date::parse("2018-03-20").unwrap();
497 assert!(d1 > d2);
498
499 let d2 = Date::parse("2018-04-01").unwrap();
500 assert!(d1 > d2);
501 }
502
503 #[test]
504 fn date_greater_than() {
505 let d1 = Date::parse("2018-04-10").unwrap();
506 let d2 = Date::parse("2018-04-10").unwrap();
507 assert!(d1 >= d2);
508
509 let d2 = Date::parse("2018-04-01").unwrap();
510 assert!(d1 >= d2);
511 }
512
513 #[test]
514 fn date_less() {
515 let d1 = Date::parse("2018-04-10").unwrap();
516 let d2 = Date::parse("2019-04-10").unwrap();
517 assert!(d1 < d2);
518
519 let d2 = Date::parse("2018-05-10").unwrap();
520 assert!(d1 < d2);
521
522 let d2 = Date::parse("2018-04-20").unwrap();
523 assert!(d1 < d2);
524 }
525
526 #[test]
527 fn date_less_than() {
528 let d1 = Date::parse("2018-04-10").unwrap();
529 let d2 = Date::parse("2018-04-10").unwrap();
530 assert!(d1 <= d2);
531
532 let d2 = Date::parse("2018-04-20").unwrap();
533 assert!(d1 <= d2);
534 }
535}