Pages: [1] 2   Go Down
Author Topic: Library for time zone conversions and automatic DST adjustments  (Read 11961 times)
0 Members and 1 Guest are viewing this topic.
Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This library evolved from procedural code that I've been using for about a year. All feedback welcome.

The Timezone library facilitates time zone conversions and automatic daylight saving (summer) time adjustments. This is accomplished by setting a Real Time Clock (RTC) to Universal Coordinated Time (UTC) and then converting UTC to the correct local time, whether it is daylight saving time (a.k.a. summer time) or standard time.

The Timezone library is designed to work in conjunction with the Arduino Time library at http://www.arduino.cc/playground/Code/Time. To download and use the library, including documentation and example sketches:

  • Go to https://github.com/JChristensen/Timezone and click the ZIP button to download the repository as a ZIP file to a convenient location on your PC.
  • Uncompress the downloaded file. This will result in a folder containing all the files for the library, that has a name that includes the branch name, for example "Timezone-master".
  • Rename the folder to just "Timezone".
  • Copy the renamed folder to the Arduino sketchbook\libraries folder.
  • Read the ReadMe.txt file!

Edit: Updated github download procedure.
« Last Edit: January 02, 2013, 10:04:27 am by Jack Christensen » Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Global Moderator
Netherlands
Online Online
Shannon Member
*****
Karma: 227
Posts: 14046
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Very usefull as there are many questions about getting the time zone right (after I had written an NTP-RTC article on the playground).

I am wondering about the DST algorithm, does it include Europe? did not dive into the code yet smiley

Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Very usefull as there are many questions about getting the time zone right (after I had written an NTP-RTC article on the playground).

I am wondering about the DST algorithm, does it include Europe? did not dive into the code yet smiley

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 smiley-wink

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:
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

Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Global Moderator
Netherlands
Online Online
Shannon Member
*****
Karma: 227
Posts: 14046
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


HI Jack,

The library is well done!

Just a few minor remarks

1) For the countries that do not have DST, only an offset to UTC, a 3rd constructor would make sense. This would imply one object less. alternative is to use a single object for both parameters?

Code:
Timezone::Timezone(int TZoffset)  ==> conflict with readRules Address constructor...

better:?

Timezone::Timezone(TimeChangeRule stdStart)
{
    _dst = stdStart;
    _std = stdStart;
}

2) Using EEPROM for storing DST makes sense, alternative is to put the TZ in PROGMEM as timezones are typically known at compiletime? Yes I can imagine at least 2 applications where this is not true.

Maybe an array of the 24 TZ's in Progmem (=288 bytes)?

Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Rob, thanks for the feedback! Actually I had changed the ReadMe file as follows but haven't pushed it up to github yet. So one less object this way as well:

Code:
For a time zone that does not change to daylight/summer time, pass the same rule
twice to the constructor, e.g.:
    Timezone usAZ(usMST, usMST);

In fact the WorldClock example does this for Arizona:

Code:
//US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420};
Timezone usMT(usMDT, usMST);

//Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST, usMST);

Still there should be little harm in the additional constructor, so I think I will add it.

In reality there are more than 24 timezones, given that a lot of locales use DST. So to predefine the 24 "standard" time zones, i.e. Alpha, Bravo, Charlie, etc. may not be all that useful. I didn't intend the library to replace the tz database, and thereby be at the whim of numerous governmental decisions smiley-wink

Thanks again, let me know if you have further suggestions!
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Global Moderator
Netherlands
Online Online
Shannon Member
*****
Karma: 227
Posts: 14046
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

1) should the EEPROM address be checked if it is valid / in range. And that there are 2 x 12 bytes free after it. Or a note in the readme.txt ...

2) _dst.offset * SECS_PER_MIN (+other) is calculated 8 times, maybe the internal representation could be an long which is calculated only once.
you would get rid of all multiplications for only 4 bytes extra per TZ. Don't know if and how much smaller the footprint of the lib would become .

Its a pity the max offset does not fit in an int.



Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Good points, thanks again.

(1) The ReadMe does talk to the size of the TimeChangeRule, and that could be elaborated on. Being very conscious of resources, mostly size in this case, I'm not terribly inclined to check the address. I also see that the Arduino EEPROM library doesn't validate addresses.

(2) Yes that bothered me just a little. Like I said, I didn't want to waste space, but on the other hand I didn't sweat processing overhead terribly, my assumption there being that there just isn't a lot of reason to do time zone conversions very frequently. I'd think once per second would be about it, that being the typical RTC resolution. BUT still a good point that is worth experimenting with. One of those things that I just didn't think through any further at the time.
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Global Moderator
Netherlands
Online Online
Shannon Member
*****
Karma: 227
Posts: 14046
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


(2) I did some testing with the worldclock sample yesterday and a version in which the TimeChangeRule was using a long (calculated in the constructor) and no multiply in the functions was larger than the original. If I recall correctly orig==7508  and new==7632  This seems quite an increase as there are only 9*2 TimeChangeRules = 18 rules * 2 bytes = 36 bytes more memory and there should be less math...

(1) No big deal, EEPROM will probably wrap around as the higher addresslines are ignored...


Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hmmm, interesting. I haven't had time to play with it. I might look at the assembly code, maybe some functions got inlined?
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Jack, thanks for this fine library! I recently wrote decoder DCF77 library (for the atomic time broadcasted by the DCF77 radiostation) (see this thread) which now includes an example of usage together with your TimeZone library.

Before I read the TimeZone README I had not considered that converting from local time to UTC is ambiguous in the hour before switching to winter time. Luckily for us DCF77 users, the DCF77 data package includes info about DST, so in the latest version I added a function to the library that unambiguously converts CET to UTC time.

Logged

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi mrTee, thanks for the feedback, glad you found the library useful! We have WWV here which is similar to DCF77 in that it has a DST/Summer time indicator. Have not tried a WWV receiver myself, although I do have several clocks around that pick up the signal.

Yes daylight and summer time is evil with the ambiguity. Back in the day, every fall we used to have to shut the mainframe computers down and let them sit for an hour, else, the system and applications could generate redundant time stamps in logs and such. I'll have to ask around and see if they ever fixed that! My preference is to always use UTC internally and convert it to local time for human consumption.
Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Brussels, Belgium
Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Jack,

I am trying to work on a DCF77 radio controlled clock with timezome support but it guess I need a bit of help with finding the timezone selection.
What I am trying to do is the following :
I would like to be able to select a timezone (with switches on the Arduino board).
At this moment I am stuck with the CET (Central European Time) showing on the LCD.

I was thinking if condition in the setSyncProvider line or unsigned long getDCFTime() {.... But no luck yet.

Could you please help me?

Thank you very much

Here is my code:

Code:
#include <LiquidCrystal.h>
#include "MsTimer2.h"
#include "DCF77.h"
#include "Time.h"
#include "Timezone.h"

const int dcf77_sample_pin = A15;
const int dcf77_analog_sample_pin = A15;
const int filtered_dcf77_pin = 2;
const int bit0_pin = 3;
const int bit15_pin = bit0_pin + 15;
const int backlightPin = 6;
const int switch1 = 22;
const int switch2 = 23;
const int switch3 = 24;
const int switch4 = 25;
const int switch5 = 26;

DCF77 DCF = DCF77(filtered_dcf77_pin, 0);
volatile time_t time = 0;

LiquidCrystal lcd(33, 32, 31, 37, 36, 35, 34);

TimeChangeRule *tcr;
Timezone UK(00);
Timezone EET(32);
Timezone CET(64);
Timezone MSK(96);
Timezone FET(128);

void low_pass_filter() {
  const unsigned int decimal_offset = 10000;
  static unsigned long int smoothed = 0*decimal_offset;
  const unsigned long int input = analogRead(dcf77_analog_sample_pin)>200? decimal_offset: 0;
  const unsigned int  N = 72;
  smoothed = ((N-1) * smoothed + input) / N;
  static int square_wave_output = 0;
  if ((square_wave_output == 0) == (smoothed >= decimal_offset/2)) {
    square_wave_output = 1-square_wave_output;
    smoothed = square_wave_output? decimal_offset: 0;
  }
  digitalWrite(filtered_dcf77_pin, square_wave_output);    
}

unsigned long getDCFTime() {
  time_t DCFtime = DCF.getUTCTime();
  if (DCFtime != 0) {
    time_t localTime = CET.toLocal(DCFtime, &tcr);
    return localTime;
  }
  return 0;
}
    
void printDigits(int digits){
  lcd.print(":");
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

void digitalClockDisplay() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(hour());
  printDigits(minute());
  printDigits(second());
  lcd.setCursor(9, 0);
  lcd.print(tcr -> abbrev);
  lcd.setCursor(13, 0);
  lcd.print(dayShortStr(weekday()));
  lcd.setCursor(2, 1);
  lcd.print(day());
  lcd.print(" ");
  lcd.print(monthShortStr(month()));
  lcd.print(" ");
  lcd.print(year());
}

void displayUpdate() {
  static time_t prevTimeDisplay = 0;
  if (now() != prevTimeDisplay)
  {
    prevTimeDisplay = now();
    digitalClockDisplay();
  }
}

void setup() {
  pinMode(switch1, INPUT);
  pinMode(switch2, INPUT);
  pinMode(switch3, INPUT);
  pinMode(switch4, INPUT);
  pinMode(switch5, INPUT);
  pinMode(filtered_dcf77_pin, OUTPUT);
  pinMode(dcf77_sample_pin, INPUT_PULLUP);
  for (int pin=bit0_pin; pin < bit15_pin; ++pin) {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
  }
  delay(500);
  for (int pin=bit0_pin; pin < bit15_pin; ++pin) {
    digitalWrite(pin, LOW);
  }
  MsTimer2::set(1, low_pass_filter);
  MsTimer2::start();
  DCF.Start();
  setSyncInterval(30);
  setSyncProvider(getDCFTime);
  digitalWrite(backlightPin, HIGH);
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("DCF77 Clock");
  delay(1500);
  lcd.clear();
}

void loop() {
  displayUpdate();
}


Moderator edit: [code] [/code] tags added.
« Last Edit: December 17, 2012, 02:52:35 am by Coding Badly » Logged

Grand Blanc, MI, USA
Offline Offline
Faraday Member
**
Karma: 95
Posts: 4094
CODE is a mass noun and should not be used in the plural or with an indefinite article.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I would like to be able to select a timezone (with switches on the Arduino board).
At this moment I am stuck with the CET (Central European Time) showing on the LCD.

Hello!

Rather than using a hard-coded Timezone to convert to local time, e.g.

Code:
   time_t localTime = CET.toLocal(DCFtime, &tcr);

instead declare a pointer to Timezone and set it to point to the desired Timezone object. Then use the pointer variable to do the conversion to local time:

Code:
   Timezone *tz;
    tz = &UK;
    time_t localTime = (*tz).toLocal(DCFtime, &tcr);

I don't see any logic in the sketch to actually read the switches, obviously that will be needed, and it will just need to set the pointer variable to the desired timezone. I think you're just asking about how to handle the timezone, so I'll assume you've got working with the switches figured out.

Here is a comprehensive example that works similarly. It uses US timezones rather than European but still should illustrate the technique smiley-wink
https://github.com/JChristensen/PowerOutageMonitor_SW

HTH ... jc
« Last Edit: December 17, 2012, 07:41:16 am by Jack Christensen » Logged

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Brussels, Belgium
Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello Jack,

Thank you very much for your help, pointers are indeed better than the if or if else conditions.
Actually I already added the switches in the code because they will be used to select the timezone, instead of using a menu on LCD screen (I still have lots to learn, and I am not at this point yet).

Thank you very much!

Kind regards,

Américo
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 80
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

thanks... works very well...

for portugual:
Code:
#include <DS1307RTC.h>
#include <Time.h>
#include <Timezone.h>
#include "DS1307.h"

DS1307 clock;

TimeChangeRule PST = {"PST", Last, Sun, Mar, 1, 60};        //Portuguese Summer Time -  "UTC + 1" or GMT + 1
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0};         //Portuguese winter Time -  "UTC + 0" or GMT
Timezone PT(PST, GMT);
TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev
time_t utc;
time_t local;


//SET TIME: (GROOVE CLOCK)
clock.begin();
  clock.fillByYMD(2013,10,29);
  clock.fillByHMS(10,52,00);  //--> GMT (Winter)
  clock.fillDayOfWeek(SUN);
  clock.setTime();//write time to the RTC chip

 char GMTtime[15];
 GMTtime[0]='\0';
Serial.println(GetGMTtime(GMTtime));
 GMTtime[0]='\0';

char* GetGMTtime(char* timeGMT){

  utc = now();

  local = PT.toLocal(utc, &tcr);

  char buffHH[6];
  buffHH[0]='\0';

  char buffMM[6];
  buffMM[0]='\0';


  timeGMT[0] = '\0';

  if(hour(local)<10){
    strcat(timeGMT,"0");
    strcat(timeGMT,itoa(hour(local),buffHH,10));
  }
  else{
    strcat(timeGMT,itoa(hour(local),buffHH,10));
  }

  strcat(timeGMT,":");

  if(minute(local)<10){
    strcat(timeGMT,"0");
    strcat(timeGMT,itoa(minute(local),buffMM,10));
  }
  else{
    strcat(timeGMT,itoa(minute(local),buffMM,10));
  }

  buffMM[0]='\0';
  buffHH[0]='\0';

  //    strcat(timeGMT,":");
  //    strcat(timeGMT,itoa(second(local),bufftemp,10));
  //    Serial.println(timeGMT);
  return timeGMT;



}
« Last Edit: October 29, 2013, 05:53:10 am by jot4p » Logged

Pages: [1] 2   Go Up
Jump to: