A tiny NTP client for ESP32 and ESP8266 that:
- Talks to NTP servers via UDP (e.g.
pool.ntp.org) - Keeps time locally using
millis()between synchronizations - Supports time zones via a simple offset in seconds
- Exposes both a Unix timestamp and a human-readable date/time struct
It is designed to be:
- Small - only depends on Arduino’s
UDPinterface - Portable - works with
WiFiUDP,EthernetUDP, etc. - Friendly to NTP servers - you decide how often to sync
#include<WiFi.h> #include<WiFiUdp.h> #include<ntp.h>// WiFi settingsconstchar *ssid = "..."; constchar *passwd = "..."; // --- Time settings ---constexprint32_t TIME_OFFSET_SECONDS = 2 * 3600; // UTC+2constexpruint32_t NTP_SYNC_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour WiFiUDP udp; NTP ntp(udp); bool connected = false; uint32_t lastSync = 0; // ESP32 WiFi event handlervoidWiFiEvent(system_event_id_t event){switch (event){case SYSTEM_EVENT_STA_GOT_IP: connected = true; ntp.begin(); // start UDP for NTPbreak; case SYSTEM_EVENT_STA_DISCONNECTED: connected = false; ntp.end(); // stop UDP when WiFi is down WiFi.reconnect(); // optional: auto reconnectbreak; default: break} } voidsetup(){Serial.begin(115200); delay(100); WiFi.onEvent(WiFiEvent); WiFi.begin(ssid, passwd); // Set time offset to GMT+2:00 ntp.setTimeOffset(TIME_OFFSET_SECONDS)} voidloop(){if (!connected){return} uint32_t nowMs = millis(); // Sync with NTP once per hour (or on first successful connection)if (!ntp.isValid() || (nowMs - lastSync >= NTP_SYNC_INTERVAL_MS)){if (ntp.update()){lastSync = nowMs; Serial.println(F("NTP time updated"))} else{Serial.println(F("NTP update failed"))} } // Print current local time once per secondif (ntp.isValid()){staticuint32_t lastPrint = 0; if (nowMs - lastPrint >= 1000){lastPrint = nowMs; ntp_datetime_t t = ntp.getDateTime(); Serial.printf("%02u.%02u.%04u %02u:%02u:%02u\n", t.day, t.month, t.year, t.hour, t.minute, t.second)} } // put your other logic here (no delay() needed) }Note You should not call
update()every second in production. Synchronize periodically (e.g. every 30-60 minutes) and use the library’s internalmillis()-based timekeeping between syncs.
The library returns a simple struct for human-readable time:
typedefstruct{uint8_t day; uint8_t month; uint16_t year; uint8_t hour; uint8_t minute; uint8_t second} ntp_datetime_t;You must pass an existing UDP instance (e.g. WiFiUDP):
NTP(UDP& udp); NTP(UDP& udp, constchar *address);udp- any object implementing theUDPinterface (e.g.WiFiUDP,EthernetUDP)address- optional NTP server hostname or IP (default:"pool.ntp.org")
You can also change the server later:
voidsetServer(constchar *address);Create a local UDP socket for NTP:
voidbegin(uint16_t port = NTP_DEFAULT_LOCAL_PORT);port- local UDP port to bind (default:9876)
Call this when the network connection is up (WiFi.status() == WL_CONNECTED or WiFi event SYSTEM_EVENT_STA_GOT_IP).
Close the UDP socket:
voidend();Call this when the network goes down or before shutting down.
Query the NTP server and update the internal UTC timestamp:
boolupdate(uint16_t timeoutMs = NTP_DEFAULT_TIMEOUT_MS);timeoutMs- maximum time to wait for a response (default:1000ms)- Returns
trueon success,falseon timeout or invalid response.
The library:
- Sends one NTP request packet
- Waits for a valid NTP response
- Extracts the server’s transmit timestamp (seconds since 1900-01-01)
- Converts it to Unix time (seconds since 1970-01-01)
- Stores this as the reference time and remembers the
millis()value at that moment
Between calls to update(), the current time is derived from the last synced time plus the elapsed millis().
Check if the client has ever synchronized successfully:
boolisValid() const;Returns true after the first successful update(). Use this to avoid printing bogus times before the first sync.
Get the current UTC time (no time offset applied):
uint32_tgetTimestamp() const;- Returns seconds since 1970-01-01 00:00:00 UTC
- Uses the last NTP sync plus elapsed
millis().
Get the current local epoch time (UTC + time offset):
uint32_tgetEpochTime() const;- Returns seconds since 1970-01-01 00:00:00 local time
- Local time = UTC + time offset set via
setTimeOffset().
Get a human-readable local date/time:
ntp_datetime_tgetDateTime() const;- Uses
getEpochTime()internally - Returns
{day, month, year, hour, minute, second}for your local time zone - If there has not been a valid sync yet, all fields will be
0.
Set your time zone offset in seconds relative to UTC:
voidsetTimeOffset(int32_t timeOffsetSeconds);Examples:
- UTC →
0 - UTC+1 (CET) →
1 * 3600 - UTC+2 →
2 * 3600 - UTC−5 →
-5 * 3600
This offset is applied by getEpochTime() and getDateTime().
- Do not spam NTP servers: Synchronize at most every few minutes, preferably every 30-60 minutes for most IoT use-cases.
- Use
isValid()before trusting the time: Only print or act on timestamps after the first successfulupdate(). - Let the MCU keep time between syncs: The library already uses
millis()to advance the time, so you do not need an external RTC for many applications. - Handle WiFi dropouts: If WiFi disconnects, stop NTP (
end()), reconnect WiFi, and then callbegin()again when the connection is restored.
Copyright (c) 2025, Robert Eisele Licensed under the MIT license.