Go Down

Topic: Library for time zone conversions and automatic DST adjustments (Read 72494 times) previous topic - next topic

madex12345

Hi, i would like to know if it's possible to disable DST during normal loop process ex: using a switch. i tried to change 
Timezone myTZ(myDST, mySTD); to  Timezone myTZ(mySTD, mySTD); during normal operation but it dont work

Thanks

odometer

Hi, i would like to know if it's possible to disable DST during normal loop process ex: using a switch. i tried to change 
Timezone myTZ(myDST, mySTD); to  Timezone myTZ(mySTD, mySTD); during normal operation but it dont work

Thanks
1) Please post your code.

2) It might be easier to create your own functions for time manipulation. That is what I did for my own projects.

Acuario

Hi,

I just used the timezone library with the ESP8266 platform and had problems due to conflicts with the xtensa time.h header that comes with the esp8266 compiler.

The solution (for me!) was to rename and edit the code as follows:
In the library directory for the Time library (.....\Arduino\libraries\Time)

Rename Time.h to TimeArd.h

In TimeArd.h change the include define to
#ifndef _TimeArd_h
#ifdef __cplusplus
#define _TimeArd_h


In Time.cpp change to #include "TimeArd.h"
In DateStrings.cpp change to #include "TimeArd.h"

It then compiled and worked fine.


pert

conflicts with the xtensa time.h header that comes with the esp8266 compiler.
This is a known issue. The author of the Time library has moved the code to a file named TimeLib.h in recent versions of the library but left Time.h for backwards compatibility. So all you need to do to fix the issue is to install the current version of the Time library and then change occurrences of:
Code: [Select]
#include <Time.h>
to:
Code: [Select]
#include <TimeLib.h>
There is a pull request to make this change to the Timezone library:
https://github.com/JChristensen/Timezone/pull/8
but unfortunately JChristensen hasn't merged it.

This issue will also occur for Arduino IDE 1.6.10/Arduino AVR Boards 1.6.12 and newer as well as any other core with time.h on a case insensitive operating system.

Markiep

Hi Rob, yes it should work for Europe, see the "WorldClock" example supplied with the library. The examples should work right out of the box on a bare Arduino. Glad to have you kick the tires, let me know if you find issues!

Should also work in the Southern hemisphere, I knew Nick would never let me live it down otherwise ;)

The algorithm works from rules that you supply. For example, US Eastern Daylight Time, which has a UTC offset of -240 minutes, starts on the 2nd Sunday in March at 02:00 local time. So the rules are coded, in pairs, like this:

Code: [Select]
TimeChangeRule usEdt = {"EDT", Second, Sun, Mar, 2, -240};    //UTC - 4 hours
TimeChangeRule usEst = {"EST", First, Sun, Nov, 2, -300};     //UTC - 5 hours


I was pulling my hair out at one point during testing but discovered I had the rules wrong! :smiley-red:


Hi
I'm hoping this is just what I'm looking for.
Do we comment out the time zones not required in the worldclock?

Many thanks

gion86

Hello everyone,

sorry for the lite OT but I'm using this library in my project and it is working beautifully, so I wanted to say thank you to the developer!
I am using an AT tiny 85 for my project, in case any of you is interested  ;D :

https://github.com/gion86/PlantLight

It's an automatic light switch, based in light conditions and time of the day. So the DST was the last thing missing in my code!

Happy Halloween!!  :smiley-evil:

bperrybap

This is a known issue. The author of the Time library has moved the code to a file named TimeLib.h in recent versions of the library but left Time.h for backwards compatibility.
It is combination of Windows not supporting upper/lower case and the way the IDE creates the -I include list.
IMO, the IDE should be placing the Arduino libraries ahead of the -I for the core headers.
If this were done then the problem would also go away.

Currently because the gcc tools are not really installed properly when the IDE is installed, the IDE must call the gcc tools with full paths and specify the include paths for the tools using -I paths.
When the gcc tools are installed properly, no -I paths are needed for the compiler and system headers and any additional -I headers will be searched *before* the system headers.

Because the IDE uses -I to specify the compiler and system headers, and those are *before* the Arduino libraries on the command line, they get searched *before* the Arduino headers.

In the case of an OS like Windows that doesn't support proper upper/lower case file names, you can get a collision between Time.h and time.h
And since the system includes were included using -I paths that were before the Arduino library -I paths, time.h vs Time.h was picked.
Had the IDE placed the -I paths for the compiler and the system headers *after* the Arduino headers, the compiler would have picked Time.h

I believe that the IDE can even be smart enough to avoid the case insensitivity so that if a sketch included <time.h> it would not include the Time library (since it doesn't have a file called time.h in it) and therefore not put it on the -I include path.

The combination of moving compiler and system includes after Arduino libraries and making the IDE smart enough to check for exact header file name matches would prevent this problem with no changes to any library or sketch code.

--- bill

PWBarrett

Hey - Great library.  This just saved me a lot of work.

However...
When I convert the current UTC time to local, then display it, the year is coming out as 47 rather than 2017, a difference of 1970 years.
I know time_t is based at 1970, but shouldn't this already be accounted for rather than having to use tm.Year+1970?

Thanks!

mickeypop

Jack
With over 85 timezone rules around the world, because of this there is just not enough memory to enter them all in arduino.

i am trying to make a web changeable clock where you would enter your timezone and your country rules like:
    -7,1,2,1,3,2,1,1,11,2
    -7,0    if no DST called for

    TZ, DST, SpringWK, SpringDOW, SpringMonth, SpringHR, FallWK, FallDOW FallMonth, FallHR  
     
    if DST = 0 both change rules offset would be set equal like the Arizona rule



Stored in JSON and parsed, calculated and passed as variables into 1 rule.

   TimeChangeRule DT = { "DT" , DTwk, DTdow, DTmonth, DThr, GMT_DST   };  //UTC +- X-60 minutes
   TimeChangeRule ST = { "ST" , STwk, STdow, STmonth, SThr, GMToffset };    //UTC +- X minutes
   Timezone       DST( myDT, myST );


i can write to the struct with DT.offset = GMToffset; etc... just fine.
the JSON, parsing, calculating and web already work.


my issue is how to "REBUILD" the Timezone object after writing to the struct.

       ie; Timezone       DST( DT, ST );

test code shows the struct changes but the time stays the same as GMT ( I'm using NTP)

a fixed rule works but not a calculated rule even though the numbers are the same when printed.

     TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360};
     TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420};


if i declare the object in setup() its not global and loop() cant see it.

How can i access the "Timezone object" directly?

MitchSF

I added a method so that rules can be changed. I'm not sure if it's the best way to do it, but it does work.


Code: [Select]
/*----------------------------------------------------------------------*
 * Arduino TimeZone Library v1.0                                        *
 * Jack Christensen Mar 2012                                            *
 * 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.                               *
 *----------------------------------------------------------------------*/

#include "TimeZone.h"

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

time_t TimeZone::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;
}

/*----------------------------------------------------------------------*
 * Convert the given local time to UTC time.                            *
 *                                                                      *
 * WARNING:                                                             *
 * This function is provided for completeness, but should seldom be     *
 * needed and should be used sparingly and carefully.                   *
 *                                                                      *
 * Ambiguous situations occur after the Standard-to-DST and the         *
 * DST-to-Standard time transitions. When changing to DST, there is     *
 * one hour of local time that does not exist, since the clock moves    *
 * forward one hour. Similarly, when changing to standard time, there   *
 * is one hour of local times that occur twice since the clock moves    *
 * back one hour.                                                       *
 *                                                                      *
 * This function does not test whether it is passed an erroneous time   *
 * value during the Local -> DST transition that does not exist.        *
 * If passed such a time, an incorrect UTC time value will be returned. *
 *                                                                      *
 * If passed a local time value during the DST -> Local transition      *
 * that occurs twice, it will be treated as the earlier time, i.e.      *
 * the time that occurs before the transistion.                         *
 *                                                                      *
 * Calling this function with local times during a transition interval  *
 * should be avoided!                                                   *
 *----------------------------------------------------------------------*/
time_t TimeZone::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 TimeZone::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 TimeZone::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 TimeZone::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 TimeZone::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 TimeZone::setRule(TimeChangeRule dstStart, TimeChangeRule stdStart)
{
    _dst = dstStart;
    _std = stdStart;
_dstSave = dstStart;
_stdSave = stdStart;
}

MitchSF

Header

Code: [Select]
/*----------------------------------------------------------------------*
 * Arduino Timezone Library v1.0                                        *
 * Jack Christensen Mar 2012                                            *
 * Mitch Feig 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 TimeZone_h
#define TimeZone_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 TimeZone
{
    public:
     TimeZone();
 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

mickeypop

Your library version posted is different than the github version I have been using.
 After several edits of  the library.

// at end of setup()
 myDST.setRule( myDT , myST );


IT WORKS !!!

MitchSF

I removed some code that wasn't related before I posted, and I probably should have tested first. Sorry!  Please post the updated programs.

If anyone can suggest a better way to do this, please do so. At some point this functionality should get to Github once it is looked over by someone who knows more about OOP. The ability to change rules programaically is useful.

mickeypop

Mitch, I have been programming in OOP for over 30 years.

When I posted, I did not have time then to track down the variables and types to find what to manipulate.

Last night I finally had some time to really look over the library code and it looks really good.  I think its ready for github posting.

I should note the library works fine on the ESP8266 and ESP32 as well.


My clock code is Atomic(NTP) with WiFi on the ESP8266 with web setup.   I plan a Fibonacci version soon.   Either one i would offer as an example to add to the library if you like.


I did re-sequence some of the timezone.h file.   For some reason on the ESP32, if setRule() is after toLocal() it had a hiccup.  (Still not sure why)   Send me an email if you want the full code.   Mi ck ey AT Dat as oft LLC DOT co m  (caps not needed)

       
class Timezone
{
    public:
        Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart);
        Timezone(int address);
void setRule(TimeChangeRule dstStart, TimeChangeRule stdStart);
        time_t toLocal(time_t utc);
        time_t toLocal(time_t utc, TimeChangeRule **tcr);
        time_t toUTC(time_t local);
        bool utcIsDST(time_t utc);
        bool locIsDST(time_t local);
        void readRules(int address);
        void writeRules(int address);

    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

MitchSF

Great work, Mickey. Please branch and upload to Github.

Next, I'd like to see it work with WiFi101 and the MKR1000. I don't think Time.cpp is used. When I have time I'll look into it.


Go Up