Library for time zone conversions and automatic DST adjustments

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

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.

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.

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:

#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:
</mark> <mark>[code]</mark> <mark>

</mark> <mark>[/code]</mark> <mark>
tags added.

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

    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:

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

HTH ... jc

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

thanks... works very well...

for portugual:

#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;



}

jot4p:
thanks... works very well...

You're very welcome, thanks for the feedback. Unfortunately, it's that time of year :frowning:

Hi,

this library works only with RTC's?
Is there something like "Auto Daylight Savings Time" for ntp?

MrGlasspoole:
Hi,
this library works only with RTC's?

Hello! The Timezone library is unaware of RTCs; it will work with or without one. It simply converts time values stored in time_t variables. Include the Arduino Time library to define the time_t data type.

Is there something like "Auto Daylight Savings Time" for ntp?

NTP sends UTC only, without adjustments for daylight time or time zone.

[quote author=Jack Christensen link=topic=96891.msg1449873#msg1449873 date=1383232148]
NTP sends UTC only, without adjustments for daylight time or time zone.
[/quote]Thats the reason i was asking. So i can do it like my router with your library.
There i can set the Time Zone and NTP Time Server and have a Auto Daylight Savings Time checkbox.

Now i see "the Time library will function as a software RTC without additional hardware" in the readme.
So the first sentence "A primary aim of this library is to allow a Real Time Clock (RTC)" did confuse me.

MrGlasspoole:
So the first sentence "A primary aim of this library is to allow a Real Time Clock (RTC)" did confuse me.

Hmmm, yes, thanks for pointing that out. I'll give some thought to clarifying that.

I like the library and I may use it for an upcomming project but it really makes my head hurt
because I have 30 years of using unix time functions burned into my head.
With the unix time functions you never calculate a time_t value for a local time.
There is typically a single system time_t (which is always utc) and then you convert that to a struct tm
using localtime() where you apply the timezone offsets and any potential DST offset to get
the broken down time values.

This library takes a different approach of converting from the normal/system time_t to a local time_t.

Then there is the differences that Michael did when he created the Time library
Michael deviated from the unix time functions by changing some of the paramters from being zero
based to being one based.
His tmElements_t wDay and Month are 1 based wherease the struct tm tm_wday and tm_mon
are zero based.

While it call can be mad to work, it always makes my head hurt when I use the Arduino Time stuff...

But nice job on the library, given the environment.

--- bill

Bill, thanks for the feedback. Time stuff in general makes my head hurt! I only did a tiny bit of Unix and it was so long ago that it no longer counts. So for better or worse, it wasn't an influence when writing this library XD

If I were to call localtime on a Unix machine, whose local time would I get? Whatever zone the machine was set for?

(I was reading about localtime here, looks like a nice site, don't think I'd come across it before.)

On thing that might be useful for unix types,
would be to add localTime() & localTime_r() functions to the TimeZone object
that returns a pointer to a filled in struct TimeElements
Then uses could do something like:

 utc = now();
 myTZ.localTime_r(&utc, &tm);

and then have the full TimeElements available.
This could be an addition to the existing code as you
you wouldn't have to change the current code to add it.
In order to support localTime() you would have to have to reserve
the space for the TimeElements structure, wherease for localTime_r
would simply write over the users variable.
But they are pretty small easy to write functions that can simply
use the existing Time and Timezone functions to fill in the TimeElements
structure.

The only real bummer is that Michael changed some of the tm elements
to be 1 based vs 0 based when he created his Arduino Time library.
And that can't be change now without likely breaking lots of existing code.

--- bill

Oh yep forgot about the user TZ variable. I was thinking that some extensions to the library along the lines you suggest wouldn't be too difficult. Actually I'm pretty used to dealing with dates and times as serial numbers, it's really the only way to go. And for the most flexibility and generality, any date/time should be stored as UTC/GMT/Zulu, this was my primary motivation with the library, to be able to set an RTC to UTC, and then also not have to worry about the daylight time changes.

Indeed the aliens will be perplexed. There have been various more or less interesting suggestions for "better" systems of keeping time but I think there is little incentive to adopting them given the investment and degree of entrenchment of the current system.

I tried library with GPS time and it works well until arduino power on and it is attached to the computer, when i power it off and on arduino after some time the bare arduino library does not sync with GPS, it starts where it stopped so i got two different times. GPS time works well but library time stayed behind as long power offed?

On restart library should sync with GPS? but it is not doing?

itech2014:
I tried library with GPS time and it works well until arduino power on and it is attached to the computer, when i power it off and on arduino after some time the bare arduino library does not sync with GPS, it starts where it stopped so i got two different times. GPS time works well but library time stayed behind as long power offed?

On restart library should sync with GPS? but it is not doing?

If you are referring to the Timezone library, it just does conversions, e.g. you give it UTC and it gives back local time. The Timezone library works in conjunction with the Arduino Time library, and the Time library has a function, setSyncProvider(), to allow it to synchronize with an external time standard like GPS, an RTC, NTP, or whatever.

bperrybap:
The only real bummer is that Michael changed some of the tm elements
to be 1 based vs 0 based when he created his Arduino Time library.
And that can't be change now without likely breaking lots of existing code.

--- bill

I just became aware of this post and wanted to add context to the reason for the change.
My original time library did follow Unix convention with days and months zero based (see: Arduino Playground - HomePage)

Back in December 2009, There was discussion in the developers mailing list about including these time functions in the Arduino core. In that thread, David Mellis made the point that that an Arduino time API should be closer to Processing (where the days and months start from 1). His reasoning was that people moving from Processing to Arduino were less likely to understand and deal with API differences than those coming from a Unix background. In the absence of any objections from the developer community at the time, the API was altered to be closer to Processing and the rest is history.

It is not always an easy task to create APIs that fully satisfy beginners and boffins, and when compromises need to be made, in the Arduino world, it is right that beginners get preferential consideration.

Michael

Michael,
Thank you for the bit of history.
It is always nice to see where things originated.
Sometimes I really shake my head at some of the decisions
that the processing & arduino teams have made.

--- bill