Skip to content

Add time crate support for Timestamp #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
### Checklist
- [ ] Formatted code using `cargo fmt --all`
- [ ] Linted code using clippy
- [ ] with reqwest feature: `cargo clippy --manifest-path influxdb/Cargo.toml --all-targets --no-default-features --features serde,derive,reqwest-client-rustls -- -D warnings`
- [ ] with surf feature: `cargo clippy --manifest-path influxdb/Cargo.toml --all-targets --no-default-features --features serde,derive,hyper-client -- -D warnings`
- [ ] with reqwest feature: `cargo clippy --manifest-path influxdb/Cargo.toml --all-targets --no-default-features --features chrono,time,serde,derive,reqwest-client-rustls -- -D warnings`
- [ ] with surf feature: `cargo clippy --manifest-path influxdb/Cargo.toml --all-targets --no-default-features --features chrono,time,serde,derive,hyper-client -- -D warnings`
- [ ] Updated README.md using `cargo doc2readme -p influxdb --expand-macros`
- [ ] Reviewed the diff. Did you leave any print statements or unnecessary comments?
- [ ] Any unfinished work that warrants a separate issue captured in an issue with a TODO code comment
24 changes: 11 additions & 13 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ jobs:
- name: Update Cargo.lock
run: cargo --config 'resolver.incompatible-rust-versions="fallback"' update
- name: Check Clippy lints (reqwest)
run: cargo clippy --manifest-path influxdb/Cargo.toml --locked --all-targets --no-default-features --features serde,derive,reqwest-client-rustls -- -D warnings
run: cargo clippy --manifest-path influxdb/Cargo.toml --locked --all-targets --no-default-features --features chrono,time,serde,derive,reqwest-client-rustls -- -D warnings
- name: Check Clippy lints (surf)
run: cargo clippy --manifest-path influxdb/Cargo.toml --locked --all-targets --no-default-features --features serde,derive,hyper-client -- -D warnings
run: cargo clippy --manifest-path influxdb/Cargo.toml --locked --all-targets --no-default-features --features chrono,time,serde,derive,hyper-client -- -D warnings

# this checks that the code is formatted with rustfmt
rustfmt:
Expand Down Expand Up @@ -108,8 +108,8 @@ jobs:
key: "${{runner.os}} Rust ${{steps.msrv-toolchain.outputs.cachekey}}"
if: matrix.rust.name == 'MSRV'
# finally we can run tests
- run: cargo test --lib --locked
- run: cargo test --doc --locked
- run: cargo test --lib --locked --features 'chrono time serde derive'
- run: cargo test --doc --locked --features 'chrono time serde derive'

# this tests that all integration tests are successful
integration_tests:
Expand All @@ -124,10 +124,6 @@ jobs:
toolchain: stable
nightly: false
http-backend:
- curl-client
- h1-client
- h1-client-rustls
- hyper-client
- reqwest-client-rustls
- reqwest-client-native-tls
- reqwest-client-native-tls-vendored
Expand Down Expand Up @@ -172,10 +168,12 @@ jobs:
key: "${{runner.os}} Rust ${{steps.rust-toolchain.outputs.cachekey}}"
- name: Run tests
run: |
for test in integration_tests{,_v2}
do
cargo test -p influxdb --no-default-features --features 'serde derive ${{matrix.http-backend}}' --no-fail-fast --test $test
done
cargo test -p influxdb \
--no-default-features \
--features "serde derive chrono time ${{matrix.http-backend}}" \
--no-fail-fast \
--test integration_tests \
--test integration_tests_v2

# this uses cargo-tarpaulin to inspect the code coverage
coverage:
Expand Down Expand Up @@ -221,7 +219,7 @@ jobs:
cargo tarpaulin -v \
--target-dir target/tarpaulin \
--workspace \
--features serde,derive \
--features chrono,time,serde,derive \
--exclude-files 'derive/*' \
--exclude-files 'target/*' \
--ignore-panics --ignore-tests \
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resolver = "2"
[workspace.package]
authors = ["Gero Gerke <git@gero.dev>", "Dominic <git@msrd0.de>"]
edition = "2018"
rust-version = "1.65"
rust-version = "1.67.1"
license = "MIT"
repository = "https://github.com/influxdb-rs/influxdb-rust"

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
<a href="https://www.rust-lang.org/en-US/">
<img src="https://img.shields.io/badge/Made%20with-Rust-orange.svg" alt='Build with Rust' />
</a>
<a href="https://github.com/rust-lang/rust/releases/tag/1.65.0">
<img src="https://img.shields.io/badge/rustc-1.65.0+-yellow.svg" alt='Minimum Rust Version: 1.65.0' />
<a href="https://github.com/rust-lang/rust/releases/tag/1.67.1">
<img src="https://img.shields.io/badge/rustc-1.67.1+-yellow.svg" alt='Minimum Rust Version: 1.67.1' />
</a>
</p>

Expand Down Expand Up @@ -155,7 +155,7 @@ To communicate with InfluxDB, you can choose the HTTP backend to be used configu
@ 2020-2024 Gero Gerke, msrd0 and [contributors].

[contributors]: https://github.com/influxdb-rs/influxdb-rust/graphs/contributors
[__cargo_doc2readme_dependencies_info]: ggGkYW0BYXSEGzJ_QpW55zB1G0S-TER-rIfLG2gXv8EYBG3jG1nuXXn-kdx-YXKEG1LaAVLASZMqG5J2qfpyCvbMG_Rohh5BobOmG0DqLv5454SZYWSBgmhpbmZsdXhkYmUwLjcuMg
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGzJ_QpW55zB1G0S-TER-rIfLG2gXv8EYBG3jG1nuXXn-kdx-YXKEG1LaAVLASZMqG5J2qfpyCvbMG_Rohh5BobOmG0DqLv5454SZYWSBgmhpbmZsdXhkYmUwLjcuMg
[__link0]: https://github.com/influxdb-rs/influxdb-rust/blob/main/CONTRIBUTING.md
[__link1]: https://github.com/influxdb-rs/influxdb-rust/blob/main/CODE_OF_CONDUCT.md
[__link10]: https://github.com/alexcrichton/curl-rust
Expand Down
9 changes: 7 additions & 2 deletions influxdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ repository.workspace = true
workspace = true

[dependencies]
chrono = { version = "0.4.23", features = ["serde"], default-features = false }
chrono = { version = "0.4.23", features = ["serde"], default-features = false, optional = true }
futures-util = "0.3.17"
http = "0.2.4"
influxdb_derive = { version = "0.5.1", optional = true }
lazy-regex = "3.1"
reqwest = { version = "0.11.4", default-features = false, optional = true }
surf = { version = "2.2.0", default-features = false, optional = true }
serde = { version = "1.0.186", optional = true }
serde_derive = { version = "1.0.186", optional = true }
serde_json = { version = "1.0.48", optional = true }
surf = { version = "2.2.0", default-features = false, optional = true }
thiserror = "1.0"
time = { version = "0.3.39", optional = true }

[features]
default = ["serde", "reqwest-client-rustls"]
Expand All @@ -44,6 +45,10 @@ reqwest-client-native-tls = ["reqwest", "reqwest/native-tls-alpn"]
reqwest-client-native-tls-vendored = ["reqwest", "reqwest/native-tls-vendored"]
wasm-client = ["surf", "surf/wasm-client"]

# etc
time = ["dep:time"]
chrono = ["dep:chrono"]

[dev-dependencies]
async-std = { version = "1.6.5", features = ["attributes", "tokio02", "tokio1"] }
indoc = "1.0"
Expand Down
99 changes: 66 additions & 33 deletions influxdb/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
//! assert!(read_query.is_ok());
//! ```

use chrono::prelude::{DateTime, TimeZone, Utc};
use std::convert::TryInto;

pub mod consts;
mod line_proto_term;
pub mod read_query;
Expand All @@ -47,6 +44,21 @@ pub enum Timestamp {
Hours(u128),
}

impl Timestamp {
pub fn nanos(&self) -> u128 {
match self {
Timestamp::Hours(h) => {
h * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLIS_PER_SECOND * NANOS_PER_MILLI
}
Timestamp::Minutes(m) => m * SECONDS_PER_MINUTE * MILLIS_PER_SECOND * NANOS_PER_MILLI,
Timestamp::Seconds(s) => s * MILLIS_PER_SECOND * NANOS_PER_MILLI,
Timestamp::Milliseconds(millis) => millis * NANOS_PER_MILLI,
Timestamp::Microseconds(micros) => micros * NANOS_PER_MICRO,
Timestamp::Nanoseconds(nanos) => *nanos,
}
}
}

impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Timestamp::*;
Expand All @@ -57,44 +69,52 @@ impl fmt::Display for Timestamp {
}
}

impl From<Timestamp> for DateTime<Utc> {
fn from(ts: Timestamp) -> DateTime<Utc> {
match ts {
Timestamp::Hours(h) => {
let nanos =
h * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLIS_PER_SECOND * NANOS_PER_MILLI;
Utc.timestamp_nanos(nanos.try_into().unwrap())
}
Timestamp::Minutes(m) => {
let nanos = m * SECONDS_PER_MINUTE * MILLIS_PER_SECOND * NANOS_PER_MILLI;
Utc.timestamp_nanos(nanos.try_into().unwrap())
}
Timestamp::Seconds(s) => {
let nanos = s * MILLIS_PER_SECOND * NANOS_PER_MILLI;
Utc.timestamp_nanos(nanos.try_into().unwrap())
}
Timestamp::Milliseconds(millis) => {
let nanos = millis * NANOS_PER_MILLI;
Utc.timestamp_nanos(nanos.try_into().unwrap())
}
Timestamp::Nanoseconds(nanos) => Utc.timestamp_nanos(nanos.try_into().unwrap()),
Timestamp::Microseconds(micros) => {
let nanos = micros * NANOS_PER_MICRO;
Utc.timestamp_nanos(nanos.try_into().unwrap())
}
}
#[cfg(feature = "chrono")]
impl From<Timestamp> for chrono::DateTime<chrono::Utc> {
fn from(ts: Timestamp) -> chrono::DateTime<chrono::Utc> {
use chrono::TimeZone as _;
chrono::Utc.timestamp_nanos(ts.nanos() as i64)
}
}

impl<T> From<DateTime<T>> for Timestamp
#[cfg(feature = "chrono")]
impl<T> From<chrono::DateTime<T>> for Timestamp
where
T: TimeZone,
T: chrono::TimeZone,
{
fn from(date_time: DateTime<T>) -> Self {
fn from(date_time: chrono::DateTime<T>) -> Self {
Timestamp::Nanoseconds(date_time.timestamp_nanos_opt().unwrap() as u128)
}
}

#[cfg(feature = "time")]
impl From<Timestamp> for time::UtcDateTime {
fn from(value: Timestamp) -> Self {
time::UtcDateTime::from_unix_timestamp_nanos(value.nanos() as i128).unwrap()
}
}

#[cfg(feature = "time")]
impl From<time::UtcDateTime> for Timestamp {
fn from(value: time::UtcDateTime) -> Self {
Timestamp::Nanoseconds(value.unix_timestamp_nanos() as u128)
}
}

#[cfg(feature = "time")]
impl From<Timestamp> for time::OffsetDateTime {
fn from(value: Timestamp) -> Self {
time::OffsetDateTime::from_unix_timestamp_nanos(value.nanos() as i128).unwrap()
}
}

#[cfg(feature = "time")]
impl From<time::OffsetDateTime> for Timestamp {
fn from(value: time::OffsetDateTime) -> Self {
Timestamp::Nanoseconds(value.unix_timestamp_nanos() as u128)
}
}

pub trait Query {
/// Builds valid InfluxSQL which can be run against the Database.
/// In case no fields have been specified, it will return an error,
Expand Down Expand Up @@ -235,7 +255,6 @@ mod tests {
MILLIS_PER_SECOND, MINUTES_PER_HOUR, NANOS_PER_MICRO, NANOS_PER_MILLI, SECONDS_PER_MINUTE,
};
use crate::query::{Timestamp, ValidQuery};
use chrono::prelude::{DateTime, TimeZone, Utc};
use std::convert::TryInto;
#[test]
fn test_equality_str() {
Expand All @@ -252,8 +271,10 @@ mod tests {
fn test_format_for_timestamp_else() {
assert!(format!("{}", Timestamp::Nanoseconds(100)) == "100");
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_hours() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Hours(2).into();
assert_eq!(
Utc.timestamp_nanos(
Expand All @@ -264,8 +285,10 @@ mod tests {
datetime_from_timestamp
)
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_minutes() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Minutes(2).into();
assert_eq!(
Utc.timestamp_nanos(
Expand All @@ -276,8 +299,10 @@ mod tests {
datetime_from_timestamp
)
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_seconds() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Seconds(2).into();
assert_eq!(
Utc.timestamp_nanos(
Expand All @@ -288,29 +313,37 @@ mod tests {
datetime_from_timestamp
)
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_millis() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Milliseconds(2).into();
assert_eq!(
Utc.timestamp_nanos((2 * NANOS_PER_MILLI).try_into().unwrap()),
datetime_from_timestamp
)
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_nanos() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Nanoseconds(1).into();
assert_eq!(Utc.timestamp_nanos(1), datetime_from_timestamp)
}
#[cfg(feature = "chrono")]
#[test]
fn test_chrono_datetime_from_timestamp_micros() {
use chrono::prelude::*;
let datetime_from_timestamp: DateTime<Utc> = Timestamp::Microseconds(2).into();
assert_eq!(
Utc.timestamp_nanos((2 * NANOS_PER_MICRO).try_into().unwrap()),
datetime_from_timestamp
)
}
#[cfg(feature = "chrono")]
#[test]
fn test_timestamp_from_chrono_date() {
use chrono::prelude::*;
let timestamp_from_datetime: Timestamp = Utc
.with_ymd_and_hms(1970, 1, 1, 0, 0, 1)
.single()
Expand Down
Loading