Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Alan De Windt
Published © GPL3+

The World's Most Accurate 7 Time Zone Clock :-)

Auto-setting (using DCF77 time signal from Frankfurt, Germany) 7 time zone clock with awesome animations

IntermediateFull instructions provided361
The World's Most Accurate 7 Time Zone Clock :-)

Things used in this project

Hardware components

TM1637 4-Digit 7 Segment Display
I chose white ones but yellow, blue, red and green are commonly available. White is the least visually intrusive/distracting in my environment which is why I went with this color.
×7
360 Degree Rotary Encoder Module with Push Button
×1
DCF Receiver Module
×1
7-26VDC To 5VDC 3A Step Down Module
×1
Through Hole Resistor, 330 kohm
Through Hole Resistor, 330 kohm
×7
LED (generic)
LED (generic)
Any standard through-hole LED will work that is bright enough for your needs. I used standard white LEDs. Note that these will be powered by the Arduino digital pins so high-powered LEDs will probably draw too much power and damage the Arduino.
×7
Arduino Mega 2560
Note that in my build I used a smaller compatible form factor which can be easily found from other vendors.
×1

Software apps and online services

SketchUp
Not a must but recommended for easily creating 3D models to get a better feel for how everything will look and fit before building.
Affinity Designer
Excellent vector (and raster) drawing tool at an affordable price. Use to draw cutting templates, etc. to sub-millimeter perfection to then be able to print out and tape to materials that need to be cut.
DaVinci Resolve
To create professional videos (free).
Arduino IDE
Arduino IDE

Story

Read more

Schematics

Multi-Time Zone Clock

Code

Multi-Time Zone Clock

Arduino
// Auto-setting 7 time zone clock
// By Alan De Windt
// November 2022

#include <DCF77.h>        //https://github.com/thijse/Arduino-DCF77
// Custom update to line 42 of DCF77.cpp from...
//   pinMode(dCF77Pin, INPUT); 
// to
//   pinMode(dCF77Pin, INPUT_PULLUP); 
// ...as DCF77 module signal goes to ground/LOW when signal is received, but otherwise "floats" so
// INPUT_PULLUP activates the built-in pull-up resistor which pulls the line up HIGH
#include <TimeLib.h>       //https://github.com/PaulStoffregen/Time
#include <Timezone.h>      //https://github.com/JChristensen/Timezone
#include <TM1637Display.h> //https://github.com/avishorp/TM1637

#define DCF_PIN 2        // Connection pin to DCF 77 device
#define DCF_INTERRUPT 0  // Interrupt number associated with pin
DCF77 DCF = DCF77(DCF_PIN,DCF_INTERRUPT,false);

TimeChangeRule rDST1 = {"CDT", Second, Sun, Mar, 2, -300};    // Fort Worth Daylight Savings Time
TimeChangeRule rST1 = {"CST", First, Sun, Nov, 2, -360};      // Fort Worth Standard Time

TimeChangeRule rDST2 = {"EDT", Second, Sun, Mar, 2, -240};  // New York Daylight Savings Time
TimeChangeRule rST2 = {"EST", First, Sun, Nov, 2, -300};     // New York Standard Time

TimeChangeRule rDST3 = {"BST", Last, Sun, Mar, 1, 60};  // London Daylight Savings Time
TimeChangeRule rST3 = {"GMT", Last, Sun, Oct, 2, 0};     // London Standard Time

TimeChangeRule rDST4 = {"CEST", Last, Sun, Mar, 2, 120};  // Zurich Daylight Savings Time
TimeChangeRule rST4 = {"CET", Last, Sun, Oct, 3, 60};     // Zurich Standard Time

TimeChangeRule rST5 = {"IST", Last, Sun, Oct, 1, 330};     // Pune Standard Time - No DST

TimeChangeRule rST6 = {"HKT", Last, Sun, Oct, 1, 480};     // Hong Kong Standard Time - No DST

TimeChangeRule rST7 = {"JST", Last, Sun, Mar, 1, 540};    // Tokyo Standard Time - No DST

Timezone Timezones[] { (rDST1, rST1), (rDST2, rST2), (rDST3, rST3), (rDST4, rST4), (rST5), (rST6), (rST7) };

const int localTimezone = 3;  // Array pointer to local time zone -> Zurich

time_t time(time_t *timer);
time_t varTime;

const int numDigitDisplays = 7;  // Number of 4 digit 7-segment display panels
const int numDigits = numDigitDisplays * 4;  // Total number of 7-segment digits available
TM1637Display DigitDisplay[] { TM1637Display(28, 29), TM1637Display(47, 46), TM1637Display(45, 44), TM1637Display(26, 27), TM1637Display(43, 42), TM1637Display(24, 25), TM1637Display(41, 40) };

int timeZoneLabels[] = {23, 20, 21, 18, 19, 16 , 17};

// Array to keep track of the current intensity set for each of the 7-segment displays
int displayIntensity[numDigitDisplays];

// We will use this array to represent what is currently shown in each of the 7-segment digits
// Think of this as the "display memory"
byte digitShown[numDigits];

// We will use this array to prepare what we want to show in each of the 7-segment digits
// Various functions will then use these two arrays to transition from what is currently being shown
// to what we want to show
byte digitToShow[numDigits];

// Binary 7-segment values corresponding to each decimal number
const byte digitToSegment[] = {
  0b00111111,    // 0
  0b00000110,    // 1
  0b01011011,    // 2
  0b01001111,    // 3
  0b01100110,    // 4
  0b01101101,    // 5
  0b01111101,    // 6
  0b00000111,    // 7
  0b01111111,    // 8
  0b01101111     // 9
};

const byte SEG_COLON = 0b10000000;

int prevMinute = 0;
int nextAnimation = 0;
int nextLabelAnimation = 4;

const int menuButton = 22;
boolean lastMenuButton = HIGH;
boolean currentMenuButton = HIGH;

boolean showDateAndTime = false;
unsigned long startTime;

const int DATA[1] = {31};  // Rotary encoder data pin
const int CLK[1] = {30};  // Rotary encoder clock pin

void setup() {

  randomSeed(analogRead(0));

  //Serial.begin(9600); 
  //Serial.println("Starting...");
  
  DCF.Start();
  setSyncInterval(30);
  setSyncProvider(getDCFTime);

  pinMode(menuButton, INPUT_PULLUP);
  pinMode(CLK[0], INPUT_PULLUP);
  pinMode(DATA[0], INPUT_PULLUP);

  // Initialize time zone label pins
  for (int i = 0; i < numDigitDisplays; i++) {
    pinMode(timeZoneLabels[i], OUTPUT);
  }
                          
  // Blank out all displays and set default intensity
  initDisplayIntensities();
  initDisplays();

  // Load 8's in array of what we want to display
  for (int i = 0; i < numDigits; i++) {
    digitToShow[i] = digitToSegment[8];
  }
  // Now show all 8's by "printing" segments (like a print head) from center outwards
  printFromCenter(0);
  delay(2000);

  // Load nothing in array of what we want to display
  for (int i = 0; i < numDigits; i++) {
    digitToShow[i] = 0;
  }
  // Now show nothing by removing segments from exterior inwards
  printFromExterior(0);
  for (int i = 0; i < numDigitDisplays; i++) {
    digitalWrite(timeZoneLabels[i], LOW);
  }
  delay(1000);

  // Show random animation until time is acquired
  animationRandomPatterns();

  // Load acquired times in array of what we want to display
  loadTimes();
  
  // Now show times by printing segments from center outwards
  printFromCenter(15);
  updateAllDisplays();
}

void loadTimes() {
  int p = 0, digitValue;
  for (int i = 0; i < numDigitDisplays; i++) {
    varTime = Timezones[i].toLocal(now());

    if (hour(varTime) > 9) { 
      digitValue = hour(varTime) / 10;
    } else {  
      digitValue = 0;
    }
    digitToShow[p] = digitToSegment[digitValue];
    p++;

    if (hour(varTime) > 0) { 
      digitValue = hour(varTime) - ((hour(varTime) / 10) * 10);
    } else {
      digitValue = hour(varTime);
    }
    digitToShow[p] = digitToSegment[digitValue] + SEG_COLON;
    p++;
    
    if (minute(varTime) > 9) { 
      digitValue = minute(varTime) / 10;
    } else {  
      digitValue = 0;
    }
    digitToShow[p] = digitToSegment[digitValue];
    p++;

    if (minute(varTime) > 0) { 
      digitValue = minute(varTime) - ((minute(varTime) / 10) * 10);
    } else {
      digitValue = minute(varTime);
    }
    digitToShow[p] = digitToSegment[digitValue];
    p++;
  }
}

void updateAllDisplays() {
  int p = 0;
  for (int i = 0; i < numDigitDisplays; i++) {
    for (int j = 0; j < 4; j++) {
      byte Segments[] = { digitToShow[p] };
      DigitDisplay[i].setSegments(Segments, 1, j);
      digitShown[p] = digitToShow[p];
      p++;
    }
  }
}

void turnOnOffLabels() {
  // Cycle through each 4 digit 7-segment display
  for (int i = 0; i < numDigitDisplays; i++) {
    int firstDigit = i * 4;
    // If something should be shown...
    if (digitToShow[firstDigit] > 0) {
      //...and it is fully shown...
      if ( (digitShown[firstDigit] == digitToShow[firstDigit]) &&
           (digitShown[firstDigit + 1] == digitToShow[firstDigit + 1]) &&
           (digitShown[firstDigit + 2] == digitToShow[firstDigit + 2]) &&
           (digitShown[firstDigit + 3] == digitToShow[firstDigit + 3]) ) {
        // then turn on the label
        digitalWrite(timeZoneLabels[i], HIGH);
      }
    } else { // Otherwise if nothing should be shown 
      // ...and nothing is currently shown...
      if ( (digitShown[firstDigit] == 0) &&
           (digitShown[firstDigit + 1] == 0) &&
           (digitShown[firstDigit + 2] == 0) &&
           (digitShown[firstDigit + 3] == 0) ) {
        //  then turn off the label
        digitalWrite(timeZoneLabels[i], LOW);
      }
    }
  }
}

void printRandomSegment(int delayMillis) {

  // Count number of digits currently shown (in digitShown array) that don't match what we want to show (in digitToShow array)
  int digitsLeft = 0;
  for (int i = 0; i < numDigits; i++) {
    if (digitShown[i] != digitToShow[i]) { digitsLeft++; }
  }

  while (digitsLeft > 0) {
    long randomDigit = random(4);
    long randomDisplay = random(numDigitDisplays);
    int p = (randomDisplay * 4) + randomDigit;
    if (digitShown[p] != digitToShow[p]) {

      int varDigitShown = digitShown[p];
      int varDigitToShow = digitToShow[p];
      int bitPositionValue = 1;  // Decimal value of 1st bit position
      int diffValueFound[8];
      int diffValuesFound = 0;
      for (int i = 0; i < 8; i++) {
        if ((varDigitShown & 1) != (varDigitToShow & 1)) {
          if (varDigitToShow & 1) {
            diffValueFound[diffValuesFound] = bitPositionValue;  // Store bit position decimal value in next available array position
          } else {
            diffValueFound[diffValuesFound] = (bitPositionValue * -1);  // Store bit position decimal value in next available array position
          }
          diffValuesFound++;
        }
        varDigitShown >>= 1;
        varDigitToShow >>= 1;
        bitPositionValue = bitPositionValue * 2; // Calculate decimal value of next bit position
      }

      // Turn a random bit off
      digitShown[p] = digitShown[p] + diffValueFound[random(diffValuesFound)];
      
      byte Segments[] = { digitShown[p] };
      DigitDisplay[randomDisplay].setSegments(Segments, 1, randomDigit);

      if (digitShown[p] == digitToShow[p]) {
        digitsLeft--; 
        turnOnOffLabels();
      }
      
      delay(delayMillis);
    } 
  }
}

void printFromCenter(int delayMillis) {
  
  // Compute starting position for left and right "print head"
  int rightDigit = (numDigits / 2);

  for (int leftDigit = (numDigits / 2) - 1; leftDigit >= 0; leftDigit--) {

    // Compute 4-digit display panel number for left digit
    int leftDisplay = leftDigit / 4;
    // Compute digit number within 4-digit display panel for left digit
    int leftDigitPanelPos = leftDigit - (leftDisplay * 4);
        
    // Compute 4-digit display panel number for right digit
    int rightDisplay = rightDigit / 4;
    // Compute digit number within 4-digit display panel for right digit
    int rightDigitPanelPos = rightDigit - (rightDisplay * 4);
    
    // Show right-most vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_COLON);
    delay(delayMillis);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_B);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_C);
    delay(delayMillis);
  
    // Show left-most vertical segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_F);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_E);
    delay(delayMillis);

    // Show center vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_A);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_G);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_D);
    delay(delayMillis);

    // Show center vertical segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_A);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_G);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_D);
    delay(delayMillis);

    // Show left-most vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_F);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_E);
    delay(delayMillis);

    // Show right-most vertical segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_B);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_C);
    delay(delayMillis);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_COLON);
    delay(delayMillis);

    turnOnOffLabels();

    rightDigit++;
    
  }
}

void printFromExterior(int delayMillis) {
  
  // Compute starting position for left and right "print head"
  int rightDigit = numDigits - 1;

  for (int leftDigit = 0 ; leftDigit <= (numDigits / 2) - 1; leftDigit++) {

    // Compute 4-digit display panel number for left digit
    int leftDisplay = leftDigit / 4;
    // Compute digit number within 4-digit display panel for left digit
    int leftDigitPanelPos = leftDigit - (leftDisplay * 4);
        
    // Compute 4-digit display panel number for right digit
    int rightDisplay = rightDigit / 4;
    // Compute digit number within 4-digit display panel for right digit
    int rightDigitPanelPos = rightDigit - (rightDisplay * 4);
    
    // Show left-most vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_F);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_E);
    delay(delayMillis);
  
    // Show right-most vertical segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_COLON);
    delay(delayMillis);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_B);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_C);
    delay(delayMillis);

    // Show center vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_A);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_G);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_D);
    delay(delayMillis);

    // Show center veritcal segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_A);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_G);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_D);
    delay(delayMillis);

    // Show right-most vertical segments in left digit
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_B);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_C);
    delay(delayMillis);
    printOneSegment(leftDigit, leftDisplay, leftDigitPanelPos, SEG_COLON);
    delay(delayMillis);

    // Show left-most vertical segments in right digit
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_F);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_E);
    printOneSegment(rightDigit, rightDisplay, rightDigitPanelPos, SEG_COLON);
    delay(delayMillis);

    turnOnOffLabels();

    rightDigit--;
    
  }
}

void printFromLeft(int delayMillis, boolean suppressLabels) {
  int p = 0;
  for (int i = 0; i < numDigitDisplays; i++) {
    for (int j = 0; j < 4; j++) {
      printOneSegment(p, i, j, SEG_F);
      printOneSegment(p, i, j, SEG_E);
      delay(delayMillis);
      printOneSegment(p, i, j, SEG_A);
      printOneSegment(p, i, j, SEG_G);
      printOneSegment(p, i, j, SEG_D);
      delay(delayMillis);
      printOneSegment(p, i, j, SEG_B);
      printOneSegment(p, i, j, SEG_C);
      delay(delayMillis);
      printOneSegment(p, i, j, SEG_COLON);
      delay(delayMillis);
      p++;
    }
    if (!suppressLabels) {
      turnOnOffLabels();
    }
  }
}

void printOneSegment(int p, int i, int j, byte segmentToShow) {
  // If the segment is present in the digit that should be shown...
  if ((digitToShow[p] & segmentToShow) == segmentToShow) {
    // If the segment is not currently being shown...
    if ((digitShown[p] & segmentToShow) == 0) {
      // ...then we should add it / show it
      digitShown[p] = digitShown[p] | segmentToShow;  
    }
  // ...else the segment is not present in the digit that should be shown...
  } else {  
    // If the digit currently shown has the segment...    
    if ((digitShown[p] & segmentToShow) == segmentToShow) {
      // ...then we should remove it / turn it off
      digitShown[p] = digitShown[p] ^ segmentToShow;  
    }
  }
  byte Segments[] = { digitShown[p] };
  DigitDisplay[i].setSegments(Segments, 1, j);
}

void displayTimesByCountingAcrossAllDisplays(int delayMillis) {
  // Load all times into a String object
  String timeString = "";
  for (int i = 0; i < numDigitDisplays; i++) {
    varTime = Timezones[i].toLocal(now());
    if (hour(varTime) < 10) {
      timeString += "0" + String(hour(varTime));
    } else {
      timeString += String(hour(varTime));
    }
    if (minute(varTime) < 10) {
      timeString += "0" + String(minute(varTime));
    } else {
      timeString += String(minute(varTime));
    }
  }

  // Cycle through numbers 0 to 9
  for (int i = 0; i < 10; i++) {
    // Cycle through each digit from left to right
    for (int j = 0; j < numDigits; j++) {
      // If the number we want to show is less than or equal to the number that needs to be shown then show it, otherwise it is already shown
      if (int(timeString.charAt(j)) - 48 >= i) {
        int displayNum = j  / 4;
        int positionNum = j - (displayNum * 4);
        DigitDisplay[displayNum].showNumberDec(i, true, 1, positionNum);
      }
      delay(delayMillis);
    }
  }
  // Turn on all of the time zone labels
  turnAllLabelsOn();
  
  loadTimes();
  updateAllDisplays();
}

void displayTimesByCountingUp() {
  for (int i = 0; i < numDigitDisplays; i++) {
    varTime = Timezones[i].toLocal(now());
    int intHour = hour(varTime);
    for (int j = 0; j <= intHour; j++) {
      DigitDisplay[i].showNumberDec(j, true, 2, 0);
      delay(15);
    }
    delay(250);
    int intMinute = minute(varTime);
    for (int j = 0; j <= intMinute; j++) {
      DigitDisplay[i].showNumberDecEx(j, 0b11100000, true, 2, 2);
      delay(15);
    }
    digitalWrite(timeZoneLabels[i], HIGH);
    delay(250);
  }
  loadTimes();
  updateAllDisplays();
}

void displayTimesByCountingUpDigitByDigit() {
  for (int i = 0; i < numDigitDisplays; i++) {
    varTime = Timezones[i].toLocal(now());
    int intHour = 0;
    if (hour(varTime) > 9) {
      intHour = hour(varTime) / 10;
      for (int j = 0; j <= intHour; j++) {
        DigitDisplay[i].showNumberDec(j, true, 1, 0);
        delay(15);
      }
    } else {
      DigitDisplay[i].showNumberDec(0, true, 1, 0);
      delay(15);
    }
    delay(250);

    int intHour2 = hour(varTime) - (intHour * 10);
    for (int j = 0; j <= intHour2; j++) {
      DigitDisplay[i].showNumberDec(j, true, 1, 1);
      delay(15);
    }
    delay(250);

    int intMinute = 0;
    if (minute(varTime) > 9) {
      intMinute = minute(varTime) / 10;
      for (int j = 0; j <= intMinute; j++) {
        DigitDisplay[i].showNumberDecEx(j, 0b11100000, true, 1, 2);
        delay(15);
      }
    } else {
      DigitDisplay[i].showNumberDecEx(0, 0b11100000, true, 1, 2);
      delay(15);
    }
    delay(250);

    int intMinute2 = minute(varTime) - (intMinute * 10);
    for (int j = 0; j <= intMinute2; j++) {
      DigitDisplay[i].showNumberDec(j, true, 1, 3);
      delay(15);
    }
    digitalWrite(timeZoneLabels[i], HIGH);
    delay(250);
  }
  loadTimes();
  updateAllDisplays();
}

void animationRandomPatterns() {

  while(timeStatus() == timeNotSet) { 

    long randomDigit = random(4);
    long randomDisplay = random(numDigitDisplays);
    byte randomNum[] = { random(1,128) };
    byte noDigit[] = { 0 };
    long randomWait = random(15,50);
    
    DigitDisplay[randomDisplay].setSegments(randomNum, 1, randomDigit);
    delay(randomWait * 2);
    for (int i = 6; i >= 0; i--) {
      DigitDisplay[randomDisplay].setBrightness(i, true);  
      DigitDisplay[randomDisplay].setSegments(randomNum, 1, randomDigit);
      delay(randomWait);
    }
    DigitDisplay[randomDisplay].setBrightness(7, true);  
    DigitDisplay[randomDisplay].setSegments(noDigit, 1, randomDigit);
  }
  delay(500);
  initDisplays();
  initDisplayIntensities();
}

void initDisplays() {
  // Clear all 7-segment displays and set to default brightness
  for (int i = 0; i < numDigitDisplays; i++) {
    DigitDisplay[i].clear();
    digitalWrite(timeZoneLabels[i], LOW);
  }
  // Reflect empty displays in array keeping track of current digits shown
  for (int i = 0; i < numDigits; i++) {
    digitShown[i] = 0;
  }
}

void initDisplayIntensities() {
  for (int i = 0; i < numDigitDisplays; i++) {
    DigitDisplay[i].setBrightness(0, true); 
    displayIntensity[i] = 0;
  }
}

void loadLocalDateAndTime() {
  varTime = Timezones[localTimezone].toLocal(now());

  // Load date and time into as String object
  String localDateAndTime = String(year(varTime));
  if (month(varTime) < 10) {
    localDateAndTime += "0" + String(month(varTime));
  } else {
    localDateAndTime += String(month(varTime));
  }
  if (day(varTime) < 10) {
    localDateAndTime += "0" + String(day(varTime));
  } else {
    localDateAndTime += String(day(varTime));
  }

  // Add spaces for number of 4 digit panels between 2nd panel and last panel
  for (int i = 0; i < (numDigitDisplays - 3); i++) {
    localDateAndTime += "    ";
  }

  if (hour(varTime) < 10) {
    localDateAndTime += "0" + String(hour(varTime));
  } else {
    localDateAndTime += String(hour(varTime));
  }
  if (minute(varTime) < 10) {
    localDateAndTime += "0" + String(minute(varTime));
  } else {
    localDateAndTime += String(minute(varTime));
  }

  // Load this into the array of what needs to be shown
  for (int i = 0; i < localDateAndTime.length(); i++) {
    if (localDateAndTime.charAt(i) != " ") {
      digitToShow[i] = digitToSegment[int(localDateAndTime.charAt(i)) - 48];      
    } else {
      digitToShow[i] = 0;      
    }
  }
}

void showTimesWithAnimation() {
  nextAnimation++;
  if (nextAnimation > 6) { 
    nextAnimation = 0;
  }
  switch (nextAnimation) {
    case 0: // From center
      loadTimes();
      printFromCenter(15);
      break;
    case 1: // From left
      loadTimes();
      printFromLeft(15,false);
      break;
    case 2: // Random segment
      loadTimes();
      printRandomSegment(5);
      break;
    case 3: // By counting up two digits at a time
      displayTimesByCountingUp();
      break;
    case 4: // By counting up one digit at a time
      displayTimesByCountingUpDigitByDigit();
      break;
    case 5: // From exterior
      loadTimes();
      printFromExterior(15);
      break;
    case 6: // By counting up across all displays at once
      displayTimesByCountingAcrossAllDisplays(30);
      break;
  }
  loadTimes();
  updateAllDisplays();
}

void removeTimesWithAnimation() {
  for (int i = 0; i < numDigits; i++) {
    digitToShow[i] = 0;
  }
  switch (nextAnimation) {
    case 0: // From center
      printFromExterior(15);
      break;
    case 1: // From left
      printFromLeft(15,false);
      break;
    case 2: // Random segment
      printRandomSegment(5);
      break;
    case 3: // By counting up two digits at a time
      printRandomSegment(5);
      break;
    case 4: // By counting up one digit at a time
      printRandomSegment(5);
      break;
    case 5: // From exterior
      printFromCenter(15);
      break;
    case 6: // From exterior
      printFromCenter(15);
      break;
  }
  initDisplays();
}

void turnAllLabelsOff() {
  for (int i = 0; i < numDigitDisplays; i++) {
    digitalWrite(timeZoneLabels[i], LOW);
  }
}

void turnAllLabelsOn() {
  for (int i = 0; i < numDigitDisplays; i++) {
    digitalWrite(timeZoneLabels[i], HIGH);
  }
}

void loop() {  

  static int8_t val[2];

  // Update time only if minute has changed
  if ( prevMinute != minute() ) {
    prevMinute = minute();

    // If local date and time are not currently shown...
    if (!showDateAndTime) {
      
      // Update all timezones shown
      loadTimes();
      updateAllDisplays();
  
      // Animate labels at top of hour and 30 minutes into the hour during business hours of weekdays
      // Get local time
      varTime = Timezones[localTimezone].toLocal(now());
      // If now is a weekday (Monday - Friday)
      if ((weekday(varTime) > 1) && (weekday(varTime) < 7)) {
        // ...and now is between 8:00 and 17:59
        if ((hour(varTime) > 7) && (hour(varTime) < 18)) {
          // ...and we are at the top of the hour or 30 minutes into the hour...
          if ((minute(varTime) == 0) || (minute(varTime) == 30)) {
            switch (nextLabelAnimation) {
              case 0:  // Random flash
                turnAllLabelsOff();
                for (int j = 0; j < 150; j++) {
                  long randomLabel = random(numDigitDisplays);
                  digitalWrite(timeZoneLabels[randomLabel], HIGH);
                  delay(50);
                  digitalWrite(timeZoneLabels[randomLabel], LOW);
                  delay(50);
                }
                break;
              case 1:  // Flash several times fast then pause before repeating
                for (int k = 0; k < 15; k++) {
                  for (int j = 0; j < 6; j++) {
                    turnAllLabelsOff();
                    delay(50);
                    turnAllLabelsOn();
                    delay(50);
                  }
                  turnAllLabelsOff();
                  delay(400);
                }
                break;
              case 2:  // "Kit" car effect
                turnAllLabelsOff();
                for (int j = 0; j < (15000 / (numDigitDisplays * 100)); j++) {
                  for (int i = 0; i < numDigitDisplays; i++) {
                    digitalWrite(timeZoneLabels[i], HIGH);
                    delay(50);
                    digitalWrite(timeZoneLabels[i], LOW);
                  }
                  for (int i = numDigitDisplays - 1; i >= 0; i--) {
                    digitalWrite(timeZoneLabels[i], HIGH);
                    delay(50);
                    digitalWrite(timeZoneLabels[i], LOW);
                  }
                }
                break;
              case 3:  // All flashing on & off
                for (int j = 0; j < (15000 / 200); j++) {
                  turnAllLabelsOff();
                  delay(100);
                  turnAllLabelsOn();
                  delay(100);
                }
                break;
              case 4:  // All flashing progressively faster and faster
                int delayMillis = 250;
                int numLoops = 1;
                do {
                  for (int j = 0; j < numLoops; j++) {
                    turnAllLabelsOff();
                    delay(delayMillis);
                    turnAllLabelsOn();
                    delay(delayMillis);
                  }
                  delayMillis = delayMillis - 30;
                  numLoops = numLoops * 2;
                } while (delayMillis > 0);
                break;
            }
            turnAllLabelsOn();
            nextLabelAnimation++;
            if (nextLabelAnimation > 4) { 
              nextLabelAnimation = 0;
            }
          }        
        }
      }
    } else {  // Local date and time are shown...
      // ...so update it
      updateAllDisplays();
    }
  }

  // Remove local date and time if it has been shown for 10 seconds already
  if ((showDateAndTime) && (millis() > (startTime + 10000))) {
    initDisplays();
    delay(250);
    showTimesWithAnimation();
    showDateAndTime = false;
  }

  // Read rotary encoder and process if it has been moved
  val[0] = read_rotary(0);
  if (val[0] != 0) {
    if (val[0] < 0) {  // Lower intensity of displays if turned counter-clockwise
      for (int i = 0; i < numDigitDisplays; i++) {
        displayIntensity[i] = displayIntensity[i] - 1;
        if ( displayIntensity[i] < 0 ) {displayIntensity[i] = 0;}
        DigitDisplay[i].setBrightness(displayIntensity[i], true);
      }
    } else {  // Increase intensity of displays if turned clockwise
      for (int i = 0; i < numDigitDisplays; i++) {
        displayIntensity[i] = displayIntensity[i] + 1;
        if ( displayIntensity[i] > 7 ) {displayIntensity[i] = 7;}
        DigitDisplay[i].setBrightness(displayIntensity[i], true);
      }
    }
    updateAllDisplays();
  }

  // Rotary push button
  currentMenuButton = debounce(lastMenuButton, menuButton);
  if (lastMenuButton == HIGH && currentMenuButton == LOW) {  // If pushbutton has been pushed...
    if (!showDateAndTime) {  // Show local date and time if not currently being shown
      // Load local date and time and then show it
      initDisplays();
      delay(250);
      loadLocalDateAndTime();
      updateAllDisplays();
      digitalWrite(timeZoneLabels[localTimezone], HIGH);
      startTime = millis();
      showDateAndTime = true;
    } else {  // Otherwise remove local date and time
      initDisplays();
      delay(250);
      showTimesWithAnimation();
      showDateAndTime = false;
    }
  }
  lastMenuButton = currentMenuButton;
}

unsigned long getDCFTime()
{ 
  time_t DCFtime = DCF.getUTCTime(); // Convert from UTC
  
  if (DCFtime!=0) {
    return DCFtime;
  }
  return 0;
}

// Rotary encoder processor (bullet proof!!!)
// A valid CW or CCW move returns 1 or -1, invalid returns 0
int8_t read_rotary(int i) {

  static uint8_t prevNextCode[1] = {0};
  static uint16_t store[1] = {0};
  static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};
  prevNextCode[i] <<= 2;
  if (digitalRead(DATA[i])) prevNextCode[i] |= 0x02;
  if (digitalRead(CLK[i])) prevNextCode[i] |= 0x01;
  prevNextCode[i] &= 0x0f;

   // If valid then store as 16 bit data.
   if  (rot_enc_table[prevNextCode[i]] ) {
      store[i] <<= 4;
      store[i] |= prevNextCode[i];
      if ((store[i]&0xff)==0x2b) return -1;
      if ((store[i]&0xff)==0x17) return 1;
   }
   return 0;
}

// Button debouncer
boolean debounce(boolean last, int pin) {
  boolean current = digitalRead(pin);
  if (last != current) {
    delay(5);
    current = digitalRead(pin);
  }
  return current;
}

Credits

Alan De Windt
4 projects • 23 followers
Currently a Business Analyst and UI/UX Designer. Started career as a developer, still coding but as a hobby only.
Contact

Comments

Please log in or sign up to comment.