1mod date;
10mod duration;
11mod span;
12mod zone;
13
14pub use self::date::Date;
15pub use self::duration::Duration;
16pub use self::span::Span;
17pub use self::zone::{Zone, LOCAL, UTC};
18
19use crate::prelude::*;
20use chrono::{TimeZone, Timelike};
21
22#[derive(Clone, Copy)]
24pub struct Time {
25 inner: chrono::DateTime<chrono::Utc>,
26 zone: Zone,
27}
28
29macro_rules! in_zone {
30 ($self:tt, |$var:ident| $expr:expr) => {
31 match $self.zone {
32 Zone::Local => {
33 let $var = $self.inner.with_timezone(&chrono::Local);
34 $expr
35 }
36
37 Zone::Tz(tz) => {
38 let $var = $self.inner.with_timezone(&tz);
39 $expr
40 }
41 }
42 };
43}
44
45impl Time {
46 pub const fn max_value() -> Time {
48 Time { inner: chrono::MAX_DATETIME, zone: LOCAL }
49 }
50
51 pub const fn min_value() -> Time {
53 Time { inner: chrono::MIN_DATETIME, zone: LOCAL }
54 }
55
56 pub fn now() -> Time {
58 Time { inner: chrono::Utc::now(), zone: LOCAL }
59 }
60
61 pub fn from_unix_ms(timestamp: i64) -> Self {
63 Self { inner: chrono::Utc.timestamp_millis(timestamp), zone: Zone::Local }
64 }
65
66 pub fn as_rfc3339(&self) -> impl Display {
68 match self.zone == UTC {
69 true => self.format("%FT%T%.fZ"),
70 false => self.format("%FT%T%.f%:z"),
71 }
72 }
73
74 pub fn date(&self) -> Date {
76 in_zone!(self, |t| t.date().naive_local().into())
77 }
78
79 pub fn elapsed(&self) -> Duration {
83 Self::now() - *self
84 }
85
86 pub fn format<'a>(&self, fmt: &'a str) -> impl Display + 'a {
88 in_zone!(self, |t| t.format(fmt))
89 }
90
91 pub fn hms(&self) -> (usize, usize, usize) {
95 in_zone!(self, |t| (t.hour() as usize, t.minute() as usize, t.second() as usize))
96 }
97
98 pub fn hour(&self) -> usize {
100 in_zone!(self, |t| t.hour() as usize)
101 }
102
103 pub fn minute(&self) -> usize {
105 in_zone!(self, |t| t.minute() as usize)
106 }
107
108 pub fn second(&self) -> usize {
110 in_zone!(self, |t| t.second() as usize)
111 }
112
113 pub fn start_of_day(&self) -> Time {
114 Time {
115 inner: in_zone!(self, |t| t.date().and_hms(0, 0, 0).with_timezone(&chrono::Utc)),
116 zone: self.zone,
117 }
118 }
119
120 pub fn start_of_hour(&self) -> Time {
122 Time {
123 inner: in_zone!(self, |t| { t.date().and_hms(t.hour(), 0, 0).with_timezone(&chrono::Utc) }),
124 zone: self.zone,
125 }
126 }
127
128 pub const fn to_local(&self) -> Self {
130 self.to_zone(LOCAL)
131 }
132
133 pub const fn to_utc(&self) -> Self {
135 self.to_zone(UTC)
136 }
137
138 pub const fn to_zone(&self, zone: Zone) -> Self {
140 Self { inner: self.inner, zone }
141 }
142
143 #[cfg(feature = "postgres")]
145 fn to_naive(&self) -> chrono::NaiveDateTime {
146 in_zone!(self, |t| t.naive_local())
147 }
148}
149
150impl PartialEq for Time {
153 fn eq(&self, other: &Self) -> bool {
154 self.inner == other.inner
155 }
156}
157
158impl Add<Duration> for Time {
159 type Output = Self;
160
161 fn add(self, rhs: Duration) -> Self::Output {
162 let rhs: chrono::Duration = rhs.into();
163
164 Self { inner: self.inner + rhs, zone: self.zone }
165 }
166}
167
168impl Debug for Time {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 write!(f, "\"{}\"", self.format("%+"))
171 }
172}
173
174impl Display for Time {
175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176 self.as_rfc3339().fmt(f)
177 }
178}
179
180impl Eq for Time {}
181
182impl Hash for Time {
183 fn hash<H: Hasher>(&self, state: &mut H) {
184 self.inner.hash(state)
185 }
186}
187
188impl Ord for Time {
189 fn cmp(&self, other: &Self) -> cmp::Ordering {
190 self.inner.cmp(&other.inner)
191 }
192}
193
194impl PartialOrd for Time {
195 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
196 self.inner.partial_cmp(&other.inner)
197 }
198}
199
200impl Sub<Duration> for Time {
201 type Output = Self;
202
203 fn sub(self, rhs: Duration) -> Self::Output {
204 let rhs: chrono::Duration = rhs.into();
205
206 Self { inner: self.inner - rhs, zone: self.zone }
207 }
208}
209
210impl Sub<Time> for Time {
211 type Output = Duration;
212
213 fn sub(self, rhs: Time) -> Self::Output {
214 (self.inner - rhs.inner).into()
215 }
216}
217
218cfg_if! {
221 if #[cfg(feature = "postgres")] {
222 use postgres_types as pg;
223
224 impl<'a> pg::FromSql<'a> for Time {
225 fn from_sql(ty: &pg::Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>>{
226 Ok(Self { inner: pg::FromSql::from_sql(ty, raw)?, zone: Zone::Local })
227 }
228
229 fn accepts(ty: &pg::Type) -> bool {
230 ty.oid() == pg::Type::TIMESTAMPTZ.oid()
231 }
232 }
233
234 impl pg::ToSql for Time {
235 fn to_sql(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>>
236 where
237 Self: Sized,
238 {
239 if ty.oid() == pg::Type::TIMESTAMP.oid() {
240 self.to_naive().to_sql(ty, out)
241 } else {
242 self.inner.to_sql(ty, out)
243 }
244 }
245
246 fn accepts(ty: &pg::Type) -> bool
247 where
248 Self: Sized,
249 {
250 ty.oid() == pg::Type::TIMESTAMP.oid() || ty.oid() == pg::Type::TIMESTAMPTZ.oid()
251 }
252
253 fn to_sql_checked(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>> {
254 if ty.oid() == pg::Type::TIMESTAMP.oid() {
255 self.to_naive().to_sql_checked(ty, out)
256 } else {
257 self.inner.to_sql_checked(ty, out)
258 }
259 }
260 }
261 }
262}