My attempt at detecting EST/EDT change automatically

Like many others, I have lots of projects that depend on time. Here in VA, the time changed from EST to EDT on Sunday the 11th at 2am. Everything in my house changed (except dumb items like the microwave) as normal, except for my Moteino’s, Arduino’s and Particle projects. I seen lots of others take various approaches to making this change transparent, so I thought I would try myself. What follows seems to work for me…I’ve tried to test it extensively.

This code works for me, and I know it’s not terribly optimized, but hey, for something that only runs twice a year, I’m ok with readability over minimum bytes or machine cycles. I found data for VA through 2029 and built a simple table. If you run this example, it will print whether or not you’re in DST (from a VA perspective), and then simply become a clock, counting by one second down the screen just to prove that it detected the right DST setting.

To make this work in my projects, I simply add this check to the routine in my existing code that does something on the hour (i.e., 9:00, 11:00, 13:00, 15:00, etc.). That way, I only check once an hour and I tried to write the code with as many shortcuts as possible to make the fewest comparisons needed.


    typedef struct DSTStruct
    {
        int year;
        int startDay;                  // DST start day in Mar
        int endDay;                    // DST end day in Nov
    } DST;

    DST dst[] = {                     // Table data for Richmond, VA
        12,0,0,                       // Year field holds # of elements
        2018,11,4,
        2019,10,3,
        2020,8,1,
        2021,14,7,
        2022,13,6,
        2023,12,5,
        2024,10,3,
        2025,9,2,
        2026,8,1,
        2027,14,7,
        2028,12,5,
        2029,11,4
    };

    void setup(void) 
    {
        int rc;
        
        Serial.begin(115200);
        Time.zone(-5);                           // EST/EDT
        rc = isDSTActive();
        if (rc == 0)
        {
            Time.endDST();    
            Serial.println("DST not active");
        }
        else if (rc == 1)
        {
            Time.setDSTOffset(1.0);            // Offset is 1 hour
            Time.beginDST();        
            Serial.println("DST active");
        }
        else Serial.println("Error determining DST");
    }

    void loop(void) 
    {
        Serial.println(Time.format("%r"));
        delay(1000);
    }

    int isDSTActive(void)
    {
        int sDay,                       // Start day in Mar    
            eDay;                       // End day in Nov

        if (Time.month() >= 4 && Time.month() <= 10)      // Apr - Oct = EDT
            return 1;
        else if (Time.month() < 3 || Time.month() == 12)   // Jan, Feb, Dec = EST
            return 0;

        if (!findYear(Time.year(), &sDay, &eDay))     // Did we find this year's data?
        {
            Serial.println("Error - couldn't find data for this year");
            return 2;
        }
        
        switch (Time.month())
        {
            case 3:                           // March (spring forward)
                if (Time.day() > sDay)
                    return 1;
                else if (Time.day() == sDay && Time.hour() >= 2)    // All changes occur at 2am
                    return 1;
                else
                    return 0;
            case 11:                             // November (fall back)
                if (Time.day() < eDay)
                    return 1;
                if (Time.day() == eDay && Time.hour() < 2)     // All changes occur at 2am
                    return 1;
                else 
                    return 0;
        }
    }

    bool findYear(int year, int *sDay, int *eDay)
    {
        int numItems = dst[0].year;             // How many structure items?
        
        for (int x = 1; x <= numItems; x++)
        {
            if (dst[x].year == year)
            {
                *sDay = dst[x].startDay;
                *eDay = dst[x].endDay;
                return 1;                   // Found current year's data
            }
        }
        return 0;
    }

I hope I commented enough so that everything is understandable.  Comments welcome.
3 Likes

There are many ways to solve this and yours similar to mine, which is available here:

2 Likes

Thanks, I’ll check out your example.

@bko, nice code…very clean.

Slightly optimized code…version 2 below:

//
//  Last update:  03/11/18
//

typedef struct DSTStruct
{
    int year;
    int startDay;                                               // DST start day in Mar
    int endDay;                                                 // DST end day in Nov
} DST;

DST dst[] = {                                                   // Table data for Richmond, VA
    12,0,0,                                                     // dst[0].year field holds # of elements
    2018,11,4,
    2019,10,3,
    2020,8,1,
    2021,14,7,
    2022,13,6,
    2023,12,5,
    2024,10,3,
    2025,9,2,
    2026,8,1,
    2027,14,7,
    2028,12,5,
    2029,11,4
};

void setup(void) 
{
    int rc;
    
    Serial.begin(115200);
    Time.zone(-5);                                              // EST/EDT
    rc = isDSTActive();
    if (rc == 0)
    {
        Time.endDST();    
        Serial.println("DST not active");
    }
    else if (rc == 1)
    {
        Time.setDSTOffset(1.0);                                 // Offset is 1 hour
        Time.beginDST();        
        Serial.println("DST active");
    }
    else Serial.println("Error determining DST");
}

void loop(void) 
{
    Serial.println(Time.format("%r"));
    delay(1000);
}

int isDSTActive(void)
{
    int sDay,                                                   // Start day in Mar    
        eDay;                                                   // End day in Nov

    if (Time.month() > 3 && Time.month() < 11)                  // Apr - Oct = EDT
        return 1;
    else if (Time.month() < 3 || Time.month() == 12)            // Jan, Feb, Dec = EST
        return 0;

    if (!findYear(Time.year(), &sDay, &eDay))                   // Did we find this year's data?
    {
        Serial.println("Error - couldn't find data for this year");
        return 2;
    }

    switch (Time.month())
    {
        case 3:                                                 // March (spring forward)
            if ((Time.day() > sDay) || (Time.day() == sDay && Time.hour() > 1))
                return 1;
            else 
                return 0;
        case 11:                                                // November (fall back)
            if ((Time.day() < eDay) || (Time.day() == eDay && Time.hour() < 2))
                return 1;
            else 
                return 0;
    }
}

bool findYear(int year, int *sDay, int *eDay)
{
    int numItems = dst[0].year;                                 // How many structure items?
    
    for (int x = 1; x <= numItems; x++)
    {
        if (dst[x].year == year)
        {
            *sDay = dst[x].startDay;
            *eDay = dst[x].endDay;
            return true;                                        // Found current year's data
        }
    }
    return false;
}

Your code seems a bit more complicated than necessary. I’ve been using a slightly modified version of code I saw here on the forum. I keep it in a Helpers file where I keep often used functions. The sevens and eights in the code are specific for my west coast location.

void setZone() {
	int month = Time.month();
	int day = Time.day();
	int weekday = Time.weekday();
	int previousSunday = day - weekday + 1;

	if (month < 3 || month > 11) {
		Time.zone(-8);
	}else if (month > 3 && month < 11) {
		Time.zone(-7);
	}else if (month == 3) {
		int offset = (previousSunday >= 8)? -7 : -8;
		Time.zone(offset);
	}else{
		int offset = (previousSunday <= 0)? -7 : -8;
		Time.zone(offset);
	}
}

I call this function once a day (at 3 AM) when I also do a time sync with the particle cloud.

4 Likes

Thanks to @bko and @ric for their suggestions and code samples…here is my hopefully last update to this code. I wanted a standalone chunk of code that didn’t depend on or set the timezone but rather just tell me whether DST was in effect. I handle the timezone setting and the DST offset handling elsewhere.

#pragma once
//
//  Last update:  03/13/18
//
/****************************************************/
/*  isDSTActive                                     */
/*  Output:                                         */
/*      false = DST not in effect                   */
/*      true = DST in effect                        */
/****************************************************/
bool isDSTActive(void)
{
    int month = Time.month(),
        day = Time.day(),
        weekday = Time.weekday(),
        previousSunday = day - weekday + 1;

    if (month > 3 && month < 11)                                    // Apr - Oct = EDT
        return true;
    else if (month < 3 || month == 12)                              // Jan, Feb, Dec = EST
        return false;

    switch (month)
    {
        case 3:                                                     // March (spring forward)
            if (previousSunday >= 8)
                return true;
            else
                return false;
        case 11:                                                    // November (fall back)
            if (previousSunday <= 0)
                return true;
            else
                return false;
    }
}

I should also say that I run this code once a day at 2am since that is when such changes occur in my timezone.
2 Likes

Did this code perform well on Sunday @syrinxtech? I will need to implement something tonight! :wink:
Thanks

I think it is easier to use a simple function:

called like this at the start of loop():

void loop(void) {
  Time.zone(IsDST(Time.day(), Time.month(), Time.weekday()) ? summerOffset : winterOffset);
  // etc...

for europe:

bool IsDst(int day, int month, int dayOfWeek)
{
  if (month < 3 || month > 10)
  {
    return false;
  }
  if (month > 3 && month < 10)
  {
    return true;
  }
  int previousSunday = day - dayOfWeek;
  if (month == 3)
  {
    return previousSunday >= 25;
  }
  if (month == 10) return previousSunday < 25;
  {
    return false;
  }
}
2 Likes

Working like a charm @daneboomer.

@BulldogLowell, I wouldn’t want this in my loop() code because it will be called many more times than needed, at least for my location. In EST, DST only changes two times a year at two AM. Calling this function repeatedly seems like a huge waste of processor.

I only call my function once a day at 2am and it seems to work very well.

@syrinxtech, the overhead of testing for a 2am condition is not much different than calling the function more often. However, in terms of program logic, your approach is clean.

1 Like

the function's overhead is trivial, but I do get your point about running once a day.

the function I provided is just a way easier to understand than your examples, in my opinion and was directed at @daneboomer

Of course there are an uncountable number of ways to do this. I'm waiting for a Particle implementation of some DST adjustment, which looks like it may have been once intended but never implemented.

3 Likes

One other reason for running the isDST() check not only at the exepcted switching time is that your device might not exactly be running there and then, so you should at least run it also after a reset or any wake from sleep.

1 Like

@BulldogLowell, agreed…this is definitely an area that I feel needs to be settled once and for all by Particle. Every major OS can handle this issue. And yes, I realize that RTOS is not a full-fledged OS, but with all of the good examples put forth by the members of this group, something should be able to be put together that works for all time zones and daylight savings time participants, everywhere in the world.

@ScruffR, great point.

In my code I always do this check on boot and everyday at 2am.

1 Like

...like abandoning Daylight Savings Time completely, as it should be. :wink:

2 Likes

agreed…but I don’t Particle could pull that off alone.

It turns out, (I think! :smiley: :smiley: ) that it's not just the sevens and eights that are specific it's the '11' too? Because the changeover day for end of DST is different in Europe too! Egg on my face. It ended last Sunday. Not sure what I'd need to change to make it work for Europe. Sorry. Not a coder!

@daneboomer, you have a similar question in another thread where you already found my implementation which works for Europe.

Double-posting is rarely helpful.

Thanks @ScruffR. I think this is one of those exceptional cases where it might be helpful. But I await schooling on why it isn’t :wink:

This post here alerts people who might be reading THIS thread that the @Ric code isn’t suitable as is for the European changeover dates.