pub mod batch;
mod client;
mod deserialize;
pub mod metadata;
pub mod symbology;
pub mod timeseries;
pub use client::*;
use time::{
format_description::BorrowedFormatItem, macros::format_description, Duration, Time, UtcOffset,
};
use crate::{Error, Symbols};
pub const API_VERSION: u32 = 0;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum HistoricalGateway {
#[default]
Bo1,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DateRange {
start: time::Date,
end: time::Date,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DateTimeRange {
start: time::OffsetDateTime,
end: time::OffsetDateTime,
}
impl From<(time::Date, time::Date)> for DateRange {
fn from(value: (time::Date, time::Date)) -> Self {
Self {
start: value.0,
end: value.1,
}
}
}
impl From<(time::Date, time::Duration)> for DateRange {
fn from(value: (time::Date, time::Duration)) -> Self {
Self {
start: value.0,
end: value.0 + value.1,
}
}
}
impl From<time::Date> for DateRange {
fn from(date: time::Date) -> Self {
Self {
start: date,
end: date.next_day().unwrap(),
}
}
}
impl From<time::Date> for DateTimeRange {
fn from(date: time::Date) -> Self {
let start = date.with_time(Time::MIDNIGHT).assume_utc();
Self {
start,
end: start + Duration::DAY,
}
}
}
impl From<DateRange> for DateTimeRange {
fn from(date_range: DateRange) -> Self {
Self {
start: date_range.start.with_time(Time::MIDNIGHT).assume_utc(),
end: date_range.end.with_time(Time::MIDNIGHT).assume_utc(),
}
}
}
impl From<DateTimeRange> for DateRange {
fn from(dt_range: DateTimeRange) -> Self {
let utc_end = dt_range.end.to_offset(UtcOffset::UTC);
Self {
start: dt_range.start.to_offset(UtcOffset::UTC).date(),
end: if utc_end.time() == Time::MIDNIGHT {
utc_end.date()
} else {
utc_end.date().next_day().unwrap()
},
}
}
}
pub(crate) const DATE_FORMAT: &[BorrowedFormatItem<'_>] =
format_description!("[year]-[month]-[day]");
impl From<(time::OffsetDateTime, time::OffsetDateTime)> for DateTimeRange {
fn from(value: (time::OffsetDateTime, time::OffsetDateTime)) -> Self {
Self {
start: value.0,
end: value.1,
}
}
}
impl From<(time::OffsetDateTime, time::Duration)> for DateTimeRange {
fn from(value: (time::OffsetDateTime, time::Duration)) -> Self {
Self {
start: value.0,
end: value.0 + value.1,
}
}
}
impl TryFrom<(u64, u64)> for DateTimeRange {
type Error = crate::Error;
fn try_from(value: (u64, u64)) -> Result<Self, Self::Error> {
let start = time::OffsetDateTime::from_unix_timestamp_nanos(value.0 as i128)
.map_err(|e| Error::bad_arg("first UNIX nanos", format!("{e:?}")))?;
let end = time::OffsetDateTime::from_unix_timestamp_nanos(value.1 as i128)
.map_err(|e| Error::bad_arg("second UNIX nanos", format!("{e:?}")))?;
Ok(Self { start, end })
}
}
trait AddToQuery<T> {
fn add_to_query(self, param: &T) -> Self;
}
impl AddToQuery<DateRange> for reqwest::RequestBuilder {
fn add_to_query(self, param: &DateRange) -> Self {
self.query(&[
("start_date", param.start.format(DATE_FORMAT).unwrap()),
("end_date", param.end.format(DATE_FORMAT).unwrap()),
])
}
}
impl AddToQuery<DateTimeRange> for reqwest::RequestBuilder {
fn add_to_query(self, param: &DateTimeRange) -> Self {
self.query(&[
("start", param.start.unix_timestamp_nanos()),
("end", param.end.unix_timestamp_nanos()),
])
}
}
impl AddToQuery<Symbols> for reqwest::RequestBuilder {
fn add_to_query(self, param: &Symbols) -> Self {
self.query(&[("symbols", param.to_api_string())])
}
}
impl DateRange {
pub(crate) fn add_to_form(&self, form: &mut Vec<(&'static str, String)>) {
form.push(("start_date", self.start.format(DATE_FORMAT).unwrap()));
form.push(("end_date", self.end.format(DATE_FORMAT).unwrap()));
}
}
impl DateTimeRange {
pub(crate) fn add_to_form(&self, form: &mut Vec<(&'static str, String)>) {
form.push(("start", self.start.unix_timestamp_nanos().to_string()));
form.push(("end", self.end.unix_timestamp_nanos().to_string()));
}
}
#[cfg(test)]
mod tests {
use super::*;
use time::macros::{date, datetime};
#[test]
fn date_range_from_lt_day_duration() {
let target = DateRange::from((date!(2024 - 02 - 16), time::Duration::SECOND));
assert_eq!(
target,
DateRange {
start: date!(2024 - 02 - 16),
end: date!(2024 - 02 - 16)
}
)
}
#[test]
fn single_date_conversion() {
let date = date!(2025 - 03 - 27);
assert_eq!(
DateRange::from(date),
DateRange::from((date!(2025 - 03 - 27), date!(2025 - 03 - 28)))
);
assert_eq!(
DateTimeRange::from(date),
DateTimeRange::from((
datetime!(2025 - 03 - 27 00:00 UTC),
datetime!(2025 - 03 - 28 00:00 UTC)
))
);
}
#[test]
fn range_equivalency() {
let date_range = DateRange::from((date!(2025 - 03 - 27), date!(2025 - 04 - 10)));
assert_eq!(
date_range,
DateRange::from(DateTimeRange::from(date_range.clone()))
);
}
#[test]
fn dt_offset_to_date_range() {
assert_eq!(
DateRange::from(DateTimeRange::from((
datetime!(2025-03-27 21:00 -4),
datetime!(2025-03-28 20:00 -4)
))),
DateRange::from((date!(2025 - 03 - 28), date!(2025 - 03 - 29)))
);
assert_eq!(
DateRange::from(DateTimeRange::from((
datetime!(2025-03-27 21:00 -4),
datetime!(2025-03-28 20:30 -4)
))),
DateRange::from((date!(2025 - 03 - 28), date!(2025 - 03 - 30)))
);
}
}