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 settings
const char *ssid = "...";
const char *passwd = "...";
// --- Time settings ---
constexpr int32_t TIME_OFFSET_SECONDS = 2 * 3600; // UTC+2
constexpr uint32_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 handler
void WiFiEvent(system_event_id_t event) {
switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
connected = true;
ntp.begin(); // start UDP for NTP
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
connected = false;
ntp.end(); // stop UDP when WiFi is down
WiFi.reconnect(); // optional: auto reconnect
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
delay(100);
WiFi.onEvent(WiFiEvent);
WiFi.begin(ssid, passwd);
// Set time offset to GMT+2:00
ntp.setTimeOffset(TIME_OFFSET_SECONDS);
}
void loop() {
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 second
if (ntp.isValid()) {
static uint32_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:
typedef struct {
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, const char *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:
void setServer(const char *address);Create a local UDP socket for NTP:
void begin(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:
void end();Call this when the network goes down or before shutting down.
Query the NTP server and update the internal UTC timestamp:
bool update(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:
bool isValid() 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_t getTimestamp() 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_t getEpochTime() 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_t getDateTime() 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:
void setTimeOffset(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.