shutterstock_308059304

Locale-Falle mit IntlCalendar::fromDateTime()

Mit PHP 5.5 wurde auch die Extension für die Internationalisierung um neue Klassen erweitert. Eine besonders interessante Erweiterung ist die Klasse IntlCalendar, in der verschiedene neue Funktionen bereitgestellt werden um mit Lokalisierungen von Jahren, Wochen oder Tagen umzugehen zu können. Dabei handelt es sich jedoch nicht um Besonderheiten in der Darstellung von Zeit, sondern mehr um deren Betrachtung. In Deutschland sehen wir den Montag als Anfang der Woche an während es in den USA der Sonntag ist. Ebenso wird dank der Lokalisierung bei der Erstellung eines IntlCalendar-Objekts die Auswahl zwischen gregorianischem und julianischem Kalender abgenommen. Das Ganze funktioniert aber auch nur, wenn die Lokalisierung stimmt.

Zur Erstellung eines Calendar-Objekts bietet die Klasse 2 Factory-Methoden:

Während IntlCalendar::createInstance() den aktuellen Zeitpunkt für Berechnungen zur Verfügung stellt, lässt sich mit IntlCalendar::fromDateTime() aus einem PHP DateTime (alternativ auch ein String) ein IntlCalendar-Objekt erstellen.

// createInstance
$calendar = IntlCalendar::createInstance('Europe/Berlin', 'de_DE');

// fromDateTime
$dateTime = new DateTime('25.01.2014', new DateTimeZone('Europe/Berlin'));
$calendar = IntlCalendar::fromDateTime($dateTime);

Da in vielen Projekten DateTime verwendet wird, bietet es sich geradezu an, daraus ein IntlCalendar-Objekt zu erstellen. Wer sich die Factory-Methoden jedoch genau angesehen hat, wird sofort etwas festgestellen: fromDateTime() bietet keine Möglichkeit, eine Locale wie de_DE zu übergeben. Stattdessen wird der Standardwert aus der php.ini verwendet. Sichtbar wird der Fehler dann, wenn zwar die Formatierung über IntlDateFormatter wie die aus dem entsprechenden Land aussieht, jedoch z.B. die Woche mit einem ganz anderen Tag beginnt.

$dateTime = new DateTime('25.01.2014', new DateTimeZone('Europe/Berlin'));
$calendar = IntlCalendar::fromDateTime($dateTime);

// int 1 for Sunday, string en_US
var_dump($calendar->getFirstDayOfWeek(), $calendar->getLocale(Locale::VALID_LOCALE));

Das IntlCalendar-Objekt übernimmt zwar die Zeitzone aus DateTime, jedoch enthält DateTime keine Lokalisierung. Im oberen Beispiel-Code wird der Sonntag als erster Tag der Woche (int 1) und der Locale-String en_us (Standardwert) zurückgegeben. Vielleicht wird der eine oder andere auf die Idee kommen nun per ini_set() oder über Locale::setDefault() die Lokalisierung zu übergeben, jedoch darf man hier nicht vergessen das dieser Wert global als Standard gesetzt wird. Mehrere IntlCalendar-Objekte mit unterschiedlichen Lokalisierungen wären damit nicht möglich wenn IntlCalendar::fromDateTime() verwendet wird. Außerdem kann das Setzen des Werts auf globaler Ebene auch unerwünschte Nebeneffekte im weiteren Verlauf mit sich bringen.

Wer anhand eines DateTime-Objekts ein IntlCalendar erzeugen möchte, kann sich selbst eine Factory erstellen und verzichtet auf IntlCalendar::fromDateTime(). Das könnte folgendermaßen aussehen:

function create_from_date_time(DateTime $dateTime, $locale) {
    $calendar = IntlCalendar::createInstance($dateTime->getTimeZone(), (string) $locale);
    $calendar->setTime($dateTime->getTimestamp() * 1000);

    return $calendar;
}

$calendar = create_from_date_time(new DateTime('25.01.2014', new DateTimeZone('Europe/Berlin')), 'de_DE');

// int 2 for Monday, string de_DE
var_dump($calendar->getFirstDayOfWeek(), $calendar->getLocale(Locale::VALID_LOCALE));

Wir verwenden wieder IntlCalendar::createInstance() und übergeben neben dem Lokalisierungs-String auch die Zeitzone aus dem DateTime. Der erste Parameter ist da sehr flexibel, da er mit einem String, einem DateTimeZone- oder einem IntlTimeZone-Objekt umgehen kann. Im Fehlerfall würde IntlCalendar::createInstance() NULL als Wert zurückgeben, also wäre hier noch ein ErrorHandling angebracht. Beim Setzen der Zeitwerte nehmen wir den Timestamp aus DateTime und übergeben diesen mit IntlCalendar::setTime(). Zu beachten ist, dass IntlCalendar mit Millisekunden arbeitet und der Timestamp mit 1000 multipliziert werden muss.

Noch schöner ist es natürlich, diese Funktionalität in einer Klasse zu haben, je nachdem wie euer Projekt aufgebaut ist und euer Coding-Standard aussieht.

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

Benachrichtige mich zu:
avatar

wpDiscuz
Weitere Beiträge zum Thema
Preisangaben in Online-Shops
TypoGento 1.0 ist released!
Mobile Werbung hat ein Problem …
Kai Greb zu Gast beim 13. Magento Stammtisch Karlsruhe