mirror of
https://github.com/BurntSushi/jiff.git
synced 2025-12-23 08:47:45 +00:00
This PR adds a new `jiff-sqlx` crate. It defines wrapper types for `Timestamp`, `DateTime`, `Date`, `Time` and `Span`. For each wrapper type, the SQLx encoding traits are implemented. (Except, with `Span`, only the decoding trait is implemented.) This is similar to #141, but organizes things a bit differently. This also comes with SQLite support. MySQL support is missing since it seems, at present, to require exposing APIs in SQLx for a correct implementation. This initial implementation also omits `Zoned` entirely. I've left a comment in the source code explaining why. The quick summary is that, at least for PostgreSQL, I don't see a way to provide support for it without either silently losing data (the time zone) or just storing it as an RFC 9557 timestamp in a `TEXT` field. The downside of the latter is that it doesn't use PostgreSQL native datetime types. (Becuase we can't. Because PostgreSQL doesn't support storing anything other than civil time and timestamps with respect to its datetime types.) I do personally lean toward just using RFC 9557 as a `TEXT` type, but I'd like to collect real use cases first to make sure that's the right way to go. Ref #50, Closes #141 Ref https://github.com/launchbadge/sqlx/issues/3487
76 lines
2.3 KiB
Rust
76 lines
2.3 KiB
Rust
use anyhow::Context;
|
|
use jiff::civil;
|
|
use jiff_sqlx::ToSqlx;
|
|
use sqlx::SqlitePool;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let tmpfile = tempfile::NamedTempFile::new()?;
|
|
let path = tmpfile.path().to_str().context("invalid temporary file")?;
|
|
let pool = SqlitePool::connect(path).await?;
|
|
|
|
example_datetime_roundtrip(&pool).await?;
|
|
example_timestamp_julian(&pool).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Performs a round-trip with all of Jiff's datetime types.
|
|
async fn example_datetime_roundtrip(pool: &SqlitePool) -> anyhow::Result<()> {
|
|
type Record = (
|
|
jiff_sqlx::Timestamp,
|
|
jiff_sqlx::DateTime,
|
|
jiff_sqlx::Date,
|
|
jiff_sqlx::Time,
|
|
);
|
|
|
|
let given = (
|
|
"1970-01-01T00:00:00Z".parse::<jiff::Timestamp>()?.to_sqlx(),
|
|
civil::date(2025, 7, 20).at(0, 0, 0, 0).to_sqlx(),
|
|
civil::date(1999, 1, 8).to_sqlx(),
|
|
civil::time(23, 59, 59, 999_999_999).to_sqlx(),
|
|
);
|
|
let query = "SELECT $1, $2, $3, $4";
|
|
let got: Record = sqlx::query_as(query)
|
|
.bind(&given.0)
|
|
.bind(&given.1)
|
|
.bind(&given.2)
|
|
.bind(&given.3)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
assert_eq!(got, given);
|
|
Ok(())
|
|
}
|
|
|
|
/// Demonstrates that Jiff works with SQLite's "julian day" format.
|
|
///
|
|
/// Here's a sample transcript to show how SQLite itself works:
|
|
///
|
|
/// ```text
|
|
/// sqlite> select julianday('2025-02-06T21:33:30-05:00');
|
|
/// julianday('2025-02-06T21:33:30-05:00')
|
|
/// --------------------------------------
|
|
/// 2460713.60659722
|
|
/// sqlite> select datetime(2460713.60659722);
|
|
/// datetime(2460713.60659722)
|
|
/// --------------------------
|
|
/// 2025-02-07 02:33:30
|
|
/// sqlite> select unixepoch(2460713.60659722);
|
|
/// unixepoch(2460713.60659722)
|
|
/// ---------------------------
|
|
/// 1738895610
|
|
/// ```
|
|
async fn example_timestamp_julian(pool: &SqlitePool) -> anyhow::Result<()> {
|
|
let given = "2025-02-06T21:33:30-05".parse::<jiff::Timestamp>()?.to_sqlx();
|
|
let query = "SELECT julianday($1);";
|
|
let (got,): (jiff_sqlx::Timestamp,) =
|
|
sqlx::query_as(query).bind(&given).fetch_one(pool).await?;
|
|
|
|
// Play stupid games, win stupid prizes. The loss of precision here
|
|
// is what you get when you use floating point to represent datetimes.
|
|
let expected =
|
|
"2025-02-07T02:33:29.99981308Z".parse::<jiff::Timestamp>()?.to_sqlx();
|
|
assert_eq!(got, expected);
|
|
|
|
Ok(())
|
|
}
|