#sntp #async #time #system-clock

rsntp

An RFC 5905 compliant Simple Network Time Protocol (SNTP) client library for Rust

15 releases (stable)

4.0.0 Jan 31, 2024
3.0.2 Mar 19, 2023
3.0.1 May 1, 2022
3.0.0 Feb 22, 2022
0.1.0 Dec 29, 2019

#78 in Network programming

Download history 1719/week @ 2023-12-31 2619/week @ 2024-01-07 1605/week @ 2024-01-14 2247/week @ 2024-01-21 2476/week @ 2024-01-28 2575/week @ 2024-02-04 2461/week @ 2024-02-11 2804/week @ 2024-02-18 3043/week @ 2024-02-25 3179/week @ 2024-03-03 3358/week @ 2024-03-10 3607/week @ 2024-03-17 2793/week @ 2024-03-24 2385/week @ 2024-03-31 3159/week @ 2024-04-07 2917/week @ 2024-04-14

11,337 downloads per month
Used in 4 crates

MIT/Apache

83KB
1.5K SLoC

CircleCI

rsntp

An RFC 5905 compliant Simple Network Time Protocol (SNTP) client library for Rust.

rsntp provides an API to synchronize time with SNTPv4 time servers with the following features:

  • Provides both a synchronous (blocking) and an (optional) asynchronous, tokio based API
  • Optional support for time and date crates chrono and time (chrono is enabled by default)
  • IPv6 support

Usage

Add this to your Cargo.toml:

[dependencies]
rsntp = "4.0.0"

Obtain the current local time with the blocking API:

use rsntp::SntpClient;
use chrono::{DateTime, Local};

let client = SntpClient::new();
let result = client.synchronize("pool.ntp.org").unwrap();

let local_time: DateTime<Local> =
  DateTime::from(result.datetime().into_chrono_datetime().unwrap());

println!("Current time is: {}", local_time);

You can also use the asynchronous API to do the same:

use rsntp::AsyncSntpClient;
use chrono::{DateTime, Local, Utc};

async fn local_time() -> DateTime<Local> {
  let client = AsyncSntpClient::new();
  let result = client.synchronize("pool.ntp.org").await.unwrap();
   
   DateTime::from(result.datetime().into_chrono_datetime().unwrap())
}

API changes in version 3.0

Version 3.0 made core code independent of time and date crates and added support for the time crate. This led to some breaking API changes, SynchronizationResult methods will return with wrappers struct instead of chrono ones. Those wrapper structs has TryInto implementation and helper methods to convert them to chrono format.

To convert old code, replace

let datetime = result.datetime();

with

let datetime = result.datetime().into_chrono_datetime().unwrap();

or with

let datetime: chrono::DateTime<Utc> = result.datetime().try_into().unwrap();

The same applies to Durations returned by SynchronizationResult.

Support for time and date crates

rsntp supports returning time and date data in different formats. Currently the format of the two most popular time and date handling crates supported: chrono and time. By default, chrono is enabled, but you can add time support with a feature:

use rsntp::SntpClient;

let client = SntpClient::new();
let result = client.synchronize("pool.ntp.org").unwrap();

let utc_time = result
  .datetime()
  .into_offset_date_time()
  .unwrap();

println!("UTC time is: {}", utc_time);

Support for both crates can be enabled independently; you can even enable both at the same time.

Disabling asynchronous API

The asynchronous API is enabled by default, but you can disable it. Disabling it has the advantage that it removes the dependency to tokio, which reduces the amount of dependencies significantly.

[dependencies]
rsntp = { version = "4.0.0", default-features = false, features = ["chrono"] }

System clock assumptions

rsntp assumes that system clock is monotonic and stable. This is especially important with the SynchronizationResult::datetime() method, as SynchronizationResult stores just an offset to the system clock. If the system clock is changed between synchronization and the call to this method, then offset will not be valid anymore and some undefined result will be returned.

IPv6 support

rsntp supports IPv6, but for compatibility reasons, it binds its UDP socket to an IPv4 address (0.0.0.0) by default. That might prevent synchronization with IPv6 servers.

To use IPv6, you need to set an IPv6 bind address:

use rsntp::{Config, SntpClient};
use std::net::Ipv6Addr;

let config = Config::default().bind_address((Ipv6Addr::UNSPECIFIED, 0).into());
let client = SntpClient::with_config(config);

let result = client.synchronize("2.pool.ntp.org").unwrap();

let unix_timestamp_utc = result.datetime().unix_timestamp();

Dependencies

~1–12MB
~90K SLoC