ntp-client 0.1.0

The ntp-client is a Rust library designed for interacting with NTP (Network Time Protocol) servers. It enables developers to easily obtain accurate time information from NTP servers. This can be applied in scenarios such as calibrating system time and performing time synchronization. The library offers a simple and user - friendly API, and also features good performance and stability.
Documentation
use e_utils::chrono::{
    china_now, now, parse_datetime_offset, parse_timestamp, DateTime, FixedOffset, TimeZone, Utc,
};

use crate::Packet;
use std::fmt::Display;
use std::io::Cursor;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket};
use std::time::Duration;

#[derive(Clone, Debug)]
pub struct Client {
    pub packet: Packet,
    pub timestamp: i64,
    pub src: String,
    pub read_timeout: Duration,
    pub write_timeout: Duration,
    pub target: SocketAddr,
    pub offset: Option<FixedOffset>,
    pub format: Option<String>,
}
impl Default for Client {
    fn default() -> Self {
        Self {
            target: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 123)),
            packet: Packet::new_client(),
            timestamp: 0,
            src: String::from("0.0.0.0:0"),
            write_timeout: Duration::from_secs(5),
            read_timeout: Duration::from_secs(5),
            offset: None,
            format: None,
        }
    }
}
impl Client {
    ///### Example: Get time from an NTP server and sync systemtime
    /// ```rust
    /// fn main() -> e_utils::AnyResult<()> {
    ///     let target = "0.pool.ntp.org:123";
    ///     let res = ntp_client::Client::new()
    ///         .target(target)?
    ///         .format(Some("%Y/%m/%d %H:%M:%S"))
    ///         .request()?;
    ///     let res_str = res.get_datetime_str().ok_or("error")?;
    ///     println!("UTC str -> {res_str}");
    ///     let datetime = res.get_datetime_utc().ok_or("get datetime utc")?;
    ///     ntp_client::sync_systemtime(datetime)?;
    ///     Ok(())
    /// }
    /// ```
    pub fn new() -> Self {
        Self::default()
    }
    pub fn offset(mut self, offset: Option<FixedOffset>) -> Self {
        self.offset = offset;
        self
    }
    pub fn format(mut self, format: Option<impl Into<String>>) -> Self {
        self.format = format.map(|v| v.into());
        self
    }
    pub fn target<A: ToSocketAddrs>(mut self, addr: A) -> e_utils::AnyResult<Self> {
        self.target = addr
            .to_socket_addrs()?
            .next()
            .ok_or("to_socket_addrs next")?;
        Ok(self)
    }
    pub fn src_ip(mut self, addr: impl Into<String>) -> Self {
        self.src = addr.into();
        self
    }
    pub fn packet(mut self, packet: Packet) -> Self {
        self.packet = packet;
        self
    }
    
    pub fn request(mut self) -> e_utils::AnyResult<Self> {
        let data: Vec<u8> = self.packet.clone().into();
        let sock = UdpSocket::bind(&self.src)?;
        sock.set_read_timeout(Some(self.read_timeout))?;
        sock.set_write_timeout(Some(self.write_timeout))?;
        let _sz = sock.send_to(&data, self.target)?;
        let mut buf = vec![0; 48];
        let _res = sock.recv(&mut buf)?;
        let rdr = Cursor::new(&buf);
        self.packet = Packet::try_from(rdr)?;
        self.timestamp = DateTime::<Utc>::from(self.packet.transmit_time).timestamp();
        Ok(self)
    }
    // UTC 时间获取保持原样
    pub fn get_datetime_utc(&self) -> Option<DateTime<Utc>> {
        DateTime::from_timestamp(self.timestamp, 0)
    }
    // UTC 时间获取保持原样
    pub fn get_datetime_shanghai(&self) -> Option<DateTime<FixedOffset>> {
        parse_timestamp(self.get_datetime_utc()?.timestamp())
    }
    fn format_datetime<T: TimeZone>(dt: &DateTime<T>, format: &Option<String>) -> String
    where
        T::Offset: Display,
    {
        match format {
            Some(fmt) => dt.format(fmt).to_string(),
            None => dt.to_string(),
        }
    }
    // 统一格式化入口
    pub fn get_datetime_str(&self) -> Option<String> {
        Some(match self.offset {
            Some(offset) => Self::format_datetime(
                &parse_datetime_offset(self.get_datetime_utc()?.naive_utc(), offset)?,
                &self.format,
            ),
            None => Self::format_datetime(&self.get_datetime_utc()?, &self.format),
        })
    }
    pub fn now_zh() -> Option<DateTime<FixedOffset>> {
        china_now()
    }
    pub fn now() -> DateTime<Utc> {
        now()
    }
}

#[test]
fn test_request_ntp_org() {
    let res = Client::new()
        .target("0.pool.ntp.org:123")
        .unwrap()
        .request()
        .unwrap();
    res.get_datetime_str().unwrap();
}