Skip to content

Commit

Permalink
Final fixes to time parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
RReverser committed Sep 15, 2024
1 parent 456df7b commit de274a2
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 36 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ serde_plain = { version = "1.0.2", optional = true }
serde_repr = "0.1.19"
socket2 = "0.5.7"
thiserror = "1.0.63"
time = { version = "0.3.36", features = ["formatting", "parsing"], optional = true }
time = { version = "0.3.36", features = ["formatting", "parsing", "macros"], optional = true }
tokio = { workspace = true, features = ["net", "rt", "io-util"] }
tracing = { workspace = true }
tracing-futures = { version = "0.2.5", features = ["futures-03"], optional = true }
Expand Down
5 changes: 2 additions & 3 deletions src/api/autogen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,11 @@ function handleType(
case 'string': {
let { format } = schema;
if (format === 'date-time' || format === 'date-time-fits') {
format = format === 'date-time' ? 'DATE_TIME_OFFSET' : 'DATE_TIME';
format = `time::format_description::well_known::Iso8601::${format}`;
format = format === 'date-time' ? 'Iso8601' : 'Fits';
let viaType = baseKind === 'Request' ? 'TimeParam' : 'TimeResponse';
return rusty(
'std::time::SystemTime',
`time_repr::${viaType}<{ time_repr::config_from(${format}) }>`
`time_repr::${viaType}<time_repr::${format}>`
);
}
return rusty('String');
Expand Down
6 changes: 3 additions & 3 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ pub trait Camera: Device + Send + Sync {
}

/// Reports the actual exposure start in the FITS-standard CCYY-MM-DDThh:mm:ss[.sss...] format.
#[http("lastexposurestarttime", method = Get, via = time_repr::TimeResponse<{ time_repr::config_from(time::format_description::well_known::Iso8601::DATE_TIME) }>)]
#[http("lastexposurestarttime", method = Get, via = time_repr::TimeResponse<time_repr::Fits>)]
async fn last_exposure_start_time(&self) -> ASCOMResult<std::time::SystemTime> {
Err(ASCOMError::NOT_IMPLEMENTED)
}
Expand Down Expand Up @@ -2297,7 +2297,7 @@ pub trait Telescope: Device + Send + Sync {
}

/// Returns the UTC date/time of the telescope's internal clock.
#[http("utcdate", method = Get, via = time_repr::TimeResponse<{ time_repr::config_from(time::format_description::well_known::Iso8601::DATE_TIME_OFFSET) }>)]
#[http("utcdate", method = Get, via = time_repr::TimeResponse<time_repr::Iso8601>)]
async fn utc_date(&self) -> ASCOMResult<std::time::SystemTime> {
Err(ASCOMError::NOT_IMPLEMENTED)
}
Expand All @@ -2307,7 +2307,7 @@ pub trait Telescope: Device + Send + Sync {
async fn set_utc_date(
&self,

#[http("UTCDate", via = time_repr::TimeParam<{ time_repr::config_from(time::format_description::well_known::Iso8601::DATE_TIME_OFFSET) }>)]
#[http("UTCDate", via = time_repr::TimeParam<time_repr::Iso8601>)]
utc_date: std::time::SystemTime,
) -> ASCOMResult {
Err(ASCOMError::NOT_IMPLEMENTED)
Expand Down
76 changes: 47 additions & 29 deletions src/api/time_repr.rs
Original file line number Diff line number Diff line change
@@ -1,78 +1,96 @@
use crate::response::ValueResponse;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::time::SystemTime;
use time::format_description::well_known::iso8601::EncodedConfig;
use time::format_description::well_known::Iso8601;
use time::formatting::Formattable;
use time::macros::format_description;
use time::parsing::Parsable;
use time::{format_description, OffsetDateTime};

pub(crate) struct TimeParam<const CONFIG: EncodedConfig>(SystemTime);
pub(crate) trait FormatWrapper {
type Format: 'static + ?Sized + Parsable + Formattable;

// `time` crate doesn't expose config from `Iso8601`, so we have to extract one manually via inference
pub(crate) const fn config_from<const CONFIG: EncodedConfig>(_: Iso8601<CONFIG>) -> EncodedConfig {
CONFIG
const FORMAT: &'static Self::Format;
}

impl<const CONFIG: EncodedConfig> std::fmt::Debug for TimeParam<CONFIG> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
pub(crate) struct Iso8601;

impl FormatWrapper for Iso8601 {
type Format = format_description::well_known::Iso8601;

const FORMAT: &'static Self::Format = &Self::Format::DEFAULT;
}

pub(crate) struct Fits;

impl FormatWrapper for Fits {
type Format = [format_description::BorrowedFormatItem<'static>];

const FORMAT: &'static Self::Format = format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second][optional [.[subsecond digits:3]]]"
);
}

impl<const CONFIG: EncodedConfig> From<SystemTime> for TimeParam<CONFIG> {
#[derive(Debug)]
pub(crate) struct TimeParam<F>(OffsetDateTime, PhantomData<F>);

impl<F> From<SystemTime> for TimeParam<F> {
fn from(value: SystemTime) -> Self {
Self(value)
Self(value.into(), PhantomData)
}
}

impl<const CONFIG: EncodedConfig> From<TimeParam<CONFIG>> for SystemTime {
fn from(wrapper: TimeParam<CONFIG>) -> Self {
wrapper.0
impl<F> From<TimeParam<F>> for SystemTime {
fn from(wrapper: TimeParam<F>) -> Self {
wrapper.0.into()
}
}

impl<const CONFIG: EncodedConfig> Serialize for TimeParam<CONFIG> {
impl<F: FormatWrapper> Serialize for TimeParam<F> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
time::OffsetDateTime::from(self.0)
.format(&Iso8601::<CONFIG>)
self.0
.format(&F::FORMAT)
.map_err(serde::ser::Error::custom)?
.serialize(serializer)
}
}

impl<'de, const CONFIG: EncodedConfig> Deserialize<'de> for TimeParam<CONFIG> {
impl<'de, F: FormatWrapper> Deserialize<'de> for TimeParam<F> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor<const CONFIG: EncodedConfig>;
struct Visitor<F>(PhantomData<F>);

impl<const CONFIG: EncodedConfig> serde::de::Visitor<'_> for Visitor<CONFIG> {
type Value = TimeParam<{ CONFIG }>;
impl<F: FormatWrapper> serde::de::Visitor<'_> for Visitor<F> {
type Value = TimeParam<F>;

fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a date string")
}

fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
match time::OffsetDateTime::parse(value, &Iso8601::<CONFIG>) {
Ok(value) => Ok(TimeParam(value.into())),
match time::PrimitiveDateTime::parse(value, &F::FORMAT) {
Ok(value) => Ok(TimeParam(value.assume_utc(), PhantomData)),
Err(err) => Err(serde::de::Error::custom(err)),
}
}
}

deserializer.deserialize_str(Visitor)
deserializer.deserialize_str(Visitor(PhantomData))
}
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(bound = "F: FormatWrapper")]
#[serde(transparent)]
pub(crate) struct TimeResponse<const CONFIG: EncodedConfig>(ValueResponse<TimeParam<CONFIG>>);
pub(crate) struct TimeResponse<F>(ValueResponse<TimeParam<F>>);

impl<const CONFIG: EncodedConfig> From<SystemTime> for TimeResponse<CONFIG> {
impl<F> From<SystemTime> for TimeResponse<F> {
fn from(value: SystemTime) -> Self {
Self(TimeParam::from(value).into())
}
}

impl<const CONFIG: EncodedConfig> From<TimeResponse<CONFIG>> for SystemTime {
fn from(wrapper: TimeResponse<CONFIG>) -> Self {
impl<F> From<TimeResponse<F>> for SystemTime {
fn from(wrapper: TimeResponse<F>) -> Self {
wrapper.0.into().into()
}
}

0 comments on commit de274a2

Please sign in to comment.