Teil von SELFHTML aktuell Teil von Artikel Teil von Programmiertechnik

Programmiertechnik:
Terminkalender

nach unten Antje Hofmann
nach unten Hinweise zum Thema
nach unten Beispiel und Erläuterungen
nach unten Weiterführende Links

Antje Hofmann

E-Mail: E-Mail ah@pc-anfaenger.de
Homepage-URL: deutschsprachige Seite http://pc-anfaenger.de/

Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!

nach obennach unten

Hinweise zum Thema

Viele Gemeinschaften versammeln sich in regelmäßigen Abständen. Verhältnismäßig problemlos ist die Bestimmung eines solches Termins, wenn er z.B. alle vier Wochen montags stattfindet.

Nicht immer ist es aber gewünscht, dass der Versammlungstermin auf den gleichen Wochentag fällt, da Überschneidungen die Teilnahme einiger Mitglieder unmöglich machen würden. Ideal ist es, wenn der Wochentag des Versammlungstermins regelmäßig wechselt.
Hin und wieder ist dabei gewünscht, dass ein Versammlungstermin nur auf einem Montag, Dienstag, Mittwoch oder Donnerstag fällt. Die automatische Ausgabe solcher Termine ist die Aufgabe der folgenden Scripts.

Datumsarithmetische Überlegungen

Die gewünschten Termine sollen im Rhythmus von ungefähr vier Wochen (28 Tage) und jeweils an einem anderen Wochentag stattfinden. Das bedeutet, es sind nicht exakt 28 Tage die zwischen 2 Terminen vergehen. Sichergestellt werden soll aber auch, dass über mehrere Monate der Rhythmus eingehalten wird und der durchschnittliche Abstand 28 Tage beträgt.

Eine solche Verschiebung wird erreicht, indem man nicht 28 Tage sondern 27 oder 29 Tage zwischen zwei Terminen vergehen lässt und beim Erreichen des ersten ungültigen Tages den Unterschied (plus 4 Tage bzw. minus 4 Tage) ausgleicht. Der Unterschied entspricht jeweils der Anzahl der erlaubt Wochentage.

Die folgende Tabelle zeigt, welche Termine sich ergeben können.

Termine 27 Tage Differenz Termine 29 Tage Differenz
Mi, 07.08.2002
Di, 03.09.2002
Mo, 30.09.2002
Do, 31.10.2002
Mi, 27.11.2002

27
27
31
27
Mi, 07.08.2002
Do, 05.09.2002
Mo, 30.09.2002
Di, 29.10.2002
Mi, 27.11.2002

29
25
29
29

Ausgehend von diesen Überlegungen ist es dann nur erforderlich, die jeweiligen Termine zu berechnen und alle ab dem aktuellen Tagesdatum auszugeben.

nach obennach unten

Beispiel und Erläuterungen

Die folgenden Beispiele verwenden jeweils die Standardmodule der einzelnen Programmiersprachen. Die logische Umsetzung ist in allen drei Sprachen die gleiche. Jedoch unterscheiden sie sich im Umgang mit den jeweiligen Datumsfunktionen erheblich voneinander.

In allen drei Beispielen werden die globalen Variablen starttag, abstand, sprung, anzahl und heuteText definiert.
Die Variable starttag enthält einen beliebigen Tag in üblicher Notation, der als Starttag für die Terminübersicht gilt. Dieser Tag muss nicht unbedingt ein gültiger Veranstaltungstag sein, da in diesem Fall der nächste gültige Veranstaltungstag ermittelt wird. Nicht geprüft wird, ob es sich um ein gültiges Datum handelt.
Die Variable abstand enthält die Abstände zwischen den einzelnen Terminen und die Variable sprung enthält die Anzahl der Tage, um die an einem ungültigen Tag weitergezählt wird.
Die Variable anzahl legt fest, wie viele Termine ausgegeben werden und in der Variablen heuteText ist der Text gespeichert, der ausgegeben wird, wenn am aktuellen Tag ein Termin stattfindet.
Die Definition als globale Variable ist nicht unbedingt notwendig, da die Variablen an die Funktion als Parameter übergeben werden. Sie wurden für die Beispiele aus Übersichtgründen definiert.

Beispiel für JavaScript:

Popup-Seite Anzeigebeispiel: So sieht's aus

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Bowlingtermine</title>
<script type="text/javascript">
<!-- gueltig fuer Browser ab Version 4, Internet Explorer ab Version 5

starttag = "29.04.2002";
abstand = 27;
sprung = 4;
anzahl = 10;
heuteText = "<b>Heute 20.00 Uhr Bowling im Sportcenter.<\/b>";

function fuehrendeNull(wert)
{
 if(wert < 10) return "0" + wert; else return wert;

}

function formatAusgabe(datum)
{
 var woche = new Array('So','Mo','Di','Mi','Do','Fr','Sa')
 return woche[datum.getDay()] + ", " + fuehrendeNull(datum.getDate()) + "." + fuehrendeNull((datum.getMonth()+1)) + "." + datum.getFullYear();
}

function erzeugeTermine(starttag,abstand,sprung,anzahl,heuteText)
{
 starttag = starttag.split(".")
 var datum = new Date(starttag[2],parseInt(starttag[1],10)-1,parseInt(starttag[0],10))

 var heute = new Date();

 var termine = new Array();

 while (termine.length < anzahl)
 {
   wochentag=datum.getDay();

   if (wochentag > 4 || wochentag == 0)
      datum.setDate(datum.getDate() + sprung);

   if (heute.getDate() == datum.getDate() && heute.getMonth() == datum.getMonth() && heute.getFullYear() == datum.getFullYear())
      termine[termine.length] = heuteText;
   else if (datum > heute)
       termine[termine.length] = formatAusgabe(datum);

   datum.setDate(datum.getDate() + abstand);
 }

 return "<tt>" + termine.join("<br>") + "<\/tt>";
}

//-->
</script>
</head>
<body>
<tt>Unsere Bowlingtermine:</tt>
<br><br>
<script type="text/javascript">
<!--
 document.write(erzeugeTermine(starttag,abstand,sprung,anzahl,heuteText));
//-->
</script>
</body>
</html>

Erläuterung:

Innerhalb der Funktion erzeugeTermine() wird der Starttag mittels der Methode split() in seine Bestandteile zerlegt. Anschließend wird aus diesen Werten ein neues Datumsobjekt gebildet. Dieses steht als Eingangswert für die folgenden Datumsberechnungen zur Verfügung.

Im weiteren Verlauf wird in der Variablen heute das aktuelle Datum gespeichert und das Array termine initialisiert. Dieses Array dient dazu, die ermittelten Termine zu speichern.

In der folgenden while-Schleife wird mit der Methode getDay() der für diesen Tag gültige Wochentag ermittelt und in der Variablen wochentag abgelegt. In JavaScript beginnt die Woche immer mit dem Sonntag, wobei die Zählung bei 0 beginnt. Deshalb ist es ausreichend zu prüfen, ob der Wochentag größer als 4 oder gleich 0 ist. Ist die Bedingung erfüllt, so handelt es sich um einen ungültigen Termin und zum aktuellen Datum werden 4 Tage hinzuaddiert.
Verwendet wird dazu die JavaScriptmethode setDate(), der als Parameter den mittels der Methode getDate() ermittelten Monatstag vermehrt um 4 (Inhalt der Variable sprung) übergeben wird. Die Methode setDate() verarbeitet dabei auch Angaben, die größer als die möglichen Monatstage sind und zählt intern einfach weiter.

Danach wird geprüft, ob das heutige Datum mit dem Wert in der Variablen datum übereinstimmt. Tritt dieser Fall ein, so findet am heutigen Tag eine Veranstaltung statt und im Array termine wird der Inhalt der Variablen heuteText abgelegt. Gilt diese Bedingung nicht, so wird geprüft, ob das in der Variablen datum gespeicherte Datum größer als das heutige Datum ist. Tritt dieser Fall ein, so ist ein neuer gültiger Termin gefunden und zum Array termine wird ein neues Element hinzugefügt.

Am Ende der Schleife wird mittels setDate() das in der Variablen datum gespeicherte Datum um 27 (Inhalt der Variable abstand) Tage erhöht und ein neuer Durchlauf mit dem geänderten Datumsobjekt beginnt.
Abgebrochen wird die Schleife genau dann, wenn die Anzahl der im Array termine gespeicherten Elemente mit der festgelegten Anzahl übereinstimmt.

Die Formatierung der Termine wurde in die Funktion formatAusgabe() ausgelagert. Diese Funktion erhält als Parameter das Datumsobjekt übergeben. Innerhalb der Funktion ist ein Array woche definiert. In diesem Array sind die gewünschten Schreibweisen für die Wochentage gespeichert. Der im Dateobjekt gespeicherte Wochentag entspricht dabei der Position des Wochentages im Array. Die Funktion fuehrendeNull() fügt gegebenenfalls eine führende Null vor dem Datumswert an.

Am Ende der Funktion erzeugeTermine() werden die Elemente des Arrays termine mittels der Methode join() in eine Zeichenkette umgewandelt, zurückgegeben und mittels document.write() ausgegeben.

Beispiel für PHP:

<?php

  $starttag = "29.04.2002";
  $abstand = 27;
  $sprung = 4;
  $anzahl = 10;
  $heuteText = "<b>Heute 20.00 Uhr Bowling im Sportcenter.</b>";

 function formatausgabe($datumarray)
 {
   $woche = array('So','Mo','Di','Mi','Do','Fr','Sa');

   return $woche[$datumarray["wday"]].", ".sprintf("%02d.%02d.%d",$datumarray["mday"],$datumarray["mon"],$datumarray["year"]);

 }

function erzeugeTermine($starttag,$abstand,$sprung,$anzahl,$heuteText)
{

 $starttag = explode(".",$starttag);
 $datum = mktime(0,0,0,$starttag[1],$starttag[0],$starttag[2]);

 $heute = time();
 $heutearray = getdate($heute);

 $termine = array();

 while (count($termine) < $anzahl)
 {
  $datumarray = getdate($datum);

  if ($datumarray["wday"] > 4 || $datumarray["wday"] == 0) {
      $datum = mktime(0,0,0,$datumarray["mon"],$datumarray["mday"]+$sprung,$datumarray["year"]);
      $datumarray = getdate($datum);
      }

  if ($datumarray["yday"] == $heutearray["yday"] && $datumarray["year"] == $heutearray["year"])
      $termine[] = $heuteText;
  elseif ($datum > $heute) $termine[] = formatAusgabe($datumarray);

  $datum = mktime(0,0,0,$datumarray["mon"],$datumarray["mday"]+$abstand,$datumarray["year"]);

 }

 return "<tt>".join("<br>",$termine)."</tt>";
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head>
  <title>Bowlingtermine</title>
 </head>
 <body>
  <tt>Unsere Bowlingtermine:</tt>
  <br><br>
     <?php

     echo erzeugeTermine($starttag,$abstand,$sprung,$anzahl,$heuteText);

     ?>
</body>
</html>

Erläuterung:

Innerhalb der Funktion erzeugeTermine() wird der Starttag mittels der Funktion explode() in seine Bestandteile zerlegt. Anschließend wird aus diesen Werten unter Verwendung der Funktion mktime() der UNIX-Zeitstempel für dieses Datum ermittelt. Dieser steht als Eingangswert für die folgenden Datumsberechnungen zur Verfügung.

Im weiteren Verlauf wird in der Variablen $heute der UNIX-Zeitstempel des aktuellen Datums gespeichert und in der Variablen $heutearray mittels der Funktion getDdate() die Datums- und Zeitangaben als assoziatives Array gespeichert. Das Array $termine dient dazu, die ermittelten Termine zu speichern.

In der folgenden while-Schleife wird mit der Funktion getdate() die Variable $datumarray erzeugt. Über dieses assoziatives Array ist es möglich, Angaben wie den Wochentag des Datums zu ermitteln. In PHP beginnt die Woche immer mit dem Sonntag, wobei die Zählung bei 0 beginnt. Deshalb ist es ausreichend zu prüfen, ob der Wochentag ($datumarray["wday"]) größer als 4 oder gleich 0 ist. Ist die Bedingung erfüllt, so handelt es sich um einen ungültigen Termin und zum aktuellen Datum werden 4 Tage hinzuaddiert.

PHP korrigiert wie auch JavaScript fehlerhafte Datumsangaben. Wird ein ungültiger Tag an die Funktion mktime() übergeben, so wird das Datum korrigiert und automatisch in das nächste gültige Datum verwandelt. Diese Eigenschaft wird auch in der Funktion erzeugeTermine() angewendet und zum Monatstag ($datumarray["mday"]) einfach die zusätzlichen Tage (Inhalt der Variable $sprung) addiert.

Danach wird geprüft, ob das heutige Datum mit dem Wert in der Variablen $datum übereinstimmt. Tritt dieser Fall ein, so findet am heutigen Tag eine Veranstaltung statt und im Array $termine wird der Inhalt der Variablen $heuteText abgelegt. Gilt diese Bedingung nicht, so wird geprüft, ob das in der Variablen $datum gespeicherte Datum größer als das heutige Datum ist. Tritt dieser Fall ein, so ist ein neuer gültiger Termin gefunden und zum Array $termine wird ein neues Element hinzugefügt.

Am Ende der Schleife wird mittels mktime() in der Variablen $datum ein neues um 27 (Inhalt der Variable $abstand) Tage größeres Datum als UNIX-Zeitstempel gespeichert und ein neuer Durchlauf mit dem geänderten Datum beginnt.
Abgebrochen wird die Schleife genau dann, wenn die Anzahl der im Array $termine gespeicherten Elemente mit der festgelegten Anzahl übereinstimmt.

Die Formatierung der Termine wurde in die Funktion formatAusgabe() ausgelagert. Diese Funktion erhält als Parameter das assoziative Array mit den Datumswerten übergeben. Innerhalb der Funktion ist ein Array $woche definiert. In diesem Array sind die gewünschten Schreibweisen für die Wochentage gespeichert. Der in $datumarray["wday"] gespeicherte Wochentag entspricht dabei der Position des Wochentages im Array. Die Formatierung des Datums erfolgt mittels der Funktion sprintf(). Die Angabe %d formatiert den Wert des Argumentes als Integer-Wert. Die Angabe %02d formatiert den übergebenen Wert als Integer-Wert und falls erforderlich mit führender Null.

Am Ende der Funktion erzeugeTermine() werden die Elemente des Arrays $termine mittels der Methode join() in eine Zeichenkette umgewandelt, zurückgegeben und mittels echo ausgegeben.

Beispiel für Perl:

#!/usr/bin/perl -w

use strict;
use CGI::Carp qw(fatalsToBrowser);
use Time::Local;

my $starttag = "29.04.2002";
my $abstand = 27;
my $sprung = 4;
my $anzahl = 10;
my $heuteText = "<b>Heute 20.00 Uhr Bowling im Sportcenter.</b>";

sub formatausgabe {
  my $mday = shift;
  my $mon = shift;
  my $year = shift;
  my $wday = shift;
  my @woche = qw(So Mo Di Mi Do Fr Sa);

 return $woche[$wday].", ".sprintf("%02d.%02d.%d",$mday,($mon+1),($year+1900));
}

sub erzeugeTermine {
   my $starttag = shift;
   my $abstand = shift;
   my $sprung = shift;
   my $anzahl = shift;
   my $heuteText = shift;

   $abstand = $abstand*60*60*24;
   $sprung = $sprung*60*60*24;

   my @starttag = split(/\./,$starttag);
   my $datum = timelocal(0,0,0,$starttag[0],$starttag[1]-1,$starttag[2]);
   my ($mday, $mon, $year, $wday, $yday) = (localtime($datum))[3,4,5,6,7];

   my $heute = time();
   my ($heute_year,$heute_yday ) = (localtime($heute))[5,7];

   my @termine;

   while (@termine < $anzahl) {

    if ($wday > 4 || $wday == 0) {
     $datum += $sprung;
     ($mday, $mon, $year, $wday, $yday) = (localtime($datum))[3,4,5,6,7];
    }

    if ($year == $heute_year && $heute_yday == $yday) {push(@termine,$heuteText);}
    else {
         if ($datum > $heute) {push(@termine,formatausgabe($mday,$mon,$year,$wday));}
         }

    $datum += $abstand;
    ($mday, $mon, $year, $wday, $yday) = (localtime($datum))[3,4,5,6,7];
  }

  return "<tt>".join("<br>",@termine)."</tt>";
}

print "Content-type: text/html\n\n";
print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "\n";
print "<html><head><title>Bowlingtermine</title></head><body>\n";
print "<tt>Unsere Bowlingtermine:</tt><br><br>\n";

print erzeugeTermine($starttag,$abstand,$sprung,$anzahl,$heuteText);

print "</body></html>\n";

Erläuterung:

Perl korrigiert im Gegensatz zu PHP keine fehlerhaften Datumsangaben. Für diese Sprache ist es deshalb notwendig, direkt mit dem UNIX-Zeitstempel zu rechnen. Der UNIX-Zeitstempel enthält die Anzahl der Sekunden, die seit dem 1.1.1970, 0.00 Uhr bis zum Aufrufsaugenblick vergangen sind. Da es notwendig ist, mit Sekundenwerten zu arbeiten, wird am Anfang der Subroutine erzeugeTermine() die Werte in den Variablen $abstand und $sprung in Sekunden umgerechnet.

Innerhalb der Subroutine erzeugeTermine() wird der Starttag mittels der Funktion split() in seine Bestandteile zerlegt. Anschließend wird aus diesen Werten unter Verwendung der Funktion timelocal() der UNIX-Zeitstempel für dieses Datum ermittelt. Dieser steht als Eingangswert für die folgenden Datumsberechnungen zur Verfügung. Anschließend werden mittels der Funktion localtime() die Variablen Monatstag $mday, Monat $mon, Jahr $year, Wochentag $wday und Tag des Jahres $yday mit Werten belegt.

Im weiteren Verlauf wird in der Variablen $heute der UNIX-Zeitstempel des aktuellen Datums gespeichert und für den heutigen Tag die Elemente Tag des Jahres $heute_yday und Jahr $heute_year mittels localtime() ermittelt. Das Array @termine dient dazu, die ermittelten Termine zu speichern.

In der folgenden while-Schleife wird zuerst geprüft, ob der Termin auf einen ungültigen Wochentag fällt. In Perl beginnt die Woche immer mit dem Sonntag, wobei die Zählung bei 0 beginnt. Deshalb ist es ausreichend zu prüfen, ob der Wochentag $wday größer als 4 oder gleich 0 ist. Ist die Bedingung erfüllt, so handelt es sich um einen ungültigen Termin und zum aktuellen Zeitstempel werden 4 Tage hinzuaddiert. Anschließend wird Liste der Datumsangaben aktualisiert.

Danach wird geprüft, ob das heutige Datum mit dem Wert in der Variablen $datum übereinstimmt. Tritt dieser Fall ein, so findet am heutigen Tag eine Veranstaltung statt und im Array @termine wird der Inhalt der Variablen $heuteText abgelegt. Gilt diese Bedingung nicht, so wird geprüft, ob das in der Variablen $datum gespeicherte Datum größer als das heutige Datum ist. Tritt dieser Fall ein, so ist ein neuer gültiger Termin gefunden und zum Array @termine wird ein neues Element hinzugefügt.

Am Ende der Schleife wird zur Variablen $datum der Inhalt der Variablen abstand addiert und die Liste mit der Datumsangaben wiederum aktualisiert. Anschließend beginnt ein neuer Durchlauf mit dem geänderten Zeitstempel.
Abgebrochen wird die Schleife genau dann, wenn die Anzahl der im Array @termine gespeicherten Elemente mit der festgelegten Anzahl übereinstimmt.

Die Formatierung der Termine wurde in die Subroutine formatAusgabe() ausgelagert. Diese Subroutine erhält als Parameter den Tag, den Monat, das Jahr und den Wochentag übergeben. Innerhalb dieser Subroutine ist ein Array @woche definiert. In diesem Array sind die gewünschten Schreibweisen für die Wochentage gespeichert. Der in $wday gespeicherte Wochentag entspricht dabei der Position des Wochentages im Array. Die Formatierung des Datums erfolgt mittels der Funktion sprintf(). Die Angabe %d formatiert den Wert des Argumentes als Integer-Wert. Die Angabe %02d formatiert den übergebenen Wert als Integer-Wert und falls erforderlich mit führender Null.

Am Ende der Subroutine erzeugeTermine() werden die Elemente des Arrays @termine mittels der Methode join() in eine Zeichenkette umgewandelt, zurückgegeben und mittels print ausgegeben.

Beispiel zum sinnvollen Einbinden:

<script type="text/javascript">
<!--
 document.write("<tt>Unsere Bowlingtermine:<\/tt><br><br>");
 document.write(erzeugeTermine(starttag,abstand,sprung,anzahl,heuteText));
//-->
</script>
<noscript>
   <iframe src="termin.php">
       <a href="termin.php">Unsere Bowlingtermine</a>
   </iframe>
</noscript>

Erläuterung:

Es ist sehr oft sinnvoll eine Reihe von Aufgaben an den Clientrechner abzugeben. Auch Terminkalender wie im Beispiel müssen nicht in jedem Fall serverseitig generiert werden. Das Beispiel zeigt, wie auf einfache Art und Weise JavaScript und serverseitige Anwendung gekoppelt werden können.

Hat ein Besucher JavaScript eingeschaltet, so wird der Terminkalender mittels JavaScript generiert. Ist aus irgendeinem Grunde JavaScript deaktiviert, so wirkt der Noscript-Bereich. Innerhalb dieses Bereiches ist ein Iframe definiert, welche das serverseitige Script lädt. Browser, die keine Iframes kennen, erhalten stattdessen einen Verweis zur entsprechenden Seite.

nach obennach unten

Weiterführende Links

Die folgenden Stellen werden empfohlen, um die obigen Beispiele besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.

bereichsübergreifende Seite SELFHTML: Date-Objekt
bereichsübergreifende Seite SELFHTML: Stringzerlegung mit split()
bereichsübergreifende Seite SELFHTML: Array als Zeichenkette zurückgeben

deutschsprachige Seite PHP-Manuel: getdate()
deutschsprachige Seite PHP-Manuel: mktime()
deutschsprachige Seite PHP-Manuel: Stringzerlegung mit explode()
deutschsprachige Seite PHP-Manuel: join()

bereichsübergreifende Seite SELFHTML: Listen bzw. Arrays (Variablen) in Perl
bereichsübergreifende Seite SELFHTML: Funktionen für Datum und Uhrzeit in Perl
bereichsübergreifende Seite SELFHTML: Zeichenkette in mehrere Zeichenketten aufsplitten
bereichsübergreifende Seite SELFHTML: Liste in Zeichenkette verwandeln

Teil von SELFHTML aktuell Teil von Artikel Teil von Programmiertechnik

© 2007 bereichsübergreifende Seite Impressum