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 {
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)
}
pub fn get_datetime_utc(&self) -> Option<DateTime<Utc>> {
DateTime::from_timestamp(self.timestamp, 0)
}
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();
}