PHP print_r writes variable mystery? - php

I'm running into some very strange behavior with php in a webclient I'm working on. Essentially I'm passing an expiration date as a string which I'm parsing into a DateTime so I can compare it to the current time (to ascertain if something has expired or not.)
Heres the code snippet in question (NOTE: $expiration is set above this snippet, I'm just rewriting over it with the value I actually intend on using)
$expiration = DateTime::createFromFormat("y-m-d h:i:s", $expiration);
$now = date('y-m-d h:i:s', time());
Common::log("before : ", $expiration->date);
//TODO figure out why this common log has to be here or this doesnt work
Common::log("expiration : ", $expiration);
Common::log("after : ", $expiration->date);
if($now > $expiration->date) $data['status'] = 14;
Common::log is an in house function which is just doing print_r
static function log ($msg, $data=null)
{
error_log ($msg . ($data ? print_r ($data, true) : ''));
}
What that code snippet puts out into terminal (which is where I'm looking at whats getting print) is the following.
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] before :
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] expiration : DateTime Object
(
[date] => 2015-06-09 06:16:55
[timezone_type] => 3
[timezone] => America/Indiana/Indianapolis
)
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] after : 2015-06-09 06:16:55
if I simply comment out or delete the line thats logging the $expiration variable then, as the before : log shows, $expiration->date evaluates to the empty string and my logic doing the comparison below breaks. Whats going on here, why does taking out that middle log have any impact on the value of $expiration->date? This is extremely perplexing, and I would appreciate any help anyone can offer on this - I don't want to use code that works if I don't understand why it works.

To be honest I am not sure why this is happening. It probably has something to do with the class constructor. However, there is no documentation for using ->date this way. So, instead of doing things like this:
Common::log("before : ", $expiration->date);
do things this way:
Common::log("before : ", $expiration->format('y-m-d h:i:s'));
In other words you are telling PHP to display the date in the output format you choose (which could be different from the input format that you created it with).

Related

Problem to convert unix time to human time

I have a online controller where i want to know the uptime and last seen of my access points. for this i use the epoch convert methode to convert unix time to human readable time.
this is the code i use
// getting controller ap info //
$name=$status=$uptime=$last_seen=[];
foreach ($ligowave->result as $index => $obj) {
$name[] = $obj->name;
$status[] = $obj->status;
$uptime[] = $obj->uptime;
$last_seen[] = $obj->last_seen;
}
// time settings //
$epoch = $uptime;
$uptimetime = (new DateTime("#$epoch"))->format(' H:i:s');
$epoch = $last_seen;
$lastseendate = (new DateTime("#$epoch"))->SetTimeZone(new DateTimeZone('Europe/Amsterdam'))->format(' d-m-Y H:i:s');
if ($status == "up") {
echo $name;
echo " is up, ";
echo "uptime is:" . $uptimetime;
} else {
echo $name;
echo " is down, ";
echo "device is last seen:" . $lastseendate;
}
return array($name, $status, $epoch, $uptimetime, $lastseendate);
}
the error i am getting is:
PHP Fatal error: Uncaught Exception: DateTime::__construct(): Failed
to parse time string (#Array) at position 0 (#): Unexpected character
Please Note:
This answer is only about resolving the specific PHP error as stated on the question. For a wider answer about how to effectively write code to iterate over arrays please see Fyrye's answer.
The error you are receiving is:
PHP Fatal error: Uncaught Exception: DateTime::__construct(): Failed to parse time string (#Array) at position 0 (#): Unexpected character
What is actually wrong, and why?
1)
You have an uncaught Exception, which throws a fatal error due to being, er, uncaught. Exceptions are central to object programming and should be researched and implemented in your PHP scripts.
2)
Why do you have an Exception in the first place? The error states the exception is caused by "Failed to parse time string (#Array)". So you are trying to give the DateTime object an array when it expects a string. It would help you to Read the PHP DateTime __construct Manual Page.
3)
Further, and more specifically the # character is unexpected; which means it should not be there. This character is only valid when followed by timestamp integer values in string format.
Because the array is output as a string (i.e in quotes) the end result is "#Array" and so the # is taken literally by DateTime; but of course this character is not expected by DateTime in any non-numeric incoming time string. This is the root cause of your fatal error here.
While PHP does employ loose typecasting to some extent, wrapping an array $var in quotes is far too loose and so the array simply outputs "Array" and issues a corresponding PHP Notice:
Notice: Array to string conversion in .....
For a valid list of correct DateTime string formats to give the object you can view this page from the PHP Manual.
4)
I do not see why you need those outer brackets?
So, how should this be done?
Reading the issues in reverse order from 4 to 1; the correct way of resolving this specific error is:
Wrap the attempt into a try/catch block to avoid these fatal errors.
Ensure that the value given to the DateTime object is a string
Remove unnecessary and invalid characters from that string.
So:
$epoch = $uptime;
try{
/***
* Uptime appears to be a numeric array of time string values
* Therefore go for the first one.
* You may want to wrap this code in a loop to catch each one.
***/
$uptimeTime = new DateTime("#".(string)$epoch[0]);
}
catch (Exception $ex){
/***
* If you wish to ignore these errors simply leave this block empty
***/
error_log("There was a problem on line ".__LINE__."! ".print_r($ex));
}
/***
* By default UTC timestamps are not zoned.
***/
// $uptimeTime->setTimeZone(new DateTimeZone('Europe/Amsterdam'));
$uptimeTimeOutput = $uptimeTime->format('H:i:s');
/***
* $uptimeTimeOutput is the correctly formatted date from $epoch[0]
***/
print $uptimeTimeOutput;
I hope with the information given above your able to correct the second DateTime instantiation code (ie the new DateTime line) yourself. :-)
TL;DR
Please read the PHP Manual and allow it to inform your coding choices.
The main issue is caused by defining $uptime[] as an array of values, resulting in $epoch = $uptime containing an array of timestamp strings. When DateTime() expects a single string value.
To resolve the issue you need to move the DateTime calls inside of the foreach iteration.
The other issue, as mentioned in the answer provided by Martin, is that you are not handling exceptions within your code. If uptime or last_seen is not of an expected value that is being supplied to the DateTime constructor, an exception will be thrown.
To handle the exceptions you can use atry/catch block in order to handle an issue that arises in your code. Exceptions are meant to point you to fatal errors in your code so that you can resolve or verify them programmatically and typically should not be ignored by using try/catch. For more details please see https://www.php.net/manual/en/language.exceptions.php
Without knowing exactly what you're trying to accomplish with your code. It appears you are wanting to echo and return all of the values from $ligowave->result. I made the appropriate changes below to reflect what I surmise are your intentions. Along with some minor simplifications.
Please clarify what you are wanting to return and echo and I will adjust my answer.
Example: https://3v4l.org/O0nS6
//...
// getting controller ap info //
$values = [];
foreach ($ligowave->result as $index => $obj) {
//convert the unix timestamps to DateTime objects
$uptime = (new DateTime('#' . $obj->uptime));
$last_seen = (new DateTime('#' . $obj->last_seen))->setTimeZone(new DateTimeZone('Europe/Amsterdam'));
//store the return values into an array
$values[] = $value = [
'name' => $obj->name,
'status' => $obj->status,
'uptimetime' => $uptime->format('H:i:s'),
'lastseendate' => $last_seen->format('d-m-Y H:i:s')
];
//output each of the statuses
printf('%s is %s, ', $obj->name, $obj->status);
if ('up' === $obj->status) {
echo 'uptime is: ' . $value['uptimetime'];
} else {
echo 'device is last seen: ' . $value['lastseendate'];
}
}
return $values;
}
Result:
foo is up, uptime is: 10:20:54
bar is down, device is last seen: 01-06-2019 08:22:30
Returns:
array (
0 =>
array (
'name' => 'foo',
'status' => 'up',
'uptimetime' => '10:20:54',
'lastseendate' => '05-06-2019 11:16:21',
),
1 =>
array (
'name' => 'bar',
'status' => 'down',
'uptimetime' => '10:20:54',
'lastseendate' => '01-06-2019 08:22:30',
),
)
It also appears that you are using a 24 hour time, to represent a duration.
If so you will need to use a DateInterval instead of DateTime, by using DateTime::diff from an appropriate timeframe. For more details please see https://php.net/manual/en/class.dateinterval.php
Assuming uptime is the started time and last_seen is the current run time, you can use $uptime->diff($last_seen), to retrieve the time that elapsed between uptime to last_seen (duration), instead of the 24 hour time value of uptime. Otherwise you can use $uptime->diff(new DateTime('now', new DateTimeZone('Europe/Amsterdam'))), to use the current date time.
One caveat, is that the hours of DateInterval are non cumulative, meaning you would need to add the days in some manner. I have used the most accurate of %a as opposed to adding on to the hours with days * 24
Example: https://3v4l.org/LHdqL
//...
$uptime = (new DateTime('#' . $obj->uptime))->setTimeZone(new DateTimeZone('Europe/Amsterdam'));
$last_seen = (new DateTime('#' . $obj->last_seen))->setTimeZone(new DateTimeZone('Europe/Amsterdam'));
$values[] = $value = [
'name' => $obj->name,
'status' => $obj->status,
'lastseendate' => $last_seen->format('d-m-Y H:i:s'),
'uptimetime' => $uptime->diff($last_seen)->format('%a.%H:%I:%S'), //DD.HH:MM:SS
];
//...
Result:
foo is up, uptime is: 12.22:48:26

How to compare date strings in the format yyyy-mm-dd in PHP

I have a range of dates in string format in the form of
'2014-10-12'
what i want to do is compare these dates so i can get the oldest and the youngest.
In PHP how do i convert these to a format where i can do the following?
$oldestdate;
$youngesdate;
//loop though all the dates
if($exampledate < $youngesdate)
$youesdate = $exampledate;
if($exampledate > $oldestdate)
$oldestdate = $exampledate;
Thanks
The nice thing about YYYY-MM-DD style dates is that they will always sort correctly, whether treated as text (as in your example), numbers (e.g. 20141012), or actual dates.
Thus, there's no need to do anything special to compare them as long as everything is the same format. Your code, as written, should work as-is (besides the typos for $youngestdate).
Note that if you want to do anything besides comparing them -- e.g. anything actually involving treating them like actual dates -- you will indeed want something like strtotime() or a mix of mktime() + substr()
have you tried strotime? reference http://php.net/manual/de/function.strtotime.php
then you can easily compare with <and > and so on.
have you tried checkdate(12, 31, 2000)? PHP.net Checkdate function
For years between 1 and 32767 inclusive.Check post 2 in the php.net link
You should use the DateTime class.
$arr = ['2012-10-12', '2004-10-12', '2014-08-12', '2014-09-12', '2014-09-13', '2014-09-11'];
$_now = new DateTime('now');
foreach ( $arr as $_t ) {
$d = new DateTime ( $_t );
if ( !isset($newest) || $d >= $newest ) $newest = $d;
if ( !isset($oldest ) || $d <= $oldest ) $oldest = $d;
}
echo 'Newest ' . $newest->format('Y-m-d');
echo 'Oldest' . $oldest->format('Y-m-d');
Take a look here: Reference on php.net
And here is an working example

strtotime date validation passes on invalid date

I am in the middle of setting up a basic CMS that allows the client to add articles to their mobile app. The CMS is coded in PHP and will use JSON to deliver the content to the mobile app.
Now my problem is there is an option to publish the article at a certain date, so I want to validate the date to check it is valid.
So to test possibilites I made a small script. I am using strtotime() to check the date is valid, my script is:
<?php
$date[] = '2011-31-01';
$date[] = '2011-02-31';
foreach($date as $str) {
if(strtotime($str) == false) {
$result[] = '<p>[' . $str . '] Resulted in an <span style="color: red;">Error.</span></p>';
} else {
$result[] = '<p>[' . $str . '] Resulted in <span style="color: green;">Success.</span></p>';
}
}
foreach($result as $return) {
echo $return;
}
?>
Now my problem is the date 2011-02-31 which is 31st February 2011 is returning as valid, when obviously it isn't. So my question is why does it do this? and is there a better method to check that the date is valid and exists?
Thanks in advance.
checkdate(); Validates a Gregorian date. Returns TRUE if the date given is valid; otherwise returns FALSE.
if(checkdate(2, 31, 2011)){
echo "Yeah";
} else {echo "nah";}
It returns false!
That's the way to go.
Unless you have one (or a small set) fixed format for your date string it will be hard to get an acceptable result. In case you know the format, you can either parse the string directly yourself (and test it afterwards with checkdate), or you use strptime to try parsing against known formats until you get a valid result.
If you don’t know the format, and you have to use strtotime, then you are required to accept that strtotime will try parsing the date string in the best possible way. This may lead to different dates than it was expected to be.

preg_match for mysql date format

im trying to validate a date to see if it matchs the mysql format
this is the code
$match = "/^\d{4}-\d{2}-\d{2} [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/";
$s = $this->input->post("report_start"). " " . $this->input->post("report_start_time").":00";
$e = $this->input->post("report_end"). " " . $this->input->post("report_end_time").":59";
if($this->input->post("action") != "")
{
echo trim($s). " => " . preg_match($match, trim($s));
echo "<br>";
echo trim($e). " => " . preg_match($match, trim($e));
}
the date format goes into $s and $e are
$s = 2011-03-01 00:00:00
$e = 2011-03-01 23:59:59
and they both return false (0).
i tested the pattern on http://www.spaweditor.com/scripts/regex/index.php and it returns true (1)
http://pastebin.com/pFZSKYpj
however if i manual inter the date strings into preg_match like
preg_match($match, "2011-03-01 00:00:00")
it works.
i have no idea what im doing wrong
======================
now that i think about it, i only need to validate the houre:min part of the datetime string.
im manually adding the seconds and the date is forced by a datepicker and users cant edit it
You're making your work harder that it needs to be. In php there are many date handling functions that mean you don't have to treat dates like strings. So, rather than test that your input dates are in the correct format, just insist on the correct format:
$adate= date_create('January 6, 1983 1:30pm'); //date format that you don't want
$mysqldate= $adate->format("Y-m-d h:i:s");//date format that you do want
There are also functions to check that a date is a real date, like checkdate.
ok heres wat i did.
since im forcing the date format and the ending seconds of the time part
i just validated the hour:mini part using "/^2[0-3]|[01][0-9]:[0-5][0-9]$";
and if that returns true i put everything together end reconstructed the final datetime string
$match = "/^2[0-3]|[01][0-9]:[0-5][0-9]$/";
$s_d = $this->input->post("report_start");
$s_t = $this->input->post("report_start_time");
$e_d = $this->input->post("report_end");
$e_t = $this->input->post("report_end_time");
if($this->input->post("action") != "")
{
if(
( preg_match($match , trim($s_d." ".$s_t.":00")) )
&& ( preg_match($match , trim($e_d." ".$e_t.":59")) )
)
{
$r = $this->model_report->client_hours_logged(array($s,$e));
$data['report'] = $r;
var_dump($r);
//$this->load->view("report/client_hours_per_client",$data);
}
}
Watch out:
[0-2][0-3] is not a good regex for hour values - it will match 01, 12, 23 and others, but it will fail 04 through 09 and 14 through 19.
Better use (2[0-3]|[01][0-9]) instead.
I use this to validate a 'Y-m-d H:i:s' format date string:
match = '/^[12][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[01]) ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/';
You could use strtotime and date to parse and format the date properly.
Why not just simply force the date into the format you want:
$e = '2011-03-01 00:00:00';
$mysqlFormat = date('Y-m-d H:i:s', strtotime($e));
Also, there is a bit of an error in your regex [0-2][0-3]:[0-5][0-9]:[0-5][0-9] will only match the hours of 00,01,02,03,10,11,12,13,20,21,22,23 so it will never match 4am, or 3pm among others. That aside I looked over your RegEx and I don't see any problems with it matching the test cases you've offered. I would check to make sure there is not extra whitespace on either side of date string with trim().
I concur with Tim : MySQL behaves in quirks mode and always tries to go easy on DATE and DATE_TIME column types. You can omit certain parts of your input and it still will try to compensate and achieve that goal successfully to some degree... That's why, most numbers your Reg-ex considers as invalid, MySQL will accept as valid.

Parsing localized date strings in PHP

I have some code (it's part of a wordpress plugin) which takes a text string, and the format specifier given to date(), and attempts to parse it into an array containing hour, minute, second, day, month, year.
Currently, I use the following code (note that strtotime is horribly unreliable with things like 01/02/03)
// $format contains the string originally given to date(), and $content is the rendered string
if (function_exists('date_parse_from_format')) {
$content_parsed = date_parse_from_format($format, $content);
} else {
$content = preg_replace("([0-9]st|nd|rd|th)","\\1",$content);
$content_parsed = strptime($content, dateFormatToStrftime($format));
$content_parsed['hour']=$content_parsed['tm_hour'];
$content_parsed['minute']=$content_parsed['tm_min'];
$content_parsed['day']=$content_parsed['tm_mday'];
$content_parsed['month']=$content_parsed['tm_mon'] + 1;
$content_parsed['year']=$content_parsed['tm_year'] + 1900;
}
This actually works fairly well, and seems to handle every combination I've thrown at it.
However, recently someone gave me 24 Ноябрь, 2010. This is Russian for November 24, 2010 [the date format was j F, Y], and it is parsed as year = 2010, month = null, day = 24.
Are there any functions that I can use that know how to translate both November and Ноябрь into 11?
EDIT:
Running print_r(setlocale(LC_ALL, 0)); returns C. Switching back to strptime() seems to fix the problem, but the docs warn:
Internally, this function calls the strptime() function provided by the system's C library. This function can exhibit noticeably different behaviour across different operating systems. The use of date_parse_from_format(), which does not suffer from these issues, is recommended on PHP 5.3.0 and later.
Is date_parse_from_format() the correct API, and if so, how do I get it to recognize the language?
Try to set the locale to Russian as hinted in the manual:
Month and weekday names and other language dependent strings respect the current locale set with setlocale() (LC_TIME).
you could try take a locale parameter and call locale_set_default($locale) before doing the date parsing.
$originalLocale = locale_get_default();
$locale ? $locale : $originalLocale;
locale_set_default(locale);
// date parsing code
locale_set_default($originalLocale);
I haven't testing this but it's work a try.
FYI, I believe the locale string for Russian is "ru-Latn"
I see that the question is already answered but none of the solutions provided worked for me.
This is my solution:
if(!preg_match('/^en_US/', $locale)){
$months_short = array('jan' => t('jan'), 'feb' => t('feb'), 'mar' => t('mar'), 'apr' => t('apr'),
'may' => t('may'), 'jun' => t('giu'), 'jul' => t('lug'), 'aug' => t('ago'),
'sep' => t('set'), 'oct' => t('ott'), 'nov' => t('nov'), 'dec' => t('dec'));
foreach ($months_short as $month_short => $month_short_translated) {
$date = preg_replace('/'.$month_short_translated.'/', $month_short, strtolower($date));
}
}
$pieces = date_parse_from_format($format,$date);
if($pieces && $pieces['error_count'] == 0 && checkdate($pieces['month'], $pieces['day'], $pieces['year'])){
return date('Y-m-d', mktime(0,0,0,$pieces['month'],$pieces['day'],$pieces['year']));
}
Where t() returns the translated abbreviation for the month.
Probably not the best solution ever (because it fails if there is no valid translation) but it works for my case.

Categories