From 5af144195617a5661b5ba891628235a22271f5a4 Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Fri, 21 Nov 2025 11:04:47 -0800 Subject: [PATCH 1/4] MOD: Enable map_symbols by default for clients --- CHANGELOG.md | 5 +++++ src/historical/batch.rs | 49 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b30dadb..b46ac23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.37.0 - TBD + +#### Breaking changes +- The `map_symbols` parameter for `SubmitJobParams::builder()` now defaults to `true` for JSON and CSV encodings + ## 0.36.0 - 2025-11-19 ### Enhancements diff --git a/src/historical/batch.rs b/src/historical/batch.rs index ca5e2fc..1d18717 100644 --- a/src/historical/batch.rs +++ b/src/historical/batch.rs @@ -3,8 +3,7 @@ use std::{ cmp::Ordering, collections::HashMap, - fmt, - fmt::Write, + fmt::{self, Write}, num::NonZeroU64, path::{Path, PathBuf}, str::FromStr, @@ -382,9 +381,9 @@ pub struct SubmitJobParams { #[builder(default)] pub pretty_ts: bool, /// If `true`, a symbol field will be included with each text-encoded - /// record, reducing the need to look at the `symbology.json`. Only valid for - /// [`Encoding::Csv`] and [`Encoding::Json`]. - #[builder(default)] + /// record. If `None`, will default to `true` for [`Encoding::Csv`] and [`Encoding::Json`] encodings, + /// and `false` for [`Encoding::Dbn`]. + #[builder(default_code = "*encoding != Encoding::Dbn")] pub map_symbols: bool, /// If `true`, files will be split by raw symbol. Cannot be requested with [`Symbols::All`]. #[builder(default)] @@ -672,6 +671,7 @@ enum Header { #[cfg(test)] mod tests { + use dbn::Dataset; use reqwest::StatusCode; use serde_json::json; use time::macros::datetime; @@ -759,6 +759,45 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_submit_job_param_map_symbols() -> crate::Result<()> { + const START: time::OffsetDateTime = datetime!(2023 - 06 - 14 00:00 UTC); + const END: time::OffsetDateTime = datetime!(2023 - 06 - 17 00:00 UTC); + + let params = SubmitJobParams::builder() + .dataset(Dataset::GlbxMdp3) + .encoding(Encoding::Dbn) + .symbols("ESM5") + .schema(Schema::Mbo) + .date_time_range(START..END) + .build(); + assert_eq!(params.encoding, Encoding::Dbn); + assert_eq!(params.map_symbols, false); + + let params = SubmitJobParams::builder() + .dataset(Dataset::GlbxMdp3) + .encoding(Encoding::Csv) + .symbols("ESM5") + .schema(Schema::Mbo) + .date_time_range(START..END) + .build(); + assert_eq!(params.encoding, Encoding::Csv); + assert_eq!(params.map_symbols, true); + + let params = SubmitJobParams::builder() + .dataset(Dataset::GlbxMdp3) + .encoding(Encoding::Json) + .symbols("ESM5") + .schema(Schema::Mbo) + .date_time_range(START..END) + .map_symbols(false) + .build(); + assert_eq!(params.encoding, Encoding::Json); + assert_eq!(params.map_symbols, false); + + Ok(()) + } + #[tokio::test] async fn test_list_jobs() -> crate::Result<()> { const SCHEMA: Schema = Schema::Trades; From 2eea435c4140fbef5ef479bcc5e67c96f3280797 Mon Sep 17 00:00:00 2001 From: Carter Green Date: Wed, 3 Dec 2025 11:49:42 -0600 Subject: [PATCH 2/4] FIX: Fix checksum for resumed download --- CHANGELOG.md | 9 +++++++-- src/historical/batch.rs | 26 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b46ac23..edb3ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,13 @@ ## 0.37.0 - TBD -#### Breaking changes -- The `map_symbols` parameter for `SubmitJobParams::builder()` now defaults to `true` for JSON and CSV encodings +### Breaking changes +- Changed the default to `true` for the `map_symbols` parameter in + `SubmitJobParams::builder()` for JSON and CSV encodings + +### Bug fixes +- Fixed checksum verification for resumed downloads in `batch().download()`. Previously, + it would erroneously fail ## 0.36.0 - 2025-11-19 diff --git a/src/historical/batch.rs b/src/historical/batch.rs index 1d18717..2fffa61 100644 --- a/src/historical/batch.rs +++ b/src/historical/batch.rs @@ -16,7 +16,10 @@ use reqwest::RequestBuilder; use serde::{de, Deserialize, Deserializer}; use sha2::{Digest, Sha256}; use time::OffsetDateTime; -use tokio::io::BufWriter; +use tokio::{ + fs::File, + io::{AsyncReadExt, BufWriter}, +}; use tracing::{debug, error, info, info_span, warn, Instrument}; use typed_builder::TypedBuilder; @@ -211,7 +214,7 @@ impl BatchClient<'_> { let mut retries = 0; 'retry: loop { let mut req = self.inner.get_with_path(url.path())?; - match Self::check_if_exists(path, exp_size).await? { + match Self::check_if_exists(path, exp_size, &mut hasher).await? { Header::Skip => { return Ok(()); } @@ -261,7 +264,11 @@ impl BatchClient<'_> { .await } - async fn check_if_exists(path: &Path, exp_size: u64) -> crate::Result
{ + async fn check_if_exists( + path: &Path, + exp_size: u64, + hasher: &mut Option, + ) -> crate::Result
{ let Ok(metadata) = tokio::fs::metadata(path).await else { return Ok(Header::Range(None)); }; @@ -273,6 +280,17 @@ impl BatchClient<'_> { total_bytes = exp_size, "Found existing file, resuming download" ); + if let Some(hasher) = hasher { + let mut buf = vec![0; 1 << 23]; + let mut file = File::open(path).await?; + loop { + let read_size = file.read(&mut buf).await?; + if read_size == 0 { + break; + } + hasher.update(&buf[..read_size]); + } + } } Ordering::Equal => { debug!("Skipping download as file already exists and matches expected size"); @@ -299,7 +317,7 @@ impl BatchClient<'_> { if hash_hex != exp_hash_hex { warn!( hash_hex, - exp_hash_hex, "Downloaded file failed checksum validation" + exp_hash_hex, "Downloaded file failed checksum verification" ); } else { debug!("Successfully verified checksum"); From 6eb1d48140a0225dc974a2e3054b30825ec241cb Mon Sep 17 00:00:00 2001 From: "William T. Nelson" <35801+wtn@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:23:45 -0600 Subject: [PATCH 3/4] ADD: Add chrono feature for datetime conversions --- CHANGELOG.md | 5 + Cargo.toml | 3 + README.md | 1 + src/historical.rs | 347 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 47 +++++ src/live.rs | 75 +++++++- src/reference.rs | 19 +- src/reference/adjustment.rs | 7 +- src/reference/corporate.rs | 6 +- src/reference/security.rs | 5 +- 10 files changed, 484 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edb3ada..0d6a343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,14 @@ ## 0.37.0 - TBD +### Enhancements +- Added optional `chrono` feature for converting `chrono` date and datetime types to + `DateRange`, `DateTimeRange`, and `Subscription::start` (credit: @wtn) + ### Breaking changes - Changed the default to `true` for the `map_symbols` parameter in `SubmitJobParams::builder()` for JSON and CSV encodings +- Moved `DateTimeLike` trait to top level ### Bug fixes - Fixed checksum verification for resumed downloads in `batch().download()`. Previously, diff --git a/Cargo.toml b/Cargo.toml index 250d057..45b5a12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,11 +30,13 @@ historical = [ "tokio/fs" ] live = ["tokio/net"] +chrono = ["dep:chrono"] [dependencies] dbn = { version = "0.44.0", features = ["async", "serde"] } async-compression = { version = "0.4", features = ["tokio", "zstd"], optional = true } +chrono = { version = ">=0.4.34", optional = true, default-features = false, features = ["alloc"] } # Async stream trait futures = { version = "0.3", optional = true } # Used for Live authentication and historical checksums @@ -55,6 +57,7 @@ zstd = { version = "0.13", optional = true } [dev-dependencies] anyhow = "1.0.100" +chrono = { version = ">=0.4.34", default-features = false, features = ["alloc"] } async-compression = { version = "0.4", features = ["tokio", "zstd"] } clap = { version = "4.5.51", features = ["derive"] } rstest = "0.26.1" diff --git a/README.md b/README.md index 936692d..8f98048 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ cargo add databento - `historical`: enables the historical client for data older than 24 hours - `live`: enables the live client for real-time and intraday historical data +- `chrono`: enables passing `chrono` types as datetime parameters By default both features are enabled and the historical client uses OpenSSL for TLS. To use `rustls`, disable default features for both the databento crate and [reqwest](https://github.com/seanmonstar/reqwest). diff --git a/src/historical.rs b/src/historical.rs index de77bee..628085f 100644 --- a/src/historical.rs +++ b/src/historical.rs @@ -202,6 +202,127 @@ impl From<(time::OffsetDateTime, time::Duration)> for DateTimeRange { } } +#[cfg(feature = "chrono")] +mod chrono_impl { + use super::{DateRange, DateTimeRange}; + + fn chrono_datetime_to_time(dt: chrono::DateTime) -> time::OffsetDateTime { + // timestamp_nanos_opt() returns None for dates outside ~1677-2262. + // from_unix_timestamp_nanos() fails for dates outside ~1000-9999. + // Practical timestamps fall well within these bounds, so unwrap is safe. + time::OffsetDateTime::from_unix_timestamp_nanos(dt.timestamp_nanos_opt().unwrap() as i128) + .unwrap() + } + + fn chrono_date_to_time(date: chrono::NaiveDate) -> time::Date { + use chrono::Datelike; + time::Date::from_calendar_date( + date.year(), + time::Month::try_from(date.month() as u8).unwrap(), + date.day() as u8, + ) + .unwrap() + } + + impl From>> for DateTimeRange { + fn from(range: std::ops::Range>) -> Self { + Self { + start: chrono_datetime_to_time(range.start.to_utc()), + end: chrono_datetime_to_time(range.end.to_utc()), + } + } + } + + impl From>> for DateTimeRange { + fn from(range: std::ops::RangeInclusive>) -> Self { + Self { + start: chrono_datetime_to_time(range.start().to_utc()), + end: chrono_datetime_to_time(range.end().to_utc()) + time::Duration::NANOSECOND, + } + } + } + + impl From<(chrono::DateTime, chrono::DateTime)> for DateTimeRange { + fn from(value: (chrono::DateTime, chrono::DateTime)) -> Self { + Self { + start: chrono_datetime_to_time(value.0.to_utc()), + end: chrono_datetime_to_time(value.1.to_utc()), + } + } + } + + impl From<(chrono::DateTime, chrono::Duration)> for DateTimeRange { + fn from(value: (chrono::DateTime, chrono::Duration)) -> Self { + let start = chrono_datetime_to_time(value.0.to_utc()); + let duration_nanos = value.1.num_nanoseconds().unwrap(); + Self { + start, + end: start + time::Duration::nanoseconds(duration_nanos), + } + } + } + + impl From for DateRange { + fn from(date: chrono::NaiveDate) -> Self { + Self { + start: chrono_date_to_time(date), + end: chrono_date_to_time(date.succ_opt().unwrap()), + } + } + } + + impl From> for DateRange { + fn from(range: std::ops::Range) -> Self { + Self { + start: chrono_date_to_time(range.start), + end: chrono_date_to_time(range.end), + } + } + } + + impl From> for DateRange { + fn from(range: std::ops::RangeInclusive) -> Self { + Self { + start: chrono_date_to_time(*range.start()), + end: chrono_date_to_time(range.end().succ_opt().unwrap()), + } + } + } + + impl From<(chrono::NaiveDate, chrono::NaiveDate)> for DateRange { + fn from(value: (chrono::NaiveDate, chrono::NaiveDate)) -> Self { + Self { + start: chrono_date_to_time(value.0), + end: chrono_date_to_time(value.1), + } + } + } + + impl From for DateTimeRange { + fn from(date: chrono::NaiveDate) -> Self { + Self::from(DateRange::from(date)) + } + } + + impl From> for DateTimeRange { + fn from(range: std::ops::Range) -> Self { + Self::from(DateRange::from(range)) + } + } + + impl From> for DateTimeRange { + fn from(range: std::ops::RangeInclusive) -> Self { + Self::from(DateRange::from(range)) + } + } + + impl From<(chrono::NaiveDate, chrono::NaiveDate)> for DateTimeRange { + fn from(value: (chrono::NaiveDate, chrono::NaiveDate)) -> Self { + Self::from(DateRange::from(value)) + } + } +} + impl TryFrom<(u64, u64)> for DateTimeRange { type Error = crate::Error; @@ -298,6 +419,232 @@ mod tests { use time::macros::{date, datetime}; + #[cfg(feature = "chrono")] + mod chrono_tests { + use super::*; + use chrono::{TimeZone, Utc}; + + #[test] + fn datetime_utc_range() { + let start = Utc.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 3, 15, 16, 0, 0).unwrap(); + + let range = DateTimeRange::from(start..end); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 09:30:00 UTC), + datetime!(2024-03-15 16:00:00 UTC) + )) + ); + } + + #[test] + fn datetime_utc_range_inclusive() { + let start = Utc.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 3, 15, 16, 0, 0).unwrap(); + + let range = DateTimeRange::from(start..=end); + + // Inclusive end should add 1 nanosecond + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 09:30:00 UTC), + datetime!(2024-03-15 16:00:00.000000001 UTC) + )) + ); + } + + #[test] + fn datetime_utc_tuple() { + let start = Utc.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 3, 15, 16, 0, 0).unwrap(); + + let range = DateTimeRange::from((start, end)); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 09:30:00 UTC), + datetime!(2024-03-15 16:00:00 UTC) + )) + ); + } + + #[test] + fn datetime_utc_with_duration() { + let start = Utc.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let duration = chrono::Duration::hours(6) + chrono::Duration::minutes(30); + + let range = DateTimeRange::from((start, duration)); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 09:30:00 UTC), + datetime!(2024-03-15 16:00:00 UTC) + )) + ); + } + + #[test] + fn naive_date_single() { + let date = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + + let range = DateTimeRange::from(date); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 00:00:00 UTC), + datetime!(2024-03-16 00:00:00 UTC) + )) + ); + } + + #[test] + fn naive_date_range() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateTimeRange::from(start..end); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 00:00:00 UTC), + datetime!(2024-03-20 00:00:00 UTC) + )) + ); + } + + #[test] + fn naive_date_range_inclusive() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateTimeRange::from(start..=end); + + // Inclusive end date means next day at midnight + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 00:00:00 UTC), + datetime!(2024-03-21 00:00:00 UTC) + )) + ); + } + + #[test] + fn naive_date_tuple() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateTimeRange::from((start, end)); + + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 00:00:00 UTC), + datetime!(2024-03-20 00:00:00 UTC) + )) + ); + } + + #[test] + fn fixed_offset_datetime_range() { + use chrono::FixedOffset; + + let offset = FixedOffset::west_opt(4 * 3600).unwrap(); // UTC-4 + let start = offset.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let end = offset.with_ymd_and_hms(2024, 3, 15, 16, 0, 0).unwrap(); + + let range = DateTimeRange::from(start..end); + + // Should convert to UTC: 09:30 UTC-4 = 13:30 UTC, 16:00 UTC-4 = 20:00 UTC + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 13:30:00 UTC), + datetime!(2024-03-15 20:00:00 UTC) + )) + ); + } + + #[test] + fn fixed_offset_datetime_tuple() { + use chrono::FixedOffset; + + let offset = FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap(); // UTC+5:30 + let start = offset.with_ymd_and_hms(2024, 3, 15, 15, 0, 0).unwrap(); + let end = offset.with_ymd_and_hms(2024, 3, 15, 21, 30, 0).unwrap(); + + let range = DateTimeRange::from((start, end)); + + // 15:00 UTC+5:30 = 09:30 UTC, 21:30 UTC+5:30 = 16:00 UTC + assert_eq!( + range, + DateTimeRange::from(( + datetime!(2024-03-15 09:30:00 UTC), + datetime!(2024-03-15 16:00:00 UTC) + )) + ); + } + + #[test] + fn date_range_from_naive_date_single() { + let chrono_date = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + + let range = DateRange::from(chrono_date); + + assert_eq!( + range, + DateRange::from((date!(2024 - 03 - 15), date!(2024 - 03 - 16))) + ); + } + + #[test] + fn date_range_from_naive_date_range() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateRange::from(start..end); + + assert_eq!( + range, + DateRange::from((date!(2024 - 03 - 15), date!(2024 - 03 - 20))) + ); + } + + #[test] + fn date_range_from_naive_date_range_inclusive() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateRange::from(start..=end); + + assert_eq!( + range, + DateRange::from((date!(2024 - 03 - 15), date!(2024 - 03 - 21))) + ); + } + + #[test] + fn date_range_from_naive_date_tuple() { + let start = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let end = chrono::NaiveDate::from_ymd_opt(2024, 3, 20).unwrap(); + + let range = DateRange::from((start, end)); + + assert_eq!( + range, + DateRange::from((date!(2024 - 03 - 15), date!(2024 - 03 - 20))) + ); + } + } + #[test] fn date_range_from_lt_day_duration() { let target = DateRange::from((date!(2024 - 02 - 16), time::Duration::SECOND)); diff --git a/src/lib.rs b/src/lib.rs index 15d34e1..b0c4f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,6 +294,53 @@ static USER_AGENT: LazyLock = LazyLock::new(|| { ) }); +/// A datetime or object that can be non-fallibly converted to a datetime. +pub trait DateTimeLike { + /// Converts the object to a datetime. + fn to_date_time(self) -> time::OffsetDateTime; +} + +impl DateTimeLike for time::OffsetDateTime { + fn to_date_time(self) -> time::OffsetDateTime { + self + } +} + +impl DateTimeLike for time::Date { + fn to_date_time(self) -> time::OffsetDateTime { + self.with_time(time::Time::MIDNIGHT).assume_utc() + } +} + +#[cfg(feature = "chrono")] +impl DateTimeLike for chrono::DateTime { + fn to_date_time(self) -> time::OffsetDateTime { + // timestamp_nanos_opt() returns None for dates outside ~1677-2262. + // from_unix_timestamp_nanos() fails for dates outside ~1000-9999. + // Practical timestamps fall well within these bounds, so unwrap is safe. + time::OffsetDateTime::from_unix_timestamp_nanos( + self.to_utc().timestamp_nanos_opt().unwrap() as i128, + ) + .unwrap() + } +} + +#[cfg(feature = "chrono")] +impl DateTimeLike for chrono::NaiveDate { + fn to_date_time(self) -> time::OffsetDateTime { + use chrono::Datelike; + // Conversion is infallible for valid NaiveDate: month is 1-12, day is 1-31. + time::Date::from_calendar_date( + self.year(), + time::Month::try_from(self.month() as u8).unwrap(), + self.day() as u8, + ) + .unwrap() + .with_time(time::Time::MIDNIGHT) + .assume_utc() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/live.rs b/src/live.rs index 8c1222d..3583e9b 100644 --- a/src/live.rs +++ b/src/live.rs @@ -11,7 +11,7 @@ use tokio::net::{lookup_host, ToSocketAddrs}; use tracing::warn; use typed_builder::TypedBuilder; -use crate::{ApiKey, Symbols}; +use crate::{ApiKey, DateTimeLike, Symbols}; pub use client::Client; @@ -32,7 +32,7 @@ pub struct Subscription { /// /// Cannot be specified after the session is started with [`LiveClient::start`](crate::LiveClient::start). /// See [`Intraday Replay`](https://databento.com/docs/api-reference-live/basics/intraday-replay). - #[builder(default, setter(strip_option))] + #[builder(default, setter(transform = |dt: impl DateTimeLike| Some(dt.to_date_time())))] pub start: Option, #[doc(hidden)] /// Request subscription with snapshot. Only supported with `Mbo` schema. @@ -204,3 +204,74 @@ impl ClientBuilder { Client::new(self).await } } + +#[cfg(test)] +mod tests { + use super::*; + use dbn::Schema; + use time::macros::datetime; + + #[test] + fn subscription_with_time_offset_datetime() { + let start = datetime!(2024-03-15 09:30:00 UTC); + let sub = Subscription::builder() + .symbols("AAPL") + .schema(Schema::Trades) + .start(start) + .build(); + assert_eq!(sub.start, Some(start)); + } + + #[test] + fn subscription_with_time_date() { + let date = time::macros::date!(2024 - 03 - 15); + let sub = Subscription::builder() + .symbols("AAPL") + .schema(Schema::Trades) + .start(date) + .build(); + assert_eq!(sub.start, Some(datetime!(2024-03-15 00:00:00 UTC))); + } + + #[cfg(feature = "chrono")] + mod chrono_tests { + use super::*; + use chrono::{TimeZone, Utc}; + + #[test] + fn subscription_with_chrono_datetime_utc() { + let start = Utc.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let sub = Subscription::builder() + .symbols("AAPL") + .schema(Schema::Trades) + .start(start) + .build(); + assert_eq!(sub.start, Some(datetime!(2024-03-15 09:30:00 UTC))); + } + + #[test] + fn subscription_with_chrono_datetime_fixed_offset() { + use chrono::FixedOffset; + let est = FixedOffset::west_opt(5 * 3600).unwrap(); + let start = est.with_ymd_and_hms(2024, 3, 15, 9, 30, 0).unwrap(); + let sub = Subscription::builder() + .symbols("AAPL") + .schema(Schema::Trades) + .start(start) + .build(); + // 09:30 EST = 14:30 UTC + assert_eq!(sub.start, Some(datetime!(2024-03-15 14:30:00 UTC))); + } + + #[test] + fn subscription_with_chrono_naive_date() { + let date = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(); + let sub = Subscription::builder() + .symbols("AAPL") + .schema(Schema::Trades) + .start(date) + .build(); + assert_eq!(sub.start, Some(datetime!(2024-03-15 00:00:00 UTC))); + } + } +} diff --git a/src/reference.rs b/src/reference.rs index b50dca2..31449a3 100644 --- a/src/reference.rs +++ b/src/reference.rs @@ -9,7 +9,7 @@ mod enums; pub mod security; use reqwest::{header::ACCEPT, RequestBuilder, Url}; -use time::{Date, OffsetDateTime}; +use time::OffsetDateTime; use crate::{ historical::{AddToForm, HistoricalGateway, ReqwestForm, API_VERSION}, @@ -262,23 +262,6 @@ impl AddToForm> for ReqwestForm { } } -/// A date time or object that can be non-fallibly converted to a datetime. -pub trait DateTimeLike { - /// Converts the object to a date time. - fn to_date_time(self) -> OffsetDateTime; -} - -impl DateTimeLike for OffsetDateTime { - fn to_date_time(self) -> OffsetDateTime { - self - } -} -impl DateTimeLike for Date { - fn to_date_time(self) -> OffsetDateTime { - self.with_time(time::Time::MIDNIGHT).assume_utc() - } -} - #[cfg(test)] mod test_infra { use wiremock::MockServer; diff --git a/src/reference/adjustment.rs b/src/reference/adjustment.rs index 1efb964..8205fa3 100644 --- a/src/reference/adjustment.rs +++ b/src/reference/adjustment.rs @@ -8,11 +8,8 @@ use typed_builder::TypedBuilder; use crate::{ deserialize::deserialize_date_time, historical::{handle_zstd_jsonl_response, AddToForm}, - reference::{ - AdjustmentStatus, Country, Currency, DateTimeLike, End, Event, Frequency, SecurityType, - Start, - }, - Symbols, + reference::{AdjustmentStatus, Country, Currency, End, Event, Frequency, SecurityType, Start}, + DateTimeLike, Symbols, }; /// A client for the adjustment factors group of Reference API endpoints. diff --git a/src/reference/corporate.rs b/src/reference/corporate.rs index 4416317..ca91e3e 100644 --- a/src/reference/corporate.rs +++ b/src/reference/corporate.rs @@ -11,10 +11,10 @@ use crate::{ deserialize::{deserialize_date_time, deserialize_opt_date_time_hash_map}, historical::{handle_zstd_jsonl_response, AddToForm, ReqwestForm}, reference::{ - Action, Country, Currency, DateTimeLike, End, Event, EventSubType, Fraction, GlobalStatus, - ListingSource, ListingStatus, MandVolu, OutturnStyle, PaymentType, SecurityType, Start, + Action, Country, Currency, End, Event, EventSubType, Fraction, GlobalStatus, ListingSource, + ListingStatus, MandVolu, OutturnStyle, PaymentType, SecurityType, Start, }, - Symbols, + DateTimeLike, Symbols, }; /// A client for the corporate actions group of Reference API endpoints. diff --git a/src/reference/security.rs b/src/reference/security.rs index a12523d..e85ab26 100644 --- a/src/reference/security.rs +++ b/src/reference/security.rs @@ -12,10 +12,9 @@ use crate::{ deserialize::deserialize_date_time, historical::{handle_zstd_jsonl_response, AddToForm}, reference::{ - Country, Currency, DateTimeLike, End, ListingSource, ListingStatus, SecurityType, Start, - Voting, + Country, Currency, End, ListingSource, ListingStatus, SecurityType, Start, Voting, }, - Symbols, + DateTimeLike, Symbols, }; /// A client for the security master group of Reference API endpoints. From 71aaf485a5ad11a37700c94ceb7561b5aec8e506 Mon Sep 17 00:00:00 2001 From: Carter Green Date: Tue, 9 Dec 2025 09:47:14 -0600 Subject: [PATCH 4/4] VER: Release Rust client 0.37.0 --- CHANGELOG.md | 5 ++++- Cargo.toml | 4 ++-- src/reference/enums.rs | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6a343..e5e641b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog -## 0.37.0 - TBD +## 0.37.0 - 2025-12-09 ### Enhancements - Added optional `chrono` feature for converting `chrono` date and datetime types to `DateRange`, `DateTimeRange`, and `Subscription::start` (credit: @wtn) +- Upgraded DBN version to 0.45.0: + - Added new venue, dataset, and publisher for Cboe Futures Exchange (`XCBF.PITCH`) + ### Breaking changes - Changed the default to `true` for the `map_symbols` parameter in diff --git a/Cargo.toml b/Cargo.toml index 45b5a12..1ac407d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "databento" authors = ["Databento "] -version = "0.36.0" +version = "0.37.0" edition = "2021" repository = "https://github.com/databento/databento-rs" description = "Official Databento client library" @@ -33,7 +33,7 @@ live = ["tokio/net"] chrono = ["dep:chrono"] [dependencies] -dbn = { version = "0.44.0", features = ["async", "serde"] } +dbn = { version = "0.45.0", features = ["async", "serde"] } async-compression = { version = "0.4", features = ["tokio", "zstd"], optional = true } chrono = { version = ">=0.4.34", optional = true, default-features = false, features = ["alloc"] } diff --git a/src/reference/enums.rs b/src/reference/enums.rs index 8a6ecd4..270665c 100644 --- a/src/reference/enums.rs +++ b/src/reference/enums.rs @@ -229,7 +229,7 @@ pub enum Country { Cu, /// Cape Verde Cv, - /// Curacao + /// Curaçao Cw, /// Christmas Islands Cx, @@ -1369,7 +1369,7 @@ pub enum Currency { Mwk, /// Mexican Nuevo Peso Mxn, - /// Mexican Unidad de Inversion (UDI) + /// Mexican Unidad de Inversión (UDI) Mxv, /// Malaysian Ringgit Myr, @@ -1399,7 +1399,7 @@ pub enum Currency { Php, /// Pakistan Rupee Pkr, - /// Polish Zloty (New) + /// Polish Złoty (New) Pln, /// Paraguay Guarani Pyg,