Sort array of objects by closest date - php

I have an array of objects in PHP with one key - date. I have future dates as well as past dates. I need to sort them in by most current date.
Here is my array from var_dump()
array (size=15)
0 =>
object(stdClass)[7852]
public 'date' => string '20200417' (length=8)
1 =>
object(stdClass)[7846]
public 'date' => string '20200302' (length=8)
2 =>
object(stdClass)[7856]
public 'date' => string '20200224' (length=8)
3 =>
object(stdClass)[7853]
public 'date' => string '20200220' (length=8)
4 =>
object(stdClass)[7847]
public 'date' => string '20200213' (length=8)
5 =>
object(stdClass)[7845]
public 'date' => string '20200211' (length=8)
...
Is there a way to sort this so that the first object would be the 20200302, then 20200224 and so on. but the future dates would be at the very end of the array.
I've tried this:
usort($arr, function($a, $b) {
return strtotime($b->date) - strtotime($a->date);
});
But this code doesn't care about the current date, it just sorts them as dates.
I have also tried a workaround with calculating interval from the date('Ymd') - $arr->date and setting that as a part of an object, but strcmp() in usrot function gives virtually the same result, because future date will give negative number and in ascending order negative number will come before positive numbers. Meaning, the most future date will be first, not the most current date.
Any help is much appreciated.

What you need to do is work out how far each date is away from today and use this to sort the items, there may be a more elegant way, but this (I think) works. Basically use abs() to ensure all differences are +ve and take each date away from today...
usort($arr, function($a, $b) {
return (abs(strtotime('today') - strtotime($a->date))
- (abs(strtotime('today') - strtotime($b->date))));
});

Nigel's answer was a big help, but it produced uneven results, sometimes the dates would be all over the place, I have no idea why. So, I figured a workaround, perhaps it will be of help to someone on the internet.
I've added one more key/value to an object - interval, value for this key I calculated in the following manner.
$interval_raw = date("Ymd") - get_field('date_nachalo') + 1;
if($interval_raw < 0) {
$interval_raw += 10000;
}
So, if I get a negative number which is any future date, I'll add 10000 which is like 30 years in advance, a bit overkill I guess because no normal human schedules events that far into the future. Also, added + 1 so that the next upcoming date would be first. If I remove that, then the first date will be the date from the past. Basically, if today is march 10th and the last event was on march 1st and the next event event is on march 11th, without the + 1 the first event date would be the march 1st not the march 11th.
If anyone knows more elegant approach to this, do let the world know.

Related

PHP select a range from an array()

I have an array() of years, they are in order lowest to highest, they are not sequential, so 1100, 1295, 1733,1734, 1980 etc. The key starts at 0 up to the number of years. So [0] => 1100, [1] => 1295.. [5] => 1980. What i am trying to do is pull a range out from the middle, so a user inputs start year = 1200 and end year = 1900. Can anyone help me create a new array from my original array with values that are between the start and end years. Thanks for your help. I am using PHP version 5.6+ cant remember the exact version.
I think array_filter with a custom callback is the shortest way to get the time span:
$array = [1100, 1295, 1733, 1734, 1980];
$start = 1200;
$end = 1733;
$result = array_filter($array, function ($value) use ($start, $end)
{return $start <= $value && $value <= $end;});
var_dump($result);
But there are many other ways.

Parse string to timestamp [duplicate]

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.

PHP - invalid argument: foreach - inside Wordpress

I am experiencing an issue with a wordpress site that I've not encountered before. Allow me to provide some quick details...
Background:
The site I am working on is for a University Radio Station. The station is setup with 9 2 hour blocks of "Show Time" which is split up between various student shows. These blocks of time start at 8AM and end at 2AM, with 2 hour intervals. So 8AM to 10AM and 10AM to 12PM Noon etc... What we need to do is display the show that is "Now Playing" and the show that comes right after "Playing Next".
That said... I have written the below code and it is confirmed working on a test site... That site is http://khill.mhostiuckproductions.com/siteNowplaying/ The code works exactly as I need it to with no faults.
Now when I switch this over to wordpress I get the following error: Warning: Invalid argument supplied for foreach() in [directory to file and line #]
What I have determined/know with the assistance of a gentleman in chat yesterday:
The above error is occurring because the array $showBlocks in the foreach is empty. I know this by doing a var_dump on that array on the wordpress site in which the var dump outputs NULL. This has something to do with my variables being global (global variables can get nasty from what I understand which makes sense).
Said gentleman provided a possible solution which I was not able to get fully working before I had to call it quits for the day, I'll go over that proposed solution below...
The code:
Now I realize this code is terrible and I need to be using classes, and ultimately a database, my code looks like this right now as I do not have a ton of PHP or SQL experience, and I have a deadline that's quickly approaching. I need to get this knocked out and working so I can move on and finish the rest of the site. I plan to develop this further in the background once the new theme launches, ultimately it will tie into the wordpress CMS as a plugin/widget.
That said... I realize what I am doing is very brute force and I am ok with brute forced solutions. As I've said above, my code works perfectly on a standalone test site, it is only when it is moved into wordpress that it breaks.
I have simplified my code to provide only what is needed for a single day (lots of duplicate stuff for each day of the week, you'll understand as you continue further down).
I have the following variables at the top of my file...
$day = date(D); // Textual representation of day in the format of "Mon, Tue, Wed" etc.
date_default_timezone_set('America/New_York'); // Set the default time zone (EST)
I have the following arrays... The first one establishes my "blocks" of time as per their starting and ending times, the second one brings in variables which store my "outputs". These outputs are in a separate file which is included above the file that has all of the code I am showing here. The second array is suplicated for each day of the week, and the variable names change accordingly.
// $showBlocks Array
$showBlocks = array ( // Define available blocks of show time | starts at 8AM ends at 2AM with 2 hour increments
'a' => array ('00:00:01', '02:00:00'), // MIDNIGHT TO 2AM
'b' => array ('02:00:00', '08:00:00'), // OFF AIR TIME
'c' => array ('08:00:00', '10:00:00'),
'd' => array ('10:00:00', '12:00:00'),
'e' => array ('12:00:00', '14:00:00'), // NOON to 2PM
'f' => array ('14:00:00', '16:00:00'),
'g' => array ('16:00:00', '18:00:00'),
'h' => array ('18:00:00', '20:00:00'),
'i' => array ('20:00:00', '22:00:00'),
'j' => array ('22:00:00', '23:59:59'),
);
$mondayShows = array (
'a' => $sunday12a_2a, // MIDNIGHT TO 2AM
'b' => $offAirTime, // OFF AIR TIME
'c' => $monday8a_10a,
'd' => $monday10a_12,
'e' => $mondayy12_2, // NOON to 2PM
'f' => $monday2_4,
'g' => $monday4_6,
'h' => $monday6_8,
'i' => $monday8_10,
'j' => $monday10_12a,
);
The first function... This just checks what day it is, and echo's the appropriate function for that day which is the next bit of code I'll show you. I echo the nowPlaying() function in my site where I want my output to appear.
function nowPlaying() {
global $day;
if ($day == "Sun") { //IF DAY IS TRUE THEN PERFORM AN ACTION
echo sundayShow();
} else if ($day == "Mon") {
echo mondayShow();
} else if ($day == "Tue") {
echo tuesdayShow();
} else if ($day == "Wed") {
echo wednesdayShow();
} else if ($day == "Thu") {
echo thursdayShow();
} else if ($day == "Fri") {
echo fridayShow();
} else if ($day == "Sat") {
echo saturdayShow();
}
}
For the sake of simplicity I am going to show you only one of the functions that appear inside the above function, we'll use Monday since it's Monday.
This function uses the two arrays seen above as inputs, the $mondayShows array variable changes to $tuesdayShows for the tuesdayShow() function. (basically exactly the same code with different variable name for the array input) The foreach here is the line where the error code says there is a problem. Again, as someone from the php chat guided me to, is because when I put this code into wordpress, suddenly my array becomes empty.
function mondayShow() {
global $mondayShows, $showBlocks; // GLOBALIZE THESE VARIABLES
foreach ($showBlocks as $name => $range) {
if (time() > strtotime($range[0]) && strtotime($range[1]) > time()) { // checks if time() is between defined ranges from $showBlocks array
echo($mondayShows[$name]);
}
}
}
Earlier I mentioned also displaying what is "Playing Next". This is handled by duplicating all of the above code with new names, for example the name of the equivalent code for the above function becomes mondayNextShow(). To make the code display the actual next show I add 7200 (number of seconds in 2 hours) to time() so... time + 7200. This addition is inside the if statement of the above code... so it now looks like this...
if (time() + 7200 > strtotime($range[0]) && strtotime($range[1]) > time() + 7200) {
What was suggested to me in chat but I was unable to get working:
In chat, it was suggested I get rid of the global variables, and include my array directly into the function via a separate file... I tried this by moving the above $showBlocks array to a separate file "now-playing-array.php" for instance. The array code was changed from the above to instead return the array so it now looks like this:
return array (
'a' => array ('00:00:01', '02:00:00'), // 12AM MIDNIGHT TO 2AM
'b' => array ('02:00:00', '08:00:00'), // OFF AIR TIME
'c' => array ('08:00:00', '10:00:00'),
....................
);
I then remove $showBlocks from global variables of the above function, and I include said file into the function using the __DIR__ magic constant.
The above function now looks like this:
function mondayShow() {
global $mondayShows; // GLOBALIZE THESE VARIABLES
$showBlocks = include __DIR__."/now-playing-arrays.php";
foreach ($showBlocks as $name => $range) {
if (time() > strtotime($range[0]) && strtotime($range[1]) > time()) { // checks if time() is between defined ranges from $showBlocks array
echo($mondayShows[$name]);
}
}
}
A var_dump on $showBlocks now produced: bool(false)
Now the questions...
Please keep in mind this code is very brute forced and I know and realize that, but that's what I want for now. I am going to be using this project to expand my PHP into using classes and databases and such but I don't have the time for that now. I am not looking for, your code is terrible you should just start over and do it the right way answers... I know that already.
First, if you understand the route this gentleman from chat was trying to take me, is it the best route to take?
If it is the best route to take then, how do I go about finishing it off? From what I gather the bool(false) thing means it can't find my file? The files are all in the same folder.
You could put your return arrays into functions, and then simply call the function and assign it to a variable from within your mondayShow() function:
function showBlocks(){
return array (
'a' => array ('00:00:01', '02:00:00'), // MIDNIGHT TO 2AM
'b' => array ('02:00:00', '08:00:00'), // OFF AIR TIME
'c' => array ('08:00:00', '10:00:00'),
'd' => array ('10:00:00', '12:00:00'),
'e' => array ('12:00:00', '14:00:00'), // NOON to 2PM
'f' => array ('14:00:00', '16:00:00'),
'g' => array ('16:00:00', '18:00:00'),
'h' => array ('18:00:00', '20:00:00'),
'i' => array ('20:00:00', '22:00:00'),
'j' => array ('22:00:00', '23:59:59'),
);
}
function mondayShows(){
return array (
'a' => "a", // MIDNIGHT TO 2AM
'b' => "b", // OFF AIR TIME
'c' => "c",
'd' => "d",
'e' => "e", // NOON to 2PM
'f' => "f",
'g' => "g",
'h' => "h",
'i' => "i",
'j' => "j",
);
}
function mondayShow() {
$showBlocks = showBlocks();
$mondayShows = mondayShows();
foreach ($showBlocks as $name => $range) {
if (time() > strtotime($range[0]) && strtotime($range[1]) > time()) {
echo($mondayShows[$name]);
}
}
}
mondayShow();
This way, you don't need to explicitly globalize anything, and you don't need to worry about having extra files. Simply make a function for each show listing array, and have it return.
One small thing: In the code above, I changed the $mondayShows array values to something that was defined (just letters), so make sure you're actually assigning those to something.

PHP - Find longest date "streak" and "slump"

I've currently got 2 dates in PHP - a 'start' date and an 'end' date. I have then created an array of dates, using a function I found called createDateRangeArray ($date_range). For simplicity, the end date will always be today's date.
I've also got a separate array that contains a bunch of dates ($valid_dates) that will always fall between the start and end dates mentioned above. On those dates, 'something' happened - in this case, a training session.
I'm trying to get my head around getting the following:
A range of dates similar to the $date_range array, populated with TRUE or FALSE based on whether or not a training session happened on that date. I'm happy for this to be an assoc array with keys named date and, say, session_found (bool).
The longest 'streak' i.e. the longest consecutive number of days that training sessions occurred.
The longest 'slump' i.e. the longest consecutive number of days that training sessions did NOT occur.
Can someone point me in the right direction for getting the above info without using a foreach on the contents of the $date_range array and then having to use another foreach for the $valid_dates array on each item in the $date_range array? That is horribly inefficient ...
Sorry if I've over-complicated things with all that info but any help would be much appreciated.
I'm currently using PHP 5.4 on Debian Wheezy, if that helps (typical LAMP stack).
Thanks
This is completely untested, but how about something like the following:
You should get the end date of the longest streak and slump, as well as how many days it took. $sessions will contain an array with the dates as the keys, and true for days with sessions, and false for days without.
Still a foreach, but I dont think you can avoid using one. Let me know if it works, I really dont have any idea how well this code will behave, but hopefully will give you a starting point?
$streak = 0;
$slump = 0;
$longeststreak = 0;
$longestslump = 0;
$longeststreakend = 0;
$longestslumpend = 0;
foreach ($date_range as $date) {
if (in_array($date, $valid_date)) {
$sessions[$date] = true;
$streak++;
// Slump broken, record the length if it beat the previous record
if ($longestslump < $slump) {
$longestslump = $slump;
$longestslumpend = $date;
}
$slump=0;
}
else {
$sessions[$date] = false;
$slump++;
// Streak broken, record the length if it beat the previous record
if ($longeststreak < $streak) {
$longeststreak = $streak;
$longeststreakend = $date;
}
$streak=0;
}
}

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