Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize with rounding and normalization #48

Merged
merged 1 commit into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 13 additions & 23 deletions ocpi-tariffs/src/pricer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use crate::{
cdr::Cdr,
tariff::{CompatibilityVat, OcpiTariff},
},
session::ChargeSession,
session::{ChargePeriod, PeriodData},
session::{ChargePeriod, ChargeSession, PeriodData},
tariff::{PriceComponent, PriceComponents, Tariffs},
types::{
electricity::Kwh,
Expand All @@ -17,7 +16,7 @@ use crate::{
Error, Result,
};

use chrono::{DateTime, Duration, Utc};
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
use serde::Serialize;

Expand Down Expand Up @@ -82,21 +81,14 @@ impl Pricer {

let dimensions = Dimensions::new(components, &period.period_data);

total_charging_time.0 = total_charging_time.0
+ dimensions
.time
.volume
.map(|hms| hms.0)
.unwrap_or_else(Duration::zero);
total_charging_time += dimensions.time.volume.unwrap_or_else(HoursDecimal::zero);

total_energy += dimensions.energy.volume.unwrap_or_else(Kwh::zero);

total_parking_time.0 = total_parking_time.0
+ dimensions
.parking_time
.volume
.map(|hms| hms.0)
.unwrap_or_else(Duration::zero);
total_parking_time += dimensions
.parking_time
.volume
.unwrap_or_else(HoursDecimal::zero);

periods.push(PeriodReport::new(period, dimensions));
}
Expand Down Expand Up @@ -215,18 +207,16 @@ impl StepSize {
billed_volume: &mut HoursDecimal,
step_size: u64,
) -> HoursDecimal {
let total_seconds = Number::from(total.0.num_seconds());
let total_seconds = total.as_num_seconds_decimal();
let step_size = Number::from(step_size);

let priced_total_seconds = ((total_seconds / step_size).ceil() * step_size)
.try_into()
.expect("overflow");
let priced_total_seconds = (total_seconds / step_size).ceil() * step_size;
let priced_total =
HoursDecimal::from_seconds_decimal(priced_total_seconds).expect("overflow");

let priced_total = Duration::seconds(priced_total_seconds);
let difference = priced_total - total.0;
billed_volume.0 = billed_volume.0 + difference;
*billed_volume += priced_total - total;

priced_total.into()
priced_total
}

fn apply_time(&self, periods: &mut [PeriodReport], total: HoursDecimal) -> HoursDecimal {
Expand Down
5 changes: 2 additions & 3 deletions ocpi-tariffs/src/types/money.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,9 @@ impl Mul<HoursDecimal> for Money {
type Output = Money;

fn mul(self, rhs: HoursDecimal) -> Self::Output {
let duration =
self.0 * (Number::from(rhs.0.num_milliseconds()) / Number::from(dec!(3_600_000)));
let cost = self.0 * rhs.as_num_hours_decimal();

Self(duration)
Self(cost)
}
}

Expand Down
16 changes: 15 additions & 1 deletion ocpi-tariffs/src/types/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub(crate) struct Number(rust_decimal::Decimal);

impl Number {
Expand All @@ -30,6 +30,20 @@ impl<'de> Deserialize<'de> for Number {
}
}

impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut decimal = self.0;

decimal.rescale(4);
decimal.normalize_assign();

Serialize::serialize(&decimal, serializer)
}
}

impl From<rust_decimal::Decimal> for Number {
fn from(value: rust_decimal::Decimal) -> Self {
Self(value)
Expand Down
83 changes: 56 additions & 27 deletions ocpi-tariffs/src/types/time.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use std::fmt::Display;
use std::{
fmt::Display,
ops::{Add, AddAssign, Sub, SubAssign},
};

use chrono::Duration;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize, Serializer};

use super::number::Number;

const SECS_IN_MIN: i64 = 60;
const MINS_IN_HOUR: i64 = 60;
const MILLIS_IN_SEC: i64 = 1000;

/// A `chrono` UTC date time.
pub type DateTime = chrono::DateTime<chrono::Utc>;

/// A generic duration type that converts from and to a decimal amount of hours.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct HoursDecimal(pub(crate) Duration);
pub struct HoursDecimal(Duration);

impl<'de> Deserialize<'de> for HoursDecimal {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
Expand All @@ -21,22 +27,21 @@ impl<'de> Deserialize<'de> for HoursDecimal {
use serde::de::Error;

let hours = <Number as serde::Deserialize>::deserialize(deserializer)?;
let duration = Self::try_from(hours).map_err(|_e| D::Error::custom("overflow"))?;
let duration =
Self::from_hours_decimal(hours).map_err(|_e| D::Error::custom("overflow"))?;
Ok(duration)
}
}

impl Serialize for HoursDecimal {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
let hours = self.as_num_hours_decimal();
hours.serialize(serializer)
}
}

impl Display for HoursDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const SECS_IN_MIN: i64 = 60;
const MINS_IN_HOUR: i64 = 60;

let duration = self.0;
let seconds = duration.num_seconds() % SECS_IN_MIN;
let minutes = (duration.num_seconds() / SECS_IN_MIN) % MINS_IN_HOUR;
Expand All @@ -58,37 +63,61 @@ impl From<Duration> for HoursDecimal {
}
}

impl TryFrom<Number> for HoursDecimal {
type Error = rust_decimal::Error;
impl AddAssign for HoursDecimal {
fn add_assign(&mut self, rhs: Self) {
self.0 = self.0 + rhs.0;
}
}

fn try_from(value: Number) -> Result<Self, Self::Error> {
let millis = value * Number::from(dec!(3_600_000));
let duration = Duration::milliseconds(millis.try_into()?);
Ok(Self(duration))
impl SubAssign for HoursDecimal {
fn sub_assign(&mut self, rhs: Self) {
self.0 = self.0 - rhs.0;
}
}

impl From<&HoursDecimal> for Number {
fn from(value: &HoursDecimal) -> Self {
use rust_decimal::Decimal;
impl Add for HoursDecimal {
type Output = Self;

let seconds: Decimal = value.0.num_seconds().into();
let hours = seconds / dec!(3600);
fn add(mut self, rhs: Self) -> Self::Output {
self += rhs;

hours.into()
self
}
}

impl From<HoursDecimal> for Number {
fn from(value: HoursDecimal) -> Self {
(&value).into()
impl Sub for HoursDecimal {
type Output = Self;

fn sub(mut self, rhs: Self) -> Self::Output {
self -= rhs;

self
}
}

impl HoursDecimal {
pub(crate) fn zero() -> Self {
Self(Duration::zero())
}

pub(crate) fn as_num_seconds_decimal(&self) -> Number {
Number::from(self.0.num_milliseconds()) / Number::from(MILLIS_IN_SEC)
}

pub(crate) fn as_num_hours_decimal(&self) -> Number {
Number::from(self.0.num_milliseconds())
/ Number::from(MILLIS_IN_SEC * SECS_IN_MIN * MINS_IN_HOUR)
}

pub(crate) fn from_seconds_decimal(seconds: Number) -> Result<Self, rust_decimal::Error> {
let millis = seconds * Number::from(MILLIS_IN_SEC);
Ok(Self(Duration::milliseconds(millis.try_into()?)))
}

pub(crate) fn from_hours_decimal(hours: Number) -> Result<Self, rust_decimal::Error> {
let millis = hours * Number::from(MILLIS_IN_SEC * SECS_IN_MIN * MINS_IN_HOUR);
Ok(Self(Duration::milliseconds(millis.try_into()?)))
}
}

impl Default for HoursDecimal {
Expand Down Expand Up @@ -226,28 +255,28 @@ mod hour_decimal_tests {
#[test]
fn zero_minutes_should_be_zero_hours() {
let hours: HoursDecimal = Duration::minutes(0).into();
let number: Number = hours.into();
let number: Number = hours.as_num_hours_decimal();
assert_eq!(number, Number::from(dec!(0.0)));
}

#[test]
fn thirty_minutes_should_be_fraction_of_hour() {
let hours: HoursDecimal = Duration::minutes(30).into();
let number: Number = hours.into();
let number: Number = hours.as_num_hours_decimal();
assert_eq!(number, Number::from(dec!(0.5)));
}

#[test]
fn sixty_minutes_should_be_fraction_of_hour() {
let hours: HoursDecimal = Duration::minutes(60).into();
let number: Number = hours.into();
let number: Number = hours.as_num_hours_decimal();
assert_eq!(number, Number::from(dec!(1.0)));
}

#[test]
fn ninety_minutes_should_be_fraction_of_hour() {
let hours: HoursDecimal = Duration::minutes(90).into();
let number: Number = hours.into();
let number: Number = hours.as_num_hours_decimal();
assert_eq!(number, Number::from(dec!(1.5)));
}
}