1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! POSIX time to/from slot conversions

use crate::chain_query::EraSummary;
use crate::error::{Error, Result};
use anyhow::anyhow;
use chrono::{DateTime, Duration, Utc};
use plutus_ledger_api::{
    csl::lib as csl,
    v2::{
        interval::{Extended, LowerBound, PlutusInterval, UpperBound},
        transaction::{POSIXTime, POSIXTimeConversionError, POSIXTimeRange},
    },
};

/// Convert a POSIX time into number of slots in the current system
pub fn posix_time_into_slot(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    posix_time: POSIXTime,
) -> Result<u64> {
    let abs_time: DateTime<Utc> = posix_time
        .try_into()
        .map_err(|source: POSIXTimeConversionError| Error::ConversionError(anyhow!(source)))?;
    time_into_slot(era_summaries, sys_start, abs_time)
}

pub fn time_into_slot(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    time: DateTime<Utc>,
) -> Result<u64> {
    // Time since system start
    let rel_time = time - sys_start;

    if era_summaries.is_empty() {
        return Err(Error::InvalidConfiguration("era_summaries".to_string()));
    }

    let era_summary = era_summaries
        .iter()
        .filter(|era_summary| era_summary.start.time <= rel_time)
        .last()
        .ok_or(Error::InvalidPOSIXTime(format!(
            "{} is before system start at {}, or era_summaries are invalid.",
            time, sys_start
        )))?;

    let time_in_era = rel_time - era_summary.start.time;
    let slots_in_era = time_in_era.num_milliseconds() as u64 / era_summary.parameters.slot_length;

    Ok(era_summary.start.slot + slots_in_era)
}

pub fn slot_into_posix_time(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    slot: u64,
) -> Result<POSIXTime> {
    Ok(slot_into_time(era_summaries, sys_start, slot)?.into())
}

pub fn slot_into_time(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    slot: u64,
) -> Result<DateTime<Utc>> {
    let era_summary = era_summaries
        .iter()
        .filter(|era_summary| era_summary.start.slot <= slot)
        .last()
        .ok_or(Error::InvalidConfiguration("era_summaries".to_string()))?;

    let time_in_era = Duration::try_milliseconds(
        ((slot - era_summary.start.slot) * era_summary.parameters.slot_length) as i64,
    )
    .ok_or(Error::Internal(anyhow!(
        "Couldn't convert era in time to milliseconds"
    )))?;

    Ok(*sys_start + era_summary.start.time + time_in_era)
}

pub fn time_range_into_slots(
    era_summaries: &[EraSummary],
    sys_start: &DateTime<Utc>,
    interval: POSIXTimeRange,
) -> Result<(Option<csl::BigNum>, Option<csl::BigNum>)> {
    let PlutusInterval {
        from:
            LowerBound {
                bound: lower_bound,
                closed: lower_closed,
            },
        to: UpperBound {
            bound: upper_bound,
            closed: upper_closed,
        },
    } = interval;

    let lb_slots = match lower_bound {
        Extended::NegInf => None,
        Extended::PosInf => Some(u64::MAX),
        Extended::Finite(posix_time) => {
            let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
            if lower_closed {
                Some(slots)
            } else {
                Some(slots + 1)
            }
        }
    };

    let ub_slots = match upper_bound {
        Extended::NegInf => Some(0),
        Extended::PosInf => None,
        Extended::Finite(posix_time) => {
            let slots = posix_time_into_slot(era_summaries, sys_start, posix_time)?;
            if upper_closed {
                Some(slots)
            } else {
                Some(slots - 1)
            }
        }
    };

    Ok((
        lb_slots.map(csl::BigNum::from),
        ub_slots.map(csl::BigNum::from),
    ))
}