Archiv

Artikel Tagged ‘Kalender’

PHP Kalender Teil 6, Mondphasen

11. April 2012 5 Kommentare

Ähnlich wie auch schon bei der Berechung der Jahreszeiten ist auch die Berechnung der Mondphasen an eine deutlich umfangreichere Formel gebunden als die hiesigen Feiertage. Aber bevor es gleich ans Eingemachte geht, folgen erstmal ein paar Grundlagen.

Einen Umlauf des Mondes um die Erde (Neumond bis Neumond) nennt man Lunation oder auch synodische Periode. Da sich der Mond auf einer annähernd elliptischen Umlaufbahn um die Erde bewegt, kann die Dauer eines vollen Umlaufes jedoch stark variieren. Zur Vereinfachung wurde daher ein Mittelwert, der synodische Monat, gebildet. Dieser beträgt 29 Tage, 12 Stunden und 44 Minuten.

Für die Überprüfung der späteren Ergebnisse werden wieder Bezugsdaten benötigt. Erneut hilft hier die Wikipedia aus (Termine von 2005 – 2020), als zweite Quelle dienen die Daten vom Munich Astro Archive (Termine von 1700 – 2199).

 
Die „einfache“ Grundformel

Ausgehend vom synodischen Monat kann man schnell eine einfache Funktion für die Berechnung der Mondphasen erstellen. Man sucht sich aus den Bezugsdaten einfach einen vergangenen Neumond-Termin als Referenzpunkt und addiert den synodischen Monat. Halbierung, Viertelung oder sonstige Teilung des synodischen Monats bringen die restlichen Daten.

Für mich kam diese Lösung jedoch nicht in Frage, da der synodische Monat einen Fehler von bis zu 7 Stunden aufweist. Bei den Jahreszeiten waren mir schon 15 Minuten zu viel ^^

 
Die komplexere Formel

Eine Quelle, viele Umsetzungen. Auf der Suche nach einer genaueren Formel stolpert man recht schnell über das Buch Astronomische Algorithmen von Jean Meeus. Dessen Formel wurde auf der Seite Computus in JavaScript und im IP-Symcon Forum in PHP umgesetzt. Diese Berechnung hat einen Fehler von unter einer Minute.

Ich war so frei, mir die vorgefertigte PHP Variante zu nehmen und diese entsprechend meiner Vorstellungen zu modifizieren. Für den Kalender wollte ich nur die Termine für Neumond, Vollmond sowie rechtes und linkes Viertel. Alle anderen Funktionen flogen raus. Abschließend wurde das ganze noch zu einer Klasse umgebaut.

class mondphasen {
  function Var_o($k, $t){
    return 124.7746 - 1.5637558 * $k + .0020691 * $t * $t + .00000215 * $t * $t * $t;
  }

  function Var_f($k, $t){
    return 160.7108 + 390.67050274 * $k - .0016341 * $t * $t - .00000227 * $t * $t * $t + .000000011 * $t * $t * $t * $t;
  }

  function Var_m1($k, $t){
    return 201.5643 + 385.81693528 * $k + .1017438 * $t * $t + .00001239 * $t * $t * $t - .000000058 * $t * $t * $t * $t;
  }

  function Var_m($k, $t){
    return 2.5534 + 29.10535669 * $k - .0000218 * $t * $t - .00000011 * $t * $t * $t;
  }

  function Var_e($t){
    return 1 - .002516 * $t - .0000074 * $t * $t;
  }

  function Var_JDE($k, $t){
    return 2451550.09765 + 29.530588853 * $k + .0001337 * $t * $t - .00000015 * $t * $t * $t + .00000000073 * $t * $t * $t * $t;
  }

  function CS($x){
    return cos($x * .0174532925199433);
  }
  
  function SN($x){
    return sin($x * .0174532925199433);
  }

  function Neumond($k){
    $k = floor($k);
    $t = $k / 1236.85;
    $e = $this->Var_e($t);
    $m = $this->Var_m($k, $t);
    $m1 = $this->Var_m1($k, $t);
    $f = $this->Var_f($k, $t);
    $o = $this->Var_o($k, $t);
    //Neumondkorrekturen
    $JDE = $this->Var_JDE($k, $t);
    $JDE += (-1) * 0.4072 * $this->SN($m1) + 0.17241 * $e * $this->SN($m) + 0.01608 * $this->SN(2 * $m1) + 0.01039 * $this->SN(2 * $f) + 0.00739 * $e * $this->SN($m1 - $m) - 0.00514 * $e * $this->SN($m1 + $m) + 0.00208 * $e * $e * $this->SN(2 * $m) - 0.00111 * $this->SN($m1 - 2 * $f) - 0.00057 * $this->SN($m1 + 2 * $f);
    $JDE += 0.00056 * $e * $this->SN(2 * $m1 + $m) - 0.00042 * $this->SN(3 * $m1) + 0.00042 * $e * $this->SN($m + 2 * $f) + 0.00038 * $e * $this->SN($m - 2 * $f) - 0.00024 * $e * $this->SN(2 * $m1 - $m) - 0.00017 * $this->SN($o) - 0.00007 * $this->SN($m1 + 2 * $m) + 0.00004 * $this->SN(2 * $m1 - 2 * $f);
    $JDE += 0.00004 * $this->SN(3 * $m) + 0.00003 * $this->SN($m1 + $m - 2 * $f) + 0.00003 * $this->SN(2 * $m1 + 2 * $f) - 0.00003 * $this->SN($m1 + $m + 2 * $f) + 0.00003 * $this->SN($m1 - $m + 2 * $f) - 0.00002 * $this->SN($m1 - $m - 2 * $f) - 0.00002 * $this->SN(3 * $m1 + $m);
    $JDE += .00002 * $this->SN(4 * $m1);
    return $this->Korrektur($JDE, $t, $k);
  }

  function Viertel($k, $modus){
    // modus = .25 = Erstes Viertel, modus = .75 = Letztes Viertel
    $k = floor($k) + $modus;
    $t = $k / 1236.85;
    $e = $this->Var_e($t);
    $m = $this->Var_m($k, $t);
    $m1 = $this->Var_m1($k, $t);
    $f = $this->Var_f($k, $t);
    $o = $this->Var_o($k, $t);
    // Viertelmondkorrekturen
    $JDE = $this->Var_JDE($k, $t);
    $JDE += -.62801 * $this->SN($m1) + .17172 * $e * $this->SN($m) - .01183 * $e * $this->SN($m1 + $m) + .00862 * $this->SN(2 * $m1) + .00804 * $this->SN(2 * $f) + .00454 * $e * $this->SN($m1 - $m) + .00204 * $e * $e * $this->SN(2 * $m) - .0018 * $this->SN($m1 - 2 * $f) - .0007 * $this->SN($m1 + 2 * $f);
    $JDE += -.0004 * $this->SN(3 * $m1) - .00034 * $e * $this->SN(2 * $m1 - $m) + .00032 * $e * $this->SN($m + 2 * $f) + .00032 * $e * $this->SN($m - 2 * $f) - .00028 * $e * $e * $this->SN($m1 + 2 * $m) + .00027 * $e * $this->SN(2 * $m1 + $m) - .00017 * $this->SN($o);
    $JDE += -.00005 * $this->SN($m1 - $m - 2 * $f) + .00004 * $this->SN(2 * $m1 + 2 * $f) - .00004 * $this->SN($m1 + $m + 2 * $f) + .00004 * $this->SN($m1 - 2 * $m) + .00003 * $this->SN($m1 + $m - 2 * $f) + .00003 * $this->SN(3 * $m) + .00002 * $this->SN(2 * $m1 - 2 * $f);
    $JDE += .00002 * $this->SN($m1 - $m + 2 * $f) - .00002 * $this->SN(3 * $m1 + $m);
    $w = .00306 - .00038 * $e * $this->CS($m) + .00026 * $this->CS($m1) - .00002 * $this->CS($m1 - $m) + .00002 * $this->CS($m1 + $m) + .00002 * $this->CS(2 * $f);
    if ($modus == .25) {$JDE += $w;} else {$JDE += (-1)*$w;}
    return $this->Korrektur($JDE, $t, $k);
  }

  function Vollmond($k){
    $k = floor($k) + .5;
    $t = $k / 1236.85;
    $e = $this->Var_e($t);
    $m = $this->Var_m($k, $t);
    $m1 = $this->Var_m1($k, $t);
    $f = $this->Var_f($k, $t);
    $o = $this->Var_o($k, $t);
    //Vollmondkorrekturen
    $JDE = $this->Var_JDE($k, $t);
    $JDE += -.40614 * $this->SN($m1) + .17302 * $e * $this->SN($m) + .01614 * $this->SN(2 * $m1) + .01043 * $this->SN(2 * $f) + .00734 * $e * $this->SN($m1 - $m) - .00515 * $e * $this->SN($m1 + $m) + .00209 * $e * $e * $this->SN(2 * $m) - .00111 * $this->SN($m1 - 2 * $f) - .00057 * $this->SN($m1 + 2 * $f);
    $JDE += .00056 * $e * $this->SN(2 * $m1 + $m) - .00042 * $this->SN(3 * $m1) + .00042 * $e * $this->SN($m + 2 * $f) + .00038 * $e * $this->SN($m - 2 * $f) - .00024 * $e * $this->SN(2 * $m1 - $m) - .00017 * $this->SN($o) - .00007 * $this->SN($m1 + 2 * $m) + .00004 * $this->SN(2 * $m1 - 2 * $f);
    $JDE += .00004 * $this->SN(3 * $m) + .00003 * $this->SN($m1 + $m - 2 * $f) + .00003 * $this->SN(2 * $m1 + 2 * $f) - .00003 * $this->SN($m1 + $m + 2 * $f) + .00003 * $this->SN($m1 - $m + 2 * $f) - .00002 * $this->SN($m1 - $m - 2 * $f) - .00002 * $this->SN(3 * $m1 + $m);
    $JDE += .00002 * $this->SN(4 * $m1);
    return $this->Korrektur($JDE, $t, $k);
  }

  function Korrektur($JDE, $t, $k){
    //Zusätzlichen Korrekturen
    $JDE += .000325 * $this->SN(299.77 + .107408 * $k - .009173 * $t * $t) + .000165 * $this->SN(251.88 + .016321 * $k) + .000164 * $this->SN(251.83 + 26.651886 * $k) + .000126 * $this->SN(349.42 + 36.412478 * $k) + .00011 * $this->SN(84.66 + 18.206239 * $k);
    $JDE += .000062 * $this->SN(141.74 + 53.303771 * $k) + .00006 * $this->SN(207.14 + 2.453732 * $k) + .000056 * $this->SN(154.84 + 7.30686 * $k) + .000047 * $this->SN(34.52 + 27.261239 * $k) + .000042 * $this->SN(207.19 + .121824 * $k) + .00004 * $this->SN(291.34 + 1.844379 * $k);
    $JDE += .000037 * $this->SN(161.72 + 24.198154 * $k) + .000035 * $this->SN(239.56 + 25.513099 * $k) + .000023 * $this->SN(331.55 + 3.592518 * $k);
    return $JDE;
  }

  function Var_k($Jahr, $Aktdatum, $tz){
    return ($Jahr + ((date("m", time()) - 1) * 30.4 + date("d", time()) + $tz) / 365 - 2000) * 12.3685;
  }

  function NaechsterVM($Jahr, $Aktdatum, $zeit){
    $tz = 0;
    $k = "";
    while($this->Vollmond($k) < $zeit) {
      $k = $this->Var_k($Jahr, $Aktdatum, $tz);
      $tz += 1;
    }
    return $this->Vollmond($k) - $zeit;
  }

  function NaechstesLV($Jahr, $Aktdatum, $zeit){
    $tz = 0;
    $k = "";
    while($this->Viertel($k, .75) < $zeit) {
      $k = $this->Var_k($Jahr, $Aktdatum, $tz);
      $tz += 1;
    }
    return $this->Viertel($k, .75) - $zeit;
  }

  function NaechsterNM($Jahr, $Aktdatum, $zeit){
    $tz = 0;
    $k = "";
    while($this->Neumond($k) < $zeit) {
      $k = $this->Var_k($Jahr, $Aktdatum, $tz);
      $tz += 1;
    }
    return $this->Neumond($k) - $zeit;
  }

  function NaechstesEV($Jahr, $Aktdatum, $zeit){
    $tz = 0;
    $k = "";
    while($this->Viertel($k, .25) < $zeit) {
      $k = $this->Var_k($Jahr, $Aktdatum, $tz);
      $tz += 1;
    }
    return $this->Viertel($k, .25) - $zeit;
  }

  function nextMV($date = false){
    $temp = date_default_timezone_get();
    date_default_timezone_set("UTC");

    if($date != false)
      $aktuell = $date;
    else
      $aktuell = time();
    $zeit = $aktuell / 86400 + 2440587.5; // Umrechnen in Julianische Tage
    $Jahr = date("Y", $aktuell);

    $JDE_mondphase['vm'] = $this->NaechsterVM($Jahr, $aktuell, $zeit);
    $JDE_mondphase['lv'] = $this->NaechstesLV($Jahr, $aktuell, $zeit);
    $JDE_mondphase['nm'] = $this->NaechsterNM($Jahr, $aktuell, $zeit);
    $JDE_mondphase['ev'] = $this->NaechstesEV($Jahr, $aktuell, $zeit);

    asort($JDE_mondphase);

    $JDE_mondphase['vm'] = $aktuell + $JDE_mondphase['vm'] * 86400;
    $JDE_mondphase['lv'] = $aktuell + $JDE_mondphase['lv'] * 86400;
    $JDE_mondphase['nm'] = $aktuell + $JDE_mondphase['nm'] * 86400;
    $JDE_mondphase['ev'] = $aktuell + $JDE_mondphase['ev'] * 86400;

    date_default_timezone_set($temp);
    return $JDE_mondphase;
  }
}

Jetzt ruft man einfach die Funktion nextMV() auf, übergibt dieser ein bestimmtes Datum und erhält die direkt auf das angegebene Datum folgenden 4 Mondphasen. Mit einer kleinen Schleife lassen sich so auch alle Termine bis zu einem bestimmten Termin abrufen.

$mondphasen = new mondphasen();
$datum = time();
while($datum < mktime(0,0,0,12,31,2017)) {
  $mv = $mondphasen->nextMV($datum);
  foreach($mv as $phase => $termin) {
    echo date("d.m.Y H:i",$termin)." ".$phase."<br>\n";
    $datum = $termin + 86400;
  }
}

PHP Kalender Teil 5

28. März 2012 Keine Kommentare

Ich komm gar nicht so oft zum schreiben, wie ich gerne würde. In letzter Zeit hat das Programmieren meine volle Aufmerksamkeit, und in der Überschrift sieht man auch gleich, worum es geht. Ich werkel endlich wieder am Kalender rum.

Mittlerweile ist das ganze auch funktionsbereit, es gab zwar anfangs einige Berechnungsprobleme dank der Zeitumstellungen, aber die haben sich lösen lassen und nun arbeitet das System wie gewünscht. Standardmäßig werden die Feiertage ausgegeben, in Schleswig Holstein arbeitsfreie Feiertage werden zudem farblich hinterlegt. Bei Bedarf können Urlaub, Geburtstage und Hochzeitstage hinzugefügt werden, allgemeine Termine stehen als nächstes auf der Todo-Liste, ebenso wie ein Login mit Session.

Zur Zeit habe ich den Kalender auf 3 Tage Rückblick und 15 Tage Vorschau eingestellt. Das bisherige Ergebnis kann im Testverzeichnis aufgerufen werden.

Mal schauen, ob ich da noch den Diskordianischen Kalender integriere. Ist zwar nur ne Spielerei, aber eine PHP-Portierung von ddate() habe ich bereits gefunden, also stände dem nichts im Wege.

SimpleXML statt mySQL

9. März 2012 Keine Kommentare

Dynamische Seiten sind in vielerlei Hinsicht praktischer als statische Seiten. Doch für so manche Anwendung ist der Einsatz einer Datenbank maßlos überdimensioniert, überhaupt nicht geeignet oder man hat noch nicht einmal einen Webspace mit Datenbankunterstützung.

Hier kommt XML ins Spiel. Man kann in einem XML-Dokument die ganze Struktur einer Datenbank nachbilden (natürlich nur mit aufgelösten Beziehungen), wobei ich insbesondere die m:n Beziehung in XML sogar als angenehmer empfinde, sowohl bei der Notation, als auch der Abfrage. Und dank fertiger Funktionen wie SimpleXML ist der Umgang mit XML-Dateien auch kaum komplizierter als eine DB-Abfrage.

Ich nehme als Beispiel mal eine kleine Linkliste.

 
Die Dokumentstruktur

Da man eine XML-Datei strukturieren kann, wie man möchte, sollte es nicht sonderlich schwer fallen, seine Ideen und Vorstellungen umzusetzen. Für eine einfache Linkliste wird ohnehin nicht viel benötigt: ein Linktitel, die URL und eine oder mehrere Kategorien, nach denen man Filtern kann. Vorteil gegenüber statischer Linklisten: keine redundanten Einträge.

<?xml version="1.0" encoding="UTF-8" ?>
<xfav>
  <entry>
    <title>SelfHTML</title>
    <url>http://de.selfhtml.org/</url>
    <category>HTML</category>
    <category>CSS</category>
    <category>JavaScript</category>
  </entry>
</xfav>

Als Datenbank wäre die Struktur schon etwas komplexer, da man die Kategorien als m:n Beziehung mit insgesamt 3 Tabellen umsetzen müsste.

 
Die Abfrage

Dank SimpleXML ist das Einlesen und Filtern der Einträge aus der XML-Datei keine große Herausforderung. Der Inhalt der Datei wird als mehrdimensionales Array zur Verfügung gestellt.

$xfav = array(
  "title"    => "SelfHTML";
  "url"      => "http://de.selfhtml.org/";
  "category" => array("HTML","CSS","JavaScript")
)

Die Ausgabe mit Filterfunktion könnte nun folgendermaßen aussehen:

<?php
$list = "HTML"; // all, none
$xfav = simplexml_load_file('xfav.xml');

if ($list == "none")
  echo "Bitte Kategorie auswählen!";
else {
  echo "Gewählte Kategorie: ".$list."<br>";
  foreach ($xfav->entry as $entry) {
    if ($list == "all")
      echo '<a href="'.$entry->url.'">'.$entry->title.'</a><br>';
    else {
      foreach ($entry->category as $category) {
        if ($category == $list)
          echo '<a href="'.$entry->url.'">'.$entry->title.'</a><br>';
      }
    }
  }
}
?>

Natürlich hat diese Variante gegenüber einer Datenbankabfrage nicht den gleichen Komfort. Möchte man in diesem Beispiel die Einträge zusätzlich zur Filterung auch noch alphabetisch sortiert haben, muss man sich die dafür nötige Sortierfunktion selbst erstellen.

Man sollte auch nicht außer Acht lassen, dass die Variante mit einer XML-Datei deutlich belastender für den Server ist. Es muss jedes Mal die komplette Datei eingelesen und in ein Array umgewandelt werden und beim Filtern muss auch jeder Eintrag einzeln geprüft werden. Es gibt weder Indexe noch Buffer oder Caches, die dem Server die Arbeit vereinfachen. Daher ist der Einsatz von XML als DB-Ersatz nur bei kleinen Datenmengen zu empfehlen.

PHP Kalender Teil 4, Jahreszeiten

8. Februar 2012 3 Kommentare

Nun kommen die etwas anspruchsvolleren Themen. Bisher gab es für alle Termine einen bestimmten Bezugspunkt, mit dem es sich leicht rechnen ließ. Bei den Jahreszeiten ist das leider nicht mehr so einfach, denn diese sind nicht von einem Datum abhängig, sondern vom Stand der Sonne. Der Frühling beispielsweise beginnt, wenn die scheinbare geozentrische Länge der Sonne 0° beträgt. Man nennt diesen Zeitpunkt auch Tag-und-Nacht-Gleiche, Äquinoktium oder noch präziser Primaräquinoktium, da es im Herbst ein weiteres Äquinoktium gibt…

Hö? Ähm…ja…sind soweit alle Klarheiten beseitigt?

Gut, dann können wir uns ja tiefer in das Thema vorwagen. Bevor hier irgendwas berechnet wird, sollte man sich erstmal Bezugsdaten holen, damit man die späteren Ergebnisse auch auf Fehlerfreiheit prüfen kann. Ich habe zu diesem Zweck 2 Seiten gefunden. Zum einen (mal wieder) den passenden Artikel im Wiki (Termine von 2006 – 2018) und zum anderen eine leicht umfangreichere Liste auf der Seite des Astronomical Applications Department of the U.S. Naval Observatory (Termine von 2000 – 2020).

Da PHP soweit mir bekannt keine Funktion beinhaltet, mit der man sich das Leben in diesem Falle leichter machen könnte, bleibt nur die Suche im Netz nach einer geeigeneten Formel. Alternativ hätte ich mir natürlich auch in den nächsten Monaten selbst das nötige Wissen anlesen können, aber Google erschien mir weniger aufwendig ^^

 
Die „einfache“ Grundformel

Bei der Suche stößt man auf das Buch Astronomical Algorithms von Jean Meeus. Darin enthalten ist eine oft zitierte Grundformel ohne Korrekturterme. Der Fehler soll bei unter 15 Minuten liegen.

Für Y = (Jahr-2000)/1000

März-Äquinoktium (Beginn des astronomischen Frühlings):
JDE = 2451623.80984 + 365242.37404*Y + 0.05169*Y^2 - 0.00411*Y^3 - 0.00057*Y^4

Juni-Solstitium (Beginn des astronomischen Sommers):
JDE = 2451716.56767 + 365241.62603*Y + 0.00325*Y^2 + 0.00888*Y^3 - 0.00030*Y^4

September-Äquinoktium (Beginn des astronomischen Herbstes)
JDE = 2451810.21715 + 365242.01767*Y - 0.11575*Y^2 + 0.00337*Y^3 + 0.00078*Y^4

Dezember-Solstitium (Beginn des astronomischen Winters):
JDE = 2451900.05952 + 365242.74049*Y - 0.06223*Y^2 - 0.00823*Y^3 + 0.00032*Y^4 

Im Forum von selfhtml hat der Benutzer EisFuX diese Grundformel in einer PHP-Klasse veröffentlicht. Leider hat die Klasse wie ich finde ein paar Schwachpunkte.

Hinzu kommt, dass der Abgleich mit den Referenzseiten einen falschen Winterbeginn 2014 offenbarte. Nicht ganz verwunderlich, denn der Winterbeginn ist um 0:03 Uhr MEZ und die Formel hat einen Fehler von 15 Minuten. Die Formel kann es somit gar nicht besser…

…also wieder auf die Suche.

 
Die komplexere Formel

Eine genauere Formel zur Berechnung der Jahreszeiten zu finden, ist schon eine etwas größere Herausforderung. Für jeden Mist bekommt man im Internet haufenweise vorgefertigte Funktionen, Klassen, Codeschnipsel oder zumindest besserwisserische Kommentare…aber hier beschleicht einen das Gefühl, dass vor einem noch niemand die Jahreszeiten selbst berechnen wollte.

Eigentlich gar nicht mal so schlecht, so erspart man sich schlichtes übernehmen und muss tatsächlich noch ein wenig Hirnschmalz aufbringen.

Die Suche führte mich letztlich auf die Seite des IMCCE (Institut de Mecanique Celeste et de Calcul des Ephemerides), welche eine deutlich komplexere Formel zur Berechnung der Jahreszeiten verwenden. Hier wird nicht nur auf den Tag, sondern auf die Minute genau gerechnet.

Freundlicherweise haben sie die Berechnung in JavaScript realisiert, so dass man sich auch den Quelltext anschauen kann.

So, bevor ich wieder lang aushole, erstmal die Fakten: die Formel des IMCCE hat nach ersten Proberechnungen nur einen Fehler von 1 Minute, eine größere Abweichung konnte ich zwischen 2000 und 2020 nicht feststellen. Dem Perfektionisten mag selbst diese eine Minute noch zu viel sein, mir reicht diese Genauigkeit jedoch vollkommen aus. Zu einem falschen Datum kann es jetzt nur noch kommen, wenn der Beginn einer Jahreszeit genau auf 23:59 oder 0:00 fällt und der Fehler in die „falsche Richtung“ geht. Sobald man das weiß, kann man händisch nachbessern, das sollte nicht allzu häufig vorkommen.

Hier nun die Formel des IMCCE als PHP-Klasse inkl. automatischer Umrechung des Ergebnisses in die Zeitzone des Servers, auf dem das Script ausgeführt wird:

/****************************************************************\
| Alle Funktionen und Formeln stammen von der Webseite des IMCCE |
| (Institut de Meécanique Celeste et de Calcul des Ephemerides)  |
|      http://www.imcce.fr/en/grandpublic/temps/saisons.php      |
\****************************************************************/
class jahreszeit {
  function datum() {
    $this->JJD = 0;
    $this->JAHR = 0;
    $this->MONAT = 0;
    $this->TAG =0;
    $this->JZ = array();
  }

  function trunc($x) {
    if($x>0.0)
      return floor($x);
    else
      return ceil($x);
  }

  function JJDATEJ(){
    $Z1 = $this->JJD+0.5;
    $Z = $this->trunc($Z1);
    $A = $Z;
    $B = $A+1524;
    $C = $this->$trunc(($B-122.1)/365.25);
    $D = $this->$trunc(365.25*$C);
    $E = $this->trunc(($B-$D)/30.6001);
    $this->TAG = $this->trunc($B-$D-$this->trunc(30.6001*$E));
    if($E<13.5)
      $this->MONAT = $this->trunc($E-1);
    else
      $this->MONAT = $this->trunc($E-13);
    if($this->MONAT >= 3)
      $this->JAHR = $this->trunc($C-4716);
    else
      $this->JAHR = $this->trunc($C-4715);
  }

  function JJDATE (){
    $Z1 = $this->JJD+0.5;
    $Z = $this->trunc($Z1);
    if($Z<2299161) 
      $A = $Z;
    else {
      $ALPHA = $this->trunc(($Z-1867216.25)/36524.25);
      $A = $Z+1+$ALPHA-$this->trunc($ALPHA/4);}
    $B = $A+1524;
    $C = $this->trunc(($B-122.1)/365.25);
    $D = $this->trunc(365.25*$C);
    $E = $this->trunc(($B-$D)/30.6001);
    $this->TAG = $this->trunc($B-$D-$this->trunc(30.6001*$E));
    if($E<13.5)
      $this->MONAT = $this->trunc($E-1);
    else
      $this->MONAT = $this->trunc($E-13);
    if($this->MONAT >= 3)
      $this->JAHR = $this->trunc($C-4716);
    else
      $this->JAHR = $this->trunc($C-4715);
  }

  function affsai($n) {
    $nomsai = Array("spring","summer", "autumn", "winter");
    $FDJ = ($this->JJD+0.5E0)-floor($this->JJD + 0.5E0);
    $HH = floor($FDJ*24);
    $FDJ-=$HH/24.0;
    $MM = floor($FDJ*1440);
    $temp = date_default_timezone_get();
    date_default_timezone_set("UTC");
    $this->JZ[$nomsai[$n]] = mktime($HH,$MM,0,$this->MONAT,$this->TAG,$this->JAHR);
    date_default_timezone_set($temp);
  }

  function saison($YY) {  
    $CODE1 = $YY;
    $nline = 1;
    $k = $YY-2000-1;
    for($n=0;$n<8;$n++) {
      $nn = $n%4;
      $dk = $k+0.25E0*$n;
      $T = 0.21451814e0+0.99997862442e0*$dk
          +0.00642125e0*sin( 1.580244e0+0.0001621008e0*$dk)                              
          +0.00310650e0*sin( 4.143931e0+6.2829005032e0*$dk)                              
          +0.00190024e0*sin( 5.604775e0+6.2829478479e0*$dk)                              
          +0.00178801e0*sin( 3.987335e0+6.2828291282e0*$dk)                              
          +0.00004981e0*sin( 1.507976e0+6.2831099520e0*$dk)                              
          +0.00006264e0*sin( 5.723365e0+6.2830626030e0*$dk)                              
          +0.00006262e0*sin( 5.702396e0+6.2827383999e0*$dk)                              
          +0.00003833e0*sin( 7.166906e0+6.2827857489e0*$dk)                              
          +0.00003616e0*sin( 5.581750e0+6.2829912245e0*$dk)                              
          +0.00003597e0*sin( 5.591081e0+6.2826670315e0*$dk)                              
          +0.00003744e0*sin( 4.3918e0  +12.56578830e0 *$dk)                                
          +0.00001827e0*sin( 8.3129e0  +12.56582984e0 *$dk)                                
          +0.00003482e0*sin( 8.1219e0  +12.56572963e0 *$dk)                                
          -0.00001327e0*sin(-2.1076e0  +0.33756278e0  *$dk)                                
          -0.00000557e0*sin( 5.549e0   +5.7532620e0   *$dk)                                 
          +0.00000537e0*sin( 1.255e0   +0.0033930e0   *$dk)                                 
          +0.00000486e0*sin(19.268e0   +77.7121103e0  *$dk)                                 
          -0.00000426e0*sin( 7.675e0   +7.8602511e0   *$dk)                                 
          -0.00000385e0*sin( 2.911e0   +0.0005412e0   *$dk)                                 
          -0.00000372e0*sin( 2.266e0   +3.9301258e0   *$dk)                                 
          -0.00000210e0*sin( 4.785e0   +11.5065238e0  *$dk)                                 
          +0.00000190e0*sin( 6.158e0   +1.5774000e0   *$dk)                                 
          +0.00000204e0*sin( 0.582e0   +0.5296557e0   *$dk)                                 
          -0.00000157e0*sin( 1.782e0   +5.8848012e0   *$dk)                                 
          +0.00000137e0*sin(-4.265e0   +0.3980615e0   *$dk)                                 
          -0.00000124e0*sin( 3.871e0   +5.2236573e0   *$dk)                                 
          +0.00000119e0*sin( 2.145e0   +5.5075293e0   *$dk)                                 
          +0.00000144e0*sin( 0.476e0   +0.0261074e0   *$dk)                                 
          +0.00000038e0*sin( 6.45e0    +18.848689e0   *$dk)                                  
          +0.00000078e0*sin( 2.80e0    +0.775638e0    *$dk)                                  
          -0.00000051e0*sin( 3.67e0    +11.790375e0   *$dk)                                  
          +0.00000045e0*sin(-5.79e0    +0.796122e0    *$dk)                                  
          +0.00000024e0*sin( 5.61e0    +0.213214e0    *$dk)                                  
          +0.00000043e0*sin( 7.39e0    +10.976868e0   *$dk)                                  
          -0.00000038e0*sin( 3.10e0    +5.486739e0    *$dk)                                  
          -0.00000033e0*sin( 0.64e0    +2.544339e0    *$dk)                                  
          +0.00000033e0*sin(-4.78e0    +5.573024e0    *$dk)                                  
          -0.00000032e0*sin( 5.33e0    +6.069644e0    *$dk)                                  
          -0.00000021e0*sin( 2.65e0    +0.020781e0    *$dk)                                  
          -0.00000021e0*sin( 5.61e0    +2.942400e0    *$dk)                                  
          +0.00000019e0*sin(-0.93e0    +0.000799e0    *$dk)                                  
          -0.00000016e0*sin( 3.22e0    +4.694014e0    *$dk)                                  
          +0.00000016e0*sin(-3.59e0    +0.006829e0    *$dk)                                  
          -0.00000016e0*sin( 1.96e0    +2.146279e0    *$dk)                                  
          -0.00000016e0*sin( 5.92e0    +15.720504e0   *$dk)                                  
          +0.00000115e0*sin(23.671e0   +83.9950108e0  *$dk)              
          +0.00000115e0*sin(17.845e0   +71.4292098e0  *$dk);
      $JJD = 2451545+$T*365.25e0;
      $D = $CODE1/100.0;
      $TETUJ = (32.23e0*($D-18.30e0)*($D-18.30e0)-15)/86400.e0;
      $JJD-=$TETUJ;
      $JJD+=0.0003472222e0;
      $this->JJD = $JJD;
      if($JJD<2299160.5e0)
        $this->JJDATEJ();
      else
        $this->JJDATE();
      if($this->JAHR == $CODE1)
        $this->affsai($nn);
    }
  return $this->JZ;
  }
}

PHP Kalender Teil 3, Zeitumstellung

8. Februar 2012 Keine Kommentare

Die aktuell gültige Regelung zur Zeitumstellung wird nach dem selben Prinzip wie die Feiertage berechnet. Seit 1996 gelten folgende Definitionen:

Beginn der Sommerzeit: letzter Sonntag im März
Ende der Sommerzeit: letzter Sonntag im Oktober

Die Termine sind in PHP nicht sonderlich schwer zu berechnen:

$sz_beginn = strtotime("last Sunday",mktime(0,0,0,4,1,$YY));
$sz_ende   = strtotime("last Sunday",mktime(0,0,0,11,1,$YY));

Möchte man aber auch die Termnie für die Zeitumstellung vor 1996 verwenden, so muss man eine Menge Sonderfälle berücksichtigen. Genaueres ist im entsprechenden Wiki-Artikel zu finden.

PHP Kalender Teil 2, Schaltjahre ermitteln

8. Februar 2012 Keine Kommentare

Diesmal wird es nur ein kurzer Beitrag, er dient eigentlich nur der Dokumentation und Erinnerung, da das Thema keine große Rolle bei meinem Projekt spielt. Schließlich werden Schaltjahre von der Funktion date() schon korrekt berücksichtigt.

Für Schaltjahre gilt eine einfache Regel, das Jahr muss ohne Rest:

  1. durch vier teilbar sein.
  2. nicht durch 100 teilbar sein,
  3. ausser es ist durch 400 teilbar.

Die Berechung kann man als kurze Funktion folgendermaßen umsetzen:

function Schaltjahr($YY) {
  return ((($YY % 4) == 0) && (($YY % 100) != 0) || (($YY % 400) == 0));
}

Als Ergebnis erhält man true oder false.

PHP Kalender Teil 1, Feiertage berechnen

7. Februar 2012 1 Kommentar

Fangen wir mit den normalen deutschen Feiertagen an, da sie relativ einfachen Regeln unterliegen. Man unterscheidet zwischen beweglichen und festen Feiertagen. Die beweglichen Feiertage richten sich dabei überwiegend nach dem Ostersonntag. Einige der nicht gesetzlichen Feiertage richten sich aber auch nach einem der Advents-Sonntage.

 
Ostersonntag berechnen

Die Berechnung des Ostersonntags ist wie folgt definiert: Der erste Sonntag nach dem ersten Vollmond im Frühling. Da sich das nicht ganz so einfach berechnen lässt, ist schon beim Lesen der Definition zu erahnen. Aber glücklicherweise gibt einem PHP geeignete Hilfsmittel an die Hand:

$ostern = easter_date($YY);

Wenn man es selbst berechnen möchte, findet man im Netz auch die passenden Formeln. Anbei eine Formel für Excel, die man sehr häufig bei Google findet:

=DATUM(B1;3;1)+REST((255-11*REST(B1;19)-21);30)+21+(REST((255-11*REST(B1;19)-21);30)+21>48)+6-REST(B1+GANZZAHL(B1/4)+REST((255-11*REST(B1;19)-21);30)+21+(REST((255-11*REST(B1;19)-21);30)+21>48)+1;7)

 
Die Adventszeit

Die Advents-Sonntage lassen sich generell leichter berechnen, dafür gibt es hier auch keine vorgefertigte PHP-Funktion. Erstmal brauchen wir nur ein Datum, vorzugsweise vom 1. Advent (erster Sonntag nach dem 26. November) oder vom 4. Advent (letzter Sonntag vor dem 25. Dezember). Ich habe mich für den 4. Advent entschieden.

$advent4 = strtotime("last Sunday",mktime(0,0,0,12,25,$YY));

Wem der Code-Schnipsel zu unverständlich ist, der setzte sich entweder mit dem PHP-Handbuch auseinander oder schaue sich den folgenden Code an, der das gleiche Ergebnis produziert:

$i=0;
$advent4 = mktime(0,0,0,12,25,$YY);
do {
  $advent4 -= 1*24*60*60;
  if(date("w",$advent4) == 0)
    $i++;
}while($i==0);

Sieht aber auch nicht viel besser aus ^^

 
Die Berechnung der Feiertage

Nachdem nun die beiden wichtigsten Zeitpunkte feststehen, macht man sich an die Berechnung der Feiertage. Da es in Deutschland leider von Bundesland zu Bundesland unterschiedliche gesetzliche Feiertage gibt, muss sich jeder die Liste ggf. auf seine eigenen Bedürfnisse zurechtstutzen. Wie die Autoren des zugehörigen Wikipedia-Artikels verzichte auch ich auf jene Feiertage, die ohnehin immer auf einen Sonntag fallen.

$YY = 2012;
$tag = 24*60*60;
$ostern = easter_date($YY);
$advent4 = strtotime("last Sunday",mktime(0,0,0,12,25,$YY));

$feiertag["neujahr"]                   = mktime(0,0,0,1,1,$YY);
$feiertag["heilige_drei_koenige"]      = mktime(0,0,0,1,6,$YY);
$feiertag["gruendonnerstag"]           = $ostern - (3*$tag);
$feiertag["karfreitag"]                = $ostern - (2*$tag);
$feiertag["ostermontag"]               = $ostern + (1*$tag);
$feiertag["tag_der_arbeit"]            = mktime(0,0,0,5,1,$YY);
$feiertag["christi_himmelfahrt"]       = $ostern + (39*$tag);
$feiertag["pfingstmontag"]             = $ostern + (50*$tag);
$feiertag["fronleichnam"]              = $ostern + (60*$tag);
$feiertag["maria_himmelfahrt"]         = mktime(0,0,0,8,15,$YY);
$feiertag["tag_der_deutschen_einheit"] = mktime(0,0,0,10,3,$YY);
$feiertag["reformationstag"]           = mktime(0,0,0,10,31,$YY);
$feiertag["allerheiligen"]             = mktime(0,0,0,11,1,$YY);
$feiertag["buss_und_bettag"]           = $advent4 - (32*$tag);
$feiertag["weihnachtstag1"]            = mktime(0,0,0,12,25,$YY);
$feiertag["weihnachtstag2"]            = mktime(0,0,0,12,26,$YY);

Einige Feiertage haben mehrere Definitionen, anhand derer man ihr Datum berechnen kann. Der Buß- und Bettag liegt wie im Code dargestellt 32 Tage vor dem 4. Advent. Er liegt aber ebenfalls am Mittwoch vor dem 23. November. Da ich erst spät über die Funktion strtotime() gelesen habe, habe ich mich in den meisten Fällen für die Nutzung des 4. Advent als Bezugspunkt bei der Berechnung entschieden. Außerdem finde ich es mit den Variablen angenehmer zu lesen, aber das muss jeder für sich selbst entscheiden. Hier zum Vergleich:

$YY=2012;

$feiertag["neujahr"]                   = mktime(0,0,0,1,1,$YY);
$feiertag["heilige_drei_koenige"]      = mktime(0,0,0,1,6,$YY);
$feiertag["gruendonnerstag"]           = strtotime("-3 days",easter_date($YY));
$feiertag["karfreitag"]                = strtotime("-2 days",easter_date($YY));
$feiertag["ostermontag"]               = strtotime("+1 day",easter_date($YY));
$feiertag["tag_der_arbeit"]            = mktime(0,0,0,5,1,$YY);
$feiertag["christi_himmelfahrt"]       = strtotime("+39 day",easter_date($YY));
$feiertag["pfingstmontag"]             = strtotime("+50 day",easter_date($YY));
$feiertag["fronleichnam"]              = strtotime("+60 day",easter_date($YY));
$feiertag["maria_himmelfahrt"]         = mktime(0,0,0,8,15,$YY);
$feiertag["tag_der_deutschen_einheit"] = mktime(0,0,0,10,3,$YY);
$feiertag["reformationstag"]           = mktime(0,0,0,10,31,$YY);
$feiertag["allerheiligen"]             = mktime(0,0,0,11,1,$YY);
$feiertag["buss_und_bettag"]           = strtotime("last Wednesday",mktime(0,0,0,11,23,$YY));
$feiertag["weihnachtstag1"]            = mktime(0,0,0,12,25,$YY);
$feiertag["weihnachtstag2"]            = mktime(0,0,0,12,26,$YY);

Nun kann man sich bei Bedarf weitere Feiertage oder sonstige Termine hinzufügen.

Mein Plan sieht eine Liste vor, die einem die Ereignisse des aktuellen Tages anzeigt sowie eine 7 Tage Vorschau. Enthalten sein sollen Geburtstage, Feiertage, Fun-Tage und ggf. auch Termine wie Konzerte und Festivals. Das ganze wird dann allerdings etwas komplexer werden als hier vorgestellt, schließlich brauch ich zur Unterscheidung noch Kategorien.

Remind, Cygwin & ein neues PHP-Projekt

7. Februar 2012 Keine Kommentare

Schon interessant, wie eins zum anderen führen kann. Grad war ich noch auf der Suche nach schönen Webseiten-Layouts, schon stolper ich über das Thema „spaßige“ Feiertage (Talk like a Pirate, etc.). Auf der Suche nach einer Übersicht selbiger kam ich dann auf eine Seite, die gleich beiden Ansprüchen gerecht wurde: mir gefiel das Layout und es gab eine Auflistung der Feiertage. Und das allerbeste…die Auflistung war maschinenlesbar. So habe ich von dem Programm Remind erfahren und war sofort begeistert davon, wie leicht man damit Terminberechnugen für fast alle denkbaren Zwecke vornehmen kann, weit über die Möglichkeiten normaler Terminverwalter hinaus.
(Ja, ich bin manchmal ein wenig sprunghaft und leicht von neuem zu begeistern ;))

Allerdings bin ich nun mal primär Windows-Nutzer. Zur Zeit ist auf keinem meiner Nutzrechner Linux installiert. Um mich mit Remind ein wenig vertraut zu machen, spielte ich daraufhin mit Cygwin herum, aber langfristig brauchte ich eine andere Lösung, die systemunabhängig funktioniert. Eine Festlegung nur auf Linux oder nur auf Windows kommt nicht in Frage, da auch auf dem Smartphone Zugriff möglich sein sollte. Und schon viel die Wahl auf PHP und erneut überkam mich der Drang, mal wieder ein kleines Projekt zu realisieren.