Timestamp für Wochenbeginn und -ende errechnen
Christian
- php
Hallo,
ich möchte aus einer gegebenen Woche und einem Jahr den Timestamp für 00:00:00 Montag und 23:59:59 Sonntag errechnen.
Bisher sieht meine Funktion so aus:
function firstmonday($year) {
$fourth = mktime(0,0,0,1,4,$year);
$weekday = date("w",$fourth);
if ($weekday == 0) {$weekday = 7;}
$firstmonday = $fourth-($weekday-1)*86400;
return $firstmonday;}
function week($week, $year) {
$firstmonday = firstmonday($year);
$week = $week-1;
$weekmonday = strtotime("@$firstmonday +$week weeks");
$weeksunday = strtotime("@$weekmonday +1 week -1 second");
$week = array($weekmonday,$weeksunday);
return $week;}
Funktioniert auch einigermaßen, die Funktion kommt nur nicht mit Sommer- und Winterzeit zurecht.
Ausgabe für diese Woche (per date()umgewandelt):
01:00:00, 15.10.2007
00:59:59, 22.10.2007
Das Problem hierbei ist, dass ich jeweils Datensätze einer Woche auslesen möchte, also zwischen 00:00:00 Montag und 23:59:59 Sonntag. Der $timestamp für das Datum eines Eintrags ist jedoch 00:00:00 des jeweiligen Tages. Der heutige Eintrag (00:00:00, 15.10.2007) wird also noch zur letzten Woche gezählt.
Wie kann ich jetzt Sommer- und Winterzeitverschiebungen ausgleichen?
Danke,
Christian
Hello,
vielleicht ist das ein Denkansatz?
$zeit1 = strtotime('01.01.2007 00:00');
echo $zeit1."<br>\n";
echo date ('d.m.Y H:i:s', $zeit1)."<br>\n";
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Wie kann ich jetzt Sommer- und Winterzeitverschiebungen ausgleichen?
mktime() verfügt über einen gesonderten Parameter, der besagt, ob Sommer- oder Winterzeit verwendet wird:
mktime(h,m,s,M,D,Y,param_sz)
Die Werte sind 1 (Sommerzeit), 0 (Winterzeit) und -1 (Default).
Zeitumstellung ist jeweils von Samstag auf Sonntag am letzten Wochenende im März (2 auf 3 Uhr) und Oktober (3 auf 2 Uhr).
Viel Spaß beim Knobeln ;-)
Hello,
Viel Spaß beim Knobeln ;-)
ist wirklich keine triviale Aufgabe mit den vorhandenen Funktionen.
Jedenfalls habe ich gestern noch ein paarmal im Kreis herumgedacht.
Im Prinzip wird es am günstigsten sein, wenn man sich eine Liste generieren lässt.
anfangen vom 01.01. des Jahres
Array aufbauen für mindestens 7 Tage.
Den Sonntag suchen.
Auf denn Sonntag folgt automatisch der Montag (zum Glück)
Die KWs dazu bestimmen.
Array mit den KWs aufbauen (entweder 52 oder 53) * 2 (für Montag und Sonntag) [1]
indem man einfach immer 7 Tage draufzählt mit strtotime()
und dann mittels des Denkansatzes aus dem anderen Posting
$zeit1 = strtotime('01.01.2007 00:00');
echo $zeit1."<br>\n";
echo date ('d.m.Y H:i:s', $zeit1)."<br>\n";
die exakten Timestamps bestimmen.
Das gesamte Array dann serialisieren und als Datei abspeichern für das Jahr.
Beim nächsten Zugriff steht diese Datei dann zur Verfügung und muss nicht jedes Mal neu berechnet werden. Allerdings habe ich nicht ausprobiert, was länger dauert: Dateizugriff + Deserialisieren oder neu berechnen.
[1] oder eben gleich für das ganze Jahr einen Kalender aufbauen mit allen relevanten Daten
$_calendar['date'][1] ## Y-m-d damit es sortierfähig ist
['kw'][1] ## zu welcher KW gehört der Tag
['dayofweek'][1] ## Nummer des Wochentages 1 = Montag 7 = Sonntag o.ä.
['dayofmonth'][1] ## Nummer des Tages im Monat 1..31
['month'][1] ## Nummer des Monats 1..12
['min_timestamp'][1] ## Zeitstempel um 00:00:00
['max_timestamp'][1] ## Zeitstempel um 23:59:59
$_calendar['date'][2]
['kw'][2]
['dayname'][2]
['dayofmonth'][2]
['month'][2]
['min_timestamp'][2]
['max_timestamp'][2]
Der index steht für 'dayofyear'
Man kann so das Array einmal berechnen, nach allen Spalten sortieren lassen, serialisieren und abspeichern. Dadurch hat man dann nach dem Laden und deserialisieren immer gleich direkten Zugriff auf alle zusammengehörigen Daten jeden Tages über die Arrayfunktionen und den Index.
Für solche wiederkehrenden Aufgaben sind Listen manchmal der praktikablere Weg.
Wenn einer Lange Weile hat, könnte er/sie ja mal teten, wie lange die Berechnung der gesamten Liste im Mittel dauert.
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hallo Tom,
ist wirklich keine triviale Aufgabe mit den vorhandenen Funktionen.
Trivial nicht, aber auch nicht überaus kompliziert, siehe mein anderes Posting.
Jedenfalls habe ich gestern noch ein paarmal im Kreis herumgedacht.
Hmm, ich sollte wirklich mal einen Artikel zum Thema Datums- und Zeitberechnung schreiben, viele Leute scheinen sich damit wirklich schwerzutun.
Im Prinzip wird es am günstigsten sein, wenn man sich eine Liste generieren lässt.
Nicht wirklich "am günstigsten", siehe mein anderes Posting. Dein Ansatz würde zwar vermutlich funktionieren (ich hab ihn mir jetzt nicht GANZ genau angesehen), wäre aber relativ ineffizient, selbst wenn Du das ganze serialisierst.
Viele Grüße,
Christian
Hello Christian,
Jedenfalls habe ich gestern noch ein paarmal im Kreis herumgedacht.
Aber scheinbar auch noch nicht genug ...
Hmm, ich sollte wirklich mal einen Artikel zum Thema Datums- und Zeitberechnung schreiben, viele Leute scheinen sich damit wirklich schwerzutun.
Das wäre sicherlich gut.
Allerdings sehe ich die Falle für eine seblstgebastelte Lösung noch ganz woanders.
Man muss ja auch sicherstellen, dass die Länder- und zeitzoneneinstellungen des Servers und der Installation berücksichtigt werden.
Sommer- und Winterzeit gibt es nicht überall auf der Welt, die Umstellungstage sind wohl auch unterschiedlich und die Anzahl der Stunden (?) auch?
Außerdem sind die Kalender dieser Erde zum teil auch noch vollkommen unterschiedlich.
Ich könnte jetgzt noch nicht einmal sagen, ob alle Völker dieser Erde die Wochentage Montag bis Sonntag haben...
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hallo Tom,
Allerdings sehe ich die Falle für eine seblstgebastelte Lösung noch ganz woanders.
Man muss ja auch sicherstellen, dass die Länder- und zeitzoneneinstellungen des Servers und der Installation berücksichtigt werden.
Deswegen nutzt meine erste Lösung für PHP > 5.2 auch das DateTime-Objekt (das macht das automatisch) und meine zweite Lösung eben mktime(), d.h. es macht nur die Kalenderberechnung (für das Datum!) selbst, nicht jedoch das Berechnen des Timestamps, den ich in dem Fall PHP und damit dem Betriebsystem überlasse. Sprich: Außer, der Rechner ist irgendwie kaputtkonfiguriert erhalte ich selbst bei meiner "manuellen" Lösung richtige Ergebnisse und muss mich um Sommer- und Winterzeit nicht kümmern.
Sommer- und Winterzeit gibt es nicht überall auf der Welt, die Umstellungstage sind wohl auch unterschiedlich und die Anzahl der Stunden (?) auch?
Ja. Das ist eine ziemlich lustige Geschichte. Zum einen können sich die Tage ändern, zum Beispiel hat vor kurzem die USA das umgestellt, sprich 2004 hat man noch zu anderen Tagen umgestellt, als 2007, zum anderen gibt's so lustige Länder, die das jedes (halbe) Jahr per Parlamentsbeschluss neu festlegen, dann gibt's noch Länder, die sich nach anderen Kalendern richten (Israel richtet sich nach Feiertagen im hebräischen Kalender). Und dann gibt's noch die Südhalbkugel, wo im Dezember Sommer und im Juli Winter ist. Ferner kann sich in den USA jeder Bundesstaat (evtl. sogar noch kleinere Einheiten) selbst aussuchen, ob sie der Sommerzeit nutzen wollen oder lieber nur in Standardzeit (= Winterzeit) bleiben und gar keine Umtsellung haben wollen, zudem gibt's in großen Ländern wie den USA oder Russland verschiedene Zeitzonen abhängig von der geographischen Lage.
In der Regel wird heutzutage bei Sommerzeit eine Stunde vorgestellt. Mir sind keine aktuellen Ausnahmen bekannt, was aber nicht heißt, dass es keine gibt. Es gab aber zum Beispiel hier in Deutschland früher mal eine Zeit lang die sogenannte "Midsommerzeit", d.h. für etwa einen Monat während des Sommers selbst wurde nochmal zusätzlich eine Stunde weitergestellt (d.h. man hatte GMT+1 im Winter, im Frühjahr und Herbst hatte man GMT+2 und im Sommer GMT+3).
Im Internet gibt's eine frei verfüg- und benutzbare "Datenbank" mit Zeitzoneninformationen, die auch alle möglichen Umstellungen der Sommer- und Winterzeit beinhaltet, teilweise bis ins 19. Jahrhundert zurück: http://www.twinsun.com/tz/tz-link.htm Diese wird auch von den meisten UNIXartigen Betriebsystemen heutzutage verwendet. Microsoft braut bei Windows mal wieder ein eigenes Süppchen, das Unicode-Consortium bietet aber im Rahmen des "Common Locale Data Repository" (http://unicode.org/cldr/) eine XML-Datei an, die die Zeichenketten, die in der Windows-Registry stehen, Zeitzonen aus der Olson-Datenbank zuordnet.
Außerdem sind die Kalender dieser Erde zum teil auch noch vollkommen unterschiedlich.
Ja klar - allerdings ist die Frage, was in diesem Fall relevant ist. In Israel, vielen muslimischen Staaten oder China (die besitzen alle einen eigenen Kalender) wird für viele Dinge - insbesondere, was Kommunikation angeht - der gregorianische Kalender (also unserer) verwendet - zudem standardisiert ISO 8601 ebendiesen gregorianischen Kalender. Und fast alle Computersysteme können auch nur den gregoriansichen Kalender berechnen.
Ich könnte jetgzt noch nicht einmal sagen, ob alle Völker dieser Erde die Wochentage Montag bis Sonntag haben...
Alle vermutlich nicht, aber sogar z.B. die Chinesen kennen (und kannten früher schon) 7 Wochentage, d.h. die Unterteilung "Woche" scheint recht universell zu sein. Die Frage ist, woher das ursrpünglich kam oder ob sich das nicht sogar unabhängig voneinander entwickelt hat...
Andererseits ist zumindest die ganze Diskussion um andere Kalender ziemlich irrelevant für diese Fragestellung, da hier ja eine Frage gestellt wurde, die ganz klar auf den hiesigen Kalender gemünzt war.
Viele Grüße,
Christian
Hello,
Danke Dir, das ist schon mal ziemlich interessant.
Andererseits ist zumindest die ganze Diskussion um andere Kalender ziemlich irrelevant für diese Fragestellung, da hier ja eine Frage gestellt wurde, die ganz klar auf den hiesigen Kalender gemünzt war.
Wenn man eine "universelle" Funktion baut, müsste man aber trotzdem darüber machdenken.
Ich hatte nämlich vorhin nur mal so aus Spaß angefangen, das von der Basis her aufzubauen, also ganz ohne die Kalenderfunktionen. Da fängt es schon an, dass man irgendwo einen Referenztag haben muss (also wie Unix das z.B. mit dem 01.01.1970 macht. Von dem muss man eben wissen, welcher Wochentag dazugehörte...
Und dann kommen die Fragen zu Schaltjahr und wieviele Wochen hat das Jahr.
Ok, dafür gibt es logische Bedingungen.
Normales Jahr: Wenn der 04. Januar ein Do ist, hat das Jahr 53KWs
Schaltjahr: Wenn der 04. Januar ein Mi oder ein Do ist, hat das Jahr 53KWs
usw.
Und was war mit dem Jahr, als der Gregorianische Kalender eingeführt wurde?
Ich habe mal vor 20 Jahren in Pascal eine Datumsreferenz-Funktion geschrieben, unwissend, was es da alles für Abweichungen gab. Die begann beim Jahre 1 n Chr. Geburt. Die letzten 350 Jahre mag sie dann vielleicht auch gestimmt haben, soweit nicht auch Schaltjahre ausgefallen sind... usw, usw.
Wenn man über solche Dinge näher nachdenkt, stellt ,man doch immer wieder fest, dass die "Experten" selten alles bedacht haben, als sie ihre Implementation der Ver 1.0 erfunden haben.
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hallo Tom,
Andererseits ist zumindest die ganze Diskussion um andere Kalender ziemlich irrelevant für diese Fragestellung, da hier ja eine Frage gestellt wurde, die ganz klar auf den hiesigen Kalender gemünzt war.
Wenn man eine "universelle" Funktion baut, müsste man aber trotzdem darüber machdenken.
Ich hatte sowas mal angefangen: </archiv/2006/9/t137256/#m891605>
Ich hatte nämlich vorhin nur mal so aus Spaß angefangen, das von der Basis her aufzubauen, also ganz ohne die Kalenderfunktionen. Da fängt es schon an, dass man irgendwo einen Referenztag haben muss (also wie Unix das z.B. mit dem 01.01.1970 macht. Von dem muss man eben wissen, welcher Wochentag dazugehörte...
Mein Vorschlag ist und bleibt die julianische Tageszahl, d.h. die Anzahl an Tage seit dem 1.1.4713 v.u.Z. JC (das war ein Montag). Algorithmen zur Konvertierung findest Du in meiner Klassensammlung bzw. inzwischen effizientere Versionen in meinen Postings hier im Thread bzw. hier im Thread verlinkt.
Und dann kommen die Fragen zu Schaltjahr und wieviele Wochen hat das Jahr.
Ok, dafür gibt es logische Bedingungen.Normales Jahr: Wenn der 04. Januar ein Do ist, hat das Jahr 53KWs
Schaltjahr: Wenn der 04. Januar ein Mi oder ein Do ist, hat das Jahr 53KWs
Wie viele Wochen ein Jahr hat, ist irrelevant für fast alle Berechnungen. Wichtig ist: Wann fängt ein Jahr an? Das ist die einzig nötige Information sowohl für das Hin- als auch das Rückrechnen.
Ok, erstmal noch allgemeine Infos: Wir reden hier über Kalenderwochen nach ISO8601 (wenn jemand was anderes meint, müsste er erstmal definieren, was er will). Das heißt: Der zugrundeliegende Kalender ist gregorianisch - auch proleptisch, d.h. sogar VOR 15. Oktober 1582 gültig - auch wenn's ihn damals noch nicht gab.
Wie ist nun der Jahres-Wochenkalender in ISO8601 definiert? Die Jahre orientieren sich an den Jahren des gregorianischen Kalenders, sind aber an Wochen ausgerichtet. Die erste Woche ergibt sich durch die Woche, die den 4. Januar enthält, das Jahr fängt immer am Montag an (damit fangen einige Kalenderwochen-Jahre z.B. auch am 31. Dezember des vorigen "normalen" Jahres an) und hat 52 oder 53 Wochen, was sich nach dem Jahresanfang des nächsten Jahres richtet. Zur Unterscheidung nenne ich mal die Wochen-Jahreszahl im folgenden "WJahr" und die normale Jahreszahl "Jahr".
Nun zu dem allgemeinen Verfahren wie man (Jahr, Monat, Tag) in (WJahr, Woche, Wochentag) umrechnet. Im folgenden werde ich das ganze mit Pseudocode erklären, folgende Funktionen existieren:
* DatumZuKontinuierlich (Jahr, Monat, Tag)
Wandelt das Datum in eine kontinuierliche Tageszählung um, d.h. irgend
eine Zählung, die die Anzahl an Tagen ab einem Referenzdatum angibt.
* KontinuierlichZuDatum (Zahl)
Wandelt ein Datum in kontinuierlicher Tageszählung in ein normales
Datum um.
* Wochentag (Zahl)
Gibt den Wochentag eines Datums in kontinuierlicher Tageszählung an,
dabei 0 = Montag, 1 = Dienstag, ..., 6 = Sonntag
Wie wandelst Du nun ein Datum (Jahr, Monat, Tag) in (WJahr, Woche, Wochentag) um?
TAG_ZAHL = DatumZuKontinuierlich (Jahr, Monat, Tag)
JAHR_ANFANG = DatumZuKontinuierlich (Jahr, 1, 4)
JAHR_ANFANG = JAHR_ANFANG - Wochentag (JAHR_ANFANG)
WENN (TAG_ZAHL < JAHR_ANFANG):
JAHR_ANFANG = DatumZuKontinuierlich (Jahr - 1, 1, 4)
JAHR_ANFANG = JAHR_ANFANG - Wochentag (JAHR_ANFANG)
WJahr = Jahr - 1
SONST:
WJahr = Jahr
Woche = ABRUNDEN ((TAG_ZAHL - JAHR_ANFANG) / 7) + 1
Wochentag = Wochentag (TAG_ZAHL)
Wie wandelst Du ein Datum (WJahr, Woche, Wochetag) in (Jahr, Montag, Tag) um?
JAHR_ANFANG = DatumZuKontinuierlich (WJahr, 1, 4)
JAHR_ANFANG = JAHR_ANFANG - Wochentag (JAHR_ANFANG)
TAG_ZAHL = JAHR_ANFANG + (Woche - 1) * 7 + Wochentag
(Jahr, Monat, Tag) = KontinuierlichZuDatum (TAG_ZAHL)
Sprich: Mit dem Jahresanfang kommst Du wunderbar aus, die Anzahl an Wochen in einem Jahr interessieren Dich für die Berechnung von Kalenderwochen nach ISO8601 erst einmal nicht. Wenn Du aber doch die Anzahl an Wochen wissen willst, kannst Du das so machen (gegeben sei Jahr):
JAHR_ANFANG = DatumZuKontinuierlich (Jahr, 1, 4)
JAHR_ANFANG = JAHR_ANFANG - Wochentag (JAHR_ANFANG)
NJAHR_ANFANG = DatumZuKontinuierlich (Jahr + 1, 1, 4)
NJAHR_ANFANG = NJAHR_ANFANG - Wochentag (NJAHR_ANFANG)
WochenImJahr = (NJAHR_ANFANG - JAHR_ANFANG) / 7
Und was war mit dem Jahr, als der Gregorianische Kalender eingeführt wurde?
Als der gregorianische Kalender _WO_ eingeführt wurde? ;-) Im Vatikan wurde er offiziell 1582 eingeführt und die Griechen haben im irgendwann um 1920 herum umgestellt.
Viele Grüße,
Christian
Moin!
Ich könnte jetgzt noch nicht einmal sagen, ob alle Völker dieser Erde die Wochentage Montag bis Sonntag haben...
Alle vermutlich nicht, aber sogar z.B. die Chinesen kennen (und kannten früher schon) 7 Wochentage, d.h. die Unterteilung "Woche" scheint recht universell zu sein. Die Frage ist, woher das ursrpünglich kam oder ob sich das nicht sogar unabhängig voneinander entwickelt hat...
Eine der letzten Karambolage-Sendungen auf arte hat die Herkunft der Wochentagsnamen im Deutschen und Französischen beleuchtet.
Die französischen Tagesnamen leiten sich von den römischen Göttern ab (die deutschen mehrheitlich von deren germanischer Entsprechung), von denen es sieben Stück gab. Und diese Götter basieren auf den mit bloßem Auge beobachtbaren beweglichen Himmelskörpern unseres Sonnensystems. Und da vermutlich auch alle anderen Völker sieben Himmelskörper beobachtet haben, ist es nicht komplett unwahrscheinlich, dass sie ebenfalls die aufeinanderfolgenden Tage nacheinander reihum irgendwie einer Gottheit oder einem Himmelskörper gewidmet haben, denn das Firmament hatte, das kann man ohne unzulässige Verallgemeinerung behaupten, in eigentlich allen Kulturen eine besondere Bedeutung.
Insofern dürfte es keine wahnsinnige Kunst gewesen sein, an mehreren Orten der Erde zugleich auf eine siebentägige Periode der Tagesabfolge zu kommen.
Andererseits hat sich der europäische Kulturraum ja durchaus expansiv in die Welt verbreitet - wer fragte denn damals nach irgendwelchen anderen Kalendern dann untergegangener Kulturen?
- Sven Rautenberg
Hello,
Andererseits hat sich der europäische Kulturraum ja durchaus expansiv in die Welt verbreitet - wer fragte denn damals nach irgendwelchen anderen Kalendern dann untergegangener Kulturen?
Das hieße also, dass der "Inka-Kalender" ganz anders gestaltet gewensen sein könnte?
Und bei dem unserer "Sparpolitiker" hat die Woche dann demnächst wohl 11 Tage?
Wäre doch noch eine Idee, um 19 bis 20 Wochenenden im Jahr einzusparen... :-(
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hello,
noch ein Link auf eine Seite http://www.kalendersysteme.de/deutsch/
Wie gut die Informationen der Seite sind, habe ich aber noch nicht untersucht...
vielleicht gibt es noch mehr Seiten, die das ganze abarbeiten.
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hallo Christian,
Das Problem hierbei ist, dass ich jeweils Datensätze einer Woche auslesen möchte, also zwischen 00:00:00 Montag und 23:59:59 Sonntag.
Warum fragst du nicht einfach alle Datensätze ab, deren Wochennummer mit der des gegebenen Datums identisch ist?
Grüße aus Nürnberg
Tobias
Hello,
Warum fragst du nicht einfach alle Datensätze ab, deren Wochennummer mit der des gegebenen Datums identisch ist?
Das wäre ja zu einfach ;-)
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom
Hallo Tobias,
Das Problem hierbei ist, dass ich jeweils Datensätze einer Woche auslesen möchte, also zwischen 00:00:00 Montag und 23:59:59 Sonntag.
Warum fragst du nicht einfach alle Datensätze ab, deren Wochennummer mit der des gegebenen Datums identisch ist?
Dann muss aber zu dem Timestamp jeweils immer die Wochennummer berechnet werden, was ineffizienter ist, als einmalig Start- und Endtimestamp für die jeweilige Woche zu erhalten und damit einzugrenzen. Zudem ist es - sofern die Timestamps in einer Datenbank gespeichert sind - möglich, beim Eingrenzen über Start- und Enddatum einen Index zu verwenden - bei der Verwendung von Funktionen, die etwas mit dem Datum machen, bevor es verglichen wird, muss jeder Datensatz, der nicht durch andere Kriterien eingeschränkt werden kann, eingelesen werden. Das kann bei vielen Datensätzen sehr ineffizient werden. Ich kann also durchaus verstehen, warum Christian hier so vorgehen will.
Viele Grüße,
Christian
Hallo Christian,
[Zu gegebener Woche einen ]
Wie kann ich jetzt Sommer- und Winterzeitverschiebungen ausgleichen?
Indem Du Sekunden auf Timestamps addierst: Gar nicht. Denn Timestamps sind kontinuierlich, unsere Zeit jedoch nicht (wegen der Sommer-/Winterzeitumstellung). Die einzige Möglichkeit, hier sinnvoll etwas zu machen, ist über Kalenderberechnungen, d.h. Dir erst den jeweiligen Tag zusammensuchen und Dir dann den Timestamp daraus und aus der Uhrzeit zu basteln.
PHP ab Version 5.2 bietet Dir die Möglichkeit, mit DateTime-Objects herumzuspielen und dort eine ganze Menge kalendarischer Berechnungen für Dich durchführen zu lassen. In Deinem Fall wäre zum Beispiel folgender Code zielführend:
function wocheInfo ($jahr, $woche) {
$datum = new DateTime;
// montag holen
$datum->setISODate ($jahr, $woche, 1);
$datum->setTime (0, 0, 0);
$start = (int)($datum->format ('U'));
// sonntag holen
$datum->setISODate ($jahr, $woche, 7);
$datum->setTime (23, 59, 59);
$ende = (int)($datum->format ('U'));
// zurückgeben
return array ($start, $ende);
}
Die Methode setISODate übernimmt dabei die komplette Wochenberechnung für Dich.
Wenn Du eine ältere PHP-Version hast, bleibt Dir immer noch die Möglichkeit, die Kalenderberechnung selbst durchzuführen. In meinen Augen ist es dabei am sinnvollsten, julianische Tageszahlen zur Kalenderberechnung zu verwenden. Hier wäre eine Implementierung für PHP < 5.2 (inklusive PHP 4):
// Hilfsfunktion: Julianische Tageszahl in Jahr, Monat und Tag
// Zurückrechnen.
// (Siehe [link:http://de.wikipedia.org/w/index.php?title=Julianisches_Datum&oldid=18407571])
function julZuDatum ($jul_tag_zahl) {
$korrektur = floor (($jul_tag_zahl - 1867216.25) / 36524.25);
$jul_tag_zahl = $jul_tag_zahl + 1 + $korrektur - floor ($korrektur / 4);
// Hilfsvariablen anlegen
$B = $jul_tag_zahl + 1524;
$C = floor (($B - 122.1) / 365.25);
$D = floor (365.25 * $C);
$E = floor (($B - $D) / 30.6001);
// Ergebnis
$tag = (int)($B - $D - floor (30.6001 * $E));
$monat = (int)($E < 14 ? $E - 1 : $E - 13);
$jahr = (int)($monat > 2 ? $C - 4716 : $C - 4715);
return array ($jahr, $monat, $tag);
}
// Wochen-Start und Ende einer ISO8601-Woche in einem gegebenen
// Jahr bestimmen (als UNIX-Timestamp)
function wocheInfo ($jahr, $woche) {
// Erster Schritt: Hole die julianische Tageszahl des 4. Januars
// des gegebenen ISO8601-Jahres.
$jahrhundert = (int) (($jahr + 4799) / 100) - 48;
$korrektur = 2 - $jahrhundert + (int) (($jahrhundert + 48) / 4) - 12;
$jahr_start = (int) (365.25 * ($jahr + 4715)) + $korrektur - 1092;
// Nun den Wochentag abziehen, damit wir den Start des ISO8601-Jahres
// erhalten
$jahr_start -= ($jahr_start % 7);
// Start- und Enddatum erzeugen
$start_datum = $jahr_start + ($woche - 1) * 7;
$end_datum = $start_datum + 6;
// Wieder zurück in Jahr/Monat/Tag rechnen
$start_datum = julZuDatum ($start_datum);
$end_datum = julZuDatum ($end_datum);
// Gib zurück
return array (mktime (0, 0, 0, $start_datum[1], $start_datum[2], $start_datum[0]),
mktime (23, 59, 59, $end_datum[1], $end_datum[2], $end_datum[0]));
}
(Da der Code Sommer- und Winterzeit selbst nicht berücksichtigt - und in der Kürze auch nicht kann - muss dies eben PHP machen, und damit braucht man mktime() und damit muss überhaupt die julianische Tageszahl erst zurückgerechnet werden auf Jahr, Monat und Tag - wenn man das Zeitzonenoffset bereits kennt, lässt sich eine julianische Tageszahl VIEL einfacher in einem Timestamp verwandeln.)
Viele Grüße,
Christian
Hallo nochmal,
Ich habe mir nochmal überlegt, ob man die Variante für PHP < 5.2 nicht auch verständlicher implementieren könnte. Vielleicht erklärt das auch viel besser, welche Herangehensweise bei Datumsberechnungen sinnvoll ist.
Dabei bin ich auf folgende Lösung gekommen:
// Wochen-Start und Ende einer ISO8601-Woche in einem gegebenen
// Jahr bestimmen (als UNIX-Timestamp)
function wocheInfo ($jahr, $woche) {
// Stelle fest, ob das Jahr ein Schaltjahr ist
$schaltjahr = (int)($jahr % 4 == 0 && (!($jahr % 100 == 0) || $jahr % 400 == 0));
// Wochentag des 4. Januars berechnen
$wochentag = (date ('w', mktime (0, 0, 0, 1, 4, $jahr)) + 6) % 7;
// Start des Jahres relativ zum 1. bestimmen
$start = 4 - $wochentag;
// Addiere $woche * 7
$start += ($woche - 1) * 7;
if ($start <= 0) {
// Start-Datum liegt im Dezember des Vorjahres
$startTs = mktime (0, 0, 0, 12, 31 + $start, $jahr - 1);
} else if ($start <= 59 + $schaltjahr) {
// Januar oder Februar
$startTs = mktime (0, 0, 0, floor ($start / 31) + 1, $start % 31, $jahr);
} else {
// März oder später
$tag = $start + 63 - $schaltjahr;
$monat = floor ($tag / 30.6001);
$tag -= floor ($monat * 30.6001);
$startTs = mktime (0, 0, 0, $monat - 1, $tag, $jahr);
}
// Addiere 6 Tage
$ende = $start + 6;
if ($ende <= 59 + $schaltjahr) {
// Januar oder Februar
$endeTs = mktime (23, 59, 59, floor ($ende / 31) + 1, $ende % 31, $jahr);
} else {
// März oder später (Januar nächsten Jahres wird automatisch
// von mktime() interpoliert)
$tag = $ende + 63 - $schaltjahr;
$monat = floor ($tag / 30.6001);
$tag -= floor ($monat * 30.6001);
$endeTs = mktime (23, 59, 59, $monat - 1, $tag, $jahr);
}
return array ($startTs, $endeTs);
}
Das Grundprinzip ist das gleiche, wie bei der vorigen Lösung: Es wird eine kontinuierliche Zeitskala verwendet, der Start des ISO-Jahres in dieser Zeitskala berechnet, die Anzahl an Wochen auf diese Skala addiert, auf die Monate zurückgerechnet und schließlich Timestamps wieder per mktime() gebildet.
Der wichtige Unterschied: Als kontinuierliche Zeitskala wird der Tag im Jahr genommen, d.h. der 1. Januar wäre "1", der 31. Dezember wäre "365" oder "366" (je nachdem ob's ein Schaltjahr ist, oder nicht). Damit ist die Jahreszahl für die Zeitskala vollkommen irrelevant (außer für die Info, ob's ein Schaltjahr ist, oder nicht) und der folgende Code verdeutlicht, wie die Berechnung von Monaten funktioniert. Gehen wir den Code Schritt für Schritt durch:
$schaltjahr = (int)($jahr % 4 == 0 && (!($jahr % 100 == 0) || $jahr % 400 == 0));
Dieser Code überprüft, ob das gegebene Jahr ein Schaltjahr ist, d.h. einen 29. Februar besitzt. Dies ist der Fall, wenn das Jahr durch 4 teilbar ist (d.h. die Modulo-Operation mit 4 den Rest 0 ergibt), aber nicht durch 100 teilbar ist oder doch wieder durch 400 teilbar ist (Schaltjahresregel des gregorianischen Kalenders). Der Ausdruck in den äußeren Klammern ist dann ein Bool-Wert, der true ist, falls das Jahr ein Schaltjahr ist und false, falls nicht. Der Witz ist nun, dass in PHP die Konvertierung nach (int) true zu 1 macht und false zu 0, d.h. in der Variable $schaltjahr steht jetzt nun genau die Anzahl an zusätzlichen Tagen im Februar: 1 in Schaltjahren und 0 in normalen Jahren.
$wochentag = (date ('w', mktime (0, 0, 0, 1, 4, $jahr)) + 6) % 7;
Dieser Code berechnet nun den Wochentag des 4. Januars des Jahres. Das ist der einzige Teil des Codes, an dem "geschummelt" wird, d.h. die eigentliche Kalenderberechnung PHP selbst überlassen wird. Dies vereinfacht den Code jedoch dramatisch. PHPs date('w') gibt jedoch 0 für Sonntag und 6 für Samstag zurück, für die weitere Berechnung ist aber 0 für Montag und 6 für Sonntag sinnvoller. Daher wird auf das Ergebnis 6 addiert und das ganze noch einmal Modulo 7 genommen, dann passt's.
$start = 4 - $wochentag;
Dies berechnet das Startdatum in unserer kontinuierlichen Zeitskala. Da die Zeitskala der Tag im Jahr ist, wäre »4« der 4. Januar. Hiervon ziehen wir noch einmal den Wochentag ab, um den Start des ISO-Jahres zu erhalten (der Start des normalen Jahres wäre ganz simpel »1«). Zur Erinnerung: Das ISO-Wochenjahr fängt mit dem Montag der ersten Woche an, die mindestens 4 Tage im normalen Jahr besitzt. Wenn der 4. Januar ein Montag ist, fängt es also am 4. Januar an (weil der 1., 2. und 3. Januar noch zur vorigen Woche gehören, die jedoch nur 3 Tage in dem Jahr hat). In dem Fall ist $wochentag 0 und wir ziehen 0 ab. Wenn der 4. Januar ein Dienstag ist, hat das Jahr am 3. Januar angefangen, dann ist $wochentag 1 (für Dienstag), wir ziehen 1 ab, dann stimmt auch das. Usw. usf.
$start += ($woche - 1) * 7;
Nun wird der Start der gewünschten Woche berechnet. Da wir bereits in Woche 1 sind, müssen wir von $woche 1 abziehen vor der Multiplikation, damit das Ergebnis stimmt. $start enthält den Tag im Jahr des Montags der Woche Nr $woche. Nun müssen Monat und Tag dazu berechnet werden, um den Start-Timestamp zu erhalten.
if ($start <= 0) {
Wenn $start <= 0 ist, dann war die erste Woche gemeint und das Startdatum liegt im vorigen Jahr (weil ja, wenn z.B. der 4. Januar ein Sonntag ist, 4 Tage der Woche, die am 29. Dezember des Vorjahres begann, bereits im neuen Jahr liegen und somit der ISO-Jahresanfang am 29. Dezember des Vorjahres liegt).
$startTs = mktime (0, 0, 0, 12, 31 + $start, $jahr - 1);
Monat ist also Dezember, der hat immer 31 Tage, daher muss einfach 31 auf $start (das 0, -1 oder -2 sein kann) addiert werden und $jahr - 1 muss genommen werden.
} else if ($start <= 59 + $schaltjahr) {
Wenn $start <= 59 (oder in Schaltjahren 60) ist, dann sind wir in Januar oder Februar...
$startTs = mktime (0, 0, 0, floor ($start / 31) + 1, $start % 31, $jahr);
... d.h. durch einfache Division / Modulo-Rechnung mit 31 erhalten wir sowohl Monat als auch Tag.
} else {
Wenn wir weder im Dezember letzten Jahres noch im Januar noch im Februar sind, müssen wir im März des darauffolgenden Jahres sein.
$tag = $start + 63 - $schaltjahr;
Hier wird Tag initialisiert, so dass man gut den Monat und Tag daraus berechnen kann. Das Grundprinzip ist folgendes: Berechnet man für alle Monate von März bis Februar des darauffolgenden Jahres floor (($monat + 1) * 30.6001), so erhält man folgende Zahlenfolge:
Monat | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
-------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----
Formel | 122 | 153 | 183 | 214 | 244 | 275 | 306 | 336 | 367 | 397 | 428 | 459
Daran erkennt man noch nicht allzu viel. Zieht man jedoch 63 davon ab, d.h. berechnet man floor (($monat + 1) * 30.6001) - 63, so erhält man die nächste Tabelle:
Monat | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
-------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----
Formel | 59 | 90 | 120 | 151 | 181 | 212 | 243 | 273 | 304 | 334 | 365 | 396
Bedenkt man nun, dass in einem normalen Jahr der 1. März der 60. Tag im Jahr ist, der 1. April der 91., der 1. Mai der 121 Tag etc., so erkennt man denke ich das Muster. Sprich: Die Formel floor (($monat + 1) * 30.6001) - 63 + $tag + $schaltjahr liefert für alle Daten ab März den korrekten Tag im Jahr.
Um das ganze zurückzurechnen, addiert man eben 63 - $schaltjahr auf den Tag im Jahr und kann dann einfach durch Division und Modulo-Rechnung (die man auf Grund der Tatsache, dass 30.6001 keine ganze Zahl ist, nachbilden muss) den Monat und Tag ausrechnen, was im folgenden auch getan wird:
$monat = floor ($tag / 30.6001);
Hier wird zuerst der Monat berechnet (genauer gesagt ist $monat nun der Monat + 1, d.h. 4 für März etc.), indem die Anzahl an Tagen durch 30.6001 dividiert wird und dann abgerundet wird.
$tag -= floor ($monat * 30.6001);
Hier wird nun der Rest zum Monatsanfang berechnet, was dann der Tag im Monat ist.
$startTs = mktime (0, 0, 0, $monat - 1, $tag, $jahr);
Von $monat muss man 1 abziehen, damit die Angabe stimmt (s.o.), $tag stimmt dagegen, da die Formel immer die Tage VOR Monatsbeginn liefert. Ein kleiner Hinweis - dies kann beim Enddatum passieren, beim Anfangsdatum nicht: Sollte der ursprüngliche Tag bereits im nächsten Jahr liegen (d.h. > 365/366 sein), dann wäre hier $monat - 1 natürlich 13 und $tag würde dennoch stimmen. Das macht aber nichts, da mktime() in der Berechnung von solchen Offsets stabil ist, d.h. ein 13. Monat wird Problemlos als 1. Monat des nächsten Jahres akzeptiert.
$ende = $start + 6;
Nun muss das Enddatum berechnet werden, was analog zum Anfangsdatum geht, nur 6 Tage später liegt (und zu einer anderen Uhrzeit). Da der Code identisch ist, gehe ich nicht näher darauf ein, lediglich die Bedingung if ($ende <= 0) wird weggelassen, da das Ende einer Woche niemals im Vorjahr liegen kann.
return array ($startTs, $endeTs);
Das Ergebnis muss natürlich auch zurückgegeben werden. ;-)
Ich hoffe, ich konnte hiermit eine Berechnungsmethode für PHP < 5.2 vorstellen, die verständlicher als die vorige ist, aber dennoch einen Einblick in die Tricks der Datums- und Zeitberechnung liefert.
Viele Grüße,
Christian
Hello,
Ich hoffe, ich konnte hiermit eine Berechnungsmethode für PHP < 5.2 vorstellen, die verständlicher als die vorige ist, aber dennoch einen Einblick in die Tricks der Datums- und Zeitberechnung liefert.
Das war zumindest schon mal "bunkerwürdig"...
Ab in die Sammlung damit!
Harzliche Grüße vom Berg
http://bergpost.annerschbarrich.de
Tom