# Kalender mit MySql - Anbindung



## ASchwiedy (6. Juni 2011)

Hi,

ich habe mit PHP einen Kalender gebastelt. Dieser wird mit der Funktion getCalender aufgerufen. In der Variablen $date ist der aktuelle timestamp gespeichert.

Hier die Funktion:

```
//AB HIER DIE FUNKTION getCalender
//------------------------------------------------------------------------------------------------

function getCalender($date,$headline) {
	$sum_days = date('t',$date); //$sum_days enthält die Anzahl Tage des aktuellen Monates ($date ist der aktuelle übergebene Timestamp)
	$LastMonthSum = date('t',mktime(0,0,0,(date('m',$date)-1),0,date('Y',$date)));  //$LastMonthSum enthält die Anzahl Tage des letzten Monates. Es wird nur -1 von m abgezogen.

foreach( $headline as $key => $value ) { 
	echo "<div class=\"day headline\">".$value."</div>"; 
	} echo "<div class=\"clear\"></div>";


		
for( $i = 1; $i <= $sum_days; $i++ ) { //Schleife läuft sooft wie der Monat Tage hat z.B. 31 ($sum_days)
        $day_name = date('D',mktime(0,0,0,date('m',$date),$i,date('Y',$date))); //$day_name enthält den aktuellen Tagnamen in Englisch                	
	
		
		if( $i == date('d',$date) AND date('m') == date('m',$date)) {
            echo "<div class=\"day current\">".sprintf("%02d",$i)."</div>\n"; //aktueller Tag
        } else {
            echo "<div class=\"day normal\">".sprintf("%02d",$i)."</div>\n"; //normaler Tag
        } 									 

    }
}
//ENDE getCalender
//------------------------------------------------------------------------------------------------
```

Nicht über das Layout wundern. Die Tage des Monats werden komplett nebeneinander in einer Zeile angezeigt .
Nun das Problem. Es existiert eine MySql-DB mit den Felder "id", "von" und "bis". Diese sind im Format DATE (außer id natürlich). Wie würde nun am besten die MySQL - Abfrage aussehen, damit die jeweilgen Zeiträume farbig markiert werden? Der aktueller Tag wird bereits markiert wie im Code zu sehen.

Gruss
Andi


----------



## Yaslaw (6. Juni 2011)

Du kannst es entweder im PHP in das MySQL-Datum konvertieren, oder den timestamp mit FROM_UNIXTIME() in MySQL konvertieren.

kuckst du: PHP MySQL Datumskonvertierung

Ich selber bevorzuge die Konvertierung in MySQL.


----------



## ASchwiedy (6. Juni 2011)

Danke für Deine schnelle Antwort******!!
Ja das Umwandeln stellt in der Tat kein Problem dar. Allerdings bekomm ich es einfach nicht in meinen Schädel, wie ich die mysql_query in meinem Fall gestalten muß, damit die Zeiträume auch im Kalender markiert werden...********************?


----------



## tombe (6. Juni 2011)

Also wenn $date das aktuelle Datum enthält, dann weiß die Funktion doch gar nicht welchen Zeitraum deine Abfrage liefert. Oder verstehe ich das was falsch.

Erweitere doch die Parameterliste der Funktion noch um die Werte *von* und *bis*. Damit kannst du dann den jeweiligen Zeitraum kennzeichnen.


----------



## ASchwiedy (6. Juni 2011)

$date wird bereits vor der Funktion per $_GET gesetzt. Und wenn dieses nicht gesetzt ist, dann wird einfach das aktuelle Datum gesetzt. Sorry hab meinen Code mal dahingehend erweitert, damit dieses ersichtlich ist:


```
<?php
if( isset($_GET['timestamp']) && !empty($_GET['timestamp'])){
    $date = $_GET['timestamp'];
} else {
    $date = time();
}

//AB HIER DIE FUNKTION getCalender
//------------------------------------------------------------------------------------------------

function getCalender($date,$headline) {
	$sum_days = date('t',$date); //$sum_days enthält die Anzahl Tage des aktuellen Monates ($date ist der aktuelle übergebene Timestamp)
	$LastMonthSum = date('t',mktime(0,0,0,(date('m',$date)-1),0,date('Y',$date)));  //$LastMonthSum enthält die Anzahl Tage des letzten Monates. Es wird nur -1 von m abgezogen.

foreach( $headline as $key => $value ) { 
	echo "<div class=\"day headline\">".$value."</div>"; 
	} echo "<div class=\"clear\"></div>";


		
for( $i = 1; $i <= $sum_days; $i++ ) { //Schleife läuft sooft wie der Monat Tage hat z.B. 31 ($sum_days)
        $day_name = date('D',mktime(0,0,0,date('m',$date),$i,date('Y',$date))); //$day_name enthält den aktuellen Tagnamen in Englisch	
           
	
		
         if( $i == date('d',$date) AND date('m') == date('m',$date)) {
            echo "<div class=\"day current\">".sprintf("%02d",$i)."</div>\n"; //aktueller Tag
        } else {
            echo "<div class=\"day normal\">".sprintf("%02d",$i)."</div>\n"; //normaler Tag
        } 
    }
}
//ENDE getCalender
//------------------------------------------------------------------------------------------------
```

Es soll der komplette aktuell in $date abgespeicherte Monat abgefragt werden. Wenn man nun die Mysql-Abfrage entsprechend setzt, dann müsste man die Werte von und bis doch gar nicht erst an die Funktion übergeben oder?


----------



## Yaslaw (6. Juni 2011)

Wenn ich dich richtig verstehe, willst du jeden Tag markeiren, der in irgend einer Periode in der DB gespeichert ist.

Da gibt es mehrere Ansätze

Ein Ansatz um die Termine in MySQL zu konsilidieren, damit du nur noch die Perioden hast, in denen ein Termin vorhanden ist. Also dass überscheidende Termine zusammengefasst werden

Beispiel. Meine Tabelle Termine

```
[termine]
id | name | von        | bis
-----------------------------------
1  | T1   | 2011-06-05 | 2011-06-07
2  | T2   | 2011-06-03 | 2011-06-06
3  | T3   | 2011-05-06 | 2011-05-18
```

T1 und T2 überscheniden sich. Diese zusammenfassen.

gewünschtes Resultat

```
von        | bis        | von_timestamp | bis_timestamp
-------------------------------------------------------
2011-05-06 | 2011-05-18 | 1304632800    | 1305669600
2011-06-03 | 2011-06-07 | 1307052000    | 1307397600
```
Dies erreicht man mit dem folgenden einfachen Query

```
SELECT
	von,
	MAX(bis) AS bis,
	UNIX_TIMESTAMP(von) AS von_timestamp,
	UNIX_TIMESTAMP(MAX(bis)) AS bis_timestamp
FROM
	(
		-- überlappende Termine auf dasselbe vo-Datum bringen
		SELECT
			-- Vergleichen on das `von` grösser las das letzte `bis` ist.
			-- Wenn ja, nimm das neue `von` als `von`, ansosnten das Alte
			@last_von := IF(von > @last_bis, von, @last_von) as von,
			-- letzes `bis`setzen 
			@last_bis := bis AS bis
		FROM
			-- tiefste von und bis auslesen und als Initialwert verwenden
			(SELECT @last_von := MIN(von), @last_bis := MIN(bis) FROM termine) AS vars,
			-- Alle Termine sortieren
			(SELECT * FROM termine ORDER BY von, bis) AS ordered_data
	) AS calculatet_periodes
GROUP BY
	von
```

Nun kann man im PHP einfach noch prüfen, ob ein Datum innerhalb dieser Timestamps liegt.

*2ter Ansatz*
Die andere möglichkeit ist, den Kalender gleich ganz in MySQL zu erstellen.
Als Grundlage dazu dient eine Virtuelle Tabelle für die Tage des Jahres: MySQL Virtuelle Tabelle mit allen Datums im Jahr
Nun kann man diese Tabelle mit den Terminen kreuzen

gewnünschte Ausgabe

```
mydate     | besetzt | heute
----------------------------
...
2011-06-01 | 0       | 0
2011-06-02 | 0       | 0
2011-06-03 | 1       | 0
2011-06-04 | 1       | 0
2011-06-05 | 1       | 0
2011-06-06 | 1       | 1
2011-06-07 | 1       | 0
2011-06-08 | 0       | 0
2011-06-09 | 0       | 0
2011-06-10 | 0       | 0
...
```

Das SQL dazu ist eigentlich relativ einfach. Ich gehe nicht weiter auf die Virtuelle Tabelle für die Tage pro Jahr ein, denn das hab ich weiter oben Verlinkt.

```
SELECT
	mydate,
	--Vergleichn ob das Datum innerhalb eines Termines ist
	MAX(mydate between von AND bis) AS besetzt,
	--Vergleichn ob das Datum heute ist
	MAX(mydate = DATE(SYSDATE())) AS heute
FROM
	termine,
   --Virtuelle Tabelle mit allen Daten vom letzten Jahr bis und mit in 2 Jahren
	(
        SELECT
            makedate(myYear, day_in_year) AS mydate
        FROM
            -- Zahlen von 1 bis 400 liefern.
				(
                SELECT
                    @day_in_year := @day_in_year +1 AS day_in_year
                FROM
                    (SELECT @day_in_year :=0) AS vars,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS d1,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS d2,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS d3,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS d4
            ) AS daysOfYear,
            -- Alle Jahre vom letzten Jahr bis und mit in 2 Jahren definieren
            -- Pro weiteres Jahr ein weiteren UNION setzen
            (
                SELECT YEAR(SYSDATE())-1 AS myYear 
					 UNION SELECT YEAR(SYSDATE())
                UNION SELECT YEAR(SYSDATE()) +1
					 UNION SELECT YEAR(SYSDATE()) +2) AS years
        WHERE
        		--Nur die Einträge nehmen, dessen Jahr auch mit dem Jahr übereinstimmt 
        		--(also bei den 400 Einträgen die Überflüssigen wegschmeissen)
            YEAR(makedate(myYear, day_in_year)) = myYear
	) AS daysOfYears
GROUP BY
	mydate;
```

Nun kann man noch ein Filter mit BETEWEEN da drauf setzen um aunzuchränken welche daten man ansehen will

```
..
	) AS daysOfYears
--Hier der Filter
WHERE
	mydate BETWEEN FROM_UNIXTIME({$anzeigeVonPhpTimestamp)) AND FROM_UNIXTIME({$anzeigeBisPhpTimestamp))
GROUP BY
	mydate;
```

Jetzt kann der ganze Kalender aus der DB geladen werden. Die 2 Flags `besetzt`und `heute`geben dabei auskunft darüber, wie es in PHP formatiert werden muss.
Und so müsste die angepasste Funktion in etwa aussehen

```
function getCalender($date,$headline) {
	//TODO: Deine Header-Geschichte

	//SQL definieren
$sql = <<<SQL

SELECT
	mydate,
	--Vergleichn ob das Datum innerhalb eines Termines ist
	MAX(mydate between von AND bis) AS besetzt,
	--Vergleichn ob das Datum heute ist
	MAX(mydate = DATE(SYSDATE())) AS heute
FROM
	termine,
   --Virtuelle Tabelle mit allen Daten vom letzten Jahr bis und mit in 2 Jahren
	(
        SELECT
            makedate(myYear, day_in_year) AS mydate
        FROM
            -- Zahlen von 1 bis 400 liefern.
				(
                SELECT
                    @day_in_year := @day_in_year +1 AS day_in_year
                FROM
                    (SELECT @day_in_year :=0) AS vars,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS d1,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS d2,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS d3,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS d4
            ) AS daysOfYear,
            -- Alle Jahre vom letzten Jahr bis und mit in 2 Jahren definieren
            -- Pro weiteres Jahr ein weiteren UNION setzen
            (
                SELECT YEAR(SYSDATE())-1 AS myYear 
					 UNION SELECT YEAR(SYSDATE())
                UNION SELECT YEAR(SYSDATE()) +1
					 UNION SELECT YEAR(SYSDATE()) +2) AS years
        WHERE
        		--Nur die Einträge nehmen, dessen Jahr auch mit dem Jahr übereinstimmt 
        		--(also bei den 400 Einträgen die Überflüssigen wegschmeissen)
            YEAR(makedate(myYear, day_in_year)) = myYear
	) AS daysOfYears
WHERE 
-- Den Monat und das Jahr vergleichen
	EXTRACT(YEAR_MONTH FROM mydate) = EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME({$date}))
GROUP BY
	mydate;

SQL;
	$result = mysql_query($sql);
	while($dayRow = mysql_fetch_assoc($result)){} 
			
        $day_name = date('D', $dayRow['mydate']); //$day_name enthält den aktuellen Tagnamen in Englisch  
        if($dayRow['heute']==1) {
            echo "<div class=\"day current\">".sprintf("%02d",$i)."</div>\n"; //aktueller Tag
        } elseif ($dayRow['besetzt']==1) {
            echo "<div class=\"day besetzt\">".sprintf("%02d",$i)."</div>\n"; //besetzter Tag
        } else {
            echo "<div class=\"day normal\">".sprintf("%02d",$i)."</div>\n"; //freier Tag
        } 
    }
}
```


----------



## ASchwiedy (6. Juni 2011)

Wow, danke für Deine beiden ausführlichen Vorschläge. Der Einfachkeit halber setze ich auf Idee 1.



> Nun kann man im PHP einfach noch prüfen, ob ein Datum innerhalb dieser Timestamps liegt.



Genau da liegt der Hase im Pfeffer. Das bekomm ich bei meinem Skript nicht zum laufen


----------



## Yaslaw (6. Juni 2011)

Naja, wenn du die etrste einfacher findest - ich merke gerade, dass die erste eigentlich überflüssig ist und man direkt die ganzen Termine abrufen kann.

```
function getCalender($date,$headline) {
    //TODO: Deine Header-Geschichte

    //SQL definieren
    //Nur die Termine auslesen, die innerhalb des Jahr-Monats von $date liegen
$sql = <<<SQL
SELECT
    UNIX_TIMESTAMP(von) AS von_timestamp,
    UNIX_TIMESTAMP(bis) AS bis_timestamp
FROM
    termine
WHERE
    EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME({$date})) 
        BETWEEN EXTRACT(YEAR_MONTH FROM von) 
        AND EXTRACT(YEAR_MONTH FROM bis);
    
SQL;
    //Alle Termine auslesen
    $result = mysql_query($sql);
    while($termin = mysql_fetch_assoc($result)){
        $termine[] = $termin;
    }

    $sum_days = date('t',$date);


    for( $i = 1; $i <= $sum_days; $i++ ) { //Schleife läuft sooft wie der Monat Tage hat z.B. 31 ($sum_days)
        $calDate = mktime(0,0,0,date('m',$date),$i,date('Y',$date));
        $day_name = date('D',$calDate); //$day_name enthält den aktuellen Tagnamen in Englisch                  
    
        //Datum gegen alle Termine prüfen
        $flagBesetzt = false;
        foreach($termine as $termin){
            if($termin['von_timestamp'] <= $calDate && $calDate <= $termin['bis_timestamp']){
                $flagBesetzt = true;
                break;  //im True-fall, kann die Schliefe abgebrochen werden
            }
        }
        
        if( $i == date('d',$date) AND date('m') == date('m',$date)) {
            echo "<div class=\"day current\">".sprintf("%02d",$i)."</div>\n"; //aktueller Tag
        } elseif($flagBesetzt) {
            echo "<div class=\"day besetzt\">".sprintf("%02d",$i)."</div>\n"; //besetzter Tag
        } else {
            echo "<div class=\"day normal\">".sprintf("%02d",$i)."</div>\n"; //normaler Tag
        }                                    
 
    }
}
```


----------



## ASchwiedy (6. Juni 2011)

Hab Deinen Vorschlag jetzt folgender Maßen eingebaut:


```
//AB HIER DIE FUNKTION getCalender
//------------------------------------------------------------------------------------------------

function getCalender($date,$headline) {
		$sum_days = date('t',$date); //$sum_days enthält die Anzahl Tage des aktuellen Monates ($date ist der aktuelle übergebene Timestamp)
	$LastMonthSum = date('t',mktime(0,0,0,(date('m',$date)-1),0,date('Y',$date)));  //$LastMonthSum enthält die Anzahl Tage des letzten Monates. Es wird nur -1 von m abgezogen.
	
	
foreach( $headline as $key => $value ) { 
	echo "<div class=\"day headline\">".$value."</div>"; 
	} echo "<div class=\"clear\"></div>";


//SQL definieren

$sql = mysql_query("SELECT UNIX_TIMESTAMP(von) AS von_timestamp, UNIX_TIMESTAMP(bis) AS bis_timestamp FROM reservations WHERE EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME({$date})) BETWEEN EXTRACT(YEAR_MONTH FROM von) AND EXTRACT(YEAR_MONTH FROM bis)");
		
		$result = mysql_query($sql);
    while($termin = mysql_fetch_assoc($result)){
        $termine[] = $termin;
    }		

		
  for( $i = 1; $i <= $sum_days; $i++ ) { //Schleife läuft sooft wie der Monat Tage hat z.B. 31 ($sum_days)
        $calDate = mktime(0,0,0,date('m',$date),$i,date('Y',$date));
        $day_name = date('D',$calDate); //$day_name enthält den aktuellen Tagnamen in Englisch                  
    
        //Datum gegen alle Termine prüfen
        $flagBesetzt = false;
        foreach($termine as $termin){
            if($termin['von_timestamp'] <= $calDate && $calDate <= $termin['bis_timestamp']){
                $flagBesetzt = true;
                break;  //im True-fall, kann die Schliefe abgebrochen werden
            }
        }
        
        if( $i == date('d',$date) AND date('m') == date('m',$date)) {
            echo "<div class=\"day current\">".sprintf("%02d",$i)."</div>\n"; //aktueller Tag
        } elseif($flagBesetzt) {
            echo "<div class=\"daybooked\">".sprintf("%02d",$i)."</div>\n"; //besetzter Tag
        } else {
            echo "<div class=\"day normal\">".sprintf("%02d",$i)."</div>\n"; //normaler Tag
        }                                    
 
    }
}  
//}
//ENDE getCalender
//------------------------------------------------------------------------------------------------
```

Der Kalender wird zwar ohne Fehlermeldung korrekt ausgegeben, jedoch werden die Zeiträume nicht markiert. Folgende 2 Zeiträume habe ich testhalber in der DB:

von          |          bis          |          von_timestamp          |          bis_timestamp
2011-06-08        2011-06-15        1307484000                         1308088800
2011-06-25        2011-06-28        1308952800                         1309212000

Der Kalender zeigt beim Aufruf ja automatisch den aktuellen Monat (Juni) an. Die beiden Zeiträume sollten also erscheinen, tun sie allerdings nicht


----------



## Yaslaw (6. Juni 2011)

Warum verhunzt du meinen Code?  
Was soll das genau bewirken? Da ist einmal mysql_query() zuviel.

```
$sql = mysql_query("SELECT UNIX_TIMESTAMP(von) AS von_timestamp, UNIX_TIMESTAMP(bis) AS bis_timestamp FROM reservations WHERE EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME({$date})) BETWEEN EXTRACT(YEAR_MONTH FROM von) AND EXTRACT(YEAR_MONTH FROM bis)");
        
        $result = mysql_query($sql);
```

$sql ist ein String. Um die SQL-Struktur zu behalten habe ich einfach die Heredoc-Schreibweise verwendet


----------



## ASchwiedy (6. Juni 2011)

ach gottchen...den query hab ich ganz übersehn  Hab alles paar mal überflogen, aber des ist mir echt entgangen...Manchmal sieht man eben den Wald vor lauter Bäume nicht
Jedenfalls..ES FUNKTIONIERT! Gute Arbeit  Und 1000x Danke!


----------



## ASchwiedy (7. Juni 2011)

Hi Du,

mir ist da gerade noch etwas eingefallen. Hab den Kalender etwas erweitert. Ist es jedoch mit Deinem MySQL-Query auch möglich die Tage heraus zu picken, welche sich überschneiden? Also z.B. ein Termin vom 01.06.11-05.06.11. Es wird im Kalender dank Deine Hilfe der Zeitraum vom 01.-05. korrekt markiert. Jetzt folgt in der DB ein weiterer Eintrag der vom 05.06.11-10.06.11 geht. Der 05.06. ist also doppelt belegt. Einmal als Ende vom 1. Termin und einmal als Start vom 2. Termin. Solche Tage möchte ich gern extra markieren und nicht nur als belegt anzeigen. Also ich denk da immer komplizierter und mir qualmt schon der Kopf


----------



## Yaslaw (7. Juni 2011)

ersetze die Schleife durch

```
$count = 0
        foreach($termine as $termin){
            if($termin['von_timestamp'] <= $calDate && $calDate <= $termin['bis_timestamp']){
                $count++;
            }
        }
```
Und prüfe nachher $count.
$count = 0 -> kein Termin
$count = 1 -> 1 Termin
$count > 1 ->mehrere Termine


----------



## ASchwiedy (8. Juni 2011)

kann es sein, dass ich dann den break da aus der Schleife nehmen muß?


----------



## Yaslaw (8. Juni 2011)

SIehe meine Antwort oben. Da ist kein break mehr in der Schleife.
Währe der Break drin, dann kähme es nie über 1 und du wüsstest nicht, dass meherere Termine vorhanden sind.


----------

