codetics-ocpp-service 0.1.0

Codetics OCPP: Charging Station Management System (CSMS) service for OCPP 1.6J, 2.0.1 and 2.1, with TLS security profiles, smart charging, reservations and tariffs
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Kevin Vavelin, Codetics (https://codetics.fr)

//! `TIMESTAMPTZ` ↔ [`DateTime`] without pulling a date-time crate into sqlx.

use ocpp_core::DateTime;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef};
use sqlx::{Decode, Encode, Postgres, Type};

/// Microseconds between 1970-01-01 and the PostgreSQL epoch 2000-01-01.
const PG_EPOCH_OFFSET_MICROS: i64 = 946_684_800_000_000;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ts(pub DateTime);

impl From<DateTime> for Ts {
    fn from(d: DateTime) -> Self {
        Ts(d)
    }
}

impl Type<Postgres> for Ts {
    fn type_info() -> PgTypeInfo {
        PgTypeInfo::with_name("timestamptz")
    }
}

impl Encode<'_, Postgres> for Ts {
    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
        buf.extend_from_slice(&(self.0.unix_micros() - PG_EPOCH_OFFSET_MICROS).to_be_bytes());
        Ok(IsNull::No)
    }
}

impl<'r> Decode<'r, Postgres> for Ts {
    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
        match value.format() {
            PgValueFormat::Binary => {
                let b: [u8; 8] = value.as_bytes()?.try_into()?;
                Ok(Ts(DateTime::from_unix_micros(
                    i64::from_be_bytes(b) + PG_EPOCH_OFFSET_MICROS,
                )))
            }
            PgValueFormat::Text => Ok(Ts(DateTime::parse(value.as_str()?)?)),
        }
    }
}

impl serde::Serialize for Ts {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        self.0.serialize(s)
    }
}

impl<'de> serde::Deserialize<'de> for Ts {
    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        DateTime::deserialize(d).map(Ts)
    }
}