Daylight Saving Time adjust with Ethernet NTP

Hi all,
I've been getting all of my questions answered by searching the forum for a couple of years now, I really want to thank the Moderators for all of their hard work. This is my first post.

My Daylight Saving Time routine didn't work as expected last Sunday so I rewrote my DST code and I think I've got it right now. I would appreciate some extra eyes on this too make sure. This is my primary time code (requestNTP and dstOffset) placed in a test sketch. I've only tested with EST.

This is US only; there are other calculations on webexhibits.org for the European Community.

#include <Ethernet.h>
#include <SPI.h>
#include <EthernetUdp.h>
#include <Time.h>

//IDE Version 1.0.5
//Arduino Uno R3
//Arduino Ethernet Shield R3 (W5100)
//No SD card in card slot
//
//Edit this section for your network setup
//---------------------------------------------
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  //Use the MAC for your shield
IPAddress ip(192,168,0,177);
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
IPAddress mydns(192,168,0,1);
char timeServer[] = "0.north-america.pool.ntp.org";  //Time server you wish to use
//Time Zone Selection
//const int TZ_OFFSET = 4*3600;  //AST UTC-4
const int TZ_OFFSET = 5*3600;  //EST UTC-5
//const int TZ_OFFSET = 6*3600;  //CST UTC-6
//const int TZ_OFFSET = 7*3600;  //MST UTC-7
//const int TZ_OFFSET = 8*3600;  //PST UTC-8
//const int TZ_OFFSET = 9*3600;  //AKST UTC-9
//const int TZ_OFFSET = 10*3600;  //HST UTC-10
//---------------------------------------------
//
const int NTP_PACKET_SIZE = 48;  //NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE];  //Buffer to hold incoming and outgoing packets
time_t prevDisplay;
EthernetUDP Udp;
//
unsigned long requestNTP() 
{ 
  //Send NTP packet
  //Set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  //Init values needed to form NTP request
  packetBuffer[0] = 0b11100011;  //LI, Version, Mode
  packetBuffer[1] = 0;  //Stratum, or type of clock
  packetBuffer[2] = 6;  //Polling Interval
  packetBuffer[3] = 0xEC;  //Peer Clock Precision
  //Skip 8 bytes and leave zeros for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  //All NTP fields have been given values, now
  //you can send a packet requesting a timestamp: 		   
  Udp.beginPacket(timeServer, 123);  //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket(); 
  //Wait to see if a reply is available
  delay(250);  //Adjust this delay for time server (effects accuracy, use shortest delay possible)
  if (Udp.parsePacket())
  {  
    //We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE);
    //The timestamp starts at byte 40 of the received packet and is four bytes,
    //or two words, long. First, extract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    //Combine the four bytes (two words) into a long integer
    //This is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    //Now convert NTP time into everyday time:
    //Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long SEVENTY_YEARS = 2208988800UL;     
    //Subtract seventy years:
    unsigned long epoch = secsSince1900 - SEVENTY_YEARS;
    epoch = epoch - TZ_OFFSET;
    epoch = epoch + dstOffset(epoch);  //Adjust for DLT
    return epoch;
  }
 return 0; 
}
int dstOffset (unsigned long unixTime)
{
  //Receives unix epoch time and returns seconds of offset for local DST
  //Valid thru 2099 for US only, Calculations from "http://www.webexhibits.org/daylightsaving/i.html"
  //Code idea from jm_wsb @ "http://forum.arduino.cc/index.php/topic,40286.0.html"
  //Get epoch times @ "http://www.epochconverter.com/" for testing
  //DST update wont be reflected until the next time sync
  time_t t = unixTime;
  int beginDSTDay = (14 - (1 + year(t) * 5 / 4) % 7);  
  int beginDSTMonth=3;
  int endDSTDay = (7 - (1 + year(t) * 5 / 4) % 7);
  int endDSTMonth=11;
  if (((month(t) > beginDSTMonth) && (month(t) < endDSTMonth))
    || ((month(t) == beginDSTMonth) && (day(t) > beginDSTDay))
    || ((month(t) == beginDSTMonth) && (day(t) == beginDSTDay) && (hour(t) >= 2))
    || ((month(t) == endDSTMonth) && (day(t) < endDSTDay))
    || ((month(t) == endDSTMonth) && (day(t) == endDSTDay) && (hour(t) < 1)))
    return (3600);  //Add back in one hours worth of seconds - DST in effect
  else
    return (0);  //NonDST
}
void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip, mydns, gateway, subnet);
  Udp.begin(8888);
  delay(3000);
  setSyncInterval(28800);  //Every 8 hours
  setSyncProvider (requestNTP);
  while (timeStatus() < 2);  //Wait for time sync
  prevDisplay = now();
}
void loop()
{  
  if( now() != prevDisplay) //Update the display only if the time has changed
  {
    prevDisplay = now();
    digitalClockDisplay();  
  }
}
void digitalClockDisplay()
{
  //Digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" - ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(monthShortStr(month()));
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
} 
void printDigits(int digits)
{
  //Utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}
1 Like

Hi jft2,
This is great, to have DST automatcally done at the right time.
This is something I had planned on doing but have been distracted by other more higher priorities on the project, and have had to manually change the DST offset in my NTP code section.

Correct time is important for my project as I use the time to timestamp historical data being sent to my host site for for trending by client side.

Did you do some tests to prove the time change is made at the specific date and time?
You can simply do this by having the Arduino not read from NTP, but a predifined variable containing the time just before your DST time change and watching that it catches it.

I'll look to incorporating your code into mine, with the appropriate change over dates, though summer time DST has already kicked in some weeks ago here.


Paul

A good way to test is to write a simulation sketch that steps the time conversion code through the DST/Standard transitions. Much easier and faster to than waiting around for the time change :wink: and of course different time zones can be tested as well.

Here is a library that I've been using for at least a year and a half, I've had good feedback on it, including from the Southern hemisphere.

Thank you both for your comments, writing a sketch tonight to step my code through predefinded NTP time stamps that will walk it through spring and fall time changes.

deleted.
see my updated post.

Am in the process of incorporating your code, but am trying to understand the begin and end dates calculations.
The information provided on the 'webexhibits.org' site does not explain clearly.

The dates for Australia are that DST starts on the first Sunday in October and DST ends on the first Sunday in April.
Taken from http://australia.gov.au/about-australia/our-country/time

I'll be keen to hear how your checks go.
It will take me a while to check mine as I have other sections of code that need working on before the next upload to the Mega.

EDIT: Just noticed your post there doughboy, that looks like it will help me sort out the correct dates, thanks.


Paul

the key is to call the function only at 2am. (well, actually I call it once on reboot also)
since on reboot, I use ntp time (which is not dst adjusted), I have to use 1am for dstEnd instead of 2am, since the ntp of end time is really 1am.
The boolean isDst I use to format the time string with PDT or PST for pacific daylight time or pacific standard time which is my local time zone.
this is my autoDST function that calls the isDST function. I call it everyday at 2am.

void autoDST(time_t t) {
  if (IsDST(t)==isDst) return;
  isDst = !isDst;
  if (isDst)
    RTC.set(t+SECS_PER_HOUR);
  else
    RTC.set(t-SECS_PER_HOUR);
  setTime(RTC.get());
  logMessage(F("Auto adjusted DST time."));
  char buf[20];
  logMessage(F("Current time is "),getdatestring(now(),buf));
}

boolean IsDST(time_t t)
{
  tmElements_t te;
  te.Year = year(t)-1970;
  te.Month =3;
  te.Day =1;
  te.Hour = 0;
  te.Minute = 0;
  te.Second = 0;
  time_t dstStart,dstEnd, current;
  dstStart = makeTime(te);
  dstStart = nextSunday(dstStart);
  dstStart = nextSunday(dstStart); //second sunday in march
  dstStart += 2*SECS_PER_HOUR;//2AM
  te.Month=11;
  dstEnd = makeTime(te);
  dstEnd = nextSunday(dstEnd); //first sunday in november
  dstEnd += SECS_PER_HOUR; //1AM
  return (t>=dstStart && t<dstEnd);
}

doughboy wrote:

the key is to call the function only at 2am

Good point.
Currently I re-sync every 24 hours at midnight as that is what I need for daily statistics, so I'll look at doing this check at 02h00 each day to see if coming in or out of DST. This method seems very easy to set the dates for different countries.

It's a tricky thing and I didn't want to consume up lots of program space for the functionality, but at the same time it's one function I needed.

Many thanks.


Paul

doughboy,
I like your code. It's not limited like the code I was using and is easily updated for other regions in the world. I updated my test sketch to use your code(and my main project). For testing I setup an XP machine as a NTP time server (Configure an authoritative time - Windows Server | Microsoft Learn) set it's time zone to UTC-0 and set it not to update from the internet. I set the sketch SyncInterval to 15 seconds and rolled my UNO into and out of DST for 2012, 2013, and 2014. I also checked a few random dates within DST and outside of DST. All tests were successful. Like you I found the beginning DST check had to be at local time 2AM but the ending DST check had to be at local time 1AM.

Results for 2013.

Into DST 10 Mar 2AM local time 7AM UTC time

Time Sync
1:59:45 - 10 Mar 2013
1:59:46 - 10 Mar 2013
1:59:47 - 10 Mar 2013
1:59:48 - 10 Mar 2013
1:59:49 - 10 Mar 2013
1:59:50 - 10 Mar 2013
1:59:51 - 10 Mar 2013
1:59:52 - 10 Mar 2013
1:59:53 - 10 Mar 2013
1:59:54 - 10 Mar 2013
1:59:55 - 10 Mar 2013
1:59:56 - 10 Mar 2013
1:59:57 - 10 Mar 2013
1:59:58 - 10 Mar 2013
1:59:59 - 10 Mar 2013
Time Sync
3:00:01 - 10 Mar 2013
3:00:02 - 10 Mar 2013
3:00:03 - 10 Mar 2013
3:00:04 - 10 Mar 2013
3:00:05 - 10 Mar 2013
3:00:06 - 10 Mar 2013
3:00:07 - 10 Mar 2013
3:00:08 - 10 Mar 2013
3:00:09 - 10 Mar 2013
3:00:10 - 10 Mar 2013
3:00:11 - 10 Mar 2013
3:00:12 - 10 Mar 2013
3:00:13 - 10 Mar 2013
3:00:14 - 10 Mar 2013
3:00:15 - 10 Mar 2013

Out of DST 3 Nov 2013 2AM local time 6AM UTC time

Time Sync
1:59:45 - 3 Nov 2013
1:59:46 - 3 Nov 2013
1:59:47 - 3 Nov 2013
1:59:48 - 3 Nov 2013
1:59:49 - 3 Nov 2013
1:59:50 - 3 Nov 2013
1:59:51 - 3 Nov 2013
1:59:52 - 3 Nov 2013
1:59:53 - 3 Nov 2013
1:59:54 - 3 Nov 2013
1:59:55 - 3 Nov 2013
1:59:56 - 3 Nov 2013
1:59:57 - 3 Nov 2013
1:59:58 - 3 Nov 2013
1:59:59 - 3 Nov 2013
Time Sync
1:00:00 - 3 Nov 2013
1:00:01 - 3 Nov 2013
1:00:02 - 3 Nov 2013
1:00:03 - 3 Nov 2013
1:00:04 - 3 Nov 2013
1:00:05 - 3 Nov 2013
1:00:06 - 3 Nov 2013
1:00:07 - 3 Nov 2013
1:00:08 - 3 Nov 2013
1:00:09 - 3 Nov 2013
1:00:10 - 3 Nov 2013
1:00:11 - 3 Nov 2013
1:00:12 - 3 Nov 2013
1:00:13 - 3 Nov 2013
1:00:14 - 3 Nov 2013

My updated test sketch with your code.

#include <Ethernet.h>
#include <SPI.h>
#include <EthernetUdp.h>
#include <Time.h>

//IDE Version 1.0.5
//Arduino Uno R3
//Arduino Ethernet Shield R3 (W5100)
//No SD card in card slot
//
//Edit this section for your network setup
//---------------------------------------------
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  //Use the MAC for your shield
IPAddress ip(192,168,0,177);
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
IPAddress mydns(192,168,0,1);
//IPAddress timeServer(192,168,0,100);  //Local time server on your network
char timeServer[] = "0.north-america.pool.ntp.org";  //Time server you wish to use
//Time Zone Selection
//const int TZ_OFFSET = 4*3600;  //AST UTC-4
const int TZ_OFFSET = 5*3600;  //EST UTC-5
//const int TZ_OFFSET = 6*3600;  //CST UTC-6
//const int TZ_OFFSET = 7*3600;  //MST UTC-7
//const int TZ_OFFSET = 8*3600;  //PST UTC-8
//const int TZ_OFFSET = 9*3600;  //AKST UTC-9
//const int TZ_OFFSET = 10*3600;  //HST UTC-10
//---------------------------------------------
//
const int NTP_PACKET_SIZE = 48;  //NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE];  //Buffer to hold incoming and outgoing packets
time_t prevDisplay;
EthernetUDP Udp;
//
unsigned long requestNTP() 
{ 
  //Send NTP packet
  //Set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  //Init values needed to form NTP request
  packetBuffer[0] = 0b11100011;  //LI, Version, Mode
  packetBuffer[1] = 0;  //Stratum, or type of clock
  packetBuffer[2] = 6;  //Polling Interval
  packetBuffer[3] = 0xEC;  //Peer Clock Precision
  //Skip 8 bytes and leave zeros for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  //All NTP fields have been given values, now
  //you can send a packet requesting a timestamp: 		   
  Udp.beginPacket(timeServer, 123);  //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket(); 
  //Wait to see if a reply is available
  delay(250);  //Adjust this delay for time server (effects accuracy, use shortest delay possible)
  if (Udp.parsePacket())
  {  
    //We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE);
    //The timestamp starts at byte 40 of the received packet and is four bytes,
    //or two words, long. First, extract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    //Combine the four bytes (two words) into a long integer
    //This is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    //Now convert NTP time into everyday time:
    //Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long SEVENTY_YEARS = 2208988800UL;     
    //Subtract seventy years:
    unsigned long epoch = secsSince1900 - SEVENTY_YEARS;
    epoch = epoch - TZ_OFFSET;
    epoch = epoch + dstOffset(epoch);  //Adjust for DLT
    Serial.println("Time Sync");
    return epoch;
  }
 return 0; 
}
int dstOffset (unsigned long unixTime)
{
  //Receives unix epoch time and returns seconds of offset for local DST
  //Second Sunday in March and First Sunday in November
  //Code idea from doughboy @ "http://forum.arduino.cc/index.php?PHPSESSID=uoj11hu5j72556mk3gh0ba5ok3&topic=197637.0"
  //Get epoch times @ "http://www.epochconverter.com/" for testing
  //DST update wont be reflected until the next time sync
  time_t t = unixTime;
  tmElements_t te;
  te.Year = year(t)-1970;
  te.Month =3;
  te.Day =1;
  te.Hour = 0;
  te.Minute = 0;
  te.Second = 0;
  time_t dstStart,dstEnd, current;
  dstStart = makeTime(te);
  dstStart = nextSunday(dstStart);  //Once, first Sunday in March
  dstStart = nextSunday(dstStart);  //Do it again, second Sunday in March
  dstStart += 2*SECS_PER_HOUR;  //2AM
  te.Month=11;
  dstEnd = makeTime(te);
  dstEnd = nextSunday(dstEnd);  //First Sunday in November
  dstEnd += SECS_PER_HOUR;  //1AM
  if (t>=dstStart && t<dstEnd) return (3600);  //Add back in one hours worth of seconds - DST in effect
  else return (0);  //NonDST
}
void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip, mydns, gateway, subnet);
  Udp.begin(8888);
  delay(3000);
  setSyncInterval(28800);  //Every 8 hours
  setSyncProvider (requestNTP);
  while (timeStatus() < 2);  //Wait for time sync
  prevDisplay = now();
}
void loop()
{  
  if( now() != prevDisplay) //Update the display only if the time has changed
  {
    prevDisplay = now();
    digitalClockDisplay();  
  }
}
void digitalClockDisplay()
{
  //Digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" - ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(monthShortStr(month()));
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
} 
void printDigits(int digits)
{
  //Utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}