DS3231 RTC & Daylight Savings Time

This is not a new subject, but when I went to post a question to the existing thread, it said it hadn't been used for 4 months and did I want to start a new thread. so I did.

I read the discussion about trying to get the DS3231 RTC to be aware of the time change, and it looks to me like the end result was, re-program the sketch for the new time. Twice a year. This seems clunky to me.

Isn't there a way to use an interrupt button to have the clock skip ahead or back an hour ? This would be much easier to use.

My own intended use is for a security light that comes on at 2:30 a.m. and goes off at 4:30 a.m. - Not a motion detector. And I think I can get it working, but the DST is a pain.

I solved same a time ago. Personally, I'm using RTC to keeping correct (winter time) without DST. Together with RTC, I'm using the time library and there is DST based on check at each second. Time keeping var is 32bit and it is matter to compare this value with the DST var and then to set or not the time. I know that it sounds very complicated and you can afraid of the MCU would be too busy but I'm runnig this on ATmega1284P without any problem.

Did you try this:

You might consider this code. This method allows you to set the time in standard time without any regard for DST. The following code will determine state of DST and add one hour if required.
The offset method came from a post from odometer.

// ********************* Calculate offset for Sunday *********************
int y = year; // DS3231 uses two digit year (required here)
int x = (y + y/4 + 2) % 7; // remainder will identify which day of month
// is Sunday by subtracting x from the one
// or two week window. First two weeks for March
// and first week for November
// *********** Test DST: BEGINS on 2nd Sunday of March @ 2:00 AM *********
if(month == 3 && dayOfMonth == (14 - x) && hour >= 2)
{
DST = 1; // Daylight Savings Time is TRUE (add one hour)
}
if((month == 3 && dayOfMonth > (14 - x)) || month > 3)
{
DST = 1;
}
// ************* Test DST: ENDS on 1st Sunday of Nov @ 2:00 AM ************
if(month == 11 && dayOfMonth == (7 - x) && hour >= 2)
{
DST = 0; // daylight savings time is FALSE (Standard time)
}
if((month == 11 && dayOfMonth > (7 - x)) || month > 11 || month < 3)
{
DST = 0;
}
if(DST == 1) // Test DST and add one hour if = 1 (TRUE)
{
hour = hour + 1;
}

1 Like

The problem is, I want it to be aware of DST. Many burglaries occur around 3 a.m. and I want my outside light to come on at 3 a.m., whether it is DST or not.

Then you need DST. Burglar uses same time as other people, isn't it?

There are several solutions to this:

  1. move to Arizona, where there is no DST.
  2. move to New Your, where the burglary rate is the lowest.
  3. use an "atomic clock" that adjusts automatically.
  4. use code like this with time from a GPS or RTC:

define some variables

byte daysPerMonth [] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const int DSTBegin [] = { 310, 309, 308, 313, 312, 311, 310, 308, 314, 313, 312, 310, 309};
const int DSTEnd [] = {1103, 1102, 1101, 1106, 1105, 1104, 1103, 1101, 1107, 1106, 1105, 1103, 1102};

int theHour;
int theDay;
int theMonth;
int theYear;
int timeZone = -7;

boolean isDST = false;

Include this code snippet in your sketch to adjust theHour, theDay, theMonth and theYear for time zones, leap years and DST:

  // The time comes from an attached GPS.
  // GPS.hour, GPS.day, and GPS.month

  ...

  // ---- Convert UTC to Local Time ------------------------------------
  theHour = GPS.hour;
  theDay = GPS.day;
  theMonth = GPS.month;
  theYear = GPS.year;

  // -------------------- check for leap year --------------------------
  if ((theYear % 4) == 0) {
    daysPerMonth[1] = 29; // leap year
  }

  // --------------------change the hour according to time zone --------
  theHour += timeZone;

  // ------------------- apply daylight savings time [rolleyes] --------
  if (isDST) {
    if (theMonth * 100 + theDay >= DSTBegin[theYear - 13] && theMonth * 100 + theDay < DSTEnd[theYear - 13]) {
      theHour += 1;
    }
  }

  // ----------------------- correct day, mo & yr for roll backward ----
  if (theHour < 0) {
    theHour += 24;
    theDay -= 1;
    if (theDay < 1) {
      if (theMonth == 1) { // Jan 1
        theMonth = 12;
        theYear -= 1;
      } else {
        theMonth -= 1;
      }
      theDay = daysPerMonth[theMonth - 1];
    }
  }

  // ----------------------- roll forward if east of prime meridian ----
  if (theHour >= 24) {
    theHour -= 24;
    theDay += 1;
    if (theDay > daysPerMonth[theMonth - 1]) {
      theDay = 1;
      theMonth += 1;
      if (theMonth > 12) { // Jan 1
        theYear += 1;
        theMonth = 1;
      }
    }
  }
  ...

I have been using this code for 3 years now, and hit hasn't failed yet. It should be good through the year 2025.

Many burglaries occur around 3 a.m. and I want my outside light to come on at 3 a.m., whether it is DST or not.

I find the 3 a.m. figure hard to believe, but you could get a dog.

Robertew -

Yes I followed your code and see how you distinguish between dst=1 or dst=0.
Using a ds3231, how would you tell it to change its time by 1 hour ?

The same way as you normally set the time in your rtc?

The only way I know of is to open the sketch and put in new numbers for the hour. But what I was asking is, how could that be done by itself in the program ?

pratto:
Robertew -

Yes I followed your code and see how you distinguish between dst=1 or dst=0.
Using a ds3231, how would you tell it to change its time by 1 hour ?

rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
forces the time and date to 2014 January 21st, 3h 00min 00 sec

So if you can determine when it's time to change Standard/DS (i.e. DST_day_flag == true, Standard_Time_day_flag == true)
you would read back the time and then execute rtc.adjust(), incr/decr the hour as appropriate.

1 Like

Much obliged. I'll give it a try.

In a project with a GPS receiver I used a table, working until 2050:

void OraLegale() // Compara anno, mese, giorno e ora correnti con quelli di inizio e fine ora legale di ogni anno.
                 // I valori finiscono tutti per "02" perché l'ora legale inizia e finisce alle ore 2 solari.
{
year=GPS.year; // la variabile year, essendo long, può contenere il risultato di GPS.year*1000000
month=GPS.month;
day=GPS.day;
hour=GPS.hour;
Adesso=1000000*year +10000*month +100*day +hour;
     if(Adesso>=15032902 && Adesso<15102502) Leg=1;
else if(Adesso>=16032702 && Adesso<16103002) Leg=1;
else if(Adesso>=17032602 && Adesso<17102902) Leg=1;
else if(Adesso>=18032502 && Adesso<18102802) Leg=1;
else if(Adesso>=19033102 && Adesso<19102702) Leg=1;
else if(Adesso>=20032902 && Adesso<20102502) Leg=1;
else if(Adesso>=21032802 && Adesso<21103102) Leg=1;
else if(Adesso>=22032702 && Adesso<22103002) Leg=1;
else if(Adesso>=23032602 && Adesso<23102902) Leg=1;
else if(Adesso>=24033102 && Adesso<24102702) Leg=1;
else if(Adesso>=25033002 && Adesso<25102602) Leg=1;
else if(Adesso>=26032902 && Adesso<26102502) Leg=1;
else if(Adesso>=27032802 && Adesso<27103102) Leg=1;
else if(Adesso>=28032602 && Adesso<28102902) Leg=1;
else if(Adesso>=29032502 && Adesso<29102802) Leg=1;
else if(Adesso>=30033102 && Adesso<30102702) Leg=1;
else if(Adesso>=31033002 && Adesso<31102602) Leg=1;
else if(Adesso>=32032802 && Adesso<32103102) Leg=1;
else if(Adesso>=33032702 && Adesso<33103002) Leg=1;
else if(Adesso>=34032602 && Adesso<34102902) Leg=1;
else if(Adesso>=35032502 && Adesso<35102802) Leg=1;
else if(Adesso>=36033002 && Adesso<36102602) Leg=1;
else if(Adesso>=37032902 && Adesso<37102502) Leg=1;
else if(Adesso>=38032802 && Adesso<38103102) Leg=1;
else if(Adesso>=39032702 && Adesso<39103002) Leg=1;
else if(Adesso>=40032502 && Adesso<40102802) Leg=1;
else if(Adesso>=41033102 && Adesso<41102702) Leg=1;
else if(Adesso>=42033002 && Adesso<42102602) Leg=1;
else if(Adesso>=43032902 && Adesso<43102502) Leg=1;
else if(Adesso>=44032702 && Adesso<44103002) Leg=1;
else if(Adesso>=45032602 && Adesso<45102902) Leg=1;
else if(Adesso>=46032502 && Adesso<46102802) Leg=1;
else if(Adesso>=47033102 && Adesso<47102702) Leg=1;
else if(Adesso>=48032902 && Adesso<48102502) Leg=1;
else if(Adesso>=49032802 && Adesso<49103102) Leg=1;
else if(Adesso>=50032702 && Adesso<50103002) Leg=1;
else Leg=0;

GPS_hf=GPS.hour+1+Leg; // 1: Fuso orario dell'Europa Centrale
      
if(GPS_hf>23)
  {
  GPS_hf-=24;
  GPS.day+=1; 
  
  // Controlliamo se ci sono riporti al mese e all'anno: 
  if(GPS.day==32) // Può esserlo solo se il mese in corso è di 31 giorni
    {
    GPS.day=1;
    GPS.month+=1;
    if(GPS.month==13) // Significa che è Capodanno
      {
      GPS.month=1;
      GPS.year+=1;
      }
    }          
  if((GPS.day==31) && (GPS.month==4||GPS.month==6||GPS.month==9||GPS.month==11)) // E' un mese di 30 giorni
    {
    GPS.day=1;
    GPS.month+=1;
    }        
  if((GPS.day==30) && (GPS.month==2) && (GPS.year%4==0)) // E' un anno bisestile
    {
    GPS.day=1;
    GPS.month+=1;
    }    
  if((GPS.day==29) && (GPS.month==2) && (GPS.year%4!=0)) // E' un anno normale
    {
    GPS.day=1;
    GPS.month+=1;
    }
  }    
} // END OraLegale

I think I found a simple solution. I use the RTClib with the now. method or function or whatever it is, with if else statements and use inequalities. So far it looks promising.

My cat got fed an hour early Sunday, googled and found this nice thread.

I grabbed ChrisTenone's code from above. I found a little problem here

if ((theYear % 4) == 0) {
    daysPerMonth[1] = 29; // leap year
}

where he adjusts the table on the fly, but never will it be fixed. I sugest

  if ((theYear % 4) == 0) {
    daysPerMonth[1] = 29; // leap year
}
else daysPerMonth[1] = 28;

The author agrees. There's prolly as many ways to do this as there are programmers… but my cat feeder is back on the air good until 2025, we'll see how the cat is doing then - of course we'll have a cat, I could express hope here that it is still oTTo.

a7

pratto:
I think I found a simple solution. I use the RTClib with the now. method or function or whatever it is, with if else statements and use inequalities. So far it looks promising.

Hello, found the solution?
can you share the code?

how did you solved the problem of turning back the time? (3 becomes 2... but an hour later it's 3 again so it'll change back to 2? ...

thx

There is a flag e.g. isDTS.

seabert:
Hello, found the solution?
can you share the code?

how did you solved the problem of turning back the time? (3 becomes 2... but an hour later it's 3 again so it'll change back to 2? ...

thx

I use an API rather than a computational way of doing it. This helps offset RTC drift, as well as potential issues with DST being different in different parts of the world. NTP clients, afaik, only report time in UTC which isn't very helpful for knowing when DST is. This API reports all that.

If you have an ESP or a wifi connected device this will work:

String TIME_API_URL = "http://worldtimeapi.org/api/timezone/America/New_York"; // change for your location @ http://worldtimeapi.org

bool syncRtc() {
 WiFiClient client;
 HTTPClient http;
 if (!http.begin(client, TIME_API_URL)) {
   addPrintError("[TIME SYNC] Unable to connect to json time server.");
   return false;
 }
 DBG_OUTPUT.println(F("[TIME SYNC] Attempting to connect to json time server..."));
 int httpCode = http.GET();
 // httpCode will be negative on error
 if (httpCode < 0) {
   // file found at server
   if (httpCode != HTTP_CODE_OK || httpCode != HTTP_CODE_MOVED_PERMANENTLY) {
     addPrintError("[TIME SYNC] json time server HTTP code not 200.");
     return false;
   }
   // https://arduinojson.org/v5/assistant/
   const int capacity = JSON_OBJECT_SIZE(15) + 400;
   String json = http.getString();
   yield(); // ESP specific
   DBG_OUTPUT.print(F("[TIME SYNC] Json string recieved: "));
   DBG_OUTPUT.println(json);
   StaticJsonDocument<capacity> doc;
   DeserializationError err = deserializeJson(doc, json);
   if (err) {
     DBG_OUTPUT.print(F("[TIME SYNC] deserializeJson() failed with code: "));
     DBG_OUTPUT.println(err.c_str());
   } else {
     const char* datetime = doc["datetime"];
     bool dst = doc["dst"];
     int dstOffset = doc["dst_offset"];
     int rawOffset = doc["raw_offset"];
     unsigned long unixTime = doc["unixtime"];
     unsigned long t;
     // compute utc/dst compensated time
     t = dst ? unixTime + (rawOffset + dstOffset) : unixTime + rawOffset;
     rtc.adjust(DateTime(t));
     DBG_OUTPUT.print(F("[TIME SYNC] Retrieved time as: "));
     DBG_OUTPUT.println(datetime);
     getRtcTimeAsString();
   }
 } else {
   addPrintError("[TIME SYNC] Connection to json time server failed: " + String(http.errorToString(httpCode).c_str()));
   return false;
 }
 http.end();
 return true;
}

String getRtcTimeAsString() {
 DateTime now = rtc.now();
 char buf[] = "MM/DD/YY hh:mm:ss";
 String t = now.toString(buf);
 DBG_OUTPUT.print(F("[RTC] Time now: "));
 DBG_OUTPUT.println(t);
 return t;
}

Note: there are some references to addPrintError which can just be replaced with a serial print.

Then you just need to sync the RTC every day at midnight by doing something like (in loop):

DateTime now = rtc.now();
if (now.hour() == 0) {
   syncRtc();
}

You will need the arduino json library as well as rtclib from adafruit.

So your includes might look something like:

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>
#include <ArduinoOTA.h>
#include <OneWire.h>
#include <Wire.h>
#include <RTClib.h>

It is simple.

// European Daylight Saving Time / Summertime
bool DST() {
  byte yy = year.now() % 100;        // adjust your RTC functions call
  byte mm = month.now();
  byte dd = day.now();
  byte x1 = 31 - (yy + yy / 4 - 2) % 7; //last Sunday March
  byte x2 = 31 - (yy + yy / 4 + 2) % 7; // last Sunday October

  return (mm > 3 && mm < 10) || (mm == 3 && dd >= x1) || (mm == 10 && dd < x2);
}
4 Likes