Cara menggunakan php datetimeimmutable yesterday

Description

/**
  * @author gzhegow > 12 years of PHP experience
  */ 

There are guys who wants Psr/Clock/ClockInterface with just one method ->now()...

  1. \DateTime, \DateTimezone, \DateInterval marked with \Exception. Most used editor PHPStorm highlights it. Since it is very internal method - using native \DateTime forces highlight all the code. Highlighting means "PLEASE TRY CATCH IT". So since 50% percent of tasks needs dates inside their logic - you have try/catch in 50% code just to hide damn exception and throw \Runtime instead. Its still an exception. But hidden one. So we have guys who wants ClockInterface

  2. Next one - Relative Date vs Absolute date. In PHP we have the date as single feature (but with 3? or more implementations) - functional way, object way, etc. The point is TIMEZONE. I dont know how exactly initial timezone comes inside PHP (while no one in config - perhaps at build from source time) - but i saw 10+ servers where unix commands show that the timezone is UTC, but PHP shows timezone as "Helsinki" or something. Yes it could be fixed with php.ini.

But with next things:
(1) You still have timezone in your database that could be different from your Unix. And with your php. And you WILL dance with that +1 -2 +3 -4 every time. Best configuration - same timezone everywhere. To do that you should be at least amateur in linux/docker/(mysql|postgres)/php+fpm. Usually this amateur is a guy known as dev-ops, he is clever guy, he wants 3000 USD/month to work only 10minutes per month in case your app falls down. Most product companies prefers outsourcing, and now you CANT ask this guy about this timezone without democratic negotiations.
(2) You know about timezone difference in maybe 2-3 months after project published. It already has a database for maybe 100k-1m records minimum with failed dates. And now you get the task "i dont know, but we differ in 2 hours, please, be THE PROGRAMMER - solve our brainpain"

The reason of above is IMPORTANT difference between relative and absolute date.

Relative date comes from your CPU clock. You write "today" or "yesterday" and you'll get for me (now):
2022-01-18 20:00:00 UTC+2

Ok, lets remember your server (and your postgres configured with UTC). You apply new \DateTimeZone('UTC') one of this way:

  1. new \DateTime($date, $timezoneUTC)
  2. (new \DateTime($date))->setTimezone($timezoneUTC)
  3. (new \DateTime($date, $timezone))->setTimezone($timezoneUTC)
  4. default_timezone_set($timezoneUTC); new \DateTime($date);
  5. ....want some more??

You ALWAYS get unexpected difference in (for me) 2 hours. So the time will be:
2022-01-18 18:00:00 UTC+0

But wait, now you should do that in your brain on every math operation, and try to remember it.

How to get same date as you see in your mac/windows/ubuntu tray, huh, developer?

Tada (it isnt "correct", its "usable", btw humans always incorrect):

new \DateTime((new \DateTime($yourStringNonObjectOrHelloDearExceptionDateHere))->format('Y-m-d H:i:s.u'), $timezoneUTC);

How many hours newbie should spent to research that ?? The point is difference between RELATIVE and ABSOLUTE dates. If you pass timezone as 2nd parameter with relative date - it will be applied like "create date, then - change timezone", instead of "create MY date in described timezone", do you get that?

BUT, we want to apologize - "today midnight" isnt a RELATIVE date. Actually not fully relative. It creates the date with 00:00:00 timestamp. And it will surprise you only if date been created inside UTC difference (for me) 2 hours from UTC. There will be previous day, but server awaits next one.

How about create DIFFERENT classes for Relative date and Absolute date?

Postgres solves that by presenting two different types \DateTime + \DateTimeWithTimezone. Problem of that - you cant just add timezone for non-timezone date, so postgres wont solve that

Or fix the behavior - "today, UTC" means "GET MY TIME IN UTC, I KNOW ITS A MISTAKE, BUT IT WILL HELP ME" :DD

And the next point.

When we serialize date into some storage, we need DATE_ATOM or maybe sql date format.
But while we're in the code - we need objects.

So creating date with new \DateTime() (and actually twice, and actually with timezone, and actually with trycatch in each class) - it wont respects use case "we already have the date object" so we need everywhere write that damn instanceof stuff:

$date = $date instanceof \DateTimeInterface // @gzhegow > whoops, interface doesnt have setTimezone, we need to rewrite...

Lets rewrite:

    /**
     * @author gzhegow > do you think it will be easy? No, its just \DateTime, you have to copy it for \DateTimeImmutable too
     *  > and it will unexpectedly change your timezone to UTC if you dont pass 2nd argument. Sometimes you need that. Sometimes no
     *
     * @param null|string|\DateTimeInterface $date
     * @param null|string|\DateTimeZone      $timezone
     *
     * @return \DateTimeInterface
     */
    private function newDate($date = null, $timezone = null) : \DateTimeInterface
    {
        $date = $date ?? 'now';
        $timezone = $timezone ?? 'UTC';
        $timezone = $timezone instanceof \DateTimeZone
            ? $timezone
            : new \DateTimeZone($timezone);

        try {
            $date = null
                ?? ( $date instanceof \DateTime ? $date->setTimezone($timezone) : null )
                ?? ( $date instanceof \DateTimeImmutable ? $date->setTimezone($timezone) : null )
                ?? ( new \DateTime(
                    ( new \DateTime($date) )->format('Y-m-d H:i:s.u'), $timezone
                ) );
        }
        catch ( \Exception $e ) {
            throw new \RuntimeException($e->getMessage(), null, $e); // @gzhegow > anyone, ask to implement `rethrow` language construct
        }

        return $date;
    }

To create date in PHP safely you need to always write that construct. In each class you need dates.

And am not speaking about humanize date stuff, localize today/yesterday/tomorrow, localize ago dates (ohh, you WILL meet the \IntlFormatter from lib-intl, and spent 3-4 hrs to remember it for long (?), am sure), and actually wont speak about localize ago in human expected format - that means "less than day = minutes/hours ago, greater than day - show date in Ymd" like Pavel Durov does.

Hello, Laravel developer, this paragraph for you. Your CARBON library eats 60ms for initialize cause of broken brain of its author that uses 1000 times preg_match. Nevermind.

Lets fix at least relative date stuff. Just to reduce syntax.

Other stuff we could speak later.

Thanks for your time, folks. Be safe.

[ Гриша, ты уже всех достал своим английским, уходи ]