1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright © 2020 Alexandra Frydl
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use super::Zone;
use crate::prelude::*;
use chrono::{Datelike, TimeZone};

#[derive(Clone, Copy, Eq, From, Into, Ord, PartialEq, PartialOrd)]
pub struct Date(chrono::NaiveDate);

impl Date {
  /// Creates a date from a given year, month, and day number.
  pub fn from_ymd(year: isize, month: usize, day: usize) -> Self {
    Self(chrono::NaiveDate::from_ymd(year as i32, month as u32, day as u32))
  }

  /// Returns the day of the month starting from `1`.
  pub fn day(&self) -> usize {
    self.0.day() as usize
  }

  /// Formats the date according to the given format string.
  pub fn format<'a>(&self, fmt: &'a str) -> impl Display + 'a {
    self.0.format(fmt)
  }

  /// Returns the month of the year starting from `1`.
  pub fn month(&self) -> usize {
    self.0.month() as usize
  }

  /// Returns the next day.
  pub fn next(&self) -> Self {
    Self(self.0.succ())
  }

  /// Returns the previous day.
  pub fn prev(&self) -> Self {
    Self(self.0.pred())
  }

  /// Convert the date to a time in the local time zone.
  pub fn to_local_time(&self) -> Time {
    self.to_time(super::LOCAL)
  }

  /// Converts the date to a time in the given time zone.
  pub fn to_time(&self, zone: Zone) -> Time {
    let inner = match &zone {
      Zone::Local => chrono::Local
        .from_local_date(&self.0)
        .and_hms_opt(0, 0, 0)
        .unwrap()
        .with_timezone(&chrono::Utc),

      Zone::Tz(tz) => {
        tz.from_local_date(&self.0).and_hms_opt(0, 0, 0).unwrap().with_timezone(&chrono::Utc)
      }
    };

    Time { inner, zone }
  }

  /// Convert the date to a time in UTC.
  pub fn to_utc_time(&self) -> Time {
    self.to_time(super::UTC)
  }

  /// Returns the year number.
  pub fn year(&self) -> isize {
    self.0.year() as isize
  }

  /// Returns the year, month of the year, and day of the month.
  ///
  /// Equivalent to `(date.year(), date.month(), date.day())`.
  pub fn ymd(&self) -> (isize, usize, usize) {
    (self.year(), self.month(), self.day())
  }
}

// Implement formatting.

impl Debug for Date {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "\"{}\"", self.format("%F"))
  }
}

impl Display for Date {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{}", self.format("%v"))
  }
}

// Implement conversion to and from postgres.

cfg_if! {
  if #[cfg(feature = "postgres")] {
    use postgres_types as pg;

    impl<'a> pg::FromSql<'a> for Date {
      fn from_sql(ty: &pg::Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>>{
        Ok(Self(pg::FromSql::from_sql(ty, raw)?))
      }

      fn accepts(ty: &pg::Type) -> bool {
        <chrono::NaiveDate as pg::FromSql>::accepts(ty)
      }
    }

    impl pg::ToSql for Date {
      fn to_sql(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>>
      where
        Self: Sized,
      {
        self.0.to_sql(ty, out)
      }

      fn accepts(ty: &pg::Type) -> bool
      where
        Self: Sized,
      {
        <chrono::NaiveDate as pg::ToSql>::accepts(ty)
      }

      fn to_sql_checked(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>> {
        self.0.to_sql_checked(ty, out)
      }
    }
  }
}