From 3ffdf8f0d7ad91a32185e95d68693ba5e0eb40dd Mon Sep 17 00:00:00 2001 From: Robert Perce Date: Fri, 3 Apr 2026 11:54:36 -0500 Subject: [PATCH] refactor: switch from chrono to jiff --- Cargo.lock | 57 +++++++++++++++++++ Cargo.toml | 1 + e2e/pages/contact.spec.ts | 23 +++++--- src/models/birthday.rs | 24 +++++--- src/models/contact.rs | 11 ++-- src/models/journal.rs | 6 +- src/models/year_optional_date.rs | 57 +++++++++---------- src/switchboard.rs | 10 +--- src/web/contact/mod.rs | 94 +++++++++++++++----------------- src/web/home.rs | 57 +++++++++---------- src/web/ics.rs | 6 +- src/web/journal.rs | 20 +++---- 12 files changed, 205 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b300c23..3c03174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,6 +1280,47 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.81" @@ -1427,6 +1468,7 @@ dependencies = [ "http", "icalendar", "itertools 0.14.0", + "jiff", "listenfd", "markdown", "maud", @@ -1847,6 +1889,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index d246956..5b03dc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ clap = { version = "4.5.53", features = ["derive"] } http = "1.3.1" icalendar = "0.17.5" itertools = "0.14.0" +jiff = { version = "0.2.23", features = ["serde"] } listenfd = "1.0.2" markdown = "1.0.0" maud = { version = "0.27.0", features = ["axum"] } diff --git a/e2e/pages/contact.spec.ts b/e2e/pages/contact.spec.ts index dad20e5..46af08e 100644 --- a/e2e/pages/contact.spec.ts +++ b/e2e/pages/contact.spec.ts @@ -30,14 +30,14 @@ test.skip("groups wrap nicely", async ({ page }) => { const groupBox = page.getByPlaceholder(/group name/i); await groupBox.fill('this is a long group name'); - await page.getByRole('button', { name: /save/i }).click(); + await page.getByRole('button', { name: /save/i }).click(); await expect(page.locator('#alpine-loaded')).not.toHaveAttribute('x-cloak'); - // TODO: this drives to the right location but i can't figure out how to assert - // that the text is all on one line. Manual inspection looks good at time of writing. + // TODO: this drives to the right location but i can't figure out how to assert + // that the text is all on one line. Manual inspection looks good at time of writing. }); -test('allow marking as hidden', async ({ page }) => { +test('allow marking as inactive', async ({ page }) => { }); @@ -45,15 +45,20 @@ test('allow exempting from stale', async ({ page }) => { }); +test('stale list considers periodicity', async ({ page }) => { + +}); + +test('page title has contact primary name', async ({ page }) => { + await expect(page.title()).toContain("Test Testerson"); +}); + + +/* test('bullet points in free text display well', async ({ page }) => { }); -twst('page title has contact primary name', async ({ page }) => { - await expect(page.title()).toContain("Test Testerson"); -}); - -/* home: contact list scrolls in screen, not off screen home: clicking off contact list closes it home: contact list is sorted ignoring case diff --git a/src/models/birthday.rs b/src/models/birthday.rs index 603191a..f7c0256 100644 --- a/src/models/birthday.rs +++ b/src/models/birthday.rs @@ -1,4 +1,4 @@ -use chrono::Local; +use jiff::{Timestamp, Unit, Zoned, civil, tz::TimeZone}; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Row}; use std::fmt::Display; @@ -29,25 +29,31 @@ impl Display for Birthday { } impl Birthday { - pub fn next_occurrence(&self) -> Option { + pub fn next_occurrence(&self) -> Option { match &self { Birthday::Text(_) => None, - Birthday::Date(date) => Some(date.next_month_day_occurrence()?), + Birthday::Date(date) => date.next_month_day_occurrence(), } } - pub fn until_next(&self) -> Option { + pub fn until_next(&self) -> Option { self.next_occurrence() - .map(|when| when.signed_duration_since(Local::now().date_naive())) + .map(|when| when.since(Zoned::now().date()).ok())? } /// None if this is a text birthday or doesn't have a year - pub fn age(&self) -> Option { + pub fn age(&self) -> Option { match &self { Birthday::Text(_) => None, - Birthday::Date(date) => date - .to_date_naive() - .map(|birthdate| Local::now().date_naive().years_since(birthdate))?, + Birthday::Date(date) => { + let now = Timestamp::now().to_zoned(TimeZone::UTC); + date.to_civil_date().map(|birthdate| { + now.since(&birthdate.to_zoned(TimeZone::UTC).unwrap()) + .unwrap() + .total((Unit::Year, &now)) + .unwrap() as i32 + }) + } } } diff --git a/src/models/contact.rs b/src/models/contact.rs index bad0ff4..8ed0be6 100644 --- a/src/models/contact.rs +++ b/src/models/contact.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, NaiveDate, Utc}; +use jiff::{Timestamp, civil}; use sqlx::sqlite::SqlitePool; use std::str::FromStr; @@ -18,7 +18,7 @@ struct RawContact { pub struct Contact { pub id: DbId, pub birthday: Option, - pub manually_freshened_at: Option>, + pub manually_freshened_at: Option, pub lives_with: String, } @@ -31,8 +31,7 @@ impl Into for RawContact { .and_then(|s| Birthday::from_str(s.as_ref()).ok()), manually_freshened_at: self .manually_freshened_at - .and_then(|str| DateTime::parse_from_str(str.as_ref(), "%+").ok()) - .map(|d| d.to_utc()), + .and_then(|str| str.parse::().ok()), lives_with: self.lives_with, } } @@ -50,7 +49,7 @@ struct RawHydratedContact { #[derive(Clone, Debug)] pub struct HydratedContact { pub contact: Contact, - pub last_mention_date: Option, + pub last_mention_date: Option, pub names: Vec, } @@ -71,7 +70,7 @@ impl Into for RawHydratedContact { .collect::>(), last_mention_date: self .last_mention_date - .and_then(|str| NaiveDate::from_str(str.as_ref()).ok()), + .and_then(|str| str.parse::().ok()), } } } diff --git a/src/models/journal.rs b/src/models/journal.rs index 4a90d86..fdc9940 100644 --- a/src/models/journal.rs +++ b/src/models/journal.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDate; +use jiff::civil::Date; use maud::{Markup, html}; use serde_json::json; use sqlx::sqlite::{SqlitePool, SqliteRow}; @@ -12,7 +12,7 @@ use crate::switchboard::{MentionHost, MentionHostType}; pub struct JournalEntry { pub id: DbId, pub value: String, - pub date: NaiveDate, + pub date: Date, } impl<'a> Into> for &'a JournalEntry { @@ -69,7 +69,7 @@ impl FromRow<'_, SqliteRow> for JournalEntry { let id: DbId = row.try_get("id")?; let value: String = row.try_get("value")?; let date_str: &str = row.try_get("date")?; - let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap(); + let date: Date = date_str.parse().unwrap(); Ok(Self { id, value, date }) } } diff --git a/src/models/year_optional_date.rs b/src/models/year_optional_date.rs index 196360d..fa1fc1f 100644 --- a/src/models/year_optional_date.rs +++ b/src/models/year_optional_date.rs @@ -1,4 +1,4 @@ -use chrono::{Datelike, Local, NaiveDate}; +use jiff::{Timestamp, civil::Date, tz::TimeZone}; use regex::Regex; use sqlx::{Database, Decode, Encode, Sqlite, encode::IsNull}; use std::fmt::Display; @@ -6,38 +6,39 @@ use std::str::FromStr; #[derive(Debug, Clone)] pub struct YearOptionalDate { - pub year: Option, - pub month: u32, - pub day: u32, + pub year: Option, + pub month: i8, + pub day: i8, } impl YearOptionalDate { - pub fn prev_month_day_occurrence(&self) -> Option { - let now = Local::now(); + pub fn prev_month_day_occurrence(&self) -> Option { + let now = Timestamp::now().to_zoned(TimeZone::UTC); let year = now.year(); - let mut date = NaiveDate::from_ymd_opt(year, self.month, self.day); - if let Some(real_date) = date { - if real_date >= now.date_naive() { - date = NaiveDate::from_ymd_opt(year - 1, self.month, self.day); + Date::new(year, self.month, self.day).ok().and_then(|date| { + if date >= now.date() { + Date::new(year - 1, self.month, self.day).ok() + } else { + Some(date) } - } - date - } - pub fn next_month_day_occurrence(&self) -> Option { - let now = Local::now(); - let year = now.year(); - let mut date = NaiveDate::from_ymd_opt(year, self.month, self.day); - if let Some(real_date) = date { - if real_date < now.date_naive() { - date = NaiveDate::from_ymd_opt(year + 1, self.month, self.day); - } - } - date + }) } - pub fn to_date_naive(&self) -> Option { + pub fn next_month_day_occurrence(&self) -> Option { + let now = Timestamp::now().to_zoned(TimeZone::UTC); + let year = now.year(); + Date::new(year, self.month, self.day).ok().and_then(|date| { + if date < now.date() { + Date::new(year + 1, self.month, self.day).ok() + } else { + Some(date) + } + }) + } + + pub fn to_civil_date(&self) -> Option { if let Some(year) = self.year { - NaiveDate::from_ymd_opt(year, self.month, self.day) + Date::new(year, self.month, self.day).ok() } else { None } @@ -68,12 +69,12 @@ impl FromStr for YearOptionalDate { let date_re = Regex::new(r"^([0-9]{4}|--)([0-9]{2})([0-9]{2})$").unwrap(); if let Some(caps) = date_re.captures(str) { let year_str = &caps[1]; - let month = u32::from_str(&caps[2]).unwrap(); - let day = u32::from_str(&caps[3]).unwrap(); + let month = i8::from_str(&caps[2]).unwrap(); + let day = i8::from_str(&caps[3]).unwrap(); let year = if year_str == "--" { None } else { - Some(i32::from_str(year_str).unwrap()) + Some(i16::from_str(year_str).unwrap()) }; return Ok(Self { year, month, day }); diff --git a/src/switchboard.rs b/src/switchboard.rs index 0b7d3f8..6635700 100644 --- a/src/switchboard.rs +++ b/src/switchboard.rs @@ -74,7 +74,7 @@ impl MentionHost<'_> { } impl Switchboard { - pub async fn gen_trie(pool: &SqlitePool) -> Result, AppError> { + pub async fn gen_trie(pool: &SqlitePool) -> Result, AppError> { let mut trie = radix_trie::Trie::new(); let mentionables = sqlx::query_as!( @@ -109,14 +109,6 @@ impl Switchboard { } } - pub fn remove(self: &mut Self, text: &String) { - self.trie.remove(text); - } - - pub fn add_mentionable(self: &mut Self, text: String, uri: String) { - self.trie.insert(text, uri); - } - pub fn extract_mentions<'a>(&self, host: impl Into>) -> HashSet { let host: MentionHost = host.into(); let name_re = Regex::new(r"\[\[(.+?)\]\]").unwrap(); diff --git a/src/web/contact/mod.rs b/src/web/contact/mod.rs index 35f66e0..6386bdd 100644 --- a/src/web/contact/mod.rs +++ b/src/web/contact/mod.rs @@ -7,19 +7,19 @@ use axum::{ }; use axum_extra::extract::Form; use cache_bust::asset; -use chrono::DateTime; +use jiff::{Timestamp, Unit, tz::TimeZone}; use maud::{Markup, html}; use serde::Deserialize; use serde_json::json; use slug::slugify; -use sqlx::{QueryBuilder, Sqlite}; +use sqlx::QueryBuilder; use super::Layout; use super::home::journal_section; use crate::db::DbId; use crate::models::user::AuthSession; use crate::models::{HydratedContact, JournalEntry}; -use crate::switchboard::{MentionHost, MentionHostType, insert_mentions, Switchboard}; +use crate::switchboard::{MentionHost, MentionHostType, Switchboard, insert_mentions}; use crate::{AppError, AppState}; pub mod fields; @@ -40,22 +40,22 @@ pub fn router() -> Router { .route("/contact/{contact_id}/edit", get(self::get::contact_edit)) } -fn human_delta(delta: &chrono::TimeDelta) -> String { - if delta.num_days() == 0 { - return "today".to_string(); - } +fn human_delta(span: &jiff::Span) -> String { + let todate = Timestamp::now().to_zoned(TimeZone::UTC).date(); + let span = span + .round( + jiff::SpanRound::new() + .largest(Unit::Year) + .smallest(Unit::Day) + .relative(todate), + ) + .unwrap(); - let mut result = "in ".to_string(); - let mut rem = delta.clone(); - if rem.num_days().abs() >= 7 { - let weeks = rem.num_days() / 7; - rem -= chrono::TimeDelta::days(weeks * 7); - result.push_str(&format!("{}w ", weeks)); + if span.is_zero() { + "today".to_string() + } else { + format!("in {:#}", span) } - if rem.num_days().abs() > 0 { - result.push_str(&format!("{}d ", rem.num_days())); - } - result.trim().to_string() } mod get { @@ -88,7 +88,9 @@ mod get { .await?; let freshened = std::cmp::max( - contact.manually_freshened_at.map(|when| when.date_naive()), + contact + .manually_freshened_at + .map(|when| when.to_zoned(jiff::tz::TimeZone::UTC).date()), entries.get(0).map(|entry| entry.date), ); @@ -213,7 +215,7 @@ mod get { let mfresh_str = contact .manually_freshened_at .clone() - .map_or("".to_string(), |m| m.to_rfc3339()); + .map_or("".to_string(), |m| m.to_string()); let text_body: String = sqlx::query!("select text_body from contacts where id = $1", contact_id) @@ -224,7 +226,7 @@ mod get { Ok(layout.render( format!("Edit: {}", contact.names.get(0).unwrap_or(&String::from("(unknown)"))), - Some(vec![asset!("contact.css")]), + Some(vec![asset!("contact.css")]), html! { form hx-ext="response-targets" { div { @@ -351,14 +353,15 @@ mod put { Some(payload.birthday) }; - let manually_freshened_at = if payload.manually_freshened_at.is_empty() { + let manually_freshened_at: Option = if payload.manually_freshened_at.is_empty() { None } else { Some( - DateTime::parse_from_str(&payload.manually_freshened_at, "%+") + payload + .manually_freshened_at + .parse::() .map_err(|_| anyhow::Error::msg("Could not parse freshened-at string"))? - .to_utc() - .to_rfc3339(), + .to_string(), ) }; @@ -386,9 +389,6 @@ mod put { .execute(pool) .await?; - if old_contact.text_body != text_body { - } - // these blocks are not in functions because payload gets progressively // partially moved as we handle each field and i don't want to deal with it @@ -488,25 +488,21 @@ mod put { let old_names: Vec = old_names.into_iter().map(|(s,)| s).collect(); if old_names != new_names { - sqlx::query!( - "delete from names where contact_id = $1", - contact_id - ) - .execute(pool) - .await?; - - if !new_names.is_empty() { - QueryBuilder::new( - "insert into names (contact_id, sort, name) " - ).push_values(new_names.iter().enumerate(), |mut b, (sort, name)| { - b - .push_bind(contact_id) - .push_bind(DbId::try_from(sort).unwrap()) - .push_bind(name); - }).build() - .persistent(false) + sqlx::query!("delete from names where contact_id = $1", contact_id) .execute(pool) .await?; + + if !new_names.is_empty() { + QueryBuilder::new("insert into names (contact_id, sort, name) ") + .push_values(new_names.iter().enumerate(), |mut b, (sort, name)| { + b.push_bind(contact_id) + .push_bind(DbId::try_from(sort).unwrap()) + .push_bind(name); + }) + .build() + .persistent(false) + .execute(pool) + .await?; } } @@ -524,12 +520,9 @@ mod put { let old_groups: Vec = old_groups.into_iter().map(|(s,)| s).collect(); if new_groups != old_groups { - sqlx::query!( - "delete from groups where contact_id = $1", - contact_id - ) - .execute(pool) - .await?; + sqlx::query!("delete from groups where contact_id = $1", contact_id) + .execute(pool) + .await?; if new_groups.len() > 0 { QueryBuilder::new("insert into groups (contact_id, name, slug) ") @@ -566,7 +559,6 @@ mod put { .await?; } - if regen_text_body { sqlx::query!( "delete from mentions where entity_id = $1 and entity_type = $2", diff --git a/src/web/home.rs b/src/web/home.rs index 36c2a31..8af2d07 100644 --- a/src/web/home.rs +++ b/src/web/home.rs @@ -1,7 +1,7 @@ use axum::extract::State; use axum::response::IntoResponse; use cache_bust::asset; -use chrono::{Local, NaiveDate, TimeDelta}; +use jiff::{Timestamp, Unit, Zoned, civil, tz::TimeZone}; use maud::{Markup, html}; use sqlx::sqlite::SqlitePool; @@ -15,7 +15,7 @@ use crate::{AppError, AppState}; struct ContactFreshness { contact_id: DbId, display: String, - fresh_date: NaiveDate, + fresh_date: civil::Date, fresh_str: String, elapsed_str: String, } @@ -46,8 +46,8 @@ fn freshness_section(freshens: &Vec) -> Result, @@ -64,7 +64,7 @@ fn birthdays_section( (contact.display) } span { - (contact.next_birthday.format("%m-%d")) + (contact.next_birthday.strftime("%m-%d")) } } } @@ -75,7 +75,7 @@ fn birthdays_section( (contact.display) } span { - (contact.prev_birthday.format("%m-%d")) + (contact.prev_birthday.strftime("%m-%d")) } } } @@ -103,7 +103,7 @@ pub async fn journal_section( added to the top of the list regardless of date; refresh the page to re-sort." } form hx-post="/journal_entry" hx-target="next .entries" hx-target-error="#journal-error" hx-swap="afterbegin" hx-on::after-request="if(event.detail.successful) this.reset()" { - input name="date" placeholder=(Local::now().date_naive().to_string()); + input name="date" placeholder=(Zoned::now().date().to_string()); textarea name="value" placeholder="New entry..." autofocus {} input type="submit" value="Add Entry"; } @@ -135,11 +135,11 @@ pub mod get { .clone() .into_iter() .map(|contact| { - let zero = NaiveDate::from_epoch_days(0).unwrap(); + let zero = jiff::civil::Date::ZERO; let fresh_date = std::cmp::max( contact .manually_freshened_at - .map(|x| x.date_naive()) + .map(|ts| ts.to_zoned(TimeZone::UTC).date()) .unwrap_or(zero), contact.last_mention_date.unwrap_or(zero), ); @@ -152,30 +152,23 @@ pub mod get { elapsed_str: "".to_string(), } } else { - let mut duration = Local::now().date_naive().signed_duration_since(fresh_date); - let mut elapsed: Vec = Vec::new(); - let y = duration.num_weeks() / 52; - let count = |n: i64, noun: &str| { - format!("{} {}{}", n, noun, if n > 1 { "s" } else { "" }) - }; - if y > 0 { - elapsed.push(count(y, "year")); - duration -= TimeDelta::weeks(y * 52); - } - let w = duration.num_weeks(); - if w > 0 { - elapsed.push(count(w, "week")); - duration -= TimeDelta::weeks(w); - } - let d = duration.num_days(); - if d > 0 { - elapsed.push(count(d, "day")); - } + let utc = TimeZone::UTC; + let todate = Timestamp::now().to_zoned(utc.clone()).date(); + let duration = todate + .since(&fresh_date.to_zoned(utc).unwrap()) + .unwrap() + .round( + jiff::SpanRound::new() + .largest(Unit::Year) + .smallest(Unit::Day) + .relative(todate), + ) + .unwrap(); - let elapsed_str = if elapsed.is_empty() { + let elapsed_str = if duration.is_zero() { "today".to_string() } else { - elapsed.join(", ") + format!("{:#}", duration) }; ContactFreshness { @@ -197,8 +190,8 @@ pub mod get { Some(KnownBirthdayContact { contact_id: contact.id, display: contact.display_name(), - prev_birthday: date.prev_month_day_occurrence().unwrap(), - next_birthday: date.next_month_day_occurrence().unwrap(), + prev_birthday: date.prev_month_day_occurrence()?, + next_birthday: date.next_month_day_occurrence()?, }) } else { None diff --git a/src/web/ics.rs b/src/web/ics.rs index b7780ec..162b3f9 100644 --- a/src/web/ics.rs +++ b/src/web/ics.rs @@ -61,9 +61,9 @@ mod get { for contact in &contacts { if let Some(Birthday::Date(yo_date)) = &contact.birthday { if let Some(date) = NaiveDate::from_ymd_opt( - yo_date.year.unwrap_or(1900), - yo_date.month, - yo_date.day, + yo_date.year.unwrap_or(1900).into(), + yo_date.month.try_into().unwrap(), + yo_date.day.try_into().unwrap(), ) { calendar.push( Event::new() diff --git a/src/web/journal.rs b/src/web/journal.rs index b3bb30e..2ff6ddc 100644 --- a/src/web/journal.rs +++ b/src/web/journal.rs @@ -4,14 +4,14 @@ use axum::{ response::IntoResponse, routing::{delete, patch, post}, }; -use chrono::{Datelike, Local, NaiveDate}; +use jiff::{Zoned, civil::Date}; use maud::Markup; use regex::Regex; use serde::Deserialize; use crate::models::JournalEntry; use crate::models::user::AuthSession; -use crate::switchboard::{MentionHost, MentionHostType, insert_mentions}; +use crate::switchboard::{MentionHostType, insert_mentions}; use crate::{AppError, AppState, DbId}; pub fn router() -> Router { @@ -39,10 +39,10 @@ mod post { let pool = &state.db(&user).pool; let sw_lock = state.switchboard(&user); - let now = Local::now().date_naive(); + let now = Zoned::now(); let date = if payload.date.is_empty() { - now + now.date() } else { let date_re = Regex::new(r"^(?:(?[0-9]{4})-)?(?:(?[0-9]{2})-)?(?[0-9]{2})$") @@ -54,17 +54,16 @@ mod post { // unwrapping these parses is safe since it's matching [0-9]{2,4} let year = caps .name("year") - .map(|m| m.as_str().parse::().unwrap()) + .map(|m| m.as_str().parse::().unwrap()) .unwrap_or(now.year()); let month = caps .name("month") - .map(|m| m.as_str().parse::().unwrap()) + .map(|m| m.as_str().parse::().unwrap()) .unwrap_or(now.month()); - let day = caps.name("day").unwrap().as_str().parse::().unwrap(); + let day = caps.name("day").unwrap().as_str().parse::().unwrap(); - NaiveDate::from_ymd_opt(year, month, day).ok_or(anyhow::Error::msg( - "invalid date: failed NaiveDate construction", - ))? + Date::new(year, month, day) + .map_err(|_| anyhow::Error::msg("invalid date: failed NaiveDate construction"))? }; // not a macro query, we want to use JournalEntry's custom FromRow @@ -131,7 +130,6 @@ mod patch { insert_mentions(&mentions, pool).await?; } - Ok(new_entry.to_html(pool).await?) } }