Skip to content

Commit

Permalink
feat(pgrx): add chrono feature for easy conversions
Browse files Browse the repository at this point in the history
This commit adds a `chrono` feature flag to `pgrx`, which enables
conversions between `pgrx` native date/time (with or without timezone)
and `chrono` types like `NaiveDateTime`.
  • Loading branch information
t3hmrman committed Apr 11, 2024
1 parent fe9ab69 commit d389f30
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 0 deletions.
63 changes: 63 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ thiserror = "1"
unescape = "0.1.0" # for escaped-character-handling
url = "2.4.1" # the non-existent std::web
walkdir = "2" # directory recursion
chrono = "0.4.35" # conversions to chrono data structures

[profile.dev]
# Only include line tables in debuginfo. This reduces the size of target/ (after
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ ASCII nor UTF-8 (as Postgres will then accept but ignore non-ASCII bytes).
For best results, always use PGRX with UTF-8, and set database encodings
explicitly upon database creation.

To easily convert `pgrx` temporal types (`pgrx::TimestampWithTimezone`, etc)
to [`chrono`] compatible types, enable the `chrono` feature.

## Digging Deeper

- [cargo-pgrx sub-command](cargo-pgrx/)
Expand Down
2 changes: 2 additions & 0 deletions pgrx-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pg_test = [ ]
proptest = [ "dep:proptest" ]
cshim = [ "pgrx/cshim" ]
no-schema-generation = [ "pgrx/no-schema-generation", "pgrx-macros/no-schema-generation" ]
chrono = [ "dep:chrono", "pgrx/chrono" ]

[package.metadata.docs.rs]
features = ["pg14", "proptest"]
Expand Down Expand Up @@ -65,6 +66,7 @@ serde = "1.0"
serde_json = "1.0"
sysinfo = "0.29.10"
rand = "0.8.5"
chrono = { workspace = true, optional = true }

[dependencies.pgrx] # Not unified in workspace due to default-features key
path = "../pgrx"
Expand Down
75 changes: 75 additions & 0 deletions pgrx-tests/src/tests/chrono_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Tests for the `chrono` features of `cargo-pgrx`
//!
#![cfg(feature = "chrono")]

#[cfg(any(test, feature = "pg_test"))]
#[pgrx::pg_schema]
mod tests {
#[allow(unused_imports)]
use crate as pgrx_tests;

use std::result::Result;

use chrono::{Datelike as _, Timelike as _, Utc};

use pgrx::pg_test;
use pgrx::DateTimeConversionError;

// Utility class for errors
type DtcResult<T> = Result<T, DateTimeConversionError>;

/// Ensure simple conversion ([`pgrx::Date`] -> [`chrono::NaiveDate`]) works
#[pg_test]
fn chrono_simple_date_conversion() -> DtcResult<()> {
let original = pgrx::Date::new(1970, 1, 1)?;
let d = chrono::NaiveDate::try_from(original)?;
assert_eq!(d.year(), original.year(), "year matches");
assert_eq!(d.month(), 1, "month matches");
assert_eq!(d.day(), 1, "day matches");
let backwards = pgrx::Date::try_from(d)?;
assert_eq!(backwards, original);
Ok(())
}

/// Ensure simple conversion ([`pgrx::Time`] -> [`chrono::NaiveTime`]) works
#[pg_test]
fn chrono_simple_time_conversion() -> DtcResult<()> {
let original = pgrx::Time::new(12, 1, 59.0000001)?;
let d = chrono::NaiveTime::try_from(original)?;
assert_eq!(d.hour(), 12, "hours match");
assert_eq!(d.minute(), 1, "minutes match");
assert_eq!(d.second(), 59, "seconds match");
assert_eq!(d.nanosecond(), 0, "nanoseconds are zero (pg only supports microseconds)");
let backwards = pgrx::Time::try_from(d)?;
assert_eq!(backwards, original);
Ok(())
}

/// Ensure simple conversion ([`pgrx::Timestamp`] -> [`chrono::NaiveDateTime`]) works
#[pg_test]
fn chrono_simple_timestamp_conversion() -> DtcResult<()> {
let original = pgrx::Timestamp::new(1970, 1, 1, 1, 1, 1.0)?;
let d = chrono::NaiveDateTime::try_from(original)?;
assert_eq!(d.hour(), 1, "hours match");
assert_eq!(d.minute(), 1, "minutes match");
assert_eq!(d.second(), 1, "seconds match");
assert_eq!(d.nanosecond(), 0, "nanoseconds are zero (pg only supports microseconds)");
let backwards = pgrx::Timestamp::try_from(d)?;
assert_eq!(backwards, original, "NaiveDateTime -> Timestamp return conversion failed");
Ok(())
}

/// Ensure simple conversion ([`pgrx::TimestampWithTimeZone`] -> [`chrono::DateTime<Utc>`]) works
#[pg_test]
fn chrono_simple_datetime_with_time_zone_conversion() -> DtcResult<()> {
let original = pgrx::TimestampWithTimeZone::with_timezone(1970, 1, 1, 1, 1, 1.0, "utc")?;
let d = chrono::DateTime::<Utc>::try_from(original)?;
assert_eq!(d.hour(), 1, "hours match");
assert_eq!(d.minute(), 1, "minutes match");
assert_eq!(d.second(), 1, "seconds match");
assert_eq!(d.nanosecond(), 0, "nanoseconds are zero (pg only supports microseconds)");
let backwards = pgrx::TimestampWithTimeZone::try_from(d)?;
assert_eq!(backwards, original);
Ok(())
}
}
2 changes: 2 additions & 0 deletions pgrx-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ mod attributes_tests;
mod bgworker_tests;
mod bytea_tests;
mod cfg_tests;
#[cfg(feature = "chrono")]
mod chrono_tests;
mod composite_type_tests;
mod datetime_tests;
mod default_arg_value_tests;
Expand Down
2 changes: 2 additions & 0 deletions pgrx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pg15 = [ "pgrx-pg-sys/pg15" ]
pg16 = [ "pgrx-pg-sys/pg16" ]
no-schema-generation = ["pgrx-macros/no-schema-generation", "pgrx-sql-entity-graph/no-schema-generation"]
unsafe-postgres = [] # when trying to compile against something that looks like Postgres but claims to be diffent
chrono = [ "dep:chrono" ]

[package.metadata.docs.rs]
features = ["pg14", "cshim"]
Expand All @@ -59,6 +60,7 @@ enum-map = "2.6.3"
atomic-traits = "0.3.0" # PgAtomic and shmem init
bitflags = "2.4.0" # BackgroundWorker
bitvec = "1.0" # processing array nullbitmaps
chrono = { workspace = true, optional = true } # Conversions to chrono date time types
heapless = "0.8" # shmem and PgLwLock
libc.workspace = true # FFI type compat
seahash = "4.1.0" # derive(PostgresHash)
Expand Down
Loading

0 comments on commit d389f30

Please sign in to comment.