Daylight savings problem


#1

I’m trying to build a simple oled display clock, it all seems to work with one small problem, daylight savings. I know that adding

Time.beginDST();

will add the hour necessary for BST but how do I get it to add the hour automatically?
Is there a function to do this, or is the only way to do it by manual method, ie checking the date and running

Time.endDST();

Forgive my ignorance but I’m not too clued up with coding!


#2

@peekay123 Has some code in his RGB Pong clock application that automatically sets DST.

You can dig through it here to pull out that part and reuse it. ;


#3

Thanks, but I’m having trouble getting my head around it. I’m in the UK so the rules are as follows
https://www.gov.uk/when-do-the-clocks-change
So far I’ve got this code but I’m not sure if it’s correct or not

bool IsDST(int dayOfMonth, int month, int dayOfWeek) {
// check for GMT/BST changeover
if (month < 3 || month > 10) {
    return false;
    }
if (month > 3 && month < 10) {
	return true;
	}
int previousSunday = dayOfMonth - (dayOfWeek - 1);

if (month == 3) {
	return previousSunday >= 25;
}
if (month == 10) {
	return previousSunday < 25;
}
return false;

}

If the truth be known, I don’t really understand how it works! Am I on the right track?


#4

Hi there, I have not dived into your code but this is what I came up with some time ago

bool isDST()
{ // (Central) European Summer Timer calculation (last Sunday in March/October)
  int dayOfMonth = Time.day();
  int month = Time.month();
  int dayOfWeek = Time.weekday() - 1; // make Sunday 0 .. Saturday 6

  if (month >= 4 && month <= 9)
  { // April to September definetly DST
    return true;
  }
  else if (month < 3 || month > 10)
  { // before March or after October is definetly standard time
    return false;
  }

  // March and October need deeper examination
  boolean lastSundayOrAfter = (dayOfMonth - dayOfWeek > 24);
  if (!lastSundayOrAfter)
  { // before switching Sunday
    return (month == 10); // October DST will be true, March not
  }

  if (dayOfWeek)
  { // AFTER the switching Sunday
    return (month == 3); // for March DST is true, for October not
  }

  int secSinceMidnightUTC = Time.now() % 86400;
  boolean dayStartedAs = (month == 10); // DST in October, in March not
  // on switching Sunday we need to consider the time
  if (secSinceMidnightUTC >= 1*3600)
  { // 1:00 UTC (=1:00 GMT/2:00 BST or 2:00 CET/3:00 CEST)
    return !dayStartedAs;
  }

  return dayStartedAs;
}

Scheduled Time sync not executing?
Simple Syslog client on a Photon
#5

@scruffr, Setting DST automatically has been on one of my to-do lists for a while. Now we are just about to implement DST in Europe I thought I would implement something to handle the European area market. The link here is a useful reference for all of the time zones and exact times DST starts and ends.

I thought I would post my attempt at this - have borrowed your algorithm! I am calling this function from a regularly called (20-30 seconds) routine which updates the displayed time.

// returns true or false should DST be enabled or disabled according to the time zone if (0.0(exc. Iceland), +1.0, +2.0) Europe exc. Russia/Belarus
// essentially from the last Sunday in March to the last Sunday in October DST is enabled at UTC 01.00 -> 02.00 and disabled at UTC 02.00 -> 01.00
// automatically begins DST at UTC 01.00 if should be enabled and is not and ends DST at UTC 02.00 if should be disabled and is enabled
bool isDSTactive()
{
    float tz = Time.zone();                                 //offset from UTC
    int dayOfMonth = Time.day();
    int hour = Time.hour();
    int month = Time.month();
    int dayOfWeek = Time.weekday() - 1;                     //make Sunday 0 .. Saturday 6
    bool shouldDSTbeEnabled = false;                        //not in European time zones for which coded DST rules apply by default

    Serial.printlnf("isDSTactive() tz %f dayofMonth %i dayOfWeek %i month %i hour %i", tz, dayOfMonth, dayOfWeek, month, hour);

    if (tz == 0.0 || tz == 1.0 || tz == 2.0)                //European time zones (WET/GMT, CET, EET)
    {
        switch (month)
        {
            case 1:                                         //Jan, Feb, Nov and Dec are definitely standard time
            case 2:
            case 11:
            case 12:
                shouldDSTbeEnabled = false;
                break;
            case 4:                                         //Apr to Sep definitely DST
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                shouldDSTbeEnabled = true;
                break;
            case 10:                                        //March and October need deeper examination
            case 3:
                if (!(dayOfMonth - dayOfWeek > 24))         //but before switching Sunday     
                {
                    shouldDSTbeEnabled = (month == 10);     //for October DST will be true, for March not
                }
                else if (dayOfWeek)                         //after the switching Sunday (dayOfWeek != 0)
                {
                    shouldDSTbeEnabled = (month == 3);      //for March DST is true, for October not
                }
                break;
        }
        if (shouldDSTbeEnabled && !Time.isDST() && hour >= (1 + (int) tz))
        {
            Time.beginDST();                                //March last Sunday and after 01.00
        }
        else if (!shouldDSTbeEnabled && Time.isDST() && hour >= (2 + (int) tz))
        {
            Time.endDST();                                  //October last Sunday and after 02.00 (hour will include DST)
        }
    }
    return shouldDSTbeEnabled;
}

#6

If you want to support muiltiple European timezones, I’d recommend you go for the UTC hour and not the local since the switch always happens last sunday in March/October at 1:00 and 2:00 UTC irrespective of the time zone.

However there are also other timezones that feature 0.0, +1.0 or +2.0 offsets that won’t adhere to these switching rules (e.g. many African zones don’t switch at all).


#7

You are right there are other countries on these time zones and everyone has different rules. This is just for the European countries that use time zone 0.0, +1.0 and +2.0. I haven’t included +3.0 as Russia (Moscow) and Belarus are not on my list of markets. Iceland is UTC+0.0 and does not change.

Most of my devices are connected to a web app which can tell the device to enable or disable DST - this is the preferred approach whereby the DST country web site can be accessed to drive the changes.

I thought I was reading the local time hour (including DST and time zone) and then checking whether it was the same as UTC 01.00 to enable (if currently DST disabled) and UTC 02.00 to disable (if currently DST enabled)? Are you saying that there is a simpler method to check these 2 conditions?


#8

As you already know there are functions to preset the time offset for DST and to enable or disable that offset, and also check whether it is enabled or disabled.
None of these do the hard work to actually decide which is the right one to use, but they are handy to just switch on/off DST based on your own calculations.

What I was meaning with UTC hour ((Time.now() % 86400) / 3600) over local Time.hour() was that this way you’d just check for hour >= 1 and hour >= 2 instead of hour >= (1 + (int)tz) and hour >= (2 + (int)tz) respectively.


#9

Thanks - that is a cleaner way of making those tests.


#10

well, this code I have automatically takes care of DST:


// This #include statement was automatically added by the Particle IDE.
#include <ntp-time.h>

// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_DHT.h>

#include<LiquidCrystal_I2C_Spark.h>

// Sensor type
#define DHTTYPE DHT22    	// DHT 22 (AM2302)

// DHT22 sensor pinout:
// Pin 1 (on the left): +3.3V
// Pin 2: output
// Pin 4 (on the right): GROUND
#define DHT_5V_PIN D6
#define DHT_SENSOR_PIN D2
#define DHT_GROUND_PIN D4

// min/max values (sanity checks)
#define MIN_TEMPERATURE -30
#define MAX_TEMPERATURE 120

#define MIN_HUMIDITY 0
#define MAX_HUMIDITY 100
// which digital pin for the Photon/Spark Core/Electron LED
#define LEDPIN D7

DHT dht(DHT_SENSOR_PIN, DHTTYPE);
int failed = 0;
// last time since we sent sensor readings
int lastUpdate = 0;
char buff[50];


// sensor sending interval (seconds)
#define SEND_INTERVAL 10

LiquidCrystal_I2C *lcd;


NtpTime* ntpTime;

String hhmmss(unsigned long int now)  //format value as "hh:mm:ss"
{
   String hour = String(Time.hourFormat12(now));
   String minute = String::format("%02i",Time.minute(now));
   String second = String::format("%02i",Time.second(now));
   return hour + ":" + minute + ":" + second;
};




void setup() {

Serial.begin(9600);
    // Give power to the sensor
    pinMode(DHT_5V_PIN, OUTPUT);
    pinMode(DHT_GROUND_PIN, OUTPUT);
    digitalWrite(DHT_5V_PIN, HIGH);
    digitalWrite(DHT_GROUND_PIN, LOW);
    // Initialize sensor
    dht.begin();
    ntpTime = new NtpTime(15);  // Do an ntp update every 15 minutes;
    ntpTime->start();
    
lcd = new LiquidCrystal_I2C(0x3F, 16, 2);

lcd->init();

lcd->backlight();

lcd->clear();

Time.zone(0); }

void loop() {
    // Read Sensor
    double temperature = dht.getTempCelcius();
    double temperatureF = (temperature * 1.8) + 32;
    // (f - 32)* 5/9
    double humidity = dht.getHumidity();
    
    
    if (temperature == NAN
        || humidity == NAN
        || temperature > MAX_TEMPERATURE
        || temperature < MIN_TEMPERATURE
        || humidity > MAX_HUMIDITY
        || humidity < MIN_HUMIDITY) {
        // if any sensor failed, bail on updates
        failed = 1;
    } else {
    
    if (Time.now(), "%H:%M:%S" == "00:00:00") {

    lcd->clear(); }
    
    
    lcd->setCursor(0 ,0 );
    static unsigned long waitMillis;
    struct epochMillis now;  //holds the unix epoch time to millisecond resolution
    if(millis() > waitMillis) {
        ntpTime->nowMillis(&now);  //get the current NTP time
        Particle.publish("NTP clock is: ", hhmmss(now.seconds) + "." + String::format("%03i",now.millis));
        lcd->print(Time.format(TIME_FORMAT_ISO8601_FULL));
        waitMillis = millis() + (15*1000);  // wait 15 seconds
    }

    lcd->setCursor(0, 14);
    lcd->print(temperature);
    lcd->print((char)223);
    lcd->print("C / ");
    lcd->print(humidity);
    lcd->print("%");
    }
}

you can ignore the DHT temperature / humidity part…


#11

There is a function Time.format() that will do the formatting for your by means of strftime() format place holdes.

What you are doing can be written like this

  Serial.println(Time.format("%I:%M:%S"));

#12

@ScruffR,

Well, it is almost that time again. We go off Daylight savings on November 2nd. I have followed the links in this and other DST posts and it seems that the goal of automatically recognizing daylight savings time has not yet been realized.

That said, for a specific use case (cellular stationary devices that have their timezone entered once at installation) is it fair to say there is a solution using the API calls you reference above (setDSToffset, beginDST, endDST and isDST)?

Is there a good example sketch for this or should I create one and share for comment? If so, would it make sense to start a new thread?

Thanks, Chip


#13

I hoped these functions would become useful some time but IMO these functions are not really.
You as developer have to set the offset, and then call beginDST() and endDST() when it’s time to switch. They don’t help you in any shape or form to actually distinguish whether it is time to switch or not nor does the device store the last state across resets.
That’s still entirely up to the dev hence it’s no real difference to just setting Time.zone(x + dstOffset).
Hence I just implemented my own bool isDST() function that calculates my local switching date and regularly (just after UTC 1:00am every day) call Time.zone(+1.0 + (isDST() ? 1.0 : 0.0)).


#14

@ScruffR,

I see, so these API names are a bit misleading. I would think that a mature platform such as Particle would have settled this issue long ago.

I am going to write a sample sketch for this so, the flow will be:

  1. Store the timezone value in FRAM / EEPROM - -5 for me looks like +1 for you

  2. Modify your isDST function for my country - 2nd Sunday in March, +1 hour. - 1st Sunday in November as DST, +0 hour. Both the calendar values and the DST offset are different by country. So, I might use your code and create isDSTusa()

  3. Once a day, run the code: Time.zone(+1.0 + (isDSTusa() ? 1.0 : 0.0)) .

One question: If all you are doing is setting Time.zone - you don’t have to be connected correct?

Thanks,


#15

Correct - providing the time was synced at some point before to actually being able to know its 1am UTC :wink:

I would have hoped so too :pensive:


#16

@ScruffR,

I took a whack at modifying your isDST function for use in the United States. I have created a simple test sketch that seems to work and posted it to Github:

I made use of the standard Particle functions isDST, beginDST, endDST so I have a slightly different syntax:

if (Time.isValid()) 
  { // We only want to run this if time is valid - otherwise random result
    isDSTusa() ? Time.beginDST() : Time.endDST();       
  }

Comments / suggestions / criticisms are welcome. I hope that other folks will find this useful as that change over is coming up.

Thanks,

Chip


#17

I don’t have any products in use in the US so not had to tackle this. Have done for Europe (essentially all countries in timezones 0.0, +1.0, +2.0 except for Iceland all change last Sunday of October and last Sunday of March. We have DST and TZ as a remotely set variables as well as an auto mode for DST that works for Europe. In theory you can get this DST and time zone information from knowing your location and through a google api / website.

I think I based my checking on the same algorithm as @Scruffr but use a switch case conditional logic instead of if’s. I also check more frequently for changes since the check is quick. The bool function below is called from a millis() time check and only if Time.isValid()! The return value is used to inform the web app that the time has been changed.

// returns true or false should DST changed event be sent V155
// according to the time zone if (0.0(exc. Iceland), +1.0, +2.0) Europe exc. Russia/Belarus
// essentially from the last Sunday in March to the last Sunday in October DST is enabled at UTC 01.00 -> 02.00 and disabled at UTC 02.00 -> 01.00
// automatically begins DST at UTC 01.00 or ends DST at UTC 02.00
bool isDSTactive()
{
    float tz = Time.zone();                                 //offset from UTC
    int dayOfMonth = Time.day();
    int hour = ((Time.now() % 86400) / 3600);
    int month = Time.month();
    int dayOfWeek = Time.weekday() - 1;                     //make Sunday 0 .. Saturday 6
    bool shouldDSTbeEnabled = false;                        //not in European time zones for which coded DST rules apply by default
    if (tz == 0.0 || tz == 1.0 || tz == 2.0)                //European time zones (WET/GMT, CET, EET)
    {
        switch (month)
        {
            case 1:                                         //Jan, Feb, Nov and Dec are definitely standard time
            case 2:
            case 11:
            case 12:
                shouldDSTbeEnabled = false;
                break;
            case 4:                                         //Apr to Sep definitely DST
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                shouldDSTbeEnabled = true;
                break;
            case 10:                                        //March and October need deeper examination
            case 3:
                if (!(dayOfMonth - dayOfWeek > 24))         //but before switching Sunday     
                {
                    shouldDSTbeEnabled = (month == 10);     //for October DST will be true, for March not
                }
                else if (dayOfWeek > 0)                     //after the switching Sunday (dayOfWeek !=)
                {
                    shouldDSTbeEnabled = (month == 3);      //for March DST is true, for October not
                }
                else                                        //switching Sunday (dayOfWeek = 0)
                {
                    if (hour >= 1 && month == 3)  shouldDSTbeEnabled = true; //time is 01:00 or later on switching sunday in march then DST should be ON
                    if (hour >= 2 && month == 10) shouldDSTbeEnabled = false; //time is 02:00 or later on switching sunday in october then DST should be OFF
                }
                break;
        }
        //then if a change condition check if switch required from current setting
        if (shouldDSTbeEnabled && !Time.isDST())            //if it should be on but is off
        {
            Time.beginDST();
            return true;
        }
        else if (!shouldDSTbeEnabled && Time.isDST())       //if it should be off but is on
        {
            Time.endDST();
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }
}

#18

There is a particle library I use that abstracts the pain …

I also have a function exposed to update the timezone manually if for example the device is behind a corporate firewall and the exit point is in another time zone.


#19

I have just seen this - wow - this is a clever piece of work. Is it reliable?


#20

Working nicely so far … :slight_smile: