taler-rust

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

time.rs (7720B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-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                     let seconds = i64::try_from(since_epoch_s).map_err(Error::custom)?;
     78                     jiff::Timestamp::from_second(seconds)
     79                         .map(Self::Timestamp)
     80                         .map_err(Error::custom)
     81                 } else {
     82                     Err(Error::custom("Expected epoch time"))
     83                 }
     84             }
     85             Value::String(str) if str == "never" => Ok(Self::Never),
     86             _ => Err(Error::custom("Expected epoch time or 'never'")),
     87         }
     88     }
     89 }
     90 
     91 impl Serialize for TalerTimestamp {
     92     fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
     93     where
     94         S: Serializer,
     95     {
     96         let mut se_struct = se.serialize_struct("Timestamp", 1)?;
     97         match self {
     98             TalerTimestamp::Never => se_struct.serialize_field("t_s", "never")?,
     99             TalerTimestamp::Timestamp(timestamp) => {
    100                 se_struct.serialize_field("t_s", &timestamp.as_second())?
    101             }
    102         }
    103         se_struct.end()
    104     }
    105 }
    106 
    107 impl Display for TalerTimestamp {
    108     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    109         match self {
    110             TalerTimestamp::Never => f.write_str("never"),
    111             TalerTimestamp::Timestamp(timestamp) => timestamp.fmt(f),
    112         }
    113     }
    114 }
    115 
    116 impl From<jiff::Timestamp> for TalerTimestamp {
    117     fn from(time: jiff::Timestamp) -> Self {
    118         Self::Timestamp(time)
    119     }
    120 }
    121 
    122 impl From<jiff::civil::Date> for TalerTimestamp {
    123     fn from(date: jiff::civil::Date) -> Self {
    124         date.to_datetime(Time::midnight())
    125             .to_zoned(TimeZone::UTC)
    126             .unwrap()
    127             .timestamp()
    128             .into()
    129     }
    130 }
    131 
    132 impl Add<jiff::Span> for TalerTimestamp {
    133     type Output = Self;
    134 
    135     fn add(self, rhs: jiff::Span) -> Self::Output {
    136         match self {
    137             TalerTimestamp::Never => TalerTimestamp::Never,
    138             TalerTimestamp::Timestamp(timestamp) => TalerTimestamp::Timestamp(timestamp + rhs),
    139         }
    140     }
    141 }
    142 
    143 impl sqlx::Type<sqlx::Postgres> for TalerTimestamp {
    144     fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
    145         Option::<i64>::type_info()
    146     }
    147 }
    148 
    149 impl<'q> sqlx::Encode<'q, sqlx::Postgres> for TalerTimestamp {
    150     fn encode_by_ref(
    151         &self,
    152         buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
    153     ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
    154         match self {
    155             TalerTimestamp::Never => None,
    156             TalerTimestamp::Timestamp(timestamp) => Some(timestamp.as_microsecond()),
    157         }
    158         .encode_by_ref(buf)
    159     }
    160 }
    161 
    162 impl<'r> sqlx::Decode<'r, sqlx::Postgres> for TalerTimestamp {
    163     fn decode(
    164         value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
    165     ) -> Result<Self, sqlx::error::BoxDynError> {
    166         let micros = Option::<i64>::decode(value)?;
    167         Ok(match micros {
    168             None | Some(i64::MAX) => Self::Never,
    169             Some(micros) => Self::Timestamp(Timestamp::from_microsecond(micros)?),
    170         })
    171     }
    172 }
    173 
    174 /// <https://docs.taler.net/core/api-common.html#tsref-type-RelativeTime>
    175 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    176 pub enum RelativeTime {
    177     Forever,
    178     Duration(Duration),
    179 }
    180 
    181 impl FromStr for RelativeTime {
    182     type Err = anyhow::Error;
    183 
    184     fn from_str(s: &str) -> Result<Self, Self::Err> {
    185         if s == "forever" {
    186             return Ok(Self::Forever);
    187         }
    188         let micros: u64 = s.parse()?;
    189 
    190         Ok(Self::Duration(Duration::from_micros(micros)))
    191     }
    192 }
    193 
    194 #[derive(Serialize, Deserialize)]
    195 struct RelativeTimeImpl {
    196     d_us: Value,
    197 }
    198 
    199 impl<'de> Deserialize<'de> for RelativeTime {
    200     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    201     where
    202         D: Deserializer<'de>,
    203     {
    204         let tmp = RelativeTimeImpl::deserialize(deserializer)?;
    205         match tmp.d_us {
    206             Value::Number(s) => {
    207                 if let Some(micros) = s.as_u64() {
    208                     Ok(Self::Duration(Duration::from_micros(micros)))
    209                 } else {
    210                     Err(Error::custom("Expected microseconds"))
    211                 }
    212             }
    213             Value::String(str) if str == "forever" => Ok(Self::Forever),
    214             _ => Err(Error::custom("Expected time or 'forever'")),
    215         }
    216     }
    217 }
    218 
    219 impl Serialize for RelativeTime {
    220     fn serialize<S>(&self, se: S) -> Result<S::Ok, S::Error>
    221     where
    222         S: Serializer,
    223     {
    224         let mut se_struct = se.serialize_struct("RelativeTime", 1)?;
    225         match self {
    226             RelativeTime::Forever => se_struct.serialize_field("d_us", "forever")?,
    227             RelativeTime::Duration(duration) => {
    228                 se_struct.serialize_field("d_us", &duration.as_micros())?
    229             }
    230         }
    231         se_struct.end()
    232     }
    233 }
    234 
    235 impl Display for RelativeTime {
    236     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    237         match self {
    238             RelativeTime::Forever => f.write_str("forever"),
    239             RelativeTime::Duration(duration) => write!(f, "{duration:?}"),
    240         }
    241     }
    242 }