Since my DCF77 library has extraordinary noise tolerance it can be used even outside of the Central European Timezone. In order to get the proper time for your timezone you “just” need to add or subtract the timezone offset. As it turns out this is not completely trivial. Whenever the offset crosses midnight the impact on the data must also be considered. This is easy except if it occurs at the begin or the end of a month. Actually the tricky part is to figure out the lengths of the months.
I put together some example code to demonstrate how this can be achieved.
// // www.blinkenlight.net // // Copyright 2014 Udo Klein // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/ #include <dcf77.h> const uint8_t dcf77_analog_sample_pin = 5; const uint8_t dcf77_sample_pin = A5; // A5 == d19 const uint8_t dcf77_inverted_samples = 1; const uint8_t dcf77_analog_samples = 1; const uint8_t dcf77_monitor_pin = A4; // A4 == d18 const int8_t timezone_offset = -1; // GB is one hour behind CET/CEST namespace Timezone { uint8_t days_per_month(const DCF77_Clock::time_t &now) { switch (now.month.val) { case 0x02: // valid till 31.12.2399 // notice year mod 4 == year & 0x03 return 28 + ((now.year.val != 0) && ((bcd_to_int(now.year) & 0x03) == 0)? 1: 0); case 0x01: case 0x03: case 0x05: case 0x07: case 0x08: case 0x10: case 0x12: return 31; case 0x04: case 0x06: case 0x09: case 0x11: return 30; default: return 0; } } void adjust(DCF77_Clock::time_t &time, const int8_t offset) { // attention: maximum supported offset is +/- 23h int8_t hour = BCD::bcd_to_int(time.hour) + offset; if (hour > 23) { hour -= 24; uint8_t day = BCD::bcd_to_int(time.day) + 1; if (day > days_per_month(time)) { day = 1; uint8_t month = BCD::bcd_to_int(time.month); ++month; if (month > 12) { month = 1; uint8_t year = BCD::bcd_to_int(time.year); ++year; if (year > 99) { year = 0; } time.year = BCD::int_to_bcd(year); } time.month = BCD::int_to_bcd(month); } time.day = BCD::int_to_bcd(day); } if (hour < 0) { hour += 24; uint8_t day = BCD::bcd_to_int(time.day) - 1; if (day < 1) { uint8_t month = BCD::bcd_to_int(time.month); --month; if (month < 1) { month = 12; int8_t year = BCD::bcd_to_int(time.year); --year; if (year < 0) { year = 99; } time.year = BCD::int_to_bcd(year); } time.month = BCD::int_to_bcd(month); day = days_per_month(time); } time.day = BCD::int_to_bcd(day); } time.hour = BCD::int_to_bcd(hour); } } uint8_t sample_input_pin() { const uint8_t sampled_data = dcf77_inverted_samples ^ (dcf77_analog_samples? (analogRead(dcf77_analog_sample_pin) > 200) : digitalRead(dcf77_sample_pin)); digitalWrite(dcf77_monitor_pin, sampled_data); return sampled_data; } void setup() { using namespace DCF77_Encoder; Serial.begin(9600); Serial.println(); Serial.println(F("Simple DCF77 Clock V1.0")); Serial.println(F("(c) Udo Klein 2014")); Serial.println(F("www.blinkenlight.net")); Serial.println(); Serial.print(F("Sample Pin: ")); Serial.println(dcf77_sample_pin); Serial.print(F("Inverted Mode: ")); Serial.println(dcf77_inverted_samples); Serial.print(F("Analog Mode: ")); Serial.println(dcf77_analog_samples); Serial.print(F("Monitor Pin: ")); Serial.println(dcf77_monitor_pin); Serial.print(F("Timezone Offset:")); Serial.println(timezone_offset); Serial.println(); Serial.println(); Serial.println(F("Initializing...")); pinMode(dcf77_monitor_pin, OUTPUT); pinMode(dcf77_sample_pin, INPUT); digitalWrite(dcf77_sample_pin, HIGH); DCF77_Clock::setup(); DCF77_Clock::set_input_provider(sample_input_pin); // Wait till clock is synced, depending on the signal quality this may take // rather long. About 5 minutes with a good signal, 30 minutes or longer // with a bad signal for (uint8_t state = DCF77::useless; state == DCF77::useless || state == DCF77::dirty; state = DCF77_Clock::get_clock_state()) { // wait for next sec DCF77_Clock::time_t now; DCF77_Clock::get_current_time(now); // render one dot per second while initializing static uint8_t count = 0; Serial.print('.'); ++count; if (count == 60) { count = 0; Serial.println(); } } Serial.println(); } void paddedPrint(BCD::bcd_t n) { Serial.print(n.digit.hi); Serial.print(n.digit.lo); } void loop() { DCF77_Clock::time_t now; DCF77_Clock::get_current_time(now); Timezone::adjust(now, timezone_offset); if (now.month.val > 0) { switch (DCF77_Clock::get_clock_state()) { case DCF77::useless: Serial.print(F("useless ")); break; case DCF77::dirty: Serial.print(F("dirty: ")); break; case DCF77::synced: Serial.print(F("synced: ")); break; case DCF77::locked: Serial.print(F("locked: ")); break; } Serial.print(' '); Serial.print(F("20")); paddedPrint(now.year); Serial.print('-'); paddedPrint(now.month); Serial.print('-'); paddedPrint(now.day); Serial.print(' '); paddedPrint(now.hour); Serial.print(':'); paddedPrint(now.minute); Serial.print(':'); paddedPrint(now.second); const int8_t offset_to_utc = timezone_offset + (now.uses_summertime? 2: 1); Serial.print(F(" UTC")); Serial.print(offset_to_utc<0? '-':'+'); if (abs(offset_to_utc) < 10) { Serial.print('0'); } Serial.println(abs(offset_to_utc)); } }
The heart of this code is the days_per_month computation. For all months but February this is hard coded. For February I use the well known leap year formula. With this code the adjust function can perform its magic. It just implements add/subtract with carry on the dates. The example implementation of the clock applies adjust with an offset of -1. With other words it converts Central European Time to Western European Time as used e.g. in Great Britain.
Here is some example output to see how it performs.
Simple DCF77 Clock V1.0 (c) Udo Klein 2014 www.blinkenlight.net Sample Pin: 19 Inverted Mode: 1 Analog Mode: 1 Monitor Pin: 18 Timezone Offset:-1 Initializing... ............................................................ ............................................................ ............................................................ ............................................................ ............................................................ ............................................................ ............................................................ ....................... synced: 2014-04-12 19:38:44 UTC+01 synced: 2014-04-12 19:38:45 UTC+01 synced: 2014-04-12 19:38:46 UTC+01
As you can see after 7-8 minutes it syncs. The timezone is UTC+1 or WEST (Western European Summer Time) although DCF77 transmits UTC+2 or CEST (Central European Summer Time). Since the timezone offset in this example is -1 this is exactly the desired behaviour. Success 🙂