Skip to content

sstaub/NTP3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NTP3

The NTP3 library allows you to receive time information from the Internet. It also has support for different timezones and daylight saving time (DST). This NTP library uses the functions of the time.h standard library.

The new library was necessary for use on Raspberry Pico W and Pico 2W because there is an internal NTP library in the earlephilhower arduino-pico core.

On Espressif (ESP32, ESP8266), the library automatically synchronizes the system RTC with NTP time, making standard C time functions available for use.

Changes for 1.0.0

Behavioral Improvements

  • improved sync status tracking: update() now returns true when a valid sync exists, not just when performing new sync
  • network delay compensation automatically adjusts timestamps for round-trip latency (uses micros() on ESP32/ESP8266, SAMD, RP2040 for best precision)
  • automatic ESP32/ESP8266 system RTC synchronization: system time is automatically updated on successful NTP sync

API Improvements

  • new isValid() method validates the last NTP response (checks leap indicator and stratum)
  • new roundTripDelay() method returns last measured NTP request round-trip delay in milliseconds as a float
  • new milliseconds() method returns current milliseconds (0-999)
  • new syncRTC() method for ESP32/ESP8266 to optionally disable automatic RTC synchronization

Kiss-of-Death Back-off

  • when the NTP server returns stratum 0 (a back-off request), the configured update interval is automatically doubled up to a maximum of 300000 ms (5 minutes)

Bug Fixes

  • NTP request packet Reference Identifier now correctly set to zero for client requests
  • timestamp assembly from NTP response bytes now casts each uint8_t to uint32_t before shifting, eliminating signed integer overflow
  • calcDateDST() pre-increment fix: December "Last week" rules no longer produce an out-of-range tm_mon = 12
  • calcDateDST() now zero-initializes struct tm before passing to mktime(), preventing DST transitions from being off by one hour
  • init() now only computes DST transition times when the initial ntpUpdate() succeeds, preventing epoch-zero corruption of utcDST/utcSTD when WiFi is not yet connected
  • ntpUpdate() now validates received packet size is within the valid NTP/SNTP range (48–68 bytes); malformed or oversized packets are rejected
  • isDST() now applies the same DST-configured guard used by currentTime(), preventing a false true return before rules are configured
  • removed unused POSIX_OFFSET and UNIXOFFSET macros
  • ntpRequest[0] NTP client request packet byte 0 corrected to 0b00100011 (LI=0, Version=4, Mode=3); previously 0b11100011, now compliant with RFC 4330
  • timezoneOffset and dstOffset are now recomputed on every successful NTP sync, not only during init(); previously a failed initial sync left both values at zero permanently, causing all time accessors to return UTC regardless of the configured timezone rules
  • packet size variable changed from uint8_t to int to match the int return type of parsePacket()
  • tzName() now applies the same hasValidSync guard as isDST() and currentTime(), preventing it from calling summerTime() before any sync has occurred and returning the wrong timezone name for southern-hemisphere DST rules at startup
  • currentTime() now applies the same hasValidSync guard, preventing it from applying DST offsets before the first successful sync
  • timeZone() now casts to int32_t before multiplying hours by 3600, preventing integer overflow on AVR for timezone offsets of UTC±9 and beyond (e.g. Japan, Australia, New Zealand)
  • ruleDST() and ruleSTD() getters now strip the trailing newline that ctime() appends before returning the formatted date string

New Example

  • new ESP32_Precision_NTP example demonstrates sub-millisecond accuracy, NTP validation, ESP32 RTC synchronization, and network delay measurement

Other Changes for 1.0.0

  • code refactoring
  • improve error handling and parameter validation
  • fix missing AVR NTP_OFFSET
  • support for AVR
  • optimizations
  • change of begin(), now you can start with hostname or IP address
  • no blocking
  • better documentation

Example

Example for WIFI boards like ESP32 or MKR1000, NANO RP2040 Connect and other, prints formatted time and date strings to console.

// change next line to use with another board/shield
//#include <ESP8266WiFi.h>
//#include <WiFi.h> // for Raspberry Pi Pico (2) W, WiFi shield or ESP32
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiNINA.h> // for UNO Wifi Rev 2 or Nano RP2040 connect
//#include "WiFiUdp.h" // not needed for WiFiNINA
#include "NTP3.h"

const char *ssid     = "yourSSID"; // your network SSID
const char *password = "yourPASSWORD"; // your network PW

WiFiUDP wifiUdp;
NTP3 ntp(wifiUdp);

void setup() {
  Serial.begin(9600);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Connecting ...");
    delay(500);
    }
  Serial.println("Connected");  
  ntp.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timetone +120min (+1 GMT + 1h summertime offset)
  ntp.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT)
  ntp.begin();
  Serial.println("start NTP");
  }

void loop() {
  if (ntp.update()) {
    Serial.println(ntp.formattedTime("%d. %B %Y")); // dd. Mmm yyyy
    Serial.println(ntp.formattedTime("%A %T")); // Www hh:mm:ss
  } else {
    Serial.println("No valid time data");
  }
  delay(1000);
  }

Documentation

Class Definitions

NTP3(UDP& udp);

Constructor for a NTP object

Example, this should done before setup()

WiFiUDP wifiUdp;
NTP3 ntp(wifiUdp);
~NTP3();

Destructor for a NTP object

begin()

void begin(const char* server = "pool.ntp.org");
void begin(IPAddress serverIP);

Start the underlaying UDP client with a hostname or IP address.

If server is nullptr, the library falls back to "pool.ntp.org".

Example, this must done in setup()

ntp.begin(); // start the NTP client with the standard host

stop()

void stop();

Stop the underlaying UDP client

Example, this must done in setup()

ntp.stop();

update()

bool update();

This must called in the main loop(). Returns true if time data is available from the current sync state, false if no sync has occurred yet or the last sync attempt failed.

The function performs a new NTP sync when the update interval has elapsed (default 60 seconds). Between syncs, it returns the status of the most recent sync attempt, making it easy to check if time data is available.

Important: update() does not verify the NTP server's reported synchronization state. If your application needs assurance that the server reported valid time data, call isValid() after update() returns true.

Example

loop() {
  if (ntp.update()) {
    if (ntp.isValid()) {
      Serial.println(ntp.formattedTime("%A %T"));
    } else {
      Serial.println("Warning: NTP server reports invalid time");
    }
  } else {
    // Waiting for initial sync or sync failed
    Serial.println("No valid time data");
  }
  }

updateInterval()

void updateInterval(uint32_t interval);

Set the update interval for connecting the NTP server in ms, default is 60000ms (60s)

Note: if the NTP server returns a back-off request, this library respects the request by doubling the configured interval value up to a maximum of 300000ms or 5 minutes.

Example, this must done in setup()

ntp.updateInterval(1000); // update every second

syncRTC()

void syncRTC(bool enable);

Enable or disable automatic system RTC synchronization (ESP32 and ESP8266 only).

When enabled, the library automatically updates the ESP32/ESP8266 system RTC on every successful NTP sync using settimeofday(). This allows standard C time functions like time() and localtime() to work correctly and makes the library compatible with other code expecting system time to be set.

This feature is automatically enabled by default on ESP32 and ESP8266 platforms. The function is only available when compiling for these platforms.

To disable automatic RTC sync on Espressif devices, in setup() add:

ntp.syncRTC(false); // disable automatic system RTC synchronization

Note: On other platforms (AVR, etc.), this function is not available and the library only maintains time internally.

ruleDST()

void ruleDST(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset);

Set the rules for the daylight save time settings

  • tzname is the name of the timezone, e.g. "CEST" (central europe summer time)
  • if tzName is nullptr, an empty time zone name is stored
  • week Last, First, Second, Third, Fourth (0 - 4)
  • wday Sun, Mon, Tue, Wed, Thu, Fri, Sat (0 - 6)
  • month Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec (0 -11)
  • hour the local hour when rule changes
  • tzOffset timezone offset in minutes

Example, this must done in setup()

ntp.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timezone +120min (+1 GMT + 1h summertime offset)

Return ruleDST()

const char* ruleDST();

Returns the DST time back, formatted as an ctime string

ruleSTD()

void ruleSTD(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset);

Set the rules for the standard time settings

  • tzname is the name of the timezone, e.g. "CET" (central europe time)
  • if tzName is nullptr, an empty time zone name is stored
  • week Last, First, Second, Third, Fourth (0 - 4)
  • wday Sun, Mon, Tue, Wed, Thu, Fri, Sat (0 - 6)
  • month Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec (0 -11)
  • hour the local hour when rule changes
  • tzOffset timezone offset in minutes

Example, this must done in setup()

ntp.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT)

Return ruleSTD()

const char* ruleSTD();

Return the STD time back, formatted as an ctime string

Return tzName()

const char* tzName();

Return you the name of the current timezone, based on your rule settings

timeZone()

void timeZone(int8_t tzHours, int8_t tzMinutes = 0);

Only use this function when you don't made the rules setting, you have to the set isDST(false)

isDST()

void isDST(bool dstZone);

Use in conjunction with timeZone, when there is no DST!

Return isDST()

bool isDST();

Return the DST status back, true if summertime

epoch()

time_t epoch();

Return the Unix epoch timestamp

year(), month(), day(), weekDay(), hours(), minutes(), seconds()

int16_t year();
int8_t month();
int8_t day();
int8_t weekDay();
int8_t hours();
int8_t minutes();
int8_t seconds();

Return the datas from the tm structure of the "time.h" library

Return formattedTime()

const char* formattedTime(const char *format);

Return a string, formated with strftime function of standard time library

Example

loop() {
  if (ntp.update()) {
    Serial.println(ntp.formattedTime("%d. %B %Y")); // dd. Mmm yyyy
    Serial.println(ntp.formattedTime("%A %T")); // Www hh:mm:ss
  } else {
    Serial.println("No valid time data");
  }
  delay(1000);
  }

Format symbols:

| symbol | explanation
/* General */
| % | writes literal %. The full conversion specification must be %%.
| n | writes newline character
| t | writes horizontal tab character
/* Year */
| Y | writes year as a decimal number, e.g. 2017
| y | writes last 2 digits of year as a decimal number (range [00,99])
| C | writes first 2 digits of year as a decimal number (range [00,99])
| G | writes ISO 8601 week-based year, i.e. the year that contains the specified week. 
	  In IS0 8601 weeks begin with Monday and the first week of the year must satisfy the following requirements:
	  - Includes January 4 
	  - Includes first Thursday of the year
| g | writes last 2 digits of ISO 8601 week-based year, i.e. the year that contains the specified week (range [00,99]).
	  In IS0 8601 weeks begin with Monday and the first week of the year must satisfy the following requirements:
	  - Includes January 4
	  - Includes first Thursday of the year
/* Month */
| b | writes abbreviated month name, e.g. Oct (locale dependent)
| h | synonym of b
| B | writes full month name, e.g. October (locale dependent)
| m | writes month as a decimal number (range [01,12])
/* Week */
| U | writes week of the year as a decimal number (Sunday is the first day of the week) (range [00,53])
| W | writes week of the year as a decimal number (Monday is the first day of the week) (range [00,53])
| V | writes ISO 8601 week of the year (range [01,53]).
	  In IS0 8601 weeks begin with Monday and the first week of the year must satisfy the following requirements:
	  - Includes January 4
	  - Includes first Thursday of the year
/* Day of the year/month */
| j | writes day of the year as a decimal number (range [001,366])
| d | writes day of the month as a decimal number (range [01,31])
| e | writes day of the month as a decimal number (range [1,31]).
	  Single digit is preceded by a space.
/* Day of the week */
| a | writes abbreviated weekday name, e.g. Fri (locale dependent)
| A | writes full weekday name, e.g. Friday (locale dependent)
| w | writes weekday as a decimal number, where Sunday is 0 (range [0-6])
| u | writes weekday as a decimal number, where Monday is 1 (ISO 8601 format) (range [1-7])
/* Hour, minute, second */
| H | writes hour as a decimal number, 24 hour clock (range [00-23])
| I | writes hour as a decimal number, 12 hour clock (range [01,12])
| M | writes minute as a decimal number (range [00,59])
| S | writes second as a decimal number (range [00,60])
/* Other */
| c | writes standard date and time string, e.g. Sun Oct 17 04:41:13 2010 (locale dependent)	
| x | writes localized date representation (locale dependent)
| X | writes localized time representation (locale dependent)
| D | equivalent to "%m/%d/%y"
| F | equivalent to "%Y-%m-%d" (the ISO 8601 date format)
| r | writes localized 12-hour clock time (locale dependent)
| R | equivalent to "%H:%M"
| T | equivalent to "%H:%M:%S" (the ISO 8601 time format)
| p | writes localized a.m. or p.m. (locale dependent)

Return utc()

uint32_t utc();

Returns the raw Unix timestamp recorded at the moment the last NTP packet was received. Unlike epoch(), this value does not advance between syncs — it is a frozen snapshot of the server response. Use epoch() when you need the current time; use utc() only when you need the exact value that arrived from the NTP server.

Return ntp()

uint32_t ntp();

Return the timestamp received from the ntp server in the NTP timestamp format

isValid()

bool isValid();

Call this after update() returns true when your application needs assurance that the NTP server reported synchronized, usable time.

Validates the last NTP response by checking:

  • Leap Indicator: Returns false if server clock is unsynchronized (LI = 3)
  • Stratum: Returns false if stratum is 0 (kiss-of-death), 16 (unsynchronized), or > 16 (reserved)
  • Sync status: Returns false if no valid sync has occurred yet

Example

loop() {
  if (ntp.update()) {
    if (ntp.isValid()) {
      Serial.println(ntp.formattedTime("%A %T"));
    } else {
      Serial.println("Warning: NTP server reports invalid time");
    }
  }
  delay(1000);
}

roundTripDelay()

float roundTripDelay();

Returns the measured round-trip network delay from the last successful NTP synchronization in milliseconds.

This value represents the total time from sending the NTP request to receiving the response. The library automatically compensates for half of this delay (one-way latency) when calculating the precise timestamp.

On ESP32/8266, SAMD, and RP2040 platforms, the delay is measured at microsecond precision and converted to milliseconds (preserving sub-millisecond accuracy as a float). On other platforms, millisecond precision is used.

Example

if (ntp.update()) {
  if (ntp.isValid()) {
    float delay = ntp.roundTripDelay();
    Serial.print("Network round-trip delay: ");
    Serial.print(delay, 3);  // 3 decimal places
    Serial.println(" ms");
  }
}

About

NTP library for Arduino framework

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages