Die Zeit – im Sommer und im Winter

Als ich angefangen habe mich mit dem Thema Zeitumstellung zur Sommer- und Winterzeit zu beschäftigen, bin ich von einigen wenigen Zeilen Code mit der einen oder anderen if-Abfrage ausgegangen, die so nebenbei mal eben vorm Frühstück geschrieben werden. Erst nach und nach ist mir erst bewusst geworden, dass die Umstellung zur Sommer- und Winterzeit doch etwas komplexer ist. Aber der Reihe nach.

Im Rahmen eines Uhrenprojektes stellte sich mir die Frage, ob ich einen einfachen freilaufenden, per Interrupt gesteuerten Sekundentaktgenerator aufbaue, einen DCF77 Empfänger verwende oder eine RTC (Real Time Clock) auch Echtzeituhr genannt einbaue.

Der DCF77-Empfänger war anfangs favorisiert, schied aber nach einigen ersten Versuchen bereits aus, da die Empfangsantenne in direkter Nähe einer gemultiplexten LED-Anzeige untergebracht werden musste. Das Störfeld der Anzeige lies einfach keinen zuverlässigen Empfang des Zeitzeichensenders in Mainflingen bei Frankfurt zu.

Am einfachsten zu realisieren ist eine freilaufende Uhr über einen Interruptgesteuerten Sekundentaktgenerator, sofern Schaltjahr und Sommerzeitumstellung keine Berücksichtigung finden müssen. Aus einem anderen Uhrenprojekt wusste ich, dass die Genauigkeit für dieses neue Uhrenprojekt von mir grundsätzlich ebenfalls völlig ausreichend sein würde, da die Abweichung bei Zimmertemperatur deutlich unter einer Minute im Jahr liegt. Eine weitere Hürde ergab sich jedoch bei Stromausfall, da die Uhr keine Bedienelemente haben sollte. Das Stellen der Uhrzeit hätte in dem Fall über einen PC stattfinden müssen, was jedoch eher nicht DAU-tauglich ist.

Also dann noch einen Blick auf die RTC. Es gibt von Dallas einen Chip mit Namen DS1307 den ich in die engere Wahl gezogen hatte. Nachdem ich mein Ansinnen auch mit dem Arduino Stammtisch Hannover andiskutiert hatte, bekam ich von dort über den Hubert noch den Tipp auf den weitgehend kompatiblen Chip DS3231 umzusteigen, da dieser durch einen temperaturkompensierten Quarzgenerator deutlich präziser arbeiten soll. Zwar fehlt diesem IC das integrierte EEPROM, es wurde hier jedoch von mir nicht wirklich benötigt. Daher gleich frisch ans Werk.

Das batteriegepufferte Echtzeituhrenmodul generiert die Uhrzeit, das Datum und den Wochentag. Eine Schaltjahrberechnung ist ebenfalls im Chip untergebracht, zumindest bis ins Jahr 2100. Einzig die Umstellung der Uhrzeit während der Sommerzeit und Rückstellung auf die Winterzeit muss Länderspezifisch per Software gelöst werden.

Die Definition von Winter- und Sommerzeit

Die derzeit aktuelle mitteleuropäische Sommerzeit beginnt jeweils am letzten Sonntag im März um 02:00 Uhr MEZ, indem die Uhrzeit um eine Stunde von 02:00 Uhr auf 03:00 Uhr vorgestellt wird. Sie endet jeweils am letzten Sonntag im Oktober um 03:00 Uhr MESZ, indem die Uhrzeit nun um die eine Stunde von 03:00 Uhr auf 02:00 Uhr zurückgestellt wird. Die Stunde von 02:00 Uhr bis 03:00 Uhr erscheint im Herbst daher zweimal. Die erste Stunde (von 02:00 Uhr bis 03:00 Uhr MESZ) wird mit „2A“ und die zweite Stunde (von 02:00 Uhr bis 03:00 Uhr MEZ) mit „2B“ bezeichnet. Im englischen Sprachraum wird die Sommerzeit übrigens als „Daylight Saving Time“ bezeichnet und die Winterzeit als „Standard Time“

Einige notwendige Vorüberlegungen

Wie nun an so ein Thema herangehen? In solchen Fällen ist Excel immer mein Freund und hilft bei der Strukturierung und Visualisierung der zu lösenden Aufgabe. Ich habe mir daher eine Tabelle erstellt, die ganz links die Wochentage anzeigt. Daneben eine Spalte mit den numerischen Werten des Wochentages, wie sie von der RTC generiert werden. Also Sonntag = 0, Montag = 1, Dienstag = 2 usw. In den folgenden sieben Spalten lasse ich den Monat jeweils um einen Tag später beginnen. In  der ersten der sieben Spalten ist der 1. des Monats ein Montag, in der nächsten Spalte der Dienstag usw. Die möglichen letzten Sonntage eines Monats habe ich rot markiert.

Mo 1 1
Di 2 2 1
Mi 3 3 2 1
Do 4 4 3 2 1
Fr 5 5 4 3 2 1
Sa 6 6 5 4 3 2 1
So 0 7 6 5 4 3 2 1
Mo 1 8 7 6 5 4 3 2
Di 2 9 8 7 6 5 4 3
Mi 3 10 9 8 7 6 5 4
Do 4 11 10 9 8 7 6 5
Fr 5 12 11 10 9 8 7 6
Sa 6 13 12 11 10 9 8 7
So 0 14 13 12 11 10 9 8
Mo 1 15 14 13 12 11 10 9
Di 2 16 15 14 13 12 11 10
Mi 3 17 16 15 14 13 12 11
Do 4 18 17 16 15 14 13 12
Fr 5 19 18 17 16 15 14 13
Sa 6 20 19 18 17 16 15 14
So 0 21 20 19 18 17 16 15
Mo 1 22 21 20 19 18 17 16
Di 2 23 22 21 20 19 18 17
Mi 3 24 23 22 21 20 19 18
Do 4 25 24 23 22 21 20 19
Fr 5 26 25 24 23 22 21 20
Sa 6 27 26 25 24 23 22 21
So 0 28 27 26 25 24 23 22
Mo 1 29 28 27 26 25 24 23
Di 2 30 29 28 27 26 25 24
Mi 3 31 30 29 28 27 26 25
Do 4 31 30 29 28 27 26
Fr 5 31 30 29 28 27
Sa 6 31 30 29 28
So 0 31 30 29
Mo 1 31 30
Di 2 31

Zusammenfassung der Erkenntnisse

  1. Der 25. des jeweiligen Monats ist der 1. mögliche letzte Sonntag, der 31. ist definitiv der letzte Sonntag
  2. Alles was vorm 25. des Monats März liegt ist daher immer Winterzeit
  3. Alles was nach dem 31. März liegt ist immer Sommerzeit
  4. Alles was im Oktober vor dem 25. liegt ist immer Sommerzeit
  5. Alles nach dem 31. Oktober liegt ist immer Winterzeit
  6. Am jeweils letzten Sonntag muss noch die Uhrzeit zwischen 2:00 und 3:00 Uhr berücksichtigt werden
  7. Die Monate Januar, Februar, November und Dezember sind immer Winterzeit
  8. Die Monate April bis September sind immer Sommerzeit

Wir müssen uns also im Wesentlichen nur um die Zeit zwischen dem 25. und dem 31. kümmern. Die von der RTC ausgegebenen Wochentage sind typ. durchnummeriert mit Sonntag=0, Montag=1, Dienstag=2 usw.  An jedem Datum in dieser Woche ab dem 25. gibt immer nur einen einzigen möglichen spezifischen Wochentag. Es kann also nicht 2 mal ein Montag sein. Daraus ergibt sich folgender Ansatz:

Wird nun einfach der aktuelle Tag der Woche vom aktuellen Datum subtrahiert und das Ergebnis ist größer oder gleich dem 25.März, so ist Sommerzeit, andernfalls noch Winterzeit. Die Erkennung der Winterzeit verläuft synonym im Oktober statt im März.

Nachfolgend noch einige Rechenbeispiele dazu:

Datum Tag der Woche Ergebnis
25 2 23 Winterzeit
28 4 24 Winterzeit
30 4 26 Sommerzeit
29 6 23 Winterzeit
29 4 25 Sommerzeit
29 3 26 Sommerzeit
31 2 29 Sommerzeit

Nach diesen Vorüberlegungen können wir nun mit der entsprechenden Bearbeitung des Sketches beginnen. Ich habe diesen zwecks besserer Übersicht in 2 Teilen aufgebaut.

/******************************************************************************************/
/******************************************************************************************/

/***  Sketch for testing the detection and adjustment of the „Daylight Saving Time“  ***/
/** Origin Author:               Olaf Meier
* Date:                                     2014/2/22
* Release:                              1.0
* Hardware connection:  None
*
*/
/***  Declare and initialize actual time variables with some valid numbers here  ***/
byte seconds                      =  0;             // Seconds (0 to 59)
byte minutes                      =  59;           // Minutes (still summer time yet)
byte hours                           =  1;             // Hours as UTC/GMT+1 (MEZ) (0 to 23)
byte dow                              =  0;              // Set day of week (0 to 6), here to Sunday
byte days                             =  30;            // Day (0 to 31)
byte months                       =  10;            // Month (0 to 12)
/***  In loop() some of the above variables wil be reentered with new values  ***/
boolean DST                       =  false;        // Reset flag „Daylight Saving Time“ to Winter time
unsigned int baudRate  =  9600;        // Set Baudrate for the serial monitor
/******************************************************************************************/
/******************************************************************************************/

void setup() {
Serial.begin(baudRate);                          // …set up the serial output
Serial.println();
Serial.println(„Debugging activated“);
Serial.println();
}                                                  // End of void setup()
/******************************************************************************************/
/******************************************************************************************/
void loop() {
/***  For testing, needed test time/date can be inserted here as e. g. GMT+1 timez one  ***/
minutes                                =  59;           // Start value of the minutes
hours                                     =  1;              // Start value of the hours as UTC/GMT+1
dow                                        =  0;              // Set day of week to Sunday
days                                       =  25;
months                                =  10;
/***  Show the originally entered time  ***/
Serial.print(„Original Date and time: „);
Serial.print(days);
Serial.print(„.“);
Serial.print(months);
Serial.print(“  „);
Serial.print(hours);
Serial.print(„:“);
Serial.print(minutes);
Serial.print(“  DST: „);
Serial.println(DST);
/***  Detect and correct am/pm and DST  ***/
clockGen();                                                //  Consideration of the leap year does the RTC
/***  Show the manipulated time and DST flag  ***/
Serial.print(„Corrected date and time: „);
Serial.print(days);
Serial.print(„.“);
Serial.print(months);
Serial.print(“  „);
Serial.print(hours);
Serial.print(„:“);
Serial.print(minutes);
Serial.print(“  DST: „);
Serial.println(DST);
}                                                  // End of void loop()
/******************************************************************************************/
/******************************************************************************************/

Der obige Block besteht im Wesentlichen aus der Definition und Initialisierung der benötigten Variablen. In der void setup() wird nur die Methode für den seriellen Monitor gestartet. In der anschließenden void loop() kann nun die gewünschte UTC+1 Zeit und das Datum testweise eingetragen werden. Im folgenden Block werden diese Daten dann nochmals über den seriellen Monitor ausgegeben, bevor sie in der Funktion clockGen() auf Sommer- und Winterzeit geprüft wird. Nach Prüfung wird die durch die Funktion clockGen() nun ggf. manipulierte Zeit nochmals über den seriellen Monitor zwecks direkter Vergleichsmöglichkeit mit der Originaleingabe ausgegeben.

In der ausgelagerten Funktion clockGen() wird zuerst die 24 Stunden Anzeige der Echtzeituhr auf 12 Stunden am/pm reduziert. Diese Art der Anzeige brauchte ich so für meine Uhr, weshalb ich diese wenigen Zeilen Code einfach in der gleichen Funktion untergebracht habe. Danach beginnt die Erkennung der Sommer- Winterzeitumstellung. Sehr einfach ist es mit den vollen Monaten Januar, Februar sowie November und Dezember. Dieses sind immer Monate mit Winterzeit. Per Default ist das DST-Flag sowieso bereits auf false gesetzt wir setzen es hier aber nochmals explizit auf false, also Winterzeit. Ähnlich verhält es sich mit den Monaten April bis September, dieses sind immer Monate mit Sommerzeit! Daher können wir hier das Flag DST auf true setzen.  Ganz am Ende dieser Funktion befindet sich dann eine if-Abfrage auf dieses DST-Flag. Ist dieses Flag true, also haben wir Sommerzeit, dann addieren wir hier zur aktuellen Zeit in der Variablen hours einfach +1 also eine Stunde hinzu. Das passiert immwe wieder mit jedem Schleifendurchlauf in der loop().

Zuvor müssen wir aber noch die fehlenden Tage aus dem März und dem Oktober analysieren. Das erfolgt in mehreren Teilschritten. Zuerst prüfen wir auf die Tage nach dem 25. des Monats bzw. ob das Ergebnis aus der Subtraktion von Wochentag und Tag der Woche größer oder gleich 25 ist, um so den letzten Sonntag zu berechnen; in einer weiteren if-Abfrage innerhalb dieser Schleife prüfen wir dann, ob es vormittags ist und größer oder gleich 02:00 oder nachmittags. Falls ja, wird das DST-Flag auf true gesetzt, denn nun ist Sommerzeit. Ähnlich verfahren wir im Oktober. bis zum 24. des Monats ist eindeutig noch Sommerzeit. Die Testroutine am 25. Oktober verläuft ähnlich der im März. Durch obige Hintergrundinformation und die Kommentare im Sketch sollte sich das Wesentliche nun schnell erschließen.

Bei weiteren Anpassungen am Sketch ist unbedingt zu beachten, dass intern die Uhr immer weiter auf UTC bzw. hier UTC+1 (vormals GMT+1) weiterläuft, egal ob Sommer- oder Winterzeit. So lassen sich recht simpel auch andere Zeitzonen darstellen und bei Bedarf sogar zeitzonenspezifische Berechnungen durchführen.

Beim Kopieren des Sketches ist bitte immer auf mögliche Zeilenumbrüche zu achten, damit es nicht zu unerwarteten Fehlern kommt.

/******************************************************************************************/
/******************************************************************************************/
/***  Filter a 24 hour cycle to 12h am/pm. Detect (MEZ/MESZ) Standard and Daylight Saving Time (DST)  ***/
/***  Create a 12 hour cycle with am/pm  ***/
void clockGen(){
  boolean pm = false;                                     // Set to am per default
  /***  Switch to a 12 hour display  ***/
  if (hours > 12){                                                // 12h am/pm
    hours -= 12;
    pm = true;
  }
/******************************************************************************************/
  /***  Determine the Daylight Saving Time DST. In Germany it is the last Sunday in March and in October  ***/
  /***  In March at 2:00am the time will be turned forward to 3:00am and in  ***/
  /***  October at 3:00am it will be turned back to 2:00am and repeated as 2A and 2B  ***/
/******************************************************************************************/
  /***    Generally checking the full month and determine the DST flag is an easy job  ***/
  if(months <= 2 || months >= 11)
    DST = false;                                   // Winter months
  if(months >= 4 && months <= 9)
    DST = true;                                    // Summer months
  /***  Detect the beginning of the DST in March and set DST = 1  ***/
  if(months == 3 && days – dow >= 25) {            // Begin of summer time
    if(pm == false && hours >= 3-1 || pm == true)  // MESZ – 1 hour
      DST = true;
  }
  /***  Still summer months time DST beginning of October, so easy to determine  ***/
  if(months == 10 && days – dow < 25)   
    DST = true;                                    // Summer months anyway until 24th of October
  /***  Test the begin of the winter time in October and set DST = 0  ***/
  if(months == 10 && days – dow >= 25) {           // Test the begin of the winter time
    if(pm == false && hours >= 3-1 || pm == true) {// -1 since the RTC is running in GMT+1 only    
      DST = false;
      Serial.println(„We have winter time“);
    }
    else {
      DST = true;
      Serial.println(„A good day! We have summer time“);
    }
  }
  /***  Only in case DST is detected, add to hour + 1  ***/
  if(DST == true)
    hours +=1;                                     // Add 1 hour due to detected DST
}                                                  // End of function
/******************************************************************************************/
/******************************************************************************************/

9 Kommentare zu „Die Zeit – im Sommer und im Winter

  1. Am 6. April 2014 wurde dieses Thema hier verfasst.
    Heute, am 7. Juni 2015 bin ich hierauf gestoßen.

    Das Thema – Zahllose Suchergebnisse im Internet
    Die Ausführung – Spannend, wie auch Professionell.

    Dies hier ist ein echter Teil des Internet’s, so wie ich Internet verstehe.
    Echte und wahre Informationen, spitze erklärt, gut durchdacht und
    „WIRKLICH“ informativ.

    Erkenntniss: Hier macht sich jemand eine Menge Arbeit um anderen etwas frei
    und kostenlos zu vermitteln, damit die Welt ein wenig besser wird.

    Danke

  2. Hallo Olaf,
    Danke – war sehr Hilfreich.
    zwei Hinweise noch:
    1. beim kopieren des Codes nicht nur auf Zeilenumbrüche achten sondern auch auf nicht passende Zeichenkodierung wie zb. “ vs. “ oder – vs. –
    Code nicht als Text einbinden sondern als Code. Da gibt’s mit Sicherheit was hübsches für WordPress 😉
    2. In der von mir verwendeten Library (http://www.rinkydinkelectronics.com/library.php?id=73) für die DS3231 beginnt „dow“ mit 1 für Montag und endet mit 7 für Sonntag – muss man also aufpassen, was die eigene Lib so draus macht 😉

    1. Ich habe ein 24h format bei meinem Sketch. wenn ich zu meiner Stunde eine stunde dazuzähle habe ich um 23:12uhr dann 24:12Uhr und nicht 0:12 uhr. gibts dafür auch eine Idee? ansonsten passt!
      LG Thomas

    2. Hallo,
      vielen Dank für Deine Ergänzungen und Hinweise. Zwischenzeitlich einen Weg gefunden, den Code anschaulicher einzubinden. Im nächsten Blogbeitrag werde ich mich entsprechend versuchen.

  3. Ich hab bei meinem Projekt einen kleinen Fehler in der Logik entdeckt, der passiert, wenn es gerade 12 Uhr und gleichzeitig Sommerzeit ist. Die Überprüfung ob es am oder pm ist schlägt fehl da 12 > 12 false ist. Am Ende wird dank der Sommerzeit aber hours um 1 erhöht, wodurch 13 Uhr rauskommt. Bei meinem Projekt (ähnlich wie dieses hier: https://www.youtube.com/watch?v=7JHSFtrk3Qg) ist dadurch plötzlich mein Stundenzeiger verschwunden, da sich die Software ausgerechnet hat, dass er sich jetzt bei 396° befinden muss.
    Das lässt sich mit einer kleinen Abfrage am Ende verhindern:

    if(DST == true){
    hours +=1; // Add 1 hour due to detected DST
    if(hours>12) hours -= 12;
    }

  4. Hi,

    erstmal vielen Dank für die „Idee“ mit dem days – dow >= 25.
    Das hat mir wirklich sehr weitergeholfen!

    Allerdings muss ich fragen, ob du oder jemand der anderen Kommentatoren den Code wirklich getestet haben. Ich habe mal ein C# – Testprogramm geschrieben, da ich mir nicht vorstellen konnte, dass der Code, so wie er auf der Webseite steht funktioniert.

    Ihr könnt es hier ansehen und auch online ausführen:
    http://rextester.com/WSPMJB2787

    Im Prinzip testet das Programm alle Zeiten von 2010 bis 2020 – jede halbe Stunde. Deine C-Funktion habe ich in C# übersetzt (ist zu 99% identisch) und vergleiche dein Ergebnis mit dem Ergebnis der .NET-Framework-Funktionen (die ja stimmen sollten). Es gibt viele Fälle, in denen dein Code ein falsches Ergebnis liefert.

    Am Ende des Quelltextes gibt es noch eine Funktion, die richtige Ergebnisse liefert.

    Gruß an alle!
    Thomas

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s