php date_parse("Feb 2010") gives day == 1 - php

There is what I would call a bug in date_parse when there is no day. $d = date_parse("Feb 2010") will give $d["day"] == 1.
See the comment on this on the date_parse manual page.
Any nice workaround for this problem? :-)
UPDATE
The date comes from published research reports. Unfortunately this means that they could look in different ways. I want to convert them to more standard ISO format when displaying the references. To help the readers I want always to include just the given fields (years, month, date). So this should be valid (and just give me the year):
2010
This should be valid, but just give me 2010-02 so to say:
Feb 2010
UPDATE 2
So far I have seen two bugs here in date_parse. It can't parse 2010. And it gives a day though there is no day in Feb 2010.
I can of course write a fix for this, but surely someone has already done this, or???

The above bugfix routine is great, Leo, thanks. Unfortunately it still trips over January, thinking that 2014-01 is the same as 2014-01-01 --- we're eleven-twelfths of the way there.
The date formats that PHP can parse, that don't contain a day-of-month, appear to be (in php_src:date/lib/parse_date.re):
gnudateshorter = year4 "-" month;
datenoday = monthtext ([ .\t-])* year4;
datenodayrev = year4 ([ .\t-])* monthtext;
Very few, conveniently. We can run the same regexes on $dateRaw, essentially reverse-engineering what the parser had decided.
(Side observations: the above excludes formats like 5/2016, which is parsed as "20 May with some extra characters at the end"; they are also similar to day-of-year and week-of-year formats, so we'll try not to trip over those.)
function date_parse_bugfix($dateRaw) {
$dateRaw = trim($dateRaw);
// Check for just-the-year:
if (strlen($dateRaw) === 4 && preg_match("/\d{4}/", $dateRaw) === 1) {
$da = date_parse($dateRaw . "-01-01");
$da["month"] = false;
$da["day"] = false;
}
else {
$da = date_parse($dateRaw);
if ($da) {
// If we have a suspicious "day 1", check for the three formats above:
if ($da["day"] === 1) {
// Hat tip to http://regex101.com
// We're not actually matching to monthtext (which is looooong),
// just looking for alphabetic characters
if ((preg_match("/^\d{4}\-(0?[0-9]|1[0-2])$/", $dateRaw) === 1) ||
(preg_match("/^[a-zA-Z]+[ .\t-]*\d{4}$/", $dateRaw) === 1) ||
(preg_match("/^\d{4}[ .\t-]*[a-zA-Z]+$/", $dateRaw) === 1)) {
$da["day"] = false;
}
}
}
}
return $da;
}

No answers so I answer my own question. Here is a workaround the problems I saw.
// Work around for some bugs in date_parse (tested in PHP 5.5.19)
// http://php.net/manual/en/function.date-parse.php
//
// Date formats that are cannot be parsed correctly withoug this fix:
// 1) "2014" - Valid ISO 8061 date format but not recognized by date_parse.
// 2) "Feb 2010" - Parsed but gives ["day"] => 1.
function date_parse_5_5_bugfix($dateRaw) {
// Check "2014" bug:
$dateRaw = rtrim($dateRaw);
$dateRaw = ltrim($dateRaw);
if (strlen($dateRaw) === 4 && preg_match("/\d{4}/", $dateRaw) === 1) {
$da = date_parse($dateRaw . "-01-01");
$da["month"] = false;
$da["day"] = false;
} else {
$da = date_parse($dateRaw);
if ($da) {
if (array_key_exists("year", $da)
&& array_key_exists("month", $da)
&& array_key_exists("day", $da))
{
if ($da["day"] === 1) {
// Check "Feb 2010" bug:
// http://www.phpliveregex.com/
if (preg_match("/\b0?1(?:\b|T)/", $dateRaw) !== 1) {
$da["day"] = false;
}
}
}
}
}
return $da;
}
Some tests (visual ;-) )
$a = date_parse_5_5_bugfix("2014"); print_r($a);
$b = date_parse_5_5_bugfix("feb 2010"); print_r($b);
$c = date_parse_5_5_bugfix("2014-01-01"); print_r($c);
$d = date_parse_5_5_bugfix("2014-11-01T06:43:08Z"); print_r($d);
$e = date_parse_5_5_bugfix("2014-11-01x06:43:08Z"); print_r($e);

Can you try:
$dateTime = strtotime('February, 2010');
echo date('Y-m', $dateTime);

Related

Too many Ifs to check variable really contains a valid, 4 digit year

The following if statement works but it's ugly and I have a feeling the same could be achieved with a preg_match REGEX. How?
Basically I don't know how to limit the amount of digits to 4 in regex yet. And once the 4 numerical digit criteria is met, I would like to limit the return of true to between 2019 and 2025.
Sample regex:
/* digits only, no dots */
function is_digits($element) {
return !preg_match ("/[^0-9]/", $element);
}
Sample PHP
// SET $TARGET_YEAR TO current year in [yyyy] format.
// Is set, Is not empty, Is numeric, Is 4 digits long?
if(isset($_GET["year"]) && !empty($_GET["year"]) && is_numeric($_GET["year"]) && strlen($_GET["year"]) == 4) {
// WE HAVE A REAL YEAR.
// SET $TARGET_YEAR TO target year in [yyyy] format.
self::$TARGET_YEAR = $_GET["year"];
} else {
// WE DON'T HAVE A REAL YEAR.
// SET $TARGET_YEAR TO current year in [yyyy] format.
self::$TARGET_YEAR = date('Y');
}
Regular expressions are unnecessary for such a simple check. Your existing code could be reduced to this:
$year = (int)$_GET["year"] ?? 0;
if ($year >= 2019 && $year <= 2025) {
// do stuff
}
Just cast the string as an integer and then check the range. If the string is not a valid number the cast will turn it to 0. If the value isn't set, the null coalesce will also turn it to zero.
This should do the trick. When you put in curly braces number 4 it will allow only 4 numbers if you use 0,4 it will allow 0 to 4 digits.
/* digits only, no dots */
function is_digits($element) {
$element = preg_match ("/^[0-9]{4}$/", $element);
if($element > 2019 && $element < 2025) {
return true; //Or $element
} else {
return false; //Or something else you want
}
}
You can use a built-in validate filter:
$opts = ['options' => ['min_range' => 2019, 'max_range' => 2025]];
if($date = filter_input(INPUT_GET, 'year', FILTER_VALIDATE_INT, $opts)) {
//yes
}

php error checking mysql date

caveat: i am new to php
I'm reading in 2 dates from a form in a mysql format (i.e. YYYY-MM-DD) and trying to error check them both for validity and that one is less than the other
if(!empty($day1) && !empty($day2)){
$sndate=array();
$sndate = explode('-',$day1);
$sndtnum = implode($sndate);
$sndtnum = (int) $sndtnum;
$unsndate=array();
$unsndate = explode('-',$day2);
$unsndtnum = implode($unsndate);
$unsndtnum = (int) $unsndtnum;
if (!checkdate($sndate[1],$sndate[2],sndate[0]) || !checkdate($unsndate[1],$unsndate[2],unsndate[0]) || $unsndtnum<$sndtnum)
{ $error=True; $errtext .="Date field is filled in wrong \n";
}else {$error=False;}
}
This does not seem to work. I'm pretty sure it's because of the checkdate, but i'm not positive that there isn't also an issue with the implode/cast.
Any ideas on how to fix this?
Apart from the sndate[0] and unsndate[0] variables missing their dollar signs, your check will actually not work if an evil user put non-numeric chars in $day1 (e.g. 2015-09abc-01 will be considered as less than 2015-05-01).
The following approach validates the dates by transforming them from a known format into a DateTime object and then back again, making sure that the two formatted dates are equal:
$f = 'Y-m-d';
$ok1 = ($d1 = DateTime::createFromFormat($f, $day1)) && $d1->format($f) == $day1;
$ok2 = ($d2 = DateTime::createFromFormat($f, $day2)) && $d2->format($f) == $day2;
if ($ok1 && $ok2 && $d1 <= $d2) {
// Ok
} else {
// Error
}
Hope this helps :)

taking the latest content from a dynamic array

A small background of myself is that I'm fairly new to php. I work as an IT assistant and have been asked to edit one of the pages our designers use for samples. I cannot point you to the page as it is an internally hosted page.
I'm honestly not even sure if the question is asked correctly but please bear with me.
The page has a 'request completion date' field within a table that outputs 6 dates in a list, the designers only want it to output the latest date from that list instead of all 6, usually these will be empty so it's no use having them printed.
The code to put them is as follows;
if ($database_data['request_confirmed_comp_date'] > "0")
{ $request_confirmed_completion_date = date("d/m/Y", $database_data['request_confirmed_comp_date']); }
else
{ $request_confirmed_completion_date = " -"; }
if $database_data['request_confirmed_comp_date2'] > "0")
{
$request_confirmed_completion_date2 = date("d/m/Y", $database_data['request_confirmed_comp_date2']);
}
else
{
$request_confirmed_completion_date2 = " -";
}
if ($database_data['request_confirmed_comp_date3'] > "0")
{
$request_confirmed_completion_date3 = date("d/m/Y", $database_data['request_confirmed_comp_date3']);
}
else
{
$request_confirmed_completion_date3 = " -";
}
if ($database_data['request_confirmed_comp_date4'] > "0")
{
$request_confirmed_completion_date4 = date("d/m/Y", $database_data['request_confirmed_comp_date4']);
}
else
{
$request_confirmed_completion_date4 = " -";
}
if ($database_data['request_confirmed_comp_date5'] > "0")
{
$request_confirmed_completion_date5 = date("d/m/Y", $database_data['request_confirmed_comp_date5']);
}
else
{
$request_confirmed_completion_date5 = " -";
}
if ($database_data['request_confirmed_comp_date6'] > "0")
{
$request_confirmed_completion_date6 = date("d/m/Y", $database_data['request_confirmed_comp_date6']);
}
else
{
$request_confirmed_completion_date6 = " -";
}
if ($database_data['request_date_required'] > "0")
{
$request_date_required = date("d/m/Y", $database_data['request_date_required']);
}
else
{
$request_date_required = "-";
}
if ($database_data['request_date'] > "0")
{
$request_date = date("d/m/Y", $database_data['request_date']);
}
else
{
$request_date = "-";
}
It is then called into play using;
echo '<td><b>1.</b>'.$request_confirmed_completion_date.'<br /><b>2.</b>'.$request_confirmed_completion_date2.'<br /><b>3.</b>'.$request_confirmed_completion_date3.'<br /><b>4.</b>'.$request_confirmed_completion_date4.'<br /><b>5.</b>'.$request_confirmed_completion_date5.'<br /><b>6.</b>'.$request_confirmed_completion_date6.'</td>';
Now I may not have much php knowledge, but I know that's a horribly long way of doing that. Is there anyway that I could pull the latest date out of an array, created by the the first block of code, and then output them into the table.
Thanks for any help or advice, even if you could just point me in the right direction as to what loop to use would be helpful.
Edit: I've uploaded the full file online here, hopefully that will clear up some confusion.
You want to use the php function asort. Since all of your values look to be numeric, you should be able to do a standard sort and pull off the last item with array_pop.
It might look something like this:
asort($database_data);
$latest = array_pop($database_data);
echo date('m/d/Y', $latest);
Create an array with the variable names, like if the variables are $A, $B and $C, then
$vars = array("A","B","C");
foreach($vars as $var_name){
if($database_data[$var_name] > "0")
$$var_name = $database_data[$var_name];
else
$$var_name = "-";
}
Note: A, B and C are dummy variable names, as the variable names are too long in your code :-)
Firstly there is a problem with your if conditions, you can't say $x > "0" because with using double-quotes you are using 0 as a string. You should use integer $x > 0.
Now here my answer :
I couldn't understand your system very well, so i'm assuming always there will be 6 dates.
for($q = 0;$q < 6; $q++)
{
if($database_data[...][$q] > 0)
$dates[] = date("d/m/Y", $database_data['...'][$q]);
else
$dates[] = " - ";
}
As you see, you have to fetch your database datas as an array $database_data['...'][].
If you can tweak the original SQL statement, which probably looks something like this:
select request_confirmed_comp_date, request_confirmed_comp_date2, request_confirmed_comp_date3, request_confirmed_comp_date4, request_confirmed_comp_date5, request_confirmed_comp_date6
from sometablename
where somefield='something'
You can tweak it to use shorter (and consistent) field names
select request_confirmed_comp_date as date1, request_confirmed_comp_date2 as date2, request_confirmed_comp_date3 as date3, request_confirmed_comp_date4 as date4, request_confirmed_comp_date5 as date5, request_confirmed_comp_date6 as date6
from sometablename
where somefield='something'
And then in PHP use an array to iterate over the field names like so:
<?php
$lastCompletionDate=""; //start by assuming there was no completion date
for($i=1;$i<=6;$i++) { //check to see if any field is after the last known completion date
if ($database_data['date'.$i] && (date("d/m/Y", $database_data['date'.$i]) > $lastCompletionDate)) {
//if so, store the new date
$lastCompletionDate=date("d/m/Y", $database_data['date'.$i]);
}
}
if($lastCompletionDate) {
echo "The last completion date was $lastCompletionDate\n";
}else {
echo "There was no completion date.\n";
}
?>
An alternative solution would be to use the SQL engine's own internal functions to find the highest date like so:
select greatest(request_confirmed_comp_date,
request_confirmed_comp_date2,
request_confirmed_comp_date3,
request_confirmed_comp_date4,
request_confirmed_comp_date5,
request_confirmed_comp_date6) as greatestcompdate
from sometablename etc...
and then refer to that in PHP like
<?php
if($database_data['greatestcompdate']) {
echo "There was a greatest completion date and it was $database_data[greatestcompdate]";
}
?>

PHP checkdate variants

I found a php function checkdate() , but strangely enough it only seems to accept data in format of int $month , int $day , int $year. However I am passing the date as a string (example "2012-06-13") so I came up with this workaround, because I would only allow date entered in such format. Unfortunately I am feeling this is both insecure and not a nice approach to the problem:
function CheckAdditional($value)
{
$data = explode("-", $value);
return checkdate($data[1], $data[2], $data[0]);
}
Question: is there a better way to check whether the date is valid?
You can try:
function checkDateFormat($date){
//match the format of the date
if (preg_match ("/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/", $date, $parts)) {
//check weather the date is valid of not
if(checkdate($parts[2],$parts[3],$parts[1]))
return true;
else
return false;
}
else
return false;}
Credits: http://roshanbh.com.np/2008/05/date-format-validation-php.html
Just in order to be safe you can do
date("Y-m-d", strtotime($yourdatestr));
In that way even if the format may be wrong it will in most cases correct it.
<?php
function CheckAdditional($value)
{
return date('Y-m-d', strtotime($value)) == $value;
}
?>
After several tests from both me and the people who tried to help me with my answer, I came up with this which suits me perfectly fine and is both easy and really reliable solution in my opinion as none was able to prove it wrong so far.
$jahr = (int) $_POST['jahr'];
$monat = (int) $_POST['monat'];
$tag = (int) $_POST['tag'];
$datum = "$tag. $monat. $jahr";
if (checkdate($monat, $tag, $jahr) == FALSE) {
$allesok = false;
$fehlermeldung .= "<p class='fehler'>Ungültiges Datum $datum!</p>";
}
If you limit the user inputs to be valid only in one format (what about localization?), then you just can parse the input by yourself, using a regexp-function or splitting the input by "-" and check whether it turns into an array with three digit values…
function DDC($dates){ // Date Day Control
$dy = substr($dates,0,4);
$dm = substr($dates,5,2);
$dd = substr($dates,8,2);
for($i=0; $i<3; $i++){
if(!checkdate($dm,$dd,$dy)){
$dd--;
}else{$i=3;}
}
return $dy.'.'.$dm.'.'.$dd;
}
echo DDC('2013.02.31');
//2013.02.28

I just wrote a horrible PHP function, I need some help (elseif chain - switch?)

I am making a site that determines the value of an array based on what time it is. I wrote this awful (functional) script, and am wondering if I could have made it more concise. I started with a case/switch statement, but had trouble getting multiple conditionals working with it. Here's the dirty deed:
if ($now < november 18th) {
$array_to_use = $home;
}
elseif (november 18th < $now && $now < november 21st ) {
$array_to_use = $driving;
}
elseif (november 21st < $now && $now < november 22nd) {
$array_to_use = $flying;
}
...
...
...
elseif (february 1st < $now) {
$array_to_use = $arrived;
}
else {
$array_to_use = $default;
}
The schedule is actually more complicated and has 13 elseifstatements in it. Can someone please confirm that I just had coder's block and that there's a better way to do this?
EDIT: I changed the Unix Timestamps to rough real times so it's easier to understand what I'm doing (hopefully)
EDIT 2: Please forgive the currently broken Javascript clock, but this is the site I'm working on:
Time Table.
Each array is based on my location, and there are 15 "they are currently" based on the time it is. It's a small problem domain with known start/end times, so flexibility isn't key, just getting it all written. You can see how the time is continuous, and only one array of strings needs to be selected at a time.
First , please please please take out your hardcoded numbers and put them into constants.
$FLIGHT_START_TIME = 1258956001;
$FLIGHT_END_TIME = 1260511201;
Second, I would make mini functions for each of the conditionals:
I.e.
function isFlying($time)
{
return ( $FLIGHT_START_TIME < $time && $time < $FLIGHT_END_TIME );
}
Third, take your whole set of conditionals, and put it into a function to get your current state, and replace in your function calls:
function getStateArrayForTime($time)
{
if (isDriving($time)
{
return $driving;
}
if ( isFlying($time) )
{
return $flying;
}
...etc
}
Last, replace the whole inline section of code with your single function call:
$currentState = getStateArrayForTime($now);
As other posters have also commented, at this point you can use a data table driven function to return the state if you know only the start and end time will be the state parameters:
so replace the implementation of getStateArrayForTime with:
function getStateArrayForTime ($time)
{
//
$states = array (
array("startTime" => 1258956001, "endTime" => 1260511201, "state" => $flying),
array("startTime" => 1260511201, "endTime" => 1260517000, "state" => $driving),
..etc...
);
foreach($states as $checkStateArray)
{
if($checkStateArray['startTime'] < $time && $time < $checkStateArray['endTime'])
{
return $checkStateArray['state'];
}
}
return null;
}
Finally, some people might ask "why do things in this order?" I can't claim credit at all, other than in the application, but Martin Fowler has a great book called "Refactoring" that explains why you clean code up one step at a time, and test at each step of the way, then finally replace functions wholesale that don't make sense, all the while testing that they are functionally equivalent.
It might be overkill, but I would have done something like this so that I could put all the time ranges in one clear spot:
#timeWindows = ({ start -> 0, end -> 1258783201, array -> $home },
... ,
{start -> 1260511201, end -> MAXVAL, array -> $arrived});
and then a loop like
$array_to_use = $default;
foreach (my $window in #timeWindows) {
if (($now > $window->start) && ($now < $window->end)) {
$array_to_use = $window->array;
last;
}
}
Sorry it's in Perl, I don't know PHP, but I imagine it's similar.
You can put the time and array to use in an array and loop them to select.
$Selctions = array(
1258783201 => $Home,
1258956001 => $Driving,
1260511201 => $Flying,
...
1260511201 => $Arriving
);
// MUST SORT so that the checking will not skip
ksort($Selction);
$TimeToUse = -1;
$Now = ...;
foreach ($Selctions as $Time => $Array) {
if ($Now < $Time) {
$TimeToUse = $Time;
break;
}
}
$ArrayToUse = ($TimeToUse != -1) ? $Selctions[$TimeToUse] : $Default;
This method can only be used when the times has no gap (one range right after another).
Hope this helps.
You can use a switch statement, doing something like this:
switch (true)
{
case $now < 1258783201:
// your stuff
break;
case $now < 1258783201
// more of your stuff
break;
//...
}
That's at least a little cleaner...
Something like this:
$array_to_use = null;
$dispatch = array(1258783201, $home, 1258956001, $driving, ..., $arrived);
for ($i=0; i<count($dispatch); $i+=2) {
if ($now<$dispatch[$i]) {
$array_to_use = $dispatch[$i+1];
break;
}
}
if ($array_to_use==null) $array_to_use = $dispatch[count($dispatch)-1];
You also need to think about whether you need "<" or "<=" condition.
You might want to learn the Command Pattern; it can also help in this circumstance.

Categories