iota_graphql_rpc/types/
date_time.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::str::FromStr;
6
7use async_graphql::*;
8use chrono::{
9    ParseError as ChronoParseError,
10    prelude::{DateTime as ChronoDateTime, TimeZone, Utc as ChronoUtc},
11};
12
13use crate::error::Error;
14
15/// The DateTime in UTC format. The milliseconds part is optional,
16/// and it will be omitted if the ms value is zero.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub(crate) struct DateTime(ChronoDateTime<ChronoUtc>);
19
20impl DateTime {
21    pub fn from_ms(timestamp_ms: i64) -> Result<Self, Error> {
22        ChronoUtc
23            .timestamp_millis_opt(timestamp_ms)
24            .single()
25            .ok_or_else(|| Error::Internal("Cannot convert timestamp into DateTime".to_string()))
26            .map(Self)
27    }
28}
29
30/// The DateTime in UTC format. The milliseconds part is optional,
31/// and it will be omitted if the ms value is zero.
32#[Scalar(use_type_description = true)]
33impl ScalarType for DateTime {
34    fn parse(value: Value) -> InputValueResult<Self> {
35        match value {
36            Value::String(s) => DateTime::from_str(&s)
37                .map_err(|e| InputValueError::custom(format!("Error parsing DateTime: {}", e))),
38            _ => Err(InputValueError::expected_type(value)),
39        }
40    }
41
42    fn to_value(&self) -> Value {
43        // Debug format for chrono::DateTime is YYYY-MM-DDTHH:MM:SS.mmmZ
44        Value::String(format!("{:?}", self.0))
45    }
46}
47
48impl Description for DateTime {
49    fn description() -> &'static str {
50        "ISO-8601 Date and Time: RFC3339 in UTC with format: YYYY-MM-DDTHH:MM:SS.mmmZ. Note that the milliseconds part is optional, and it may be omitted if its value is 0."
51    }
52}
53
54impl FromStr for DateTime {
55    type Err = ChronoParseError;
56
57    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
58        Ok(DateTime(s.parse::<ChronoDateTime<ChronoUtc>>()?))
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_parse() {
68        let dt: &str = "2023-08-19T15:37:24.761850Z";
69        let date_time = DateTime::from_str(dt).unwrap();
70        let Value::String(s) = async_graphql::ScalarType::to_value(&date_time) else {
71            panic!("Invalid date time scalar");
72        };
73        assert_eq!(dt, s);
74
75        let dt: &str = "2023-08-19T15:37:24.700Z";
76        let date_time = DateTime::from_str(dt).unwrap();
77        let Value::String(s) = async_graphql::ScalarType::to_value(&date_time) else {
78            panic!("Invalid date time scalar");
79        };
80        assert_eq!(dt, s);
81
82        let dt: &str = "2023-08-";
83        assert!(DateTime::from_str(dt).is_err());
84    }
85}