Network Time Protocol (NTP), Timezone and Daylight saving time (DST) with esp8266, esp32 or Arduino
When you create a device you probably need to know the correct time, on wifi device the resonably choiche is to use Internet to get time via NTP.
The Network Time Protocol (NTP) is a networking protocol for clock synchronization between computer systems over packet-switched, variable-latency data networks. In operation since before 1985, NTP is one of the oldest Internet protocols in current use. NTP was designed by David L. Mills of the University of Delaware. (cit. wiki)
NTPClient Library
The Arduino IDE have a library very simple to use NTPClient.
You can download It directly from Arduino IDE --> Manage Libraries
.
I add directly the example code, in this article I try to explain by comment on code, because the code is very simple.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 1;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
}
void loop() {
// I'm going to update time from internet
timeClient.update();
// Print the updated time
Serial.println(timeClient.getFormattedTime());
delay(1000);
}
Now your arduino or esp became a clock.
Time library
This example have some limitation, to update the date you must call service, a simple way to update less time the date you can use Time library. This Arduino library use internal clock as a normal clock that after you adjust It via NTP is a normal clock.
You can find the library here GitHub.
But I show you a simple example.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <time.h>
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 1;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60*60*1000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
delay ( 1000 );
if (timeClient.update()){
Serial.print ( "Adjust local clock" );
unsigned long epoch = timeClient.getEpochTime();
// HERE I'M UPDATE LOCAL CLOCK
setTime(epoch);
}else{
Serial.print ( "NTP Update not WORK!!" );
}
}
void loop() {
// Here I print time from local clock
Serial.println(getEpochStringByParams(now()));
delay(1000);
}
As you can see I create some simple function to parse and format the date, very usefully for me.
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
You must pay attention to line 20, there you can choiche if you want UTC time, by set GTMOffset
to 0 or if you want set your area GTM offset.
I live in Italy so I’m going to set GTM+1 and I set the last parameter to 60000 the update interval in milliseconds, this protocol use UDP as you can see, and you must choiche an NTP server.
But this is not sufficient, some zone have DST (Daylight saving time), so you must calculate the offset change, in the summer time Italy put another hour to the offset (so we are going formally in GTM+2), but a lot of country for energy saving purpose use DST.
But calculate that dynamic offset is very tedious, but exist a library that do It for you.
Timezone library
To manage DST you can use Timezone library.
You can download It directly from Arduino IDE --> Manage Libraries
.
The usage is not so simple, but with the example, you can understand It.
First you must configure your DST, there is an example with some configuration that you can use as starting point.
// Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours
Timezone ausET(aEDT, aEST);
// Moscow Standard Time (MSK, does not observe DST)
TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180};
Timezone tzMSK(msk);
// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
Timezone CE(CEST, CET);
// United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time
Timezone UK(BST, GMT);
// UTC
TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC
Timezone UTC(utcRule);
// US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);
// US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST);
// US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
In my case I must set CET and CEST parameter (line 10), and I’m going to modify original NTP example.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
const char *ssid = "<YOURSSID>";
const char *password = "<YOURPASSWD>";
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <time.h>
#include <Timezone.h> // https://github.com/JChristensen/Timezone
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 0; // SET TO UTC TIME
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60*60*1000);
// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
Timezone CE(CEST, CET);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
delay ( 1000 );
if (timeClient.update()){
Serial.print ( "Adjust local clock" );
unsigned long epoch = timeClient.getEpochTime();
setTime(epoch);
}else{
Serial.print ( "NTP Update not WORK!!" );
}
}
void loop() {
// I print the time from local clock but first I check DST
// to add hours if needed
Serial.println(getEpochStringByParams(CE.toLocal(now())));
delay(1000);
}
Now you have your time with DST correctly set.
Renzo, Great you made the NTP receiver with DST.
But I got a compile error on line 37.
can you please help me to find the cause of it?
Arduino: 1.8.12 (Linux), Board: "LOLIN(WEMOS) D1 R2 & mini, 80 MHz, Flash, Legacy (new can return nullptr), All SSL ciphers (most compatible), 4MB (FS:2MB OTA:~1019KB), v2 Lower Memory, Disabled, None, Only Sketch, 921600"
/home/wim/Desktop/Tijd/Tijd.ino: In function 'tm getDateTimeByParams(long int)':
Tijd:25:26: error: 'amp' was not declared in this scope
newtime = localtime(&tim);
^
Tijd:25:33: error: expected ';' before ')' token
newtime = localtime(&tim);
Multiple libraries were found for "TimeLib.h"
Used: /home/wim/Programs/Arduino/arduino-1.8.12/libraries/TimeLib
Not used: /home/wim/Programs/Arduino/arduino-1.8.12/libraries/Time
Not used: /home/wim/Arduino/libraries/Time
Multiple libraries were found for "NTPClient.h"
Used: /home/wim/Arduino/libraries/NTPClient
Not used: /home/wim/Programs/Arduino/arduino-1.8.12/libraries/NTPClient
^
/home/wim/Desktop/Tijd/Tijd.ino: In function 'String getEpochStringByParams(long int, char*)':
Tijd:37:39: error: 'amp' was not declared in this scope
return getDateTimeStringByParams(&newtime, pattern);
^
Tijd:37:59: error: expected ';' before ')' token
return getDateTimeStringByParams(&newtime, pattern);
^
exit status 1
'amp' was not declared in this scope
This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
Hi Willem,
there is a problem in the code viewer of wordpress, you must substitute
& amp;
(without space) with &, It’s uncorrectly rendered.I think now the highlighter is fixed.
Bye Renzo
Hi Renzo,
are you sure, that using of function adjustTime(epoch); is correct? It is defined in the library as increment of systime:
void adjustTime(long adjustment) {
sysTime += adjustment;
}
I use function setTime in this case.
And second remark: In zour scatch isn’t defined format function getDateTimeStringByParams
Pavel
Hi Pavel,
you are right in all part, I lost the function, and I used adjust but It was an error (but I did not notice it because by initializing everything immediately the gap was minimal).
Thanks for your support, I fixed all.
Bye Renzo
I am lost. I thought I understood how to apply this to my code, but apparently not.
Salient portion of my code:tmElements_t tm;
time_t current_time() {
return make_unixtime(usPT.toLocal(second()), usPT.toLocal(minute()), usPT.toLocal(hour()), usPT.toLocal(day()), usPT.toLocal(month()), usPT.toLocal(year()));
}
time_t make_unixtime(uint8_t sec, uint8_t mn, uint8_t hr, uint8_t dy, uint8_t mon, uint16_t yr) {
tm.Second = sec;
tm.Hour = hr;
tm.Minute = mn;
tm.Day = dy;
tm.Month = mon;
tm.Year = yr – 1970;
return makeTime(tm);
}
Not matter, it is still 8 hours behind. Please, I am so lost.
Hi Cam,
It’s difficult to understand, please open a topic on forum and attach the code, so I can check launch and test locally.
Bye Renzo
I like the code, but as I understand timeclient.update() only is called in the setup loop ?
So I don’t understand the hourly update-interval (60*60*100).
Am I missing somtehing ? Or how is it triggered ?
For testing purposes it would be convenient having a signal or counter when time is really updated. Is that possible ?
Hi Robin,
yes, you can put the update in loop and when retrieve data the function return true.
But, unless there is a problem in the microcontroller clock it is sufficient to update at the beginning and update the time.
Bye Renzo
Is this code known to be compatible with RPi Pico W? Getting a warning about library timezone being compatible with AVR architecture only, plus, Serial.println(getEpochStringByParams(CE.toLocal(now())));
outputs 01/01/1970 01:04:26 ….
I Bart,
sorry but I never tested that solution on Raspberry Pi Pico W.
When I take a look on It i write also here.
Bye Renzo
Hello, I am running the code on Pico W by using windows 11 and Arduino IDE 2.3.2. It works whether I used ESP8266.h or WiFi.h. When I move the mouse pointer on the tm (static tm getDateTimeByParams{…}, the pop up wrote : struct tm As required by POSIX.1-2008, declare tm as incomplete type. How to declare as a complete type?