From 9ba10eac35debd6b77a6b4acbaf1b65a96e52a86 Mon Sep 17 00:00:00 2001 From: Achieve Date: Sat, 28 Mar 2026 22:08:25 +0800 Subject: [PATCH] fix(db): add tracing warn for naive timestamp fallback and improve parse_timestamp tests (#1700) * fix(db): add tracing warn for naive timestamp fallback and improve parse_timestamp tests * style: fix formatting --- src/db/libsql/mod.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/db/libsql/mod.rs b/src/db/libsql/mod.rs index f3a323f3..5fadb5d9 100644 --- a/src/db/libsql/mod.rs +++ b/src/db/libsql/mod.rs @@ -17,7 +17,6 @@ mod workspace; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; @@ -34,8 +33,6 @@ use crate::workspace::MemoryDocument; use crate::db::libsql_migrations; -static NAIVE_TIMESTAMP_LOGGED: AtomicBool = AtomicBool::new(false); - /// Explicit column list for routines table (matches positional access in `row_to_routine_libsql`). pub(crate) const ROUTINE_COLUMNS: &str = "\ id, name, description, user_id, enabled, \ @@ -167,13 +164,11 @@ impl LibSqlBackend { /// /// Returns an error if none of the formats match. pub(crate) fn parse_timestamp(s: &str) -> Result, String> { - let log_naive_timestamp_once = || { - if !NAIVE_TIMESTAMP_LOGGED.swap(true, Ordering::Relaxed) { - tracing::debug!( - timestamp = %s, - "parsed naive timestamp without timezone; assuming UTC for backward compatibility" - ); - } + let log_naive_timestamp = || { + tracing::warn!( + timestamp = %s, + "parsed naive timestamp, assuming UTC — consider migrating to RFC 3339" + ); }; // RFC 3339 (our canonical write format) @@ -182,12 +177,12 @@ pub(crate) fn parse_timestamp(s: &str) -> Result, String> { } // Naive with fractional seconds (legacy or SQLite datetime() output) if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") { - log_naive_timestamp_once(); + log_naive_timestamp(); return Ok(ndt.and_utc()); } // Naive without fractional seconds (legacy format) if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { - log_naive_timestamp_once(); + log_naive_timestamp(); return Ok(ndt.and_utc()); } Err(format!("unparseable timestamp: {:?}", s)) @@ -439,7 +434,7 @@ mod tests { use chrono::{TimeZone, Utc}; use crate::db::Database; - use crate::db::libsql::{LibSqlBackend, normalize_notify_user, parse_timestamp}; + use crate::db::libsql::{LibSqlBackend, fmt_ts, normalize_notify_user, parse_timestamp}; #[test] fn test_normalize_notify_user_treats_legacy_default_as_missing() { @@ -468,6 +463,15 @@ mod tests { assert_eq!(naive_without_millis, expected); } + #[test] + fn test_fmt_ts_roundtrips_through_parse_timestamp() { + let original = Utc.with_ymd_and_hms(2026, 6, 15, 8, 30, 45).unwrap() + + chrono::Duration::milliseconds(123); + let formatted = fmt_ts(&original); + let parsed = parse_timestamp(&formatted).unwrap(); + assert_eq!(parsed, original); + } + #[tokio::test] async fn test_libsql_now_format_is_rfc3339_and_parseable() { let backend = LibSqlBackend::new_memory().await.unwrap();