Scope issue with library

Hi All,

I’ve run into a problem trying to modify this excellent library so that rules can be dynamically updated. While I expected the values in TimeChangeRule to remain global within the library, they are lost at some point and I’m not sure where or why.

Check the last method TimeZoneOne::setRule(). I used an extra set of variables _dstSave and _stdSave, to preserve the values of _dst and _std.

Then in TimeZoneOne::toLocal(), _dst and _std values are restored from _dstSave and _stdSave.

This is an ugly patch and I’m sure there is a better way. I tried making TimeZoneRule variables static, but that just created compiler errors.

Help is greatly appreciated. Thanks in advance.

TimeZoneOne.cpp

#copyright notice was removed here because the post exceeds the maximum message length, with no intent to claim that this was written by anyone but  * Jack Christensen Mar 2012                                            

#include "TimeZoneOne.h"

/*----------------------------------------------------------------------*
 * Create a Timezone object from the given time change rules.           *
 *----------------------------------------------------------------------*/
TimeZoneOne::TimeZoneOne()
{
    //instantiate object
}
/*----------------------------------------------------------------------*
 
/*----------------------------------------------------------------------*
 * Convert the given UTC time to local time, standard or                *
 * daylight time, as appropriate.                                       *
 *----------------------------------------------------------------------*/

time_t TimeZoneOne::toLocal(time_t utc)
{
	_dst = _dstSave;
	_std = _stdSave;
    //recalculate the time change points if needed
 if (year(utc) != year(_dstUTC)) calcTimeChanges(year(utc));
    if (utcIsDST(utc))
        return utc + _dst.offset * SECS_PER_MIN;
    else
        return utc + _std.offset * SECS_PER_MIN;
}


time_t TimeZoneOne::toUTC(time_t local)
{
    //recalculate the time change points if needed
    if (year(local) != year(_dstLoc)) calcTimeChanges(year(local));

    if (locIsDST(local))
        return local - _dst.offset * SECS_PER_MIN;
    else
        return local - _std.offset * SECS_PER_MIN;
}

/*----------------------------------------------------------------------*
 * Determine whether the given UTC time_t is within the DST interval    *
 * or the Standard time interval.                                       *
 *----------------------------------------------------------------------*/
boolean TimeZoneOne::utcIsDST(time_t utc)
{
    //recalculate the time change points if needed
    if (year(utc) != year(_dstUTC)) calcTimeChanges(year(utc));
    if (_stdUTC > _dstUTC)    //northern hemisphere
        return (utc >= _dstUTC && utc < _stdUTC);
    else                      //southern hemisphere
        return !(utc >= _stdUTC && utc < _dstUTC);
}

/*----------------------------------------------------------------------*
 * Determine whether the given Local time_t is within the DST interval  *
 * or the Standard time interval.                                       *
 *----------------------------------------------------------------------*/
boolean TimeZoneOne::locIsDST(time_t local)
{
    //recalculate the time change points if needed
    if (year(local) != year(_dstLoc)) calcTimeChanges(year(local));

    if (_stdLoc > _dstLoc)    //northern hemisphere
        return (local >= _dstLoc && local < _stdLoc);
    else                      //southern hemisphere
        return !(local >= _stdLoc && local < _dstLoc);
}

/*----------------------------------------------------------------------*
 * Calculate the DST and standard time change points for the given      *
 * given year as local and UTC time_t values.                           *
 *----------------------------------------------------------------------*/
void TimeZoneOne::calcTimeChanges(int yr)
{
    _dstLoc = toTime_t(_dst, yr);
    _stdLoc = toTime_t(_std, yr);
    _dstUTC = _dstLoc - _std.offset * SECS_PER_MIN;
    _stdUTC = _stdLoc - _dst.offset * SECS_PER_MIN;
}

/*----------------------------------------------------------------------*
 * Convert the given DST change rule to a time_t value                  *
 * for the given year.                                                  *
 *----------------------------------------------------------------------*/
time_t TimeZoneOne::toTime_t(TimeChangeRule r, int yr)
{
    tmElements_t tm;
    time_t t;
    uint8_t m, w;            //temp copies of r.month and r.week

    m = r.month;
    w = r.week;
    if (w == 0) {            //Last week = 0
        if (++m > 12) {      //for "Last", go to the next month
            m = 1;
            yr++;
        }
        w = 1;               //and treat as first week of next month, subtract 7 days later
    }

    tm.Hour = r.hour;
    tm.Minute = 0;
    tm.Second = 0;
    tm.Day = 1;
    tm.Month = m;
    tm.Year = yr - 1970;
    t = makeTime(tm);        //first day of the month, or first day of next month for "Last" rules
    t += (7 * (w - 1) + (r.dow - weekday(t) + 7) % 7) * SECS_PER_DAY;
    if (r.week == 0) t -= 7 * SECS_PER_DAY;    //back up a week if this is a "Last" rule
    return t;
}

// update rules

void TimeZoneOne::setRule(TimeChangeRule dstStart, TimeChangeRule stdStart)
{
    	_dst = dstStart;
    	_std = stdStart;
	_dstSave = dstStart;
	_stdSave = stdStart;
}



TimeZoneOne.h
/*----------------------------------------------------------------------*
 * Arduino Timezone Library v1.0                                        *
 * Jack Christensen Mar 2012                                            *
 * Mitch added setRule method so rules can be updated 6/16                                                                    *
 * This work is licensed under the Creative Commons Attribution-        *
 * ShareAlike 3.0 Unported License. To view a copy of this license,     *
 * visit http://creativecommons.org/licenses/by-sa/3.0/ or send a       *
 * letter to Creative Commons, 171 Second Street, Suite 300,            *
 * San Francisco, California, 94105, USA.                               *
 *----------------------------------------------------------------------*/ 
 
#ifndef TimeZoneOne_h
#define TimeZoneOne_h
#include <Arduino.h> 
#include <TimeLib.h>              

//structure to describe rules for when daylight/summer time begins,
//or when standard time begins.
struct TimeChangeRule
{
    char abbrev[6];    //five chars max
    uint8_t week;      //First, Second, Third, Fourth, or Last week of the month
    uint8_t dow;       //day of week, 1=Sun, 2=Mon, ... 7=Sat
    uint8_t month;     //1=Jan, 2=Feb, ... 12=Dec
    uint8_t hour;      //0-23
    int offset;        //offset from UTC in minutes
};
        
class TimeZoneOne
{
    public:
     TimeZoneOne();
	void setRule(TimeChangeRule dstStart, TimeChangeRule stdStart);
        time_t toLocal(time_t utc);
        time_t toUTC(time_t local);
        boolean utcIsDST(time_t utc);
        boolean locIsDST(time_t local);
    private:
        void calcTimeChanges(int yr);
        time_t toTime_t(TimeChangeRule r, int yr);
       TimeChangeRule _dst;    //rule for start of dst or summer time for any year
       TimeChangeRule _std;    //rule for start of standard time for any year
TimeChangeRule _dstSave;
TimeChangeRule _stdSave;
       time_t _dstUTC;         //dst start for given/current year, given in UTC
        time_t _stdUTC;         //std time start for given/current year, given in UTC
        time_t _dstLoc;         //dst start for given/current year, given in local time
        time_t _stdLoc;         //std time start for given/current year, given in local time
};
#endif

Check the last method TimeZoneOne::setRule(). I used an extra set of variables _dstSave and _stdSave, to preserve the values of _dst and _std.

Why? What function changes them? That is, why do they need to be "preserved"?

Sorry I wasn't clear on that. I call toLocal() often within the sketch to convert different times to DST. I don't want to have to set the rules before each call, since they don't often change.

I don't want to have to set the rules before each call, since they don't often change.

You don't need to. Why did you think you did?

That goes back to the original problem, and the ugly fix.

After setRules() is called more than once, the rules variables are initialized.

They are declared globally as:
TimeChangeRule dSt;
TimeChangeRule sTd;

They are set this way once, but must be set again each time a call is made to setRules():
TimeChangeRule dSt = {"DST", preset[37], preset[38], preset[36], preset[39], tzOffset};
TimeChangeRule sTd = {"STD", preset[43], preset[44], preset[42], preset[45], 0};

They are set this way once, but must be set again each time a call is made to setRules()

What I don't understand is why you need to call setRules() more than once. The day of the year when daylight savings time starts doesn't change while your program is running. The day of the year when daylight savings time ends does not change while your program is running.

Sorry, I see what you are asking now. I pass various dates and times to the function, to convert to DST or not, using toLocal(). I'd like to be able to call toLocal() as many times as necessary, without calling setRules() each time.

The rules can change for different time zones and countries, but that works, at least the first time new rules are set.

I'd like to be able to call toLocal() as many times as necessary, without calling setRules() each time.

I guess I don't understand why you can't. To my mind, setRules() should be called once in setup().

It should work that way, but it doesn't. setRules() should only have to be called if rules from a different timezone are needed. Unfortunately without the ugly patch, it must be called each time toLocal() is called because the variable values in TimeChangeRule variables are not preserved.