Summer / winter time

I have a page that need to compare too date.

For those who don’t know it, in Europe the last sunday of march we add 1 hour to the current time so we are in UTC+2h) , and the last sunday of october, we remove the one added in march (so we are in utc+1).

now here is my concern :
I have created a FrozzenTime with the method now().
First, I try

$firstDate =  FrozenTime::now('Europe/Paris');

that give :

'time' => '2023-03-28 19:27:11.278474+02:00'
'timezone' => 'Europe/Paris'

I have also try to do something like this too :

$seconddate = FrozenTime::now('UTC')->modify("+2 hours");

added two hours because it is summer time, So I do GMT+1 + 1 hour for the change summer/winter time gives :

'time' => '2023-03-28 19:27:11.278478+00:00'
'timezone' => 'UTC'

I try to compare those two date with this one:
let’s call this $now :

'time' => '2023-03-28 19:35:00.000000+00:00'
'timezone' => 'UTC'
debug($now->diffForHumans($firstDate));//gives '1 hour after'
debug($now->diffForHumans($secondDate));//gives '8 minutes before'

the second is the correct answer.

I can use it the second way, because it gives the correct answer, but the problem is that I have to manage manually the winter/summer time and add +1 hour when it is winter time and +2 hours when it is summer time, I can code it easily but would like to do it the right way without reinventing the wheel

I am sure I miss something and I don’t manage that the correct way,
any help is appreciate

There is a dst property present on your FrozenTime object which indicates if the currently present datetime is currently in daylight saving time or not.

public function testDST(): void
{
    $time = new FrozenTime('2023-03-26 00:00:00', 'Europe/Paris');
    $this->assertFalse($time->dst);
    $time = new FrozenTime('2023-03-26 02:00:00', 'Europe/Paris');
    $this->assertTrue($time->dst);
}

But I am not really able to reproduce whatever problem you have right there. If I do

$now = new FrozenTime('2023-03-26 08:00:00', 'Europe/Vienna');
$nonDSTtime = new FrozenTime('2023-03-26 00:00:00', 'Europe/Vienna');
$DSTtime = new FrozenTime('2023-03-26 02:00:00', 'Europe/Vienna');

var_dump($now->diffForHumans($nonDSTtime));
var_dump($now->diffForHumans($DSTtime));

I get

string(13) "7 hours after"
string(13) "5 hours after"

which is what I’d expect.

But be aware that if you don’t specify a timezone (which in most cases you shouldn’t) then PHP will default back to what your PHP timezone (date.timezone in your php.ini) is configured.

In CakePHP we overwrite this setting via what you set as your timezone in your config/.env or wherever you set your App.defaultTimezone

I definitely wouldn’t recommend you mess around with timezones and DST time detection so it would be better if you can better explain what you are trying to do.

I investigate more, I understand why you have not reproduce it and come with some elements :slight_smile:
the problem is with the constructor of FrozenTime when parameter is a DateTimeInterface

Here is a code I have done to test :

$toCompare = new FrozenTime('2023-03-29 11:20:00', "Europe/Paris");

I got a date received from an entity from db;

debug($p->launch_date);//'2023-03-29 09:20:00.000000+00:00';UTC

I want to change the timezone

$fromdb= new FrozenTime($p->launch_date,'Europe/Paris');
$launchWithTimeZoneForced = $p->launch_date->setTimezone(new \DateTimeZone('Europe/Paris'));

I try to recreate the same time and timezone in PHP

$fromphp= new FrozenTime('2023-03-29 09:20:00','Europe/Paris');

Note that launchdate and the string are the same

debug($fromdb); //'2023-03-29 09:20:00.000000+00:00'; UTC => Time zone is not set, and to me, it should be
debug($fromphp); //'2023-03-29 09:20:00.000000+02:00'; Europe/Paris; => timezone is set, +02:00 is added
$fromdbWithTimeZoneForced = $fromdb->setTimezone(new \DateTimeZone('Europe/Paris'));
debug($fromdbWithTimeZoneForced); //'2023-03-29 09:20:00.000000+00:00'; 'Europe/Paris' 

Time zone is set,+02:00 is not added, that’s why I add manually +1hour in winter and +2h in summer (goal of my post)

so when I compare date,

var_dump($fromdb->diffForHumans($toCompare));//0 seconds before
var_dump($fromphp->diffForHumans($toCompare));//2 hours before (good answer)
var_dump($fromdbWithTimeZoneForced->diffForHumans($toCompare));//0 second before
var_dump($launchWithTimeZoneForced->diffForHumans($toCompare));//0 second before

my conclusion : creating a time from a String or for an DateTimeInterface doesn’t create the same time/timezone couple. the code of the constructor is

if ($time instanceof DateTimeInterface) {
    $tz = $time->getTimezone();
    $time = $time->format('Y-m-d H:i:s.u');
}

so it is logical because it take the time zone of the object given. don’t know if it is a bug or the expected behaviour my question is how to have the same TIme/timezone couple that the one in PHP

ps : the dst seems also buggy :
I got a date

debug($date);//'2023-03-29 08:37:17.693077+00:00';utc

create a new one from DateTimeInterface

$date2 =  new FrozenTime($date, 'Europe/Paris');
// create a new one from String
$date3 = new FrozenTime('2023-03-29 08:37:17', 'Europe/Paris');
    
debug($date2->dst); //=>return false;Bug ?
debug($date3->dst); //=>return true;

You need to go back a few steps, and realize that now('UTC') and now('Europe/Paris') are actually equal from a “point in time” perspective, as both values represent the exact same moment, “now”, just in different timezones. Hence the diff would give you 0.

Changing the timezone of a time object will do a timezone aware conversion, so that the point in time stays the same, hence $launchWithTimeZoneForced having a difference of 0 is the expected, correct result, as the point in time has not changed, it still points to the exact same moment as $toCompare, only in a different timezone.

And no, that’s not a DST bug there, creating new time objects from existing time objects will take the timezone of the input object, that is the intended behavior, $date2->dst is false because the input object is UTC, hence the output is UTC too, and so there’s no DST.

All that being said, if you wanted to calculate the, let’s call it “clock difference”, meaning the offset that two people in different timezones will observe when looking at their clocks at the same point in time, then you would indeed have to account for the timezone offset accordingly, for example:

$now = FrozenTime::now('UTC');
$compare = FrozenTime::now('Europe/Paris');

// no difference
debug($now->diffForHumans($compare)); // 0 seconds before

// point in time difference
debug($now->addMinutes(5)->diffForHumans($compare)); // 5 minutes after

// clock difference

$adjustedNow = $now->subSeconds($compare->getOffset());
$adjustedCompare = $compare->subSeconds($now->getOffset());

debug($adjustedNow->diffForHumans($adjustedCompare)); // 2 hours before

$adjustedNow = new FrozenTime($now->format('Y-m-d H:i:s.u'), 'UTC');
$adjustedCompare = new FrozenTime($compare->format('Y-m-d H:i:s.u'), 'UTC');

debug($adjustedNow->diffForHumans($adjustedCompare)); // 2 hours before

ps. please format your code:

I took the liberty and formated his code a bit :wink:

I am agree with your explanations. Time and date are difficult to manage depending on what we expect and understand. I am agree with the different way to interpret things.

One thing I am not sure to understand what the DST should return … is that the date and time we change between the winter<->summer time…or does it return the fact that we are in winter or summer time.

To me it is the second interpretation. for instance 2023-03-25 is in winter time (DST=false) and 2023-03-28 is in summer time (DST=true). I have loosely code a function that return this for a date, depending on if the date is between the last sunday of march and the last sunday of october (I haven’t manage the hour because it is not important for my needs) . Here is the (formated :slight_smile: ) code:

 $year=$date->year;
      $summer = new FrozenTime(date('Y-m-d', strtotime('last sunday of March' . $year))); 
       $winter = new FrozenTime(date('Y-m-d', strtotime('last sunday of October' . $year))); 
 
    if($date->greaterThan($summer) && $date->lessThan($winter)){
        return true;
    } else {
        return false;
    }

Isn’t there $date->dst that will tell you this?

Daylight saving time is summer time, the time span in which the clocks have been advanced, so that is when dst will return true, given that the the time object uses a timezone that actually recognizes daylight saving time.

I’m not sure what you mean by time not being important for your needs, is $date a date-only object, eg FrozenDate? If not, and it’s actually date and time, then your function will fail depending on the time of $date and your app’s default timezone, as DST changes happen at different points in time depending on the location.