Ankündigung

Einklappen
Keine Ankündigung bisher.

Zeitzonenvergleich DateTime

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Zeitzonenvergleich DateTime

    Ich möchte ermitteln ob ein DateTime-Objekt eine bestimmte Zeitzone hat. Dafür wollte ich zwei Objekte vom Typ DateTimeZone vergleichen.
    Laut Manual liefert der typeschwache Vergleich true wenn alle Eigenschaften gleich sind. Das folgende Beispiel sollte daher false liefern.
    Das Resultat ist aber true.

    PHP-Code:
    $tza = new DateTimeZone("America/New_York");
    $tzb = new DateTimeZone("Europe/Berlin");
    var_dump($tza == $tzb);  //true 
    Erstelle ich eigene Objekte, funktioniert ein Vergleich wie im Manual beschrieben:
    PHP-Code:
    class MyTimeZone{
      private 
    $timezone_type 3;
      private 
    $timezone;

      public function 
    __construct($tz ""){
        
    $this->timezone $tz;
      }
    }

    $tza = new MyTimeZone("America/New_York");
    $tzb = new MyTimeZone("Europe/Berlin");
    var_dump($tza == $tzb);  //false 
    Vermutlich wieder eine der vielen Eigenheiten wie sie auch bei DateTime zu finden sind.

    Bin jetzt am überlegen wie ich den Vergleich mache. Per getName() scheint mir nicht zuverlässig, per getOffset() auf den ersten Blick etwas umständlich.

    Gibt es bessere Ideen?


    PHP-Klassen auf github


  • #2
    DateTime ist ein Sonderfall. == liefert TRUE, wenn der Zeitpunkt identisch ist.

    Warum verwendest du nicht getTimezone(), wenn du die Zeitzone vergleichen möchtest.

    Kommentar


    • #3
      Das erste ist klar. Ich bin aber bereits beim Vergleich bei DateTimeZone, das von getTimeZone() geliefert wird !
      PHP-Klassen auf github

      Kommentar


      • #4
        Da es (in PHP) 3 Typen von Zeitzonen gibt (Offset (+01:00), politisch (CET), geographisch (Europe/Berlin)), wird das ziemlich schwierig werden.

        Kommentar


        • #5
          Bin jetzt am überlegen wie ich den Vergleich mache. Per getName() scheint mir nicht zuverlässig, per getOffset() auf den ersten Blick etwas umständlich.

          Gibt es bessere Ideen?
          Zur Not, serialize().
          PHP-Code:
          $tza = new DateTimeZone("America/New_York");
          $tzb = new DateTimeZone("Europe/Berlin");

          var_dump($tza == $tzb); // true
          var_dump(serialize($tza) == serialize($tzb)); // false 
          Doch nicht... Wenn man z.B. "europe/Berlin" eingibt wirds nicht korrigiert womit ich gerechnet hätte.
          PHP-Code:
          var_dump(mb_strtolower(serialize($tza)) === mb_strtolower(serialize($tzb))); 
          Relax, you're doing fine.
          RTFM | php.de Wissenssammlung | Datenbankindizes | Dateien in der DB?

          Kommentar


          • #6
            getName() & strtolower() ?

            Kommentar


            • #7
              Sowas hilft dir nicht?

              PHP-Code:
              $tzA = new DateTimeZone("America/New_York");
              $tzB = new DateTimeZone("Europe/Berlin");

              $dateA = new DateTime(''$tzA);
              $dateB = new DateTime(''$tzB);

              $tzA $dateA->getTimezone();
              $tzB $dateB->getTimezone();

              var_dump($tzA->getName());
              var_dump($tzB->getName());

              /*
              string(16) "America/New_York"
              string(13) "Europe/Berlin"
              */ 
              Debugging: Finde DEINE Fehler selbst! | Gegen Probleme beim E-Mail-Versand | Sicheres Passwort-Hashing | Includes niemals ohne __DIR__
              PHP.de Wissenssammlung | Kein Support per PN

              Kommentar


              • #8
                Noch etwas zum Hintergrund:
                Ich möchte im Rahmen eines Test's prüfen, ob ein DateTime-Objekt mit einer bestimmten Zeitzone auch so wie gewünscht erzeugt wurde. Beim einfachen Vergleich von DateTime-Objekten können diese ja auch mit unterschiedlichen Zeitzoneninformationen gleich sein.
                Beispiel:
                PHP-Code:
                $tza = new DateTimeZone("UTC");
                $tzb = new DateTimeZone("Europe/Berlin");

                $da = new DateTime('today 6:00',$tza);
                $db = new DateTime('today 8:00',$tzb);

                var_dump($da == $db);  //true 
                Variablen

                $tza: object(DateTimeZone)#2 (0) { }
                $tzb: object(DateTimeZone)#3 (0) { }
                $da: object(DateTime)#4 (3) { ["date"]=> string(19) "2019-07-11 06:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" }
                $db: object(DateTime)#5 (3) { ["date"]=> string(19) "2019-07-11 08:00:00" ["timezone_type"]=> int(3) ["timezone"]=> string(13) "Europe/Berlin" }

                getName() allein reicht mir nicht. VPh's Beitrag hat mich auf eine Idee gebracht:

                PHP-Code:
                $tza = new DateTimeZone("America/New_York");
                $tzb = new DateTimeZone("Europe/Berlin");
                $tzc = new DateTimeZone("America/New_York");

                var_dump($tza == $tzb); // true
                var_dump(json_encode($tza) == json_encode($tzb)); // false  
                var_dump(json_encode($tza) == json_encode($tzc)); // true 
                Funktioniert erstmal unter PHP 7.2.19. Per json_encode können auch DateTime-Objekte überprüft werden, ohne explizit die Zeitzone rausholen zu müssen.
                Muss mal schauen wie weit das noch bei den Alt-Versionen klappt.

                Vielen Dank für die Anregungen.
                PHP-Klassen auf github

                Kommentar


                • #9
                  Zusammenfassung

                  Müssen DateTime-Objekte überprüft werden (z.B. bei Unit-Test's) reicht es genau genommen nicht aus, diese mit einen anderen DateTime-Objekt zu vergleichen. Der typeschwache Vergleich von DateTime-Objekten liefert TRUE, wenn diese Objekte gleiche Zeitpunkte repräsentieren. So können auch DateTime-Objekte mit unterschiedlichen Zeiten/Timezonen ein "gleich" liefern. Um die Objekte bez. Zeit und Zeitzone zu überprüfen bietet sich an die Resultate vom json_encode der DateTime-Objekte zu vergleichen.

                  Der typeschwache Vergleich von Zeitzonenobjekten (DateTimeZone) liefert immer true, unabhängig vom Inhalt (Ein Bug oder gewollt?). Um diese Objekte zu überprüfen bietet sich für PHP Versionen ab 7 ebenfalls an json_encode zu nehmen. Bei mehrern alten PHP-Versionen funktioniert das nicht.

                  LG jspit



                  PHP-Klassen auf github

                  Kommentar


                  • #10
                    Zitat von jspit
                    Um diese Objekte zu überprüfen bietet sich für PHP Versionen ab 7 ebenfalls an json_encode zu nehmen. Bei mehrern alten PHP-Versionen funktioniert das nicht.

                    Unter 7 teilweise mit serialize() statt json_encode(). Zumindest ab 5.5.5

                    PHP-Code:
                    $tza = new DateTimeZone("America/New_York");
                    $tzb = new DateTimeZone("Europe/Berlin");
                    $tzc = new DateTimeZone("America/New_York");

                    var_dump($tza == $tzb);  
                    var_dump(serialize($tza) == serialize($tzb));  
                    var_dump(serialize($tza) == serialize($tzc)); 
                    PHP-Code:

                    /* PHP 7
                    bool(true)
                    bool(false)
                    bool(true)
                    */

                    /*
                    5.5.5
                    bool(true)
                    bool(false)
                    bool(true)
                    */

                    /*
                    5.4
                    bool(true)
                    bool(true)
                    bool(true)
                    */ 
                    Debugging: Finde DEINE Fehler selbst! | Gegen Probleme beim E-Mail-Versand | Sicheres Passwort-Hashing | Includes niemals ohne __DIR__
                    PHP.de Wissenssammlung | Kein Support per PN

                    Kommentar


                    • #11
                      Würde folgendes funktionieren?

                      PHP-Code:
                      function isEqualDateTimeZone(DateTimeZone $fooDateTimeZone $bar): bool {
                          
                      $now = new DateTime();
                          return 
                      $foo->getOffset($now) === $bar->getOffset($now);
                      }

                      $tza = new DateTimeZone("America/New_York");
                      $tzb = new DateTimeZone("Europe/Berlin");
                      var_dump(isEqualDateTimeZone($tza$tzb)); 

                      Kommentar


                      • #12
                        Ein gleicher Offset bedeutet nicht die gleiche Zeitzone. Es gibt mehrere Zeitzonen mit einem gleichen Offset, wobei der Offset auch abhängig vom Datum ist, das in $now steckt. Also je nach Zeitpunkt des Aufrufs liefert die Funktion ein anderes Ergebnis.

                        Kommentar


                        • #13
                          Ja. Diese nützliche Funktion liefert eine Aussage ob 2 Zeitzonen zum aktuellen Zeitpunkt unabhängig vom Typ die gleiche Zeitzone repräsentieren (genau genommen eben den selben Offset haben).

                          Beispiel:
                          PHP-Code:
                          function isEqualDateTimeZone(DateTimeZone $fooDateTimeZone $bar): bool {
                              
                          $now = new DateTime();
                              return 
                          $foo->getOffset($now) === $bar->getOffset($now);
                          }

                          $tza = new DateTimeZone("+200");
                          $tzb = new DateTimeZone("Europe/Berlin");

                          debug::write(
                            
                          isEqualDateTimeZone($tza$tzb),
                            
                          json_encode($tza) === json_encode($tzb),
                            
                          $tza,
                            
                          $tzb
                          ); 
                          Der Vergleich mit isEqualDateTimeZone() liefert jetzt zur Sommerzeit "true". Im Winter dagegen false.
                          Der Vergleich mit json_encode dagegen liefert immer false.
                          [16.07.2019 09:42:41,345](368k/395k) Debug::write "console.php" Line 13
                          0 boolean true
                          1 boolean false
                          2 object(DateTimeZone)(2) DateTimeZone::__set_state(array( 'timezone_type' => 1, 'timezone' => "+02:00", ))
                          3 object(DateTimeZone)(2) DateTimeZone::__set_state(array( 'timezone_type' => 3, 'timezone' => "Europe/Berlin", ))
                          Kommt halt darauf an was genau geprüft werden soll.

                          Edit: Antwort von hellbringer bei Erstfassung noch nicht gelesen.
                          PHP-Klassen auf github

                          Kommentar


                          • #14
                            Die falschen Ergebnisse beim typeschwachen Vergleich mit == sind leider nicht nur auf die Zeitzonen-Objekte beschränkt.
                            Betrifft auch DateInterval:
                            PHP-Code:
                            $i1 = new DateInterval('P1D');
                            $i1c = new DateInterval('P1D');
                            $i2 = new DateInterval('P1DT1S');
                            var_dump(
                              
                            $i1 == $i1c//true
                              
                            $i1 == $i2,  //liefert true, richtig wäre false
                              
                            json_encode($i1) == json_encode($i1c),  //true
                              
                            json_encode($i1) == json_encode($i2)  //false OK!
                            ); 
                            Es gibt Hinweise auf ein bestehenden Bug im Netz.
                            3v4l.org wirft für die PHP 7.4 alpha diese Warnung: Warning: Cannot compare DateInterval objects
                            PHP-Klassen auf github

                            Kommentar


                            • #15
                              Es ist immer wieder interessant zu lesen, was du so ausgräbst. :thumbsup:

                              Zitat von jspit Beitrag anzeigen
                              Die falschen Ergebnisse beim typeschwachen Vergleich mit == sind leider nicht nur auf die Zeitzonen-Objekte beschränkt.
                              Betrifft auch DateInterval: (...)
                              Nackte DateInterval::-Objekte lassen sich nicht vergleichen, weil sie Zeitabstände wie bspw. Jahre, Monate oder Tage enthalten, deren exakte Länge vom Ausgangsdatum abhängt. Das korrekte Verhalten ist also wie das von PHP 7.4, beim direkten Vergleich eine Fehlermeldung rauszuhauen.

                              Zitat von jspit Beitrag anzeigen
                              Laut Manual liefert der typeschwache Vergleich true wenn alle Eigenschaften gleich sind. Das folgende Beispiel sollte daher false liefern.
                              Das Resultat ist aber true.
                              Vermutlich konvertiert der schwach(sinnig)e Vergleich beide Objekte zu irgendwelchen skalaren (String- oder Integer-) Werten, die unabhängig von den Zeitzonen-Interna immer gleich sind, also 0 oder '' ... Deswegen vergleicht der vorsichtige PHP-Schrauber niemals mit zu wenigen Gleichheitszeichen!

                              Warnung: Textwand einkommend ...

                              Zu den Zeitzonen: Abseits von PHP-Interna ist eine Zeitzone entweder ein Datentyp, der einen festen Zeitabstand zu einer Basiszeit festlegt; oder sie verwaltet mehrere solcher Abstände, die nach einem bestimmten Regelsatz aktiviert werden. Vergleichen könnte man also den einen Subtyp nach festem Offset und den anderen nach dem (gleichen) Regelsatz. Beachtenswert in Bezug auf die von dir angestrebte Anwendung in Tests: Die Regelsätze können sich (dummerweise gerne aufgrund politischer Entscheidungen) jederzeit ändern. Ein Vergleich, der heute TRUE ergibt, kann morgen FALSE sein, und kann trotzdem korrekt sein.

                              PHP bringt derzeit zwei Zeitzonen-Typen mit:
                              * Die "eingebaute" DateTimeZone:: kennt (unnötigerweise) drei Subtypen, wobei Typ 1 und Typ 2 feste Abstände haben, während Typ 3 mit den Regelsätzen arbeitet. Von welchem Subtyp das jeweilige Objekt ist, lässt sich nur mit Tricks herausbekommen.
                              * Die Interna der "mitgelieferten" IntlTimeZone:: sind mir jetzt nicht bekannt, sie bringt aber eine besser ausgestattete API mit. Die Regelsätze oder Offsets von IntlTimeZone::-Objekte kann man direkt per ->hasSameRules() vergleichen. Das funktioniert für beide Zeitzonentypen. Wahlweise holt man sich für Zonen mit "Alias-Namen" per ::getCanonicalID() die "kanonischen" (oder normalisierten) Zonen-IDs und vergleicht diese dann.

                              IntlTimeZone::-Objekte lassen sich aus DateTimeZone::-Objekten erzeugen. Allerdings kennt IntlTimeZone:: diverse Zonen-IDs nicht, die DateTimeZone:: ohne Widerspruch schluckt (bspw. kennt es "CET" aber nicht "CEST"). Darüberhinaus betrachtet es Zone-IDs als strikte Bezeichner (eben als IDs) und nicht als "unscharfe" Sprache. Bspw. lehnt es "EUROPE/berlin" ab, während DateTimeZone:: das (intern per strcasecmp()-Vergleich) als "Europe/Berlin" akzeptiert. Ein weiterer Nachteil: Die Aktualität der Regelsätze für IntlTimeZone:: kann denen von DateTimeZone:: hinterherhinken. Welcher PHP-Host-Meister updatet schon seine ICU|Intl-Komponenten öfter als die (der) PHP-Installation?

                              DateTimeZone:: bietet keine Schnittstelle für den Vergleich per Regelsatz. Allenfalls die Liste der Transitions könnte man dafür hernehmen. Die kann aber sehr schnell sehr umfangreich werden. Der Vergleich der "Interna" per serialize() oder json_encode() sieht Unterschiede, wo keine sind, weil die so extrahierten Objekt-Eigenschaften nur das wiedergeben, was der Konstruktor mitbekommen hat. Bliebe der Vergleich per Zone-ID oder per festem Offset. (TL;DR: Beides klappt auch nicht wirklich.)

                              Für Typ-3-Zeitzonen akzeptiert DateTimeZone:: jede Variation der (lateinischen) Groß- oder Kleinbuchstaben, ohne sie zu normalisieren. Die Arbeit müsste man sich also selbermachen, bspw. so:
                              PHP-Code:
                              function normalizedZoneID(\DateTimeZone $zone) {
                                  if (!
                              is_array($loc $zone->getLocation())) {
                                      return 
                              null// not of TIMELIB_ZONE_TYPE 3
                                  
                              }
                                  foreach (
                                      
                              '??' === $loc['country_code']
                                          ? \
                              DateTimeZone::listIdentifiers(0// traverse over all zone IDs
                                          
                              : \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY$loc['country_code'])
                                      as 
                              $canonicalId
                                  
                              ) {
                                      
                              // timelib uses strcasecmp() equivalent internally
                                      
                              if (=== strcasecmp($canonicalId$id)) {
                                          return 
                              $canonicalId;
                                      }
                                  }
                                  return 
                              null;     

                              Der Vergleich schlägt aber trotzdem fehl, wenn eine Zeitzone bei gleichem Regelsatz mehrere unterschiedliche IDs besitzt. Ein Beispiel wäre "America/Havana" und "Cuba".

                              Bei Typ-2-Zeitzonen akzeptiert DateTimeZone:: auch die unterschiedliche Schreibung, normalisiert sie aber intern zu Großbuchstaben. Den (fixen) Offset könnte man sich per ->getOffset() herauspopeln. Typ-1-Zonen tragen ihren Offset als (teilweise normalisierte und) formatierte Zeichenkette im Muster "+HH:II" mit sich herum. Man müsste eine von beiden Angaben also umwandeln, um einen Vergleich zu ermöglichen.

                              Lange Rede, kurzer Sinn: Hier ein paar Vergleiche, die zeigen, dass sich DateTimeZone::-Objekte alleine nicht verlässlich vergleichen lassen:
                              PHP-Code:
                              $pairs = [
                                  
                              // geht nicht
                                  
                              ['Cuba''America/Havana'], // identical
                                  
                              ['UTC''UCT'], // should be identical (or equal)
                                  
                              ['+0000''UTC'], // should be equal

                                  // geht nicht, trotz gleichem fixen offset
                                  // offsets should actually differ (UTC+1 vs. UTC+2), but I may be wrong about this
                                  
                              ['CET''CEST'],

                                  
                              // geht
                                  
                              ['Europe/Busingen''Europe/Berlin'], // should differ
                                  
                              ['Europe/Vatican''Europe/Vatican'], // identical
                              ];

                              // \xerror::writefln() ist ein aufgebrezeltes printf()

                              foreach ($pairs as $pair) {
                                  list (
                              $aID$bID) = $pair;
                                  \
                              xerror::writefln('compare IDs: "%s" with "%s"'$aID$bID);
                                  if (!
                              is_object($a timezone_open($aID))) {
                                      \
                              xerror::writefln(':: could not create DateTimeZone:: for ID "%s"'$aID);
                                      continue;
                                  }
                                  if (!
                              is_object($b timezone_open($bID))) {
                                      \
                              xerror::writefln(':: could not create DateTimeZone:: for ID "%s"'$bID);
                                      continue;
                                  }

                                  
                              // comparing reference equality does not work
                                  //\xerror::writefln('object_id(): %S %S', spl_object_id($a), spl_object_id($b));

                                  /* loose gives always bool(true), strict gives always bool(false)
                                  $eqLoose = $a == $b;
                                  $eqStrict = $a === $b;
                                  \xerror::writefln(
                                      'by-value: loose? %S strict? %S',
                                      $eqLoose,
                                      $eqStrict,

                                  );
                                  //*/

                                  
                              $eqJson serialize($a) === serialize($b);
                                  
                              $eqSerialized json_encode($a) === json_encode($b);

                                  \
                              xerror::writefln(
                                      
                              'by-dump: json? %S serialized? %S',
                                      
                              $eqJson,
                                      
                              $eqSerialized
                                  
                              );

                                  
                              // \IntlTimeZone (a.k.a. ICU timezone)
                                  
                              if (!is_string(\IntlTimeZone::getCanonicalID($a->getName()))) {
                                      \
                              xerror::writefln(':: invalid timezone ID for IntlTimeZone:: "%s"'$a->getName());
                                      continue;
                                  }
                                  if (!
                              is_string(\IntlTimeZone::getCanonicalID($b->getName()))) {
                                      \
                              xerror::writefln(':: invalid timezone ID for IntlTimeZone:: "%s"'$b->getName());
                                      continue;
                                  }                
                                  
                              $eqCanonicalID =
                                      \
                              IntlTimeZone::getCanonicalID(\IntlTimeZone::fromDateTimeZone($a)->getID()) ===
                                      \
                              IntlTimeZone::getCanonicalID(\IntlTimeZone::fromDateTimeZone($b)->getID());

                                  
                              $hasSameRules = \IntlTimeZone::fromDateTimeZone($a)
                                      ->
                              hasSameRules(\IntlTimeZone::fromDateTimeZone($b));

                                  \
                              xerror::writefln(
                                      
                              'in-ICU: canonical? %S rules? %S',
                                      
                              $eqCanonicalID,
                                      
                              $hasSameRules
                                  
                              );    
                              // end foreach 
                              Das gibt folgenden Output:
                              Code:
                              compare IDs: "Cuba" with "America/Havana"
                                by-dump: json? bool(false) serialized? bool(false)
                                in-ICU: canonical? bool(true) rules? bool(true)
                              compare IDs: "UTC" with "UCT"
                                by-dump: json? bool(false) serialized? bool(false)
                                in-ICU: canonical? bool(true) rules? bool(true)
                              compare IDs:"+0000" with "UTC"
                                by-dump: json? bool(false) serialized? bool(false)
                              
                                Unknown error: IntlTimeZone::getCanonicalID(): intltz_get_canonical_id: error obtaining canonical ID in ...
                              
                              :: invalid timezone ID for IntlTimeZone:: "+00:00"
                                compare IDs:"CET" with "CEST"
                                by-dump: json? bool(false) serialized? bool(false)
                              
                                Unknown error: IntlTimeZone::getCanonicalID(): intltz_get_canonical_id: error obtaining canonical ID in ...
                              
                              :: invalid timezone ID for IntlTimeZone:: "CEST"
                              compare IDs: "Europe/Busingen" with "Europe/Berlin"
                                by-dump: json? bool(false) serialized? bool(false)
                                in-ICU: canonical? bool(false) rules? bool(false)
                              compare IDs: "Europe/Vatican" with "Europe/Vatican"
                                by-dump: json? bool(true) serialized? bool(true)
                                canonical? bool(true) rules? bool(true)
                              Ein kleines Detail am Rande: DateTimeZone:: baut für "CET" und "CEST" Typ-2-Objekte, die (IMHO) fälschlicherweise den gleichen Offset haben. Beim Vergleich der Interna mit serialize() oder json_encode() werden die aber als unterschiedlich gewertet. Regelsätze oder Offsets per IntlTimeZone:: zu vergleichen, geht leider nicht, da IntlTimeZone:: nur "CET" aber kein "CEST" kennt.

                              Eigentlich müsste "CET" einen UTC-Abstand von "+01:00" haben, "CEST" aber "+02:00". Möglicherweise steht da ein internes Detail quer, weil "CEST" Sommerzeit ist und damit irgendwo anders die "DST"-Stunde addiert wird.

                              Wenn man die Wurst schräg anschneidet, hält sie länger, weil die Scheiben größer sind.

                              Kommentar

                              Lädt...
                              X