taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

timestamp.rs (7641B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::{fmt::Display, ops::Add, str::FromStr, time::Duration};
     18 
     19 use jiff::{SignedDuration, Timestamp, civil::Time, tz::TimeZone};
     20 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeStruct}; // codespell:ignore
     21 use serde_json::Value;
     22 
     23 /// <https://docs.taler.net/core/api-common.html#tsref-type-Timestamp>
     24 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
     25 pub enum TalerTimestamp {
     26     Never,
     27     Timestamp(Timestamp),
     28 }
     29 
     30 impl TalerTimestamp {
     31     /** Encode timestamp for signature */
     32     pub fn signature_bytes(self) -> [u8; 8] {
     33         match self {
     34             TalerTimestamp::Never => u64::MAX,
     35             // Truncate to second and then encode into microseconds as JSON format only support second precision
     36             TalerTimestamp::Timestamp(timestamp) => (timestamp.as_second() as u64) * 1000 * 1000,
     37         }
     38         .to_be_bytes()
     39     }
     40 
     41     /// Returns an absolute duration representing the elapsed time from this timestamp until the given other timestamp.
     42     pub fn duration_until(self, other: Timestamp) -> SignedDuration {
     43         match self {
     44             TalerTimestamp::Never => SignedDuration::MAX,
     45             TalerTimestamp::Timestamp(tm) => tm.duration_until(other),
     46         }
     47     }
     48 }
     49 
     50 impl FromStr for TalerTimestamp {
     51     type Err = anyhow::Error;
     52 
     53     fn from_str(s: &str) -> Result<Self, Self::Err> {
     54         if s == "never" {
     55             return Ok(Self::Never);
     56         }
     57         let s: i64 = s.parse()?;
     58 
     59         Ok(Self::Timestamp(jiff::Timestamp::from_second(s)?))
     60     }
     61 }
     62 
     63 #[derive(Serialize, Deserialize)]
     64 struct TimestampImpl {
     65     t_s: Value,
     66 }
     67 
     68 impl<'de> Deserialize<'de> for TalerTimestamp {
     69     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     70     where
     71         D: Deserializer<'de>,
     72     {
     73         let tmp = TimestampImpl::deserialize(deserializer)?;
     74         match tmp.t_s {
     75             Value::Number(s) => {
     76                 if let Some(since_epoch_s) = s.as_u64() {
     77                     jiff::Timestamp::from_second(since_epoch_s as i64)
     78                         .map(Self::Timestamp)
     79                         .map_err(Error::custom)
     80                 } else {
     81                     Err(Error::custom("Expected epoch time"))
     82                 }
     83             }
     84             Value::String(str) if str == "never" => Ok(Self::Never),
     85             _ => Err(Error::custom("Expected epoch time or 'never'")),
     86         }
     87     }
     88 }
     89 
     90 impl Serialize for TalerTimestamp {
     91     fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
     92     where
     93         S: Serializer,
     94     {
     95         let mut se_struct = se.serialize_struct("Timestamp", 1)?;
     96         match self {
     97             TalerTimestamp::Never => se_struct.serialize_field("t_s", "never")?,
     98             TalerTimestamp::Timestamp(timestamp) => {
     99                 se_struct.serialize_field("t_s", &timestamp.as_second())?
    100             }
    101         }
    102         se_struct.end()
    103     }
    104 }
    105 
    106 impl Display for TalerTimestamp {
    107     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    108         match self {
    109             TalerTimestamp::Never => f.write_str("never"),
    110             TalerTimestamp::Timestamp(timestamp) => timestamp.fmt(f),
    111         }
    112     }
    113 }
    114 
    115 impl From<jiff::Timestamp> for TalerTimestamp {
    116     fn from(time: jiff::Timestamp) -> Self {
    117         Self::Timestamp(time)
    118     }
    119 }
    120 
    121 impl From<jiff::civil::Date> for TalerTimestamp {
    122     fn from(date: jiff::civil::Date) -> Self {
    123         date.to_datetime(Time::midnight())
    124             .to_zoned(TimeZone::UTC)
    125             .unwrap()
    126             .timestamp()
    127             .into()
    128     }
    129 }
    130 
    131 impl Add<jiff::Span> for TalerTimestamp {
    132     type Output = Self;
    133 
    134     fn add(self, rhs: jiff::Span) -> Self::Output {
    135         match self {
    136             TalerTimestamp::Never => TalerTimestamp::Never,
    137             TalerTimestamp::Timestamp(timestamp) => TalerTimestamp::Timestamp(timestamp + rhs),
    138         }
    139     }
    140 }
    141 
    142 impl sqlx::Type<sqlx::Postgres> for TalerTimestamp {
    143     fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
    144         Option::<i64>::type_info()
    145     }
    146 }
    147 
    148 impl<'q> sqlx::Encode<'q, sqlx::Postgres> for TalerTimestamp {
    149     fn encode_by_ref(
    150         &self,
    151         buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
    152     ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
    153         match self {
    154             TalerTimestamp::Never => None,
    155             TalerTimestamp::Timestamp(timestamp) => Some(timestamp.as_microsecond()),
    156         }
    157         .encode_by_ref(buf)
    158     }
    159 }
    160 
    161 impl<'r> sqlx::Decode<'r, sqlx::Postgres> for TalerTimestamp {
    162     fn decode(
    163         value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
    164     ) -> Result<Self, sqlx::error::BoxDynError> {
    165         let micros = Option::<i64>::decode(value)?;
    166         Ok(match micros {
    167             Some(micros) => Self::Timestamp(Timestamp::from_microsecond(micros)?),
    168             None => Self::Never,
    169         })
    170     }
    171 }
    172 
    173 /// <https://docs.taler.net/core/api-common.html#tsref-type-RelativeTime>
    174 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    175 pub enum RelativeTime {
    176     Forever,
    177     Duration(Duration),
    178 }
    179 
    180 impl FromStr for RelativeTime {
    181     type Err = anyhow::Error;
    182 
    183     fn from_str(s: &str) -> Result<Self, Self::Err> {
    184         if s == "forever" {
    185             return Ok(Self::Forever);
    186         }
    187         let micros: u64 = s.parse()?;
    188 
    189         Ok(Self::Duration(Duration::from_micros(micros)))
    190     }
    191 }
    192 
    193 #[derive(Serialize, Deserialize)]
    194 struct RelativeTimeImpl {
    195     d_us: Value,
    196 }
    197 
    198 impl<'de> Deserialize<'de> for RelativeTime {
    199     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    200     where
    201         D: Deserializer<'de>,
    202     {
    203         let tmp = RelativeTimeImpl::deserialize(deserializer)?;
    204         match tmp.d_us {
    205             Value::Number(s) => {
    206                 if let Some(micros) = s.as_u64() {
    207                     Ok(Self::Duration(Duration::from_micros(micros)))
    208                 } else {
    209                     Err(Error::custom("Expected microseconds"))
    210                 }
    211             }
    212             Value::String(str) if str == "forever" => Ok(Self::Forever),
    213             _ => Err(Error::custom("Expected epoch time or 'forever'")),
    214         }
    215     }
    216 }
    217 
    218 impl Serialize for RelativeTime {
    219     fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
    220     where
    221         S: Serializer,
    222     {
    223         let mut se_struct = se.serialize_struct("RelativeTime", 1)?;
    224         match self {
    225             RelativeTime::Forever => se_struct.serialize_field("d_us", "forever")?,
    226             RelativeTime::Duration(duration) => {
    227                 se_struct.serialize_field("d_us", &duration.as_micros())?
    228             }
    229         }
    230         se_struct.end()
    231     }
    232 }
    233 
    234 impl Display for RelativeTime {
    235     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    236         match self {
    237             RelativeTime::Forever => f.write_str("forever"),
    238             RelativeTime::Duration(duration) => write!(f, "{duration:?}"),
    239         }
    240     }
    241 }