ntp_client/
client.rs

1use e_utils::chrono::{
2    china_now, now, parse_datetime_offset, parse_timestamp, DateTime, FixedOffset, TimeZone, Utc,
3};
4
5use crate::Packet;
6use std::fmt::Display;
7use std::io::Cursor;
8use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket};
9use std::time::Duration;
10
11#[derive(Clone, Debug)]
12pub struct Client {
13    pub packet: Packet,
14    pub timestamp: i64,
15    pub src: String,
16    pub read_timeout: Duration,
17    pub write_timeout: Duration,
18    pub target: SocketAddr,
19    pub offset: Option<FixedOffset>,
20    pub format: Option<String>,
21}
22impl Default for Client {
23    fn default() -> Self {
24        Self {
25            target: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 123)),
26            packet: Packet::new_client(),
27            timestamp: 0,
28            src: String::from("0.0.0.0:0"),
29            write_timeout: Duration::from_secs(5),
30            read_timeout: Duration::from_secs(5),
31            offset: None,
32            format: None,
33        }
34    }
35}
36impl Client {
37    ///### Example: Get time from an NTP server and sync systemtime
38    /// ```rust
39    /// fn main() -> e_utils::AnyResult<()> {
40    ///     let target = "0.pool.ntp.org:123";
41    ///     let res = ntp_client::Client::new()
42    ///         .target(target)?
43    ///         .format(Some("%Y/%m/%d %H:%M:%S"))
44    ///         .request()?;
45    ///     let res_str = res.get_datetime_str().ok_or("error")?;
46    ///     println!("UTC str -> {res_str}");
47    ///     let datetime = res.get_datetime_utc().ok_or("get datetime utc")?;
48    ///     ntp_client::sync_systemtime(datetime)?;
49    ///     Ok(())
50    /// }
51    /// ```
52    pub fn new() -> Self {
53        Self::default()
54    }
55    pub fn offset(mut self, offset: Option<FixedOffset>) -> Self {
56        self.offset = offset;
57        self
58    }
59    pub fn format(mut self, format: Option<impl Into<String>>) -> Self {
60        self.format = format.map(|v| v.into());
61        self
62    }
63    pub fn target<A: ToSocketAddrs>(mut self, addr: A) -> e_utils::AnyResult<Self> {
64        self.target = addr
65            .to_socket_addrs()?
66            .next()
67            .ok_or("to_socket_addrs next")?;
68        Ok(self)
69    }
70    pub fn src_ip(mut self, addr: impl Into<String>) -> Self {
71        self.src = addr.into();
72        self
73    }
74    pub fn packet(mut self, packet: Packet) -> Self {
75        self.packet = packet;
76        self
77    }
78    
79    pub fn request(mut self) -> e_utils::AnyResult<Self> {
80        let data: Vec<u8> = self.packet.clone().into();
81        let sock = UdpSocket::bind(&self.src)?;
82        sock.set_read_timeout(Some(self.read_timeout))?;
83        sock.set_write_timeout(Some(self.write_timeout))?;
84        let _sz = sock.send_to(&data, self.target)?;
85        let mut buf = vec![0; 48];
86        let _res = sock.recv(&mut buf)?;
87        let rdr = Cursor::new(&buf);
88        self.packet = Packet::try_from(rdr)?;
89        self.timestamp = DateTime::<Utc>::from(self.packet.transmit_time).timestamp();
90        Ok(self)
91    }
92    // UTC 时间获取保持原样
93    pub fn get_datetime_utc(&self) -> Option<DateTime<Utc>> {
94        DateTime::from_timestamp(self.timestamp, 0)
95    }
96    // UTC 时间获取保持原样
97    pub fn get_datetime_shanghai(&self) -> Option<DateTime<FixedOffset>> {
98        parse_timestamp(self.get_datetime_utc()?.timestamp())
99    }
100    fn format_datetime<T: TimeZone>(dt: &DateTime<T>, format: &Option<String>) -> String
101    where
102        T::Offset: Display,
103    {
104        match format {
105            Some(fmt) => dt.format(fmt).to_string(),
106            None => dt.to_string(),
107        }
108    }
109    // 统一格式化入口
110    pub fn get_datetime_str(&self) -> Option<String> {
111        Some(match self.offset {
112            Some(offset) => Self::format_datetime(
113                &parse_datetime_offset(self.get_datetime_utc()?.naive_utc(), offset)?,
114                &self.format,
115            ),
116            None => Self::format_datetime(&self.get_datetime_utc()?, &self.format),
117        })
118    }
119    pub fn now_zh() -> Option<DateTime<FixedOffset>> {
120        china_now()
121    }
122    pub fn now() -> DateTime<Utc> {
123        now()
124    }
125}
126
127#[test]
128fn test_request_ntp_org() {
129    let res = Client::new()
130        .target("0.pool.ntp.org:123")
131        .unwrap()
132        .request()
133        .unwrap();
134    res.get_datetime_str().unwrap();
135}