Skip to content

fix(libsql): standardize timestamp storage to RFC 3339 with UTC offset#705

Open
zmanian wants to merge 1 commit intomainfrom
fix/663-libsql-timestamp-format
Open

fix(libsql): standardize timestamp storage to RFC 3339 with UTC offset#705
zmanian wants to merge 1 commit intomainfrom
fix/663-libsql-timestamp-format

Conversation

@zmanian
Copy link
Collaborator

@zmanian zmanian commented Mar 8, 2026

Summary

Closes #663.

  • Replace all datetime('now') defaults in libsql_migrations.rs with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') so new rows get proper RFC 3339 timestamps with UTC offset
  • Add tracing::warn! to parse_timestamp() naive fallback paths so legacy timestamps produce a deprecation signal
  • Add regression tests: RFC 3339 parsing, naive fallback parsing, invalid input, and a DB-level test verifying DEFAULT column values produce RFC 3339 format

Test plan

  • test_parse_timestamp_rfc3339 -- verifies RFC 3339 with Z and +00:00 offsets
  • test_parse_timestamp_naive_fallback -- verifies backward compat with legacy naive format
  • test_parse_timestamp_invalid -- verifies error on bad input
  • test_default_timestamps_are_rfc3339 -- inserts a row with DEFAULT values and asserts RFC 3339 format
  • cargo clippy --all --all-features -- zero warnings
  • cargo test --lib -- 2539 passed (libsql), 2548 passed (postgres)

Generated with Claude Code

#663)

Replace all `datetime('now')` defaults in libsql_migrations.rs with
`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` so new rows get proper RFC 3339
timestamps (e.g. `2024-01-15T10:30:00.123Z`) instead of naive datetimes
(e.g. `2024-01-15 10:30:00`).

Add tracing::warn! to parse_timestamp() naive fallback paths so legacy
timestamps are still accepted but produce a visible deprecation signal.

Backward compatible: no data migration needed; existing naive timestamps
continue to parse correctly via the multi-format fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added scope: db/libsql libSQL / Turso backend size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules labels Mar 8, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the handling of timestamps within the libsql database backend by enforcing a consistent RFC 3339 format with UTC offset for all newly created timestamps. This change improves data interoperability and reduces ambiguity. It also includes mechanisms to gracefully handle and warn about older, less precise timestamp formats, alongside a robust suite of tests to ensure the reliability and correctness of the new timestamp logic.

Highlights

  • Timestamp Standardization: Standardized all new timestamp entries in libsql_migrations.rs to use RFC 3339 format with UTC offset, replacing datetime('now') with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') for consistency and clarity.
  • Legacy Timestamp Warning: Added tracing::warn! calls to the parse_timestamp() function in src/db/libsql/mod.rs to signal when legacy naive timestamp formats are encountered, encouraging migration to the standardized format.
  • Comprehensive Testing: Introduced new regression tests to validate RFC 3339 parsing, ensure backward compatibility with naive fallback parsing, handle invalid timestamp inputs gracefully, and verify that default database column values are correctly stored in RFC 3339 format.
Changelog
  • src/db/libsql/mod.rs
    • Added tracing::warn! statements to parse_timestamp for naive timestamp formats.
    • Introduced new test cases for RFC 3339, naive fallback, and invalid timestamp parsing.
    • Added a new #[tokio::test] to verify default database timestamps are RFC 3339 compliant.
  • src/db/libsql_migrations.rs
    • Replaced datetime('now') with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') for default timestamp values across all relevant table schemas and triggers.
Activity
  • The pull request addresses and closes issue Standardize libSQL timestamp storage to RFC 3339 with UTC offset #663.
  • The author has verified the test plan, including test_parse_timestamp_rfc3339, test_parse_timestamp_naive_fallback, test_parse_timestamp_invalid, and test_default_timestamps_are_rfc3339.
  • Code has been checked with cargo clippy --all --all-features with zero warnings.
  • All library tests passed with cargo test --lib for both libsql (2539 passed) and postgres (2548 passed).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added the contributor: core 20+ merged PRs label Mar 8, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request standardizes timestamp storage to RFC 3339 format in UTC for the libsql backend. This is achieved by replacing datetime('now') with strftime in all database migrations. To handle legacy data, warnings are now logged when parsing naive timestamps. The changes are supported by a comprehensive set of new tests. My review includes a couple of suggestions to improve code maintainability by reducing duplication and to enhance the new tests for better coverage.

Comment on lines 171 to 184
if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f") {
tracing::warn!(
timestamp = s,
"parsing naive timestamp without timezone; assuming UTC — consider re-running migrations"
);
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") {
tracing::warn!(
timestamp = s,
"parsing naive timestamp without timezone; assuming UTC — consider re-running migrations"
);
return Ok(ndt.and_utc());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These two blocks for parsing naive datetimes are very similar and contain duplicated code, including the warning message. You can refactor this into a loop over an array of possible formats to improve maintainability and reduce duplication.

    // Naive fallback for legacy formats
    let naive_formats = ["%Y-%m-%d %H:%M:%S%.f", "%Y-%m-%d %H:%M:%S"];
    for fmt in naive_formats {
        if let Ok(ndt) = NaiveDateTime::parse_from_str(s, fmt) {
            tracing::warn!(
                timestamp = s,
                "parsing naive timestamp without timezone; assuming UTC — consider re-running migrations"
            );
            return Ok(ndt.and_utc());
        }
    }

Comment on lines +610 to +613
assert!(
DateTime::parse_from_rfc3339(&started_at).is_ok(),
"started_at not valid RFC 3339: {started_at}"
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For completeness and to make the test more robust, you should also verify that last_activity is a valid RFC 3339 timestamp using DateTime::parse_from_rfc3339, just as you do for started_at.

        assert!(
            DateTime::parse_from_rfc3339(&started_at).is_ok(),
            "started_at not valid RFC 3339: {started_at}"
        );
        assert!(
            DateTime::parse_from_rfc3339(&last_activity).is_ok(),
            "last_activity not valid RFC 3339: {last_activity}"
        );

Copy link
Member

@ilblackdragon ilblackdragon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean, well-scoped PR. All 55 datetime('now') occurrences are replaced, backward compat is preserved via the naive fallback, and test coverage is solid. A few suggestions:

1. Warning noise on legacy data (medium)

The tracing::warn! in parse_timestamp() will fire on every row read from an existing database with naive timestamps. On a DB with thousands of rows this will flood logs. Consider:

  • Downgrading to tracing::debug! or tracing::trace!
  • Or using std::sync::Once / AtomicBool to warn once per process

2. Update src/db/CLAUDE.md (low)

Line 84 still documents DEFAULT (datetime('now')) as the libSQL convention. Per project rules ("Update both sides"), this should be updated to DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) to keep spec and code in sync.

3. Overall

Core change is correct and complete — no missed occurrences in migrations or runtime SQL. Tests are well-structured (tempfile, tokio::test, covers RFC 3339 + naive + invalid + DB-level DEFAULT). Low risk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: db/libsql libSQL / Turso backend size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize libSQL timestamp storage to RFC 3339 with UTC offset

2 participants