Correct epoch to DateTime conversion - php

Whit this code:
$epoch= '1609455600';
$date = new DateTime( '#'.$epoch);
echo $date-> format( 'Y-m-d');
I see this result 2020-12-31. The server timezone is reported as Europe/Zurich (with date_default_timezone_get). But in this time zone that date should be 2021-1-1.
What is going on here?

In addition to the comment from #tuckbros. The output with var_export shows that the DateTime object has the time zone 00:00 (UTC).
date_default_timezone_set('Europe/Zurich');
$epoch= '1609455600';
$date = new DateTime( '#'.$epoch);
var_export($date);
/*
DateTime::__set_state(array(
'date' => '2020-12-31 23:00:00.000000',
'timezone_type' => 1,
'timezone' => '+00:00',
))
*/
The clean way to get the local time is to transfer the object to the desired time zone (and not to add any offset times).
$date->setTimeZone(new DateTimeZone(date_default_timezone_get()));
var_export($date);
/*
DateTime::__set_state(array(
'date' => '2021-01-01 00:00:00.000000',
'timezone_type' => 3,
'timezone' => 'Europe/Zurich',
))
*/
You can now continue to work with the DateTime object, since it has the correct time zone in addition to the correct local time.

Related

Get day from date Laravel

I got a date with format 'Y-m-d', and want to get the day from it. Like if I have 2021.01.01, I want for example Friday, or Thursday depending on what day it actually is. I already got the date stored as $date and I want the day stored as $day.
I've already tried this, without any error, and without anything happening:
$day = Carbon::createFromFormat('Y-m-d', $date)->format('1');
var_dump($day);
I've found another solution for you :
$timestamp = strtotime('2009-10-22');
$day = date('l', $timestamp);
echo $days;
output:
Thursday
You can try this ,
$d=unixtojd(mktime(0,0,0,6,20,2007));
var_dump(cal_from_jd($d,CAL_GREGORIAN));
output :
array (size=9)
'date' => string '6/20/2007' (length=9)
'month' => int 6
'day' => int 20
'year' => int 2007
'dow' => int 3
'abbrevdayname' => string 'Wed' (length=3)
'dayname' => string 'Wednesday' (length=9)
'abbrevmonth' => string 'Jun' (length=3)
'monthname' => string 'June' (length=4)
And for your code , you just need to add your date like this
$d=unixtojd(mktime(0,0,0,month,days,year));
$calendar = cal_from_jd($d,CAL_GREGORIAN);
var_dump($calendar['dayname']);
It seems like you used a 1 (one) instead of an l (lowercase L). If you change that, it works fine.
$day = Carbon::createFromFormat('Y-m-d', $date)->format('l');
var_dump($day);
This works, you seem to be using 1 instead of l
$today = Carbon::now();
$dayName = $today->format('l');
When using Carbon, ->dayName is the obvious and more explicit way:
Carbon::createFromFormat('Y-m-d', $date)->dayName
It also allow you to have it in any language:
Carbon::createFromFormat('Y-m-d', $date)->locale('fr_FR')->dayName

PHP datediff overday issue

I wish to get datediff between two times: first is in the evening (like 23:59:59) and the second is on new day (like 02:02:02). When using datediff, it doesn't show correct difference:
echo date_diff(date_create("02:02:02"), date_create("23:59:59"))->format('%H:%I:%S');
response: 21:57:57 (IS WRONG SOMEHOW)
echo date_diff(date_create("02:02:02"), date_create("00:00:00"))->format('%H:%I:%S');
response: 02:02:02 (ECHOS CORRECT TIME)
How could I get it work?
If the date has changed, then you have to tell it that, or it will assume today. You can check it like this:
echo date_diff(date_create("tomorrow 02:02:02"), date_create("23:59:59"))->format('%H:%I:%S');
// 02:02:03
You can verify what the date_create is creating by just dumping it:
var_dump(date_create("02:02:02"));
// object(DateTime)(
// 'date' => '2019-08-16 02:02:02.000000',
// 'timezone_type' => 3,
// 'timezone' => 'America/New_York'
// )
var_dump(date_create("tomorrow 02:02:02"));
// object(DateTime)(
// 'date' => '2019-08-17 02:02:02.000000',
// 'timezone_type' => 3,
// 'timezone' => 'America/New_York'
// )
var_dump(date_create("00:00:00")); // 00:00 being start of day, not end
// object(DateTime)(
// 'date' => '2019-08-16 00:00:00.000000',
// 'timezone_type' => 3,
// 'timezone' => 'America/New_York'
// )

Laravel Carbon does not consider timezone DST

Given my UsersTableSeeder.php class, I am seeding my database with fake data, using a loop:
$numberOfUsers = 150;
DB::table('users')->delete();
$faker = Faker::create();
for ($i = 1; $i <= $numberOfUsers; $i++) {
DB::table('users')->insert([
'id' => $i,
'firstName' => $faker->firstName,
'lastName' => $faker->lastName,
'email' => $faker->email,
'password' => bcrypt("123"),
'created_at' => Carbon::now()->addDays((-5 * $i) - 2)->format('Y-m-d H:i:s'),
'updated_at' => Carbon::now()->addDays(-5 * $i)->format('Y-m-d H:i:s'),
]);
}
The problem here is that when my datetime values are generated, there is a chance it might fall in a DST zone, like between 2017-03-12 02:00:00 and 2017-03-12 02:59:59 (which does happen) and it gives me the following error:
[PDOException]
SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: '2016-03-13 02:08:11' for column 'created_at' at row 1
Now I understand that I cannot put such a value in my database, because my database is smart enough to know that this zone in time doesn't exactly exit. But is there any way I can 'make' Carbon smart enough to consider DST ? I do not want to manually check it with something like:
if ($my_date > 2017-03-12 02:00:00 && $my_date < 2017-03-12 02:59:59)
In Fact Carbon can handle DST zones.
$date = '2017-03-26 02:01:01';
$date = \Carbon\Carbon::parse($date, 'Europe/Berlin');
dd((string)$date);
Results in
// an extra hour was added automatically
"2017-03-26 03:01:01"
Per default Laravel uses 'UTC' for all date and datetime operations (including Carbon). This value can be set in app/config.php
If you actually want to your datetimes be considered in 'UTC' in database a dirty workaround could be something like that:
EDIT:
// calculate current offset to UTC:
$offset = \Carbon\Carbon::now('America/Montreal')->offsetHours;
'created_at' => Carbon::now('America/Montreal')->addHours(-4 + $i)->addDays((-1 * $i) - 2)->tz('UTC')->addHours($offset)->format('Y-m-d H:i:s')
You can generate your timestamps with Carbon and provide the timezone. Try just:
Carbon::now()->format('c')

DateTime "first day of last month" not returning the first day

I looked at this answer already, and it's quite close to what I have.
Here is my PHP code:
$start = new DateTime('0:00 first day of previous month', new DateTimeZone('UTC'));
/*
if (isset($_GET['year']) && isset($_GET['month']) && checkdate($_GET['month'], 1, $_GET['year'])) {
$start = DateTime::createFromFormat('Y-m-d', $_GET['year'] . '-' . $_GET['month'] . '-1');
}*/
$middle = DateTime::createFromFormat('U', strtotime('first day of last month', $start->format('U')));
$middle->setTimezone(new DateTimeZone('UTC'));
$end = DateTime::createFromFormat('U', strtotime('first day of 2 months ago', $start->format('U')));
$end->setTimezone(new DateTimeZone('UTC'));
var_dump($start);
var_dump($middle);
var_dump($end);
Today is August 27th, so I would expect July 1, June 1, and May 1. Here's what the actual output is:
object(DateTime)[1]
public 'date' => string '2013-07-01 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
object(DateTime)[2]
public 'date' => string '2013-05-02 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
object(DateTime)[3]
public 'date' => string '2013-04-02 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
Why is it returning the second day of the months for me?
I've also tried it without the new DateTimeZone('GMT') as the second parameter of the constructor for the initial DateTime but it still gives me the same result, just with different times.
this part irrelevant - question was edited
Because of the timezone difference. $start is calculated in the 'Rainy River timezone', while $middle and $end are in UTC time.
The 'Rainy River timezone has a -06:00 hour offset from UTC (exactly the difference in hours between the first with the second and third results).
update 1 - solution
It seems the problem lies somewhere around strtotime. For some reason it yields a result with an offset of one day (further explanation needed). A simple solution, is to subtract one second from that date and it will produce the correct result.
$timezone = new DateTimeZone('UTC');
$start = new DateTime('0:00 first day of previous month', $timezone );
$middle = DateTime::createFromFormat('U', strtotime('first day of last month',($start ->format('U'))-1),$timezone);
echo $middle->format('Y-m-d')."\n";
Result:
2013-05-01
update 2 - reason for problem
Eventually I find out that the problem originates from the instantiation of the fisrt date object. Here is an illustration.
This will give a correct result:
$original = new DateTime('2013-05-01');
echo $original->format('Y-m-d')."\n";
$previous= DateTime::createFromFormat('U', strtotime('first day of last month',($original->format('U'))),new DateTimeZone('UTC'));
echo $previous->format('Y-m-d')."\n";
Result (OK):
2013-05-01
2013-04-01 <--- OK
However, this will not (only first line different, as in the original code):
$original = new DateTime('0:00 first day of previous month', new DateTimeZone('UTC'));
echo $original->format('Y-m-d')."\n";
$previous= DateTime::createFromFormat('U', strtotime('first day of last month',($original->format('U'))),new DateTimeZone('UTC'));
echo $previous->format('Y-m-d')."\n";
Result:
2013-07-01
2013-05-02 <--- BAD
After reading the answer here, I had a better idea:
$start = new DateTime('0:00 first day of previous month');
/*
if (isset($_GET['year']) && isset($_GET['month']) && checkdate($_GET['month'], 1, $_GET['year'])) {
$start = DateTime::createFromFormat('Y-m-d', $_GET['year'] . '-' . $_GET['month'] . '-1');
}*/
$middle = clone $start;
$middle->modify('first day of last month');
$end = clone $start;
$end->modify('first day of 2 months ago');
var_dump($start);
var_dump($middle);
var_dump($end);
Output:
object(DateTime)[1]
public 'date' => string '2013-07-01 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'America/Rainy_River' (length=19)
object(DateTime)[2]
public 'date' => string '2013-06-01 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'America/Rainy_River' (length=19)
object(DateTime)[3]
public 'date' => string '2013-05-01 00:00:00' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'America/Rainy_River' (length=19)
Also, I realize that a DateTimeImmutable would be a better choice for the $start instance (so that I don't have to clone the other two), but I don't have access to PHP 5.5 yet.

Is this a bug in PHP's DateTime:diff?

>> $start_dt = new DateTime()
DateTime::__set_state(array(
'date' => '2012-04-11 08:34:01',
'timezone_type' => 3,
'timezone' => 'America/Los_Angeles',
))
>> $end_dt = new DateTime()
DateTime::__set_state(array(
'date' => '2012-04-11 08:34:06',
'timezone_type' => 3,
'timezone' => 'America/Los_Angeles',
))
>> $start_dt->setTimestamp(strtotime('31-Jan-2012'))
DateTime::__set_state(array(
'date' => '2012-01-31 00:00:00',
'timezone_type' => 3,
'timezone' => 'America/Los_Angeles',
))
>> $end_dt->setTimestamp(strtotime('1-Mar-2012'))
DateTime::__set_state(array(
'date' => '2012-03-01 00:00:00',
'timezone_type' => 3,
'timezone' => 'America/Los_Angeles',
))
>> $interval = $start_dt->diff($end_dt)
DateInterval::__set_state(array(
'y' => 0,
'm' => 0,
'd' => 30,
'h' => 0,
'i' => 0,
's' => 0,
'invert' => 0,
'days' => 30,
))
>> $interval->format('%mm %dd')
'0m 30d'
i.e., 31-Jan-2012 to 1-Mar-2012 yields less than a month! I'd expect the output to be 1 month, 1 day. It shouldn't matter the number of days in February; that's the point of using a time library -- it's supposed to handle these things. WolframAlpha agrees.
Should I file a bug to PHP? Is there a hack/fix/workaround to get months to work as expected?
Updated answer
This behavior of DateTime::diff is certainly unexpected, but it's not a bug. In a nutshell, diff returns years, months, days etc such that if you did
$end_ts = strtotime('+$y years +$m months +$d days' /* etc */, $start_ts);
you would get back the timestamp that corresponds to end original end date.
These additions are performed "blindly" and then date correction applies (e.g. Jan 31 + 1 month would be Feb 31, corrected to Mar 2 or Mar 3 depending on the year). In this specific example you cannot add even one month as salathe also explains.
Should I file a bug to PHP?
No.
The "month" part of the interval means that the month part of the start date can be incremented by that many months. The behaviour in PHP, taking your start date of 31-Jan-2012 and incrementing the month (literally, 31-Feb-2012) and then correcting for a valid date (PHP does this for you) would give 02-Mar-2012 which is later than the target date that you are working with.
To demonstrate this, take your start date and add n months for a few months to see the behaviour.
31-Jan-2012 (Interval)
02-Mar-2012 (P1M)
31-Mar-2012 (P2M)
01-May-2012 (P3M)
31-May-2012 (P4M)
01-Jul-2012 (P5M)
You can see that the month is being incremented, then adjusted to make a valid date.

Categories