FrozenDate::parseDate() doesn't reflect the date passed as parameter - php

It doesn't seem to matter what format I specify to FrozenDate::parseDate(), I always get the same wrong date.
Code:
$d1 = '8/28/2021';
$d2 = '10/27/2021';
debug($d1);
debug($d2);
debug(FrozenDate::parseDate($d1, 'm/d/Y'));
debug(FrozenDate::parseDate($d2, 'm/d/Y'));
exit;
Output:
\src\Controller\AdministratorsController.php (line 559)
'8/28/2021'
\src\Controller\AdministratorsController.php (line 560)
'10/27/2021'
\src\Controller\AdministratorsController.php (line 561)
object(Cake\I18n\FrozenDate) {
'time' => '2020-12-20 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
\src\Controller\AdministratorsController.php (line 562)
object(Cake\I18n\FrozenDate) {
'time' => '2020-12-20 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}

Check the API docs, the second argument accepts:
array|string|int|null $format Any format accepted by IntlDateFormatter.
The docblock also has some examples:
$time = Time::parseDate('10/13/2013');
$time = Time::parseDate('13 Oct, 2013', 'dd MMM, y');
$time = Time::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT);
So that's not the syntax as known from for example PHP's date() function, it's the same syntax that is accepted by i18nFormat(), the ICU Date/Time Format Syntax, where the pattern you're looking for would be:
M/d/y
It is worth noting that parsing is rather lenient, and also locale dependent. For example while M would only ever format as the month without a leading zero, when parsing it will accept the month as number with and without leading zero, as well as short and long month names (in the current language), meaning all of these would actually work (when en is set as the language):
FrozenDate::parseDate('8/6/2021', 'M/d/y')
FrozenDate::parseDate('08/06/2021', 'M/d/y')
FrozenDate::parseDate('Aug/6/2021', 'M/d/y')
FrozenDate::parseDate('August/06/21', 'M/d/y')

Related

Normalize DateInterval in PHP

The situation/issue
I have an issue with DateInterval in PHP.
Given the following code
$interval = new DateInterval("PT6000S");
echo $interval->h; // 0
echo $interval->i; // 0
echo $interval->s; // 6000
echo $interval->format("%h:%i"); // 0:0
I want this to represent 1 hour and 40 minutes, not 6000 seconds.
The question
Is there a built-in way to normalize the DateInterval? A specific way of writing the duration string? Or is this something that must be done normally?
I can modify the way it's created and formated if anyone has a "work-around" to suggest.
I have done my research, but strangely enough I have not found anything helpful with this issue.
The DateInterval class is a bit annoying like that. Strictly it's doing the right thing, since your interval spec string specified zero minutes and hours, but it's definitely not helpful that it doesn't roll over excess seconds into minutes and hours.
One option I've used before is to add the interval to a new DateTime object initialised to the Unix epoch, then format the resulting time instead. This would mean using the standard date format strings (so h rather than %h, etc)
echo (new DateTime('#0'))->add(new DateInterval("PT6000S"))->format('h:i');
// 01:40
See https://3v4l.org/OX6JF
The solution
Like every time I ask for help, I cannot stop trying and I generally find the answer right after. No exception this time.
I've found a user contributed note in the DateInterval documentation.
You have to create two DateTime (they can be any date and time), add your interval to the second and substract the first from the second. This will populate the DateInterval in a normalized way.
Here is the code the user wrote, wrapped in a handy-dandy function:
public function createDateInterval(string $duration): DateInterval
{
$d1 = new DateTime();
$d2 = new DateTime();
$d2->add(new DateInterval($duration));
return $d2->diff($d1);
}
(Note that this function can throw an exception if your $duration string is not correctly formatted.)
This solution uses DateTime with a fixed time. Why ?
DateTime also supports microseconds. There is therefore a certain probability that 2 calls to new Date() will not return the same time. Then we can get wrong results.
//360000 Sec = 100 hours = 4 Days + 4 hours
$di = new DateInterval("PT360000S");
$diNormalize = date_create('#0')->diff(date_create('#0')->add($di));
var_export($diNormalize);
/*
DateInterval::__set_state(array(
'y' => 0,
'm' => 0,
'd' => 4,
'h' => 4,
'i' => 0,
's' => 0,
'f' => 0.0,
'weekday' => 0,
'weekday_behavior' => 0,
'first_last_day_of' => 0,
'invert' => 0,
'days' => 4,
'special_type' => 0,
'special_amount' => 0,
'have_weekday_relative' => 0,
'have_special_relative' => 0,
))
*/
echo $diNormalize->format('%a days %h hours %i minutes');
//4 days 4 hours 0 minutes
DateInterval has a special format method for the output. This should be used here and no detour via date, gmdate or DateTime should be taken.

Diff between dates calculates incorrect

I have two dateTime fields and I am trying to get the difference between them. However my results are coming out less than they should. I have looked at examples and the code seems fine.
$entryTime = new \DateTime($journalEntry->entry_date_time->toTimeString());
$closeTime = new \DateTime($journalEntry->close_date_time->toTimeString());
$interval = $entryTime->diff($closeTime);
debug($journalEntry->entry_date_time);
debug($journalEntry->close_date_time);
echo $interval->format('%d days %h hours %m minutes');
I am getting 0 days 11 hours 0 minutes
/src/Controller/JournalEntriesController.php (line 145)
object(Cake\I18n\FrozenTime) {
'time' => '2020-09-05 07:49:04.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
/src/Controller/JournalEntriesController.php (line 146)
object(Cake\I18n\FrozenTime) {
'time' => '2020-09-07 19:36:53.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
If you want to diff date and time, then there's no reason to create new DateTime objects, especially not from a time-only string, which would result in diffing only the time.
Simply diff the instances that you already have, they extend from \DateTime and \DateTimeImmutable respectively:
$interval = $journalEntry->entry_date_time->diff($journalEntry->close_date_time);
echo $interval->format('%d days %h hours %i minutes');
and as mentioned in the comments, m is for months, the pattern character for minutes is i.
CakePHP's datetime objects also provide additional diffing functionality for obtaining specific units (diffInHours(), diffInMinutes(), etc) or human readable formats (diffForHumans()).
See also
Chronos Cookbook > Chronos > Generating Differences
Chronos API > \Cake\Chronos\Traits\DifferenceTrait
PHP Manual > Function Reference > Date and Time Related Extensions > Date/Time > DateInterval > DateInterval::format

Laravel Carbon formatting error

I'm experiencing a weird problem.
I'm using Carbon for dates. I want to use the format Y-W (year,week) which is working correctly.
Here i store it into the DB:
$weekDate = Carbon::createFromFormat('d-m-y', "{$key}")->format('Y-W');
DB::table('backorder_voorspelling')->insert([
'artikelcode' => $articlecode,
'week' => $weekDate,
'aantal' => $value,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
The database record is correct:
{#426 ▼
+"id": 1
+"artikelcode": "articlecode"
+"week": "2017-44"
+"aantal": "6"
+"created_at": "2018-01-18 11:46:45"
+"updated_at": "2018-01-18 11:46:45"
}
Later on i want to convert the Y-W back to a carbon time and this is telling me:
The code i use to create the carbon time:
$startOfWeek = Carbon::createFromFormat('Y-W', $row->week);
The formats are the same, when storing i use the format ('Y-W') and when creatingFromFormat i use the format ('Y-W'), yet it's not working...
I tried replacing the - for / but this returns the same error.
Any help is appreciated.
Not all date format characters can be used in DateTime::createFromFormat (which is what Carbon extends). Unfortunately for you, W is one of the ones that's missing.
From the manual:
The format that the passed in string should be in. See the formatting options below. In most cases, the same letters as for the date() can be used.
You can work round this by manually calling setISODate on a new DateTime (or Carbon) instance:
list ($year, $week) = explode('-', '2017-44');
$d = new DateTime;
$d->setISODate($year, $week);
setISODate also accepts a third $day parameter - by default it will set to the first day of the week, which I think is what you want.
See https://eval.in/937360

PHP strptime() Doesn't seem to work? I can't see anything wrong

I am currently trying to get a database transfered from an old system to a new one, and i have run into a small issue.
I have a db row formatted at:
2007-04-24 00:23:59
(yyyy-mm-dd hh:mm:ss)
And i cant seem to get it to work through strptime()
Here is what i have:
$format = '%Y-%m-%d %H:%i:%s';
$ct = strptime($row['time'], $format );
//$row['time'] == 2007-01-11 00:47:27
This returns nothing.
Can anyone see anything wrong with that?
Thanks.
Format should be a bit different:
$format = '%Y-%m-%d %H:%M:%S'; // or just '%F %T'
Here's the corresponding section of strftime format documentation:
%M Two digit representation of the minute [00 through 59]
%S Two digit representation of the second [00 through 59]
%T Same as "%H:%M:%S"
%F Same as "%Y-%m-%d" (commonly used in database datestamps)
Refer to the strftime() documentation for the proper format.
Your foramt should be:
$format = '%Y-%m-%d %T';
From manual http://www.php.net/manual/en/function.strptime.php
<?php
$format = '%d/%m/%Y %H:%M:%S';
$strf = strftime($format);
echo "$strf\n";
print_r(strptime($strf, $format));
?>
Result
03/10/2004 15:54:19
Array
(
[tm_sec] => 19
[tm_min] => 54
[tm_hour] => 15
[tm_mday] => 3
[tm_mon] => 9
[tm_year] => 104
[tm_wday] => 0
[tm_yday] => 276
[unparsed] =>
)
strptime() is not available in Windows.
Note: This function is not implemented on Windows platforms.
Source: http://php.net/manual/en/function.strptime.php
This is how I usually handle date formatting.
$somedate = strtotime($dateValue);
$formatted_date = date('Y-m-d H:i:s', $somedate);

Check if time is between two times, gives false answers

<?php
$saturday = array(
'00:00 - 01:00' => 'Nightshow',
'01:00 - 02:00' => 'Nightshow',
'02:00 - 03:00' => 'Nightshow',
'03:00 - 04:00' => 'Nightshow',
'04:00 - 05:00' => 'Nightshow',
'05:00 - 06:00' => 'Early birds',
'06:00 - 07:00' => 'Early birds',
'07:00 - 08:00' => 'Cox n Crendor in the morning',
'08:00 - 09:00' => 'Cox n Crendor in the morning',
...
'23:00 - 24:00' => 'John late night'
);
foreach($saturday as $timeD => $show){
if(!empty($timeD) && isset($timeD)){
$timesplit = explode(' -', $timeD);
if (time() > strtotime('today '. $timesplit[0]) && time() < strtotime('today '. $timesplit[1])) {
echo $show;
}
}
}
?>
Hello again Stackoverflow!
I have an array, containing times of the day (24h format) that represents a schedule. What I'm trying to do, is check wich show is currently on. What I've tried above didn't work - Times turned out to be a few hours off or they would be completely wrong, not even in a certain order. I don't understand what's wrong. How do I solve this?
Thanks in advance!
I believe you ran into some basic timezone problem.
Timezone Basic
Timezone conversion could be frustrating, but once you get used to it, it's pretty straight forward.
When we deal with time, we also need to be careful with functions like strtotime() and date(). These two functions are affected by timezone settings. When I say timezone setting, I mean the value date.timezone in your php.ini.
Take, for example, I set date.timezone = Asia/Singapore, and it's a UTC+8 timezone:
Let's say UTC time now is 2013-02-05 00:00:00 UTC and timestamp should be 1360022400:
time() will gives me 1360022400
strtotime('2013-02-05 00:00:00') will gives me 1359993600 (or -28800, or -8 hours!)
Notice how strtotime behaves? It actually uses the fact that the PHP environment is in UTC+8 timezone and thinks that the datetime you had specified is in a +8 timezone, and hence it pushed back 8 hours for you, to correctly represent it in UTC timestamp. This is where the problem is.
Solution
Usually, what I will do (perhaps someone can offer a better solution?) is to hard append a UTC behind the ambiguous datetime and force us to calculate everything in UTC timezone. So simply do this:
strtotime('2013-02-05 00:00:00 UTC')
will now gives you 1360022400, or exactly 00:00:00 in UTC time. This is especially good when you do time sensitive calculation when time() is returning you a real UTC timestamp.
Some notes:
time() will always give you UTC timestamp regardless of timezone setting.
strtotime() will always affected by timezone setting so if you are doing time sensitive comparison, try to make sure they are all in UTC.
date() as well, affected by timezone setting, where date('Y-m-d H:i:s', 1360022400) will gives you 2013-02-05 08:00:00 and to overcome this we use gmdate('Y-m-d H:i:s', 1360022400) instead.
Your Question
So since it seems like you are dealing with time-sensitive decision here, one question, do I need to see the same result as you if I live in a different timezone with you?
If we (I and you) need to see the exact same result, then you should do this:
//Let's say the timezone required for that decision made is UTC+6
$event_timezone_offset = 6 * 3600;
foreach($saturday as $timeD => $show){
if(!empty($timeD)){ //you don't need isset here as !empty taken care of it
list($startTime, $endTime) = explode(' - ', $timeD);
$start = strtotime('today '.$startTime.' UTC') - $event_timezone_offset;
$end = strtotime('today '.$endTime.' UTC') - $event_timezone_offset;
$now = time();
if ($start <= $now && $now < $end) { //notice the inclusive <=
echo $show;
}
}
For the line strtotime('today '.$startTime.' UTC') - $event_timezone_offset;, let's step through:
Let's assume the local time now is 07:00 (UTC+6) and you should see 'Cox n Crendor..':
time() will gives you the exact UTC timestamp of 2013-09-21 01:00:00 UTC
strtotime('today 07:00 UTC') will gives you the timestamp of 2013-09-21 07:00:00 UTC
and once you do - $event_timezone_offset, you get a time of 2013-09-21 01:00:00 UTC
so if ($start <= $now) will then be true, and you should see 'Cox n Crendor..'
If I and you need to get a different result based on our respective local timestamp, you will then need to either sniff user's timezone using javascript, or ask them to set a timezone through some backend settings, and use the same logic to determine the result, by setting $event_timezone_offset = $user_timezone_offset.
I hope this is not too confusing but it's really fun dealing with all these maths.
(You will have to replace $fake_timestamp with the current timestamp time() later in your script ... but I needed it that way for testing.)
<?php
$saturday = array(
'00:00 - 01:00' => 'Nightshow',
'01:00 - 02:00' => 'Nightshow',
'02:00 - 03:00' => 'Nightshow',
'03:00 - 04:00' => 'Nightshow',
'04:00 - 05:00' => 'Nightshow',
'05:00 - 06:00' => 'Early birds',
'06:00 - 07:00' => 'Early birds',
'07:00 - 08:00' => 'Cox n Crendor in the morning',
'08:00 - 09:00' => 'Cox n Crendor in the morning',
'23:00 - 24:00' => 'John late night'
);
//$current_day_timestamp = gettimeofday(); //bug? returns time(), not day!time
$current_day_timestamp = strtotime('TODAY');
//print 'Current day time: '.$current_day_timestamp.'<br />';
//Trying to "find" Early birds 05:00-06.00, setting "current time" to 05:30
$fake_timestamp = $current_day_timestamp + 5.5 * 3600;
//print 'fake time: '.$fake_timestamp.'<br />';
foreach ($saturday AS $hours => $show) {
$hours = explode(' - ', $hours);
$show_start = strtotime($hours[0]);
$show_end = strtotime($hours[1]);
if ($show_start < $fake_timestamp AND $show_end > $fake_timestamp) {
print $show;
}
//print $show.'<hr />';
//print 'show start: '.$show_start.'<br />';
//print 'show end: '.$show_end.'<br /><br />';
}
?>

Categories