skip to Main Content

I’m using php8.2 and I have some trouble comparing dates inside a composer package running on my CI.
In short, the date comparison in php is failing because the timezone is not the expected one.

$timezone = new DateTimeZone(date_default_timezone_get());
$now = new DateTimeImmutable('now', $timezone);
$date = DateTimeImmutable::createFromFormat('U.u', $now->format('U.u'), $timezone);
var_dump($now, $date, $now === $date, $now > $date, $now < $date);

The code inside the package is creating a DateTimeImmutable Object for the current date and compares another DateTimeImmutable coming from a UNIX timestamp. However, the timezone for the DateTimeImmutable coming from a UNIX timestamp is ignoring the passed timezone Object.

The result looks like this:

...BearerTokenValidatorTest.php:61:
class DateTimeImmutable#445 (3) {
  public $date =>
  string(26) "2023-09-28 12:20:51.615050"
  public $timezone_type =>
  int(3)
  public $timezone =>
  string(13) "Europe/Berlin"
}
...BearerTokenValidatorTest.php:61:
class DateTimeImmutable#444 (3) {
  public $date =>
  string(26) "2023-09-28 10:20:51.615050"
  public $timezone_type =>
  int(1)
  public $timezone =>
  string(6) "+00:00"
}
...BearerTokenValidatorTest.php:61:
bool(false)
...BearerTokenValidatorTest.php:61:
bool(false)
...BearerTokenValidatorTest.php:61:
bool(false)

Interestingly, each comparison is false.

The original code I’m using and creates trouble can be found here: https://github.com/lcobucci/jwt/blob/5.1.x/src/Token/Parser.php#L160C69-L160C69

2

Answers


  1. The issue you’re facing is related to how DateTimeImmutable objects are compared in PHP. When comparing two DateTimeImmutable objects using ===, PHP compares not only the date and time but also the timezone.

    In your code, $now has the timezone set to "Europe/Berlin," and $date has the timezone set to "+00:00" (UTC). When you compare them using ===, PHP checks if both the date/time and the timezone are identical, which is why the comparison returns false.

    If you want to compare DateTimeImmutable objects based on their date and time, ignoring the timezone, you can convert both objects to UTC before comparing them. Here’s how you can modify your code:

    $timezone = new DateTimeZone(date_default_timezone_get());
    $now = new DateTimeImmutable('now', $timezone);
    $date = DateTimeImmutable::createFromFormat('U.u', $now->format('U.u'), $timezone);
    
    // Convert both DateTimeImmutable objects to UTC
    $nowUtc = $now->setTimezone(new DateTimeZone('UTC'));
    $dateUtc = $date->setTimezone(new DateTimeZone('UTC'));
    
    var_dump($nowUtc, $dateUtc, $nowUtc == $dateUtc, $nowUtc > $dateUtc, $nowUtc < $dateUtc);
    

    By setting both $now and $date to the UTC timezone before comparing them, you will ignore the timezone difference, and the comparison should work as expected.

    Note that I used == for equality comparison instead of === because you want to compare the date and time values, and not necessarily the object references.

    Login or Signup to reply.
  2. The === operator will always return false for even identical objects:

    $a = new DateTime('2020-01-01 12:34:56 UTC');
    $b = clone $a;
    var_dump($a, $b, $a === $b);
    
    object(DateTime)#1 (3) {
      ["date"]=>
      string(26) "2020-01-01 12:34:56.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    object(DateTime)#2 (3) {
      ["date"]=>
      string(26) "2020-01-01 12:34:56.000000"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(3) "UTC"
    }
    bool(false)
    

    Demo and reference.

    When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

    You can use ==, even though the definition suggest it wouldn’t work:

    Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.

    $timezone = new DateTimeZone(date_default_timezone_get());
    $now = new DateTimeImmutable('now', $timezone);
    $date = DateTimeImmutable::createFromFormat('U.u', $now->format('U.u'), $timezone);
    var_dump($now, $date, $now == $date);
    $now = new DateTimeImmutable('next year', $timezone);
    var_dump($now, $date, $now == $date);
    
    object(DateTimeImmutable)#2 (3) {
      ["date"]=>
      string(26) "2023-09-29 13:59:39.005630"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(16) "Europe/Amsterdam"
    }
    object(DateTimeImmutable)#3 (3) {
      ["date"]=>
      string(26) "2023-09-29 11:59:39.005630"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "+00:00"
    }
    bool(true)
    object(DateTimeImmutable)#4 (3) {
      ["date"]=>
      string(26) "2024-09-29 13:59:39.005746"
      ["timezone_type"]=>
      int(3)
      ["timezone"]=>
      string(16) "Europe/Amsterdam"
    }
    object(DateTimeImmutable)#3 (3) {
      ["date"]=>
      string(26) "2023-09-29 11:59:39.005630"
      ["timezone_type"]=>
      int(1)
      ["timezone"]=>
      string(6) "+00:00"
    }
    bool(false)
    

    Demo

    Aside this, during my experience with date/time comparisons in PHP I’ve encountered a couple of rare bugs when dealing with daylight saving time boundaries, so if you want to be extra sure you can always normalise data convert everything to UTC before comparison, or extract the Unix time and compare that.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search