JavaScript-Uhr mit automatischer Zeitzonenerkennung
Letztens hatten wir in einem Projekt die Anforderung, die Uhrzeit mit automatischer Zeitzonenerkennung auf einer Webseite darzustellen. Automatisch heißt hier, dass der Benutzer in seinem System die Zeitzone nicht selbst einstellen muss. Beispiel: Ich fahre fliege mit meinem MacBook nach Australien, lasse die Systemzeit aber auf mitteleuropäische Zone eingestellt. Der folgende Artikel beschreibt, wie sich eine solche Uhr mit den Diensten Loki und GeoNames realisieren lässt.
Schritt 1: Die Uhr
Startpunkt in JavaScript ist die Klasse "Date" und deren Default-Konstruktor, der bei der Erzeugung einer Instanz die jeweils aktuelle Systemzeit des Clients zurückgibt.
var userTime = new Date();
Mittels der Methoden "getDate", "getMonth" und "getFullYear" lassen sich Tag, Monat (mit 0 für Januar) und das Jahr als vierstellige Zahl ermitteln.
var currentDay = userTime.getDate();
var currentMonth = userTime.getMonth() + 1;
var currentYear = userTime.getFullYear();
Für die Uhrzeit werden die Methoden "getHours", "getMinutes" und "getSeconds" verwendet:
var currentHours = userTime.getHours();
var currentMinutes = userTime.getMinutes();
var currentSeconds = userTime.getSeconds();
Die sechs Variablen können anschließend zu einem String zusammengebaut werden:
var currentTimeString =
currentDay + "." + currentMonth + "." + currentYear + " " +
currentHours + ":" + currentMinutes + ":" + currentSeconds;
Die Darstellung soll in einer h1-Überschrift erfolgen, die die ID "clock" hat. Dazu benutzen wir die Variable "document", die das Document Object Model (DOM) der Seite abbildet und rufen die Methode "getElementById" mit dem Parameter "clock" auf. Der Aufruf liefert eine Referenz auf die h1-Überschrift zurück, deren erstes Kindelement wir auf die oben erzeugte Zeichenkette setzen:
document.getElementById("clock").firstChild.nodeValue = currentTimeString;
Der Quelltext der kompletten Seite sieht dann wie folgt aus:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Clock 1</title> <script type="text/javascript">//<![CDATA[ function updateClock() { var userTime = new Date(); var currentDay = userTime.getDate(); var currentMonth = userTime.getMonth() + 1; var currentYear = userTime.getFullYear(); var currentHours = userTime.getHours(); var currentMinutes = userTime.getMinutes(); var currentSeconds = userTime.getSeconds(); currentHours = ( currentHours < 10 ? "0" : "" ) + currentHours; currentMinutes = ( currentMinutes < 10 ? "0" : "" ) + currentMinutes; currentSeconds = ( currentSeconds < 10 ? "0" : "" ) + currentSeconds; currentDay = ( currentDay < 10 ? "0" : "" ) + currentDay; currentMonth = ( currentMonth < 10 ? "0" : "" ) + currentMonth; var currentTimeString = currentDay + "." + currentMonth + "." + currentYear + " " + currentHours + ":" + currentMinutes + ":" + currentSeconds; document.getElementById("clock").firstChild.nodeValue = currentTimeString; } //]]></script> </head> <body onload="updateClock(); setInterval('updateClock()', 500)"> <h1 id="clock"> </h1> </body> </html>
Im Attribute "onload" des body-Elements wird erst die Funktion "updateClock" aufgerufen und anschließend ein Timer gesetzt, der dieselbe Funktion alle 500ms triggert. Im Prinzip würde hier auch 1s (d.h. 1000ms) ausreichen, allerdings hängt dann die Sekundenanzeige im schlechtesten Fall fast 1s hinterher. Dieser Fall tritt ein, wenn der erste Aufruf der Seite kurz vor dem Wechsel auf die nächste Sekunde geschieht. Je kleiner man den Timer wählt, desto genauer ist die Anzeige, aber desto mehr Last wird auch erzeugt.
Beispielseite im selben Fenster oder im neuen Fenster ansehen.
Schritt 2: Position des Benutzers bestimmen
Wie oben bereits erwähnt, soll die Position mit Loki bestimmt werden. Loki ist ein Dienst der Firma Skyhook Wireless (bekannt u.A. durch die LocateMe Funktion im iPhone) und steht derzeit kostenlos zur Verfügung. Nach der Registrierung erhält man einen "API key", für unsere Webseite lautet er entsprechend der Domain "komprovisation.de".
Die Verwendung von Loki in unserem Skript ist einfach. Es wird die Datei loki.js vom Loki-Server eingebunden
<script type="text/javascript" src="http://loki.com/plugin/files/loki.js"></script>
und drei Funktionen definiert:
function processLocation(location) {
document.getElementById("loki_status").firstChild.nodeValue =
"Position (" + location.latitude + "; " + location.longitude + ") von Loki erhalten.";
}
function errorLoki() {
document.getElementById("loki_status").firstChild.nodeValue =
"Fehler von Loki. Bitte versuchen Sie, die Seite neu zu laden.";
}
function loadLoki()
{
var loki = LokiAPI();
loki.onSuccess = function(location) { processLocation(location) }
loki.onFailure = function(error) { errorLoki(); }
loki.setKey('YOUR_KEY'); // Hier den eigenen Key einfügen
loki.requestLocation(true, loki.NO_STREET_ADDRESS_LOOKUP);
}
Die Funktion "load" erzeugt zunächst eine Instanz der Klasse "LokiApi", definiert dann je eine Callback-Funktion für die erfolgreiche Bestimmung der Position bzw. den Fehlerfall, setzt den "API key" und ruft schließlich die Funktion requestLocation auf. Deren erster Parameter gibt an, dass wir Längen- und Breitengrad des Benutzers ermitteln wollen, der zweite Parameter gibt an, dass wir keine Adresse benötigen.
Die beiden Callback-Funktionen geben einen Hinweistext aus, in dem sie diesen an das HTML-Element mit der ID "loki_status" einfügen. Insofern müssen wir dieses Element noch im Body definieren und außerdem die Methode "loadLoki" aufrufen:
<body onload="loadLoki(); updateClock(); setInterval('updateClock()', 500)">
<h1 id="clock"> </h1>
<p id="loki_status"> </p>
</body>
Beispielseite im selben Fenster oder im neuen Fenster ansehen. Beim Aufruf im Browser erscheint folgender Dialog:

Bestätigt man mit "Allow", so erscheint zunächst die Uhr und kurze Zeit später die Position von Loki.
Schritt 3: Zeitzone des Benutzers bestimmen
Sofern Loki erfolgreich war, bekommen wir als Rückgabewert die Position als Längen- und Breitengrad. Mit diesen können wir nun auf den passenden WebService von GeoNames zugreifen. Der Aufruf erfolgt über die URL http://ws.geonames.org/timezoneJSON?lat=47.01&lng=10.2, wobei die Parameter "lat" und "lng" entsprechend der Position von Loki zu setzen sind. Zurückgeliefert wird ein JSON-Objekt mit dem folgenden Inhalt:
{"time":"2009-01-13 17:07",
"countryName":"Austria",
"rawOffset":1,
"dstOffset":2,
"countryCode":"AT",
"gmtOffset":1,
"lng":10.2,
"timezoneId":"Europe/Vienna",
"lat":47.01}
Das Feld "gmtOffset" gibt den Offset zur Greenwich Mean Time für die Winterzeit an, für die Sommerzeit ist entsprechend "dstOffset" zu verwenden.
Der Aufruf des Webservice in unseren Skript wird in der Funktion "processLocation" realisiert:
function processLocation(location) {
document.getElementById("loki_status").firstChild.nodeValue =
"Position (" + location.latitude + "; " + location.longitude + ") von Loki erhalten.";
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src =
"http://ws.geonames.org/timezoneJSON?lat=" + location.latitude + "&lng=" +
location.longitude + "&callback=processTimeZone";
head.appendChild(script);
}
Nach erfolgter Antwort wird erneut ein Callback aufgerufen:
function processTimeZone(feed) {
document.getElementById("geo_status").firstChild.nodeValue =
"Zeitzone GMT (" + (feed.gmtOffset > 0 ? "+" : "") +
feed.gmtOffset + "h) von GeoNames erhalten.";
}
Wie schon bei Loki geben wir erst mal die Informationen auf der Seite aus, der Einfachheit halber in einem HTML-Element mit der ID "geo_status":
<body onload="load(); updateClock(); setInterval('updateClock()', 500)">
<h1 id="clock"> </h1>
<p id="loki_status"> </p>
<p id="geo_status"> </p>
</body>
Beispielseite im selben Fenster oder im neuen Fenster ansehen.
Schritt 4: Uhrzeit stellen
Im letzten Schritt wollen wir nun die Zeitzone verwenden, um die Uhr entsprechend einzustellen. Dazu wird die Zeile
var userTime = new Date();
durch den folgenden Code ersetzt:
var currentTime = new Date();
var localTime = currentTime.getTime();
var localOffset = currentTime.getTimezoneOffset() * 60000;
var gmt = localTime + localOffset;
var userTime = new Date(gmt + (3600000 * offset));
Die Funktion "getTime" liefert die aktuelle Zeit in Millisekunden zurück, die Funktion "getTimezoneOffset" die Zeitzone des Betriebssystems in Minuten und wird durch Multiplikation mit 60000 ebenfalls in Millisekunden umgerechnet. Die Variable "gmt" enthält dann die Greenwich Mean Time in Sekunden und wird in der letzten Zeile wieder in ein Objekt der Klasse "Date" umgewandelt. Dabei wird bereits ein "offset" berücksichtigt, der in Stunden angeben werden kann (Variable "offset"). Um diesen der Funktion "updateClock" nur einmal übergeben zu müssen, erweitern sie um zwei Parameter: den Offset selbst und ein boolesches Flag, welches anzeigt, ob wir den Offset setzen wollen.
function updateClock(offset, setOffset) {
if (setOffset) {
updateClock.offset = offset;
}
else if (updateClock.offset) {
offset = updateClock.offset;
}
// ...
Der Trick ist hier, dass in JavaScript Funktionen wie Objekte behandelt werden. Hat der Parameter "setOffset" den Wert "true", dann erzeugen wir eine neue Objektvariable "updateClock.offset" und setzen diese mit dem Wert von "offset". Ansonsten verwenden wir den Wert von "updateClock.offset" als Offset für die Zeit.
Anschließend können wir die Zeitzone in der Funktion "processTimeZone" setzen:
function processTimeZone(feed) {
document.getElementById("geo_status").firstChild.nodeValue =
"Zeitzone GMT (" + (feed.gmtOffset > 0 ? "+" : "") +
feed.gmtOffset + "h) von GeoNames erhalten.";
updateClock(feed.gmtOffset, true);
}
Zudem muss der Aufruf von "updateClock" im "onload"-Attribut des Body-Elements modifiziert werden:
<body onload="load(); updateClock(0, false); setInterval('updateClock(0, false)', 500)">
<h1 id="clock"> </h1>
<p id="loki_status"> </p>
<p id="geo_status"> </p>
</body>
Wir starten also mit einem Offset von 0h, d.h. mit Greenwich Mean Time, und setzen die richtige Zeitzone im Callback vom GeoNames.
Fertige Beispielseite, die noch etwas mit CSS verschönert wurde, im selben Fenster oder im neuen Fenster ansehen.
Nachbereitung
Das beschriebene Beispiel hat den Nachteil, dass die Dienste von Loki und GeoNames bei jeder Aktualisierung der Seite oder, sofern die Uhr beispielsweise in Kopf- oder Fußzeile einer Plattform verwendet werden soll, mit jeder Seite aufgerufen. Dies ist nicht nur langsam, sondern man stößt im Fall von Loki auch schnell an die in den Nutzungsbedingungen definierte Grenze von 10000 Abfragen pro Tag. Insofern erscheint es sinnvoll, die Zeitzone beim ersten Aufruf der Seite durch den jeweiligen Benutzer zu ermitteln und dann in seiner Session zu cachen. Besitzt die Plattform ferner die Möglichkeit der Benutzerregistrierung, kann man dem Benutzer eine Auswahl der Standardzeitzone anbieten, so dass die Uhr nicht mit GMT, sondern mit der gewünschten Zeitzone startet.
Ende. Kommentare, Fragen und Kritik sind wie immer herzlich willkommen. :)
Schlüsselwörter: javascript, ortung, programmierung
Auch abrufbar als: Atom
Schlüsselwörter
- berlin (2)
- blog (5)
- browser (2)
- cocoaheads (5)
- dropbox (1)
- git (7)
- idisk (1)
- iphone (28)
- javascript (2)
- kurztip (4)
- linktips (17)
- mac (9)
- macruby (1)
- objective-c (8)
- ortung (1)
- programmierung (22)
- rails (1)
- railsconf (7)
- ruby (6)
- ruby on rails (7)
- schnipsel (14)
- server (2)
- spiele (1)
- statistiken (3)
- stuttgart (3)
- testen (4)
- tidy (1)
- versionskontrolle (5)
- wwdc (1)
- xcode (9)
- xml (1)