summaryrefslogtreecommitdiff
path: root/backend/app/src/models/date_time_utc.rs
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2024-10-16 12:51:21 +0100
committerJoe Carstairs <me@joeac.net>2024-10-16 12:51:21 +0100
commit27b95c9186bd45edd4c982a7cc89cf69cf2c5d46 (patch)
treebf104ff9d64e601bfec5516eb51ef298f3095e9b /backend/app/src/models/date_time_utc.rs
parent76974785294089965efc3f55b2a08d4089029c52 (diff)
All backend stuff lives in backend folder
Diffstat (limited to 'backend/app/src/models/date_time_utc.rs')
-rw-r--r--backend/app/src/models/date_time_utc.rs131
1 files changed, 131 insertions, 0 deletions
diff --git a/backend/app/src/models/date_time_utc.rs b/backend/app/src/models/date_time_utc.rs
new file mode 100644
index 0000000..03d5802
--- /dev/null
+++ b/backend/app/src/models/date_time_utc.rs
@@ -0,0 +1,131 @@
+use chrono::{DateTime, Datelike, Utc};
+use diesel::{
+ backend::Backend,
+ deserialize::{self, FromSql, FromSqlRow},
+ expression::AsExpression,
+ serialize::{self, ToSql},
+ sql_types::Text,
+ sqlite::Sqlite,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(
+ Eq,
+ PartialEq,
+ Ord,
+ PartialOrd,
+ Serialize,
+ Deserialize,
+ Copy,
+ Clone,
+ Debug,
+ AsExpression,
+ FromSqlRow,
+)]
+#[diesel(sql_type=Text)]
+pub struct DateTimeUtc(DateTime<Utc>);
+
+impl DateTimeUtc {
+ pub fn day_diff(&self, other: &DateTimeUtc) -> u32 {
+ self.0
+ .num_days_from_ce()
+ .abs_diff(other.0.num_days_from_ce())
+ }
+
+ pub fn to_day_str(&self) -> String {
+ self.0
+ .to_rfc3339()
+ .split_at_checked(10)
+ .map(|(day_str, _)| String::from(day_str))
+ .unwrap_or_else(|| String::from(""))
+ }
+
+ pub fn new(date_time_utc: DateTime<Utc>) -> Self {
+ Self(date_time_utc)
+ }
+
+ pub fn now() -> Self {
+ Self(Utc::now())
+ }
+}
+
+impl ToSql<Text, Sqlite> for DateTimeUtc {
+ fn to_sql<'b>(
+ &'b self,
+ out: &mut diesel::serialize::Output<'b, '_, Sqlite>,
+ ) -> serialize::Result {
+ out.set_value(self.to_day_str());
+ Ok(diesel::serialize::IsNull::No)
+ }
+}
+
+impl FromSql<Text, Sqlite> for DateTimeUtc {
+ fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<DateTimeUtc> {
+ let str: String = FromSql::<Text, Sqlite>::from_sql(bytes)?;
+ let date_time = str.as_str().parse::<DateTime<Utc>>()?;
+ Ok(DateTimeUtc(date_time))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use chrono::TimeZone;
+
+ use super::*;
+
+ #[test]
+ fn given_midnight_when_day_diff_next_midnight_less_1s_then_return_0() {
+ let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc());
+ let end = DateTimeUtc::new(
+ Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59)
+ .unwrap()
+ .to_utc(),
+ );
+
+ let day_diff = start.day_diff(&end);
+ let neg_day_diff = end.day_diff(&start);
+
+ assert_eq!(day_diff, 0);
+ assert_eq!(day_diff, neg_day_diff);
+ }
+
+ #[test]
+ fn given_midnight_when_day_diff_next_midnight_then_return_1() {
+ let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc());
+ let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc());
+
+ let day_diff = start.day_diff(&end);
+ let neg_day_diff = end.day_diff(&start);
+
+ assert_eq!(day_diff, 1);
+ assert_eq!(day_diff, neg_day_diff);
+ }
+
+ #[test]
+ fn given_midnight_less_1s_when_day_diff_midnight_then_return_1() {
+ let start = DateTimeUtc::new(
+ Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59)
+ .unwrap()
+ .to_utc(),
+ );
+ let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc());
+
+ let day_diff = start.day_diff(&end);
+ let neg_day_diff = end.day_diff(&start);
+
+ assert_eq!(day_diff, 1);
+ assert_eq!(day_diff, neg_day_diff);
+ }
+
+ #[test]
+ fn given_23_nov_1970_when_to_day_str_then_return_iso_day_str() {
+ let date_time_utc = DateTimeUtc::new(
+ Utc.with_ymd_and_hms(1970, 11, 23, 0, 0, 0)
+ .unwrap()
+ .to_utc(),
+ );
+ let day_str = date_time_utc.to_day_str();
+
+ assert_eq!(day_str, "1970-11-23");
+ }
+}