I have a sorted array of dates that looks like so :
$dates = Array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
In this array we have two times (sometimes more, but it's normal in my script) the date Oct 1st but it's missing the 4th.
How can I detect that there is a discontinuity between the first value of the array, and the last value of the array (again, my array is sorted by ascending dates) ?
$dates = array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
$continued = true;
foreach ($dates as $key => $date) {
if (!empty($dates[$key+1])) {
$cDate = new DateTime($date);
$next = new DateTime($dates[$key+1]);
$interval = $next->diff($cDate);
if ($interval->d > 1) {
$continued = false;
}
}
}
$continued = false then discontinuity present.
It's probably worth looking into the DateTime class, and its goodies. In particular DateTime::diff, which returns a DateInterval instance. This enables you to safely subtract 2 dates, and determine what the time difference between those dates actually is. cf the manual.
In short, I've put together a little function that can be used to determine oddities in an array of date strings:
$dates = array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
$obj = array();
foreach ($dates as $date)
{//create an array of DateTime instances from the $dates values
$obj[] = new DateTime($date);
}
/**
* #param array $dates (an array of DateTime instances)
* #param string $intervalProperty = 'd' (valid valus are y, m, d, h, i, s)
* #param int $interval = null (the value $intervalProperty should have)
* #return array of null|DateInterval
*/
function getOddDiffs(array $dates, $intervalProperty = 'd', $interval = null)
{
$diffs = array();
for($i=0, $j=count($dates)-1;$i<$j;++$i)
{//iterate $dates
$diff = $dates[$i]->diff($dates[$i+1]);//compute diff
if ($interval === null && $diff->{$intervalProperty})
$interval = $diff->{$intervalProperty};//set $interval if needed/possible
if ($diff->{$intervalProperty} !== $interval)
{//if interval value !== $interval (type+value check required in case $interval is null)
$diffs[] = $diff;//return the diff
}
else
{
$diffs[] = null;//null means ok
}
}
return $diffs;//return results
}
If you want to see it in action:
Demo here
function hasDiscontinuities($dates)
{
// remove duplicates
$dates = array_unique($dates);
// get the last day
$parts = explode('-', end($dates));
$lastDay = (int) $parts[2];
// if the amount of dates is smaller than the last day, there must be discontinuities
if (count($dates) < $lastDay) {
return true;
}
return false;
}
Edit regarding Elias Van Ootegem's comment, duplicates now are threated as discontinuities:
function hasDiscontinuities($dates)
{
// duplicates count as discontinuities
if ($dates != array_unique($dates)) {
return true;
}
// get the last day
$parts = explode('-', end($dates));
$lastDay = (int) $parts[2];
// if the amount of dates is smaller than the last day, there must be discontinuities
if (count($dates) < $lastDay) {
return true;
}
return false;
}
Second edit because of the added details in the comments:
function hasDiscontinuities($dates)
{
// duplicates count as discontinuities
if ($dates != array_unique($dates)) {
return true;
}
$firstDate = new DateTime($dates[0]);
$lastDate = new DateTime(end($dates));
// if the difference in days is not the same as the amount of elements in the array, there are discontinuities
if (count($dates) != $lastDate->diff($firstDate)->days + 1) {
return true;
}
return false;
}
Related
I have a table, OperatingHours where 7 rows of data are recorded for the day of the week (Monday, Tuesday, Wednesday until Sunday). And there's a data column where user's can set true/false whether the clinic is open for the week.
I want to generate a list of date from current date till the end of the year, by the day of the week where the clinic is open. Currently below are my controller codes for the methods and I am getting the following error:
DateTime::modify(): Failed to parse time string (this
[{"day":"Monday"},{"day":"Tuesday"},{"day":"Wednesday"},{"day":"Thursday"},{"day":"Friday"}])
at position 0 (t): The timezone could not be found in the database
public function index(){
$operatingDays = OperatingHour::where('clinic_open', true)->get('day');
$currentDate = Carbon::now()->toDateTimeString();
$endOfYear - Carbon::now()->endOfYear()->toDateTimeString();
$dt = $this->getWorkDayInRange($operatingDays, $currentDate, $endOfYear);
$data = [
'dt' = $dt,
];
return view('appointment', $data);
}
public function getWorkDayInRange($workday, $fromDate, $toDate){
$dates = [];
$startDate = Carbon::parse($fromDate)->modify("this $workday");
$endDate = Carbon::parse($toDate);
//lte = less than or equal
for ($date = $startDate; $date->lte($endDate); $date->addWeek()) {
$dates[] = $date->toDateString();
}
return $dates;
}
I have used something similar recently, check this out:
function generateDateRange($start, $end){
$result = []
$end = = strtotime($end);
$current = = strtotime($start);
while( $current <= $end ) {
$result[] = date('Y-m-d', $current);
$current = strtotime('+1 day', $current);
}
return $result;
}
I have managed to retrieve out the lists of dates based on my operating hours details and holiday table.
Below are the changes to the codes I have made.
private function getHolidays()
{
$holidaysList = Holiday::where('clinic_id', '=', $_SESSION['clinic_ID'])->get();
$holidayArray = json_decode($holidaysList, true);
foreach($holidayArray as $holiday){
//Convert each data from table to Y-m-d format to compare
$holidays[] = date('Y-m-d', strtotime($holiday['date']));
// $holidays[] = $holiday['date'];
}
return $holidays;
}
public function getWorkDayInRange($workday, $dateFrom, $dateTo)
{
$holidays = $this->getHolidays();
$startDate = new DateTime($dateFrom);
$endDate = new DateTime($dateTo);
$interval = new DateInterval('P1D');
$dateRange = new DatePeriod($startDate, $interval, $endDate);
$results = [];
foreach ($dateRange as $date) {
$name = $date->format('l');
if (in_array($name, $workday) && !in_array($date->format('Y-m-d'), $holidays)) {
$results[] = $date->format('Y-m-d');
}
}
return $results;
}
Let's say I have an array called $selectedDates containing the following
$selectedDates = [
"2000-01-31",
"2000-02-01",
"2000-02-02",
"2000-02-20"
];
Is there some way in PHP to calculate out what # each day is consecutively.
So that the first one returns 1, the 2nd one would be returning 2, the 3rd would be 3 but the 4th would be 1 again.
I'm trying to loop through them all at the moment but am not really getitng anywhere.
<?php foreach ($selectedDates as $date): ?>
Seriously lost about what to put here.
<?php echo $consecDayCount; ?>
<?php endforeach; ?>
I'm thinking inside of the loop I may need to loop through it again? And start at the first one and check each day going up one more, adding to some incrementer for each day that says the previous date in the array was the previous day in time. I'm just kind of banging my head against a wall figuring out that part.
One way to Rome...
# We assume $selectedDates is sorted at this point.
$selectedDates = [
"2000-01-31",
"2000-02-01",
"2000-02-02",
"2000-02-20"
];
# The array we will print.
$coll = array();
# We use array_reverse to start from the last entry.
foreach(array_reverse($selectedDates) as $date) {
$tmp = $date;
$con = 1;
do $tmp = date('Y-m-d', strtotime("$tmp -1day"));
# ++$con is used to count only up if in_array gives true.
while(in_array($tmp, $selectedDates) && (++$con));
$coll[$date] = $con;
}
print_r($coll);
Result: Array ( [2000-02-20] => 1 [2000-02-02] => 3 [2000-02-01] => 2 [2000-01-31] => 1 )
Here you go:
$selectedDates = [
'2000-01-31',
'2000-02-01',
'2000-02-02',
'2000-02-20',
'2000-02-21',
];
$format = 'Y-m-d';
$consecutive = [];
$length = count($selectedDates);
$streak = false;
$last = null;
for($index = 0; $index < $length - 1; $index++) {
$firstDate = DateTime::createFromFormat($format, $selectedDates[$index]);
$secondDate = DateTime::createFromFormat($format, $selectedDates[$index + 1]);
$diff = $firstDate->diff($secondDate);
if ($diff->days === 1) {
// consecutive dates
if ($streak) {
$consecutive[$last]++; // we have another consecutive day to the current streak
} else {
$consecutive[$selectedDates[$index]] = 2; // we already have 2 consecutive days
$last = $selectedDates[$index];
$streak = true;
}
} else {
$streak = false;
}
}
var_dump($consecutive);
This will give you an array in the date => number of consecutive days starting on that date format.
Another way
$dates = array(
new DateTime('2000-01-31'),
new DateTime('2000-02-01'),
new DateTime('2000-02-02'),
new DateTime('2000-02-20'),
);
// process the array
$lastDate = null;
$ranges = array();
$currentRange = array();
foreach ($dates as $date) {
if (null === $lastDate) {
$currentRange[] = $date;
} else {
// get the DateInterval object
$interval = $date->diff($lastDate);
// DateInterval has properties for
// days, weeks. months etc. You should
// implement some more robust conditions here to
// make sure all you're not getting false matches
// for diffs like a month and a day, a year and
// a day and so on...
if ($interval->days === 1) {
// add this date to the current range
$currentRange[] = $date;
} else {
// store the old range and start anew
$ranges[] = $currentRange;
$currentRange = array($date);
}
}
// end of iteration...
// this date is now the last date
$lastDate = $date;
}
// messy...
$ranges[] = $currentRange;
// print dates
foreach ($ranges as $range) {
// there'll always be one array element, so
// shift that off and create a string from the date object
$startDate = array_shift($range);
$str = sprintf('%s', $startDate->format('D j M'));
// if there are still elements in $range
// then this is a range. pop off the last
// element, do the same as above and concatenate
if (count($range)) {
$endDate = array_pop($range);
$str .= sprintf(' to %s', $endDate->format('D j M'));
}
echo "<p>$str</p>";
} ?>
output::: Mon 31 Jan to Wed 2 Feb
Sun 20 Feb
For delivery of our webshop, we need to calculate 5 working days from the current date in php.
Our working days are from monday to friday and we have several closing days (holidays) which cannot be included either.
I've found this script, but this doesn't include holidays.
<?php
$_POST['startdate'] = date("Y-m-d");
$_POST['numberofdays'] = 5;
$d = new DateTime( $_POST['startdate'] );
$t = $d->getTimestamp();
// loop for X days
for($i=0; $i<$_POST['numberofdays']; $i++){
// add 1 day to timestamp
$addDay = 86400;
// get what day it is next day
$nextDay = date('w', ($t+$addDay));
// if it's Saturday or Sunday get $i-1
if($nextDay == 0 || $nextDay == 6) {
$i--;
}
// modify timestamp, add 1 day
$t = $t+$addDay;
}
$d->setTimestamp($t);
echo $d->format('Y-m-d'). "\n";
?>
You can use the "while statement", looping until get enough 5 days. Each time looping get & check one next day is in the holiday list or not.
Here is the the example:
$holidayDates = array(
'2016-03-26',
'2016-03-27',
'2016-03-28',
'2016-03-29',
'2016-04-05',
);
$count5WD = 0;
$temp = strtotime("2016-03-25 00:00:00"); //example as today is 2016-03-25
while($count5WD<5){
$next1WD = strtotime('+1 weekday', $temp);
$next1WDDate = date('Y-m-d', $next1WD);
if(!in_array($next1WDDate, $holidayDates)){
$count5WD++;
}
$temp = $next1WD;
}
$next5WD = date("Y-m-d", $temp);
echo $next5WD; //if today is 2016-03-25 then it will return 2016-04-06 as many days between are holidays
A function based on Tinh Dang's answer:
function getFutureBusinessDay($num_business_days, $today_ymd = null, $holiday_dates_ymd = []) {
$num_business_days = min($num_business_days, 1000);
$business_day_count = 0;
$current_timestamp = empty($today_ymd) ? time() : strtotime($today_ymd);
while ($business_day_count < $num_business_days) {
$next1WD = strtotime('+1 weekday', $current_timestamp);
$next1WDDate = date('Y-m-d', $next1WD);
if (!in_array($next1WDDate, $holiday_dates_ymd)) {
$business_day_count++;
}
$current_timestamp = $next1WD;
}
return date('Y-m-d', $current_timestamp);
}
I made it limit the loop to 1000 business days. There could be no limit if desired.
Based on Luke's answer:
Difference is that this one generates holidays for every year
<?php
class DateHelper
{
//change at will
const HOLIDAY_DATES = [
['day' => 25, 'month' => 12],//christimas
['day' => 1, 'month' => 1],//new year
['day' => 13, 'month' => 4]//easter
];
/**
* #param int $numBusinessDays
* #param \DateTimeInterface $date
* #return \DateTime
*/
public static function getFutureBusinessDay(int $numBusinessDays, \DateTimeInterface $date)
{
$numBusinessDays = min($numBusinessDays, 1000);
$businessDayCount = 0;
$currentTimestamp = strtotime($date->format('Y-m-d'));
$holidayDates = self::getHolidayDates();
while ($businessDayCount < $numBusinessDays) {
$next1WD = strtotime('+1 weekday', $currentTimestamp);
$next1WDDate = date('Y-m-d', $next1WD);
if (!in_array($next1WDDate, $holidayDates)) {
$businessDayCount++;
}
$currentTimestamp = $next1WD;
}
return (new \DateTime())->setTimestamp($currentTimestamp);
}
/**
* #return array
*/
private static function getHolidayDates()
{
$holidays = [];
foreach (self::HOLIDAY_DATES as $holidayDate) {
$date = new \DateTime();
$date->setDate($date->format('Y'), $holidayDate['month'], $holidayDate['day']);
$holidays[] = $date->format('Y-m-d');
}
return $holidays;
}
}
how to check is birthday is in this week,2week,or in month i have used below code to check but it return wrong calculation.
public function CountDown($birthdate, $days=7)
{
list($y,$d,$m) = explode('/',$birthdate);
$today = time();
$event = mktime(0,0,0,$m,$d,$y);
$apart = $event - $today;
if ($apart >= -86400)
{
$myevent = $event;
}
else
{
$myevent = mktime(09,0,0,$m,$d,$y);
}
$countdown = round(($myevent - $today)/86400);
if ($countdown <= $days)
{
return true;
}
return false;
}
Try this:
function CountDown($birthdate, $days=7)
{
# create today DateTime object
$td = new DateTime('today');
# create birth DateTime object, from format Y/d/m
$bd = DateTime::createFromFormat('!Y/d/m', $birthdate);
# set current year to birthdate
$bd->setDate($td->format('Y'), $bd->format('m'), $bd->format('d'));
# if birthdate is still in the past, set it to new year
if ($td > $bd) $bd->modify('+1 year');
# calculate difference in days
$countdown = $bd->diff($td)->days;
# return true if day difference is within your range
return $countdown <= $days;
}
demo
This worked for me
class Birthday{
public function CountDown($birthdate, $days=7)
{
list($y,$d,$m) = explode('/',$birthdate);
$today = time();
$event = mktime(0,0,0,$m,$d,$y);
$apart = $event - $today;
if ($apart >= -86400)
{
$myevent = $event;
}
else
{
$myevent = mktime(09,0,0,$m,$d);
}
$countdown = round(($myevent - $today)/86400);
if (($countdown <= $days))
{
return true;
}
return false;
}
}
$bday = new Birthday;
$count = $bday->CountDown("1969/16/11"); //today is 2014/14/11
var_dump($count); //returns true.
I just removed the year from the mktime() in $myevent. This changed the answers to be accurate.
The other way that it was being done made $countdown to be a huge negative number.
I have a function which returns all the dates between two dates in an array, But I need to exclude Sundays in that array.
public function dateRange($first, $last, $step = '+1 day', $format = 'd/m/Y' ) {
$dates = array();
$current = strtotime($first);
$last = strtotime($last);
while( $current <= $last ) {
$dates[] = date($format, $current);
$current = strtotime($step, $current);
}
return $dates;
}
After excluding the Sundays, I have a table where I will be storing some dates, I need to exclude those dates from the array too.
like, If I enter the date range as 01-05-2012(DD-MM-YYYY) to 10-05-2012,
The 06-05-2012 will be Sunday & the date 01-05-2012 & 08-05-2012 will be in the table which I mentioned above,
The final out put should be like,
02-05-2012
03-05-2012
04-05-2012
05-05-2012
07-05-2012
09-05-2012
10-05-2012
How to do this in PHP ?
I tried some but couldn't find the right way to do it.
For the Sundays part:
public function dateRange($first, $last, $step = '+1 day', $format = 'd/m/Y' ) {
$dates = array();
$current = strtotime($first);
$last = strtotime($last);
while( $current <= $last ) {
if (date("D", $current) != "Sun")
$dates[] = date($format, $current);
$current = strtotime($step, $current);
}
return $dates;
}
For the holidays part:
First you need to load the dates into some kind of array and then loop through the array for each of your dates and check if they match.
I found the answer for my question, Thanks for the people who helped me.
public function dateRange($first, $last, $step = '+1 day', $format = 'd/m/Y' ) {
$dates = array();
$current = strtotime($first);
$last = strtotime($last);
while( $current <= $last ) {
$sql = "SELECT * FROM ost_holidays where holiday_date='".date('Y-m-d', $current)."' LIMIT 1";
$sql = db_query($sql);
$sql = db_fetch_array($sql);
if($sql['holiday_date'] != date('Y-m-d',$current))
if (date('w', $current) != 0)
$dates[] = date($format, $current);
$current = strtotime($step, $current);
}
return $dates;
}
The above code is for removing the holidays & the Sundays in the given range.
I did this same above method in Jquery
//Convert dates into desired formatt
function convertDates(str) {
var date = new Date(str),
mnth = ("0" + (date.getMonth() + 1)).slice(-2),
day = ("0" + date.getDate()).slice(-2);
return [date.getFullYear(), mnth, day].join("-");
}
// Returns an array of dates between the two dates
var getDates = function(startDate, endDate, holidays) {
var dates = [],
currentDate = startDate,
addDays = function(days) {
var date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
};
while (currentDate <= endDate) {
dates.push(currentDate);
currentDate = addDays.call(currentDate, 1);
}
return dates;
};
//Indise Some Function
var datesTemp = [];
var dates = getDates(new Date(prodDet.details.date1), new Date(prodDet.details.date2));
dates.forEach(function(date) {
if (date.getDay() != 0) {
datesTemp.push(convertDates(date));
}
});
datesTemp.forEach(function(date) {
for (var j = 0; j < prodDet.holidays.length; j++) {
if ((prodDet.holidays[j] != date)) {
ideal.idates.push(date);
}
}
});
console.log(ideal.idates);
//Function Ends Here