2

I am upgrading a codebase from PHP 7.4 to 8.2. My unit tests picked up an oddity in an area of code that modifies a provided date.

It looks like PHP 8.2 introduced some behaviour with how date functions work when you modify a date using "--" (double arithmetic operators).

public static function getCorrectedTime(
        int $year,
        int $month,
        int $day,
        int $index,
        string $period
    ): int {

        // PHP 7.4 - if $index=0, $timestamp=1625443200, if $index=-1, $timestamp=1625529600 - test passes
        // PHP 8.2 - if $index=0, $timestamp=1625443200, if $index=-1, $timestamp=1625356800 - test fails
        $timestamp = strtotime("{$year}-{$month}-{$day} -{$index} {$period}");
        return $timestamp;
    }

This seems to be the case when using DateTime objects and it's ->modify() method. It seems like it's more to do with the double "-" modifier that will be used if $index is a negative number. See scratch pad here: https://3v4l.org/fNR2X

But this method needs to support both positive and negative modifiers being sent to it.

Before I completely refactor this old code, I was trying to hunt down an actual description of the change to PHP 8.2 so I can understand exactly what was changed. I can see nothing about this in the changelog or Google hunt. Is anyone aware?

Aaryn
  • 1,601
  • 2
  • 18
  • 31
  • "test passes"/"test fails" – what is the test? What is `$period`? Is `--1` supported by `strtotime` according to the documentation? `--1 day` doesn't look valid (if it worked in the past, I guess you got lucky) – knittl May 28 '23 at 08:52
  • The problem is not in `--1`. when you change code from: `$year . "-" . $month . "-" . $day . " -" . $index . " " . $period;` to: `$year . "-" . $month . "-" . $day . " " . -1*$index . " " . $period;` the difference between PHP version is still 48 hours. – Luuk May 28 '23 at 08:56
  • Rather than hardcode the '-', use code (https://stackoverflow.com/questions/2682397/how-to-prefix-a-positive-number-with-plus-sign-in-php got example) to make sure there is a prefix to the number. – Nigel Ren May 28 '23 at 09:02
  • The test is comparing that two dates passed to this method resulting in the pass where $index is zero returns a timestamp that is small the pass where $index is -1. This is why I'm trying to work out why PHP basically made "--1 day" the same as "+1 day". See here where I add output dates as well. https://3v4l.org/aMlICM – Aaryn May 28 '23 at 10:02

3 Answers3

1

When you add $index = -1*$index; to your code, and remove the - in the expressions before the interval:

<?php
$year=2023;
$month=5;
$day=28;
$index=-1;
$period='day';

$index = -1*$index;
// strtotime test
$dateString = $year . "-" . $month . "-" . $day . " " . $index . " " . $period;
$timestamp1 = strtotime("{$year}-{$month}-{$day} {$index} {$period}");

// DateTime test
$dateStamp = new \DateTime("{$year}-{$month}-{$day}");
$dateStamp->modify("{$index} {$period}");
$timestamp2 = $dateStamp->getTimeStamp();

echo $dateString . PHP_EOL;
echo "strtotime: " . $timestamp1 . PHP_EOL;
echo "DateTime: " . $timestamp2;

The output is the same in 8.1.2 and 8.2.6

BTW: (on linux) date -d "--1 day" also returns the date for yesterday, while date -d "-1 day"

Luuk
  • 12,245
  • 5
  • 22
  • 33
  • Thanks. I think this will help with a small refactor while keeping the method with the same intention as the original author. Looks like "--1" was the same as "+1" so doing what you did above does the same thing. – Aaryn May 28 '23 at 10:43
1

Using sprintf to force the sign for the index would allow you to have a string with either +/- before the days rather than having a -- or -...

The important part is the format string %+d...

// strtotime test
$dateString = sprintf('%d-%d-%d %+d %s', $year, $month, $day, $index, $period);
$timestamp1 = strtotime($dateString);

// DateTime test
$dateString = sprintf('%+d %s', $index, $period);
$dateStamp = new \DateTime("{$year}-{$month}-{$day}");
$dateStamp->modify($dateString);
$timestamp2 = $dateStamp->getTimeStamp();
Nigel Ren
  • 56,122
  • 11
  • 43
  • 55
1

It's listed in the chapter Migrating from PHP 8.1.x to PHP 8.2.x / Backward Incompatible Changes and the bottom of Relative Formats.

number symbols in relative formats no longer accept multiple signs, e.g. +-2.

Here's a related issue: #11237

shingo
  • 18,436
  • 5
  • 23
  • 42
  • Thank you! This helped understand the cause better. It looks like the original author of this method was using multiple signs to make "--1" the same as "+1". Does that sound right? Because after outputting the dates, that's what it looks like it does before PHP 8.2. https://3v4l.org/aMlICM – Aaryn May 28 '23 at 10:21
  • Right, this is the [commit](https://github.com/php/php-src/commit/06d4c70e5179baab2311c4b6e77760a101715de6#diff-21abc342fb3a88b8e51c474e7c82b266517c2d5d0e82632fea68f293392fdddb) made this change, in parse_date.re@557, you can see the original method will deal `-` by multiplying `-1`. But the commit didn't tell why they made such change. – shingo May 28 '23 at 10:47