Date function to bypass weekends and specific dates - php

I am trying to implement the code below to force dates to skip weekends and holiday array. The weekend part is working but I am having difficulty with the holiday section, here is my code:
$holidayDates = array(
'10-10-2017', '11-10-2017'
);
$fcount = 0;
$temp = strtotime("9-10-2017");
while ($fcount < 1) {
$nextfcount = strtotime('+1 weekday', $temp);
$nextfcount2 = date('d-m-Y', $temp);
if (!in_array($nextfcount2, $holidayDates)) {
$fcount++;
}
$temp = $nextfcount;
}
$newDater = date("d-m-Y", $temp);

Let me debug this for you:
$holidayDates = array(
'10-10-2017', '11-10-2017'
);
$fcount = 0;
// 1. $temp is a terrible name for a variable
$temp = strtotime("9-10-2017");
// 2. in other words - if $fcount == 1, exit loop
while ($fcount < 1) {
// 3. $temp is now the 9th
$nextfcount = strtotime('+1 weekday', $temp);
// 4. $nextfcount2 is now the 10th
$nextfcount2 = date('d-m-Y', $temp);
if (!in_array($nextfcount2, $holidayDates)) {
// 5. Yes, found your date. $fcount is now 1
$fcount++;
}
// 6. $temp is now the 10th
$temp = $nextfcount;
// 7. $fcount is now 1, `while` condition is met, exit loop
}
// 8. The date is now the 10th
$newDater = date("d-m-Y", $temp);
You question is much too vague for me to provide you with a quality answer.
I will assume, you want to get 12-10-2017 as the result of your loop. This code will do the trick:
<?php
$holidayDates = [
'10-10-2017',
'11-10-2017',
];
$foundCounter = 0;
$theDate = strtotime("9-10-2017");
while ($foundCounter < 1) {
$theDate = strtotime('+1 weekday', $theDate);
if (!in_array(date('d-m-Y', $theDate), $holidayDates)) {
$foundCounter++;
}
}
$newDater = date("d-m-Y", $theDate);
var_dump($newDater);
// string(10) "12-10-2017"
https://3v4l.org/DcWBZ

Related

Counting the (year)quarters between two dates

I have project built using laravel and a I have to build a function that counts all the complete quarters that are in the selected date range - the dates used are inserted via input.
Here are the quarters(i used numerical representations for the months)
01 - 03 first quarter
04 - 06 second quarter
07 - 09 third quarter
10 - 12 forth quarter
I would really appreciate your help,because I've been at it for an entire day now and basically have nothing to show for it,i thing I've been trying so hard i'm actually at the point where i'm so tired, i can t think straight.
I do have some code but it;s worthless, because it doesn't work, and any kind of idea or snippet of code is welcomed.
Thanks for your help in advance.
I managed to do this using multiple functions; basically, if this is needed for chart statistics, then a more specific approach might be the case.
I have done this in Laravel with timestamp dates as input (this code can be adapted for getting semesters also :) , it works and is already tested):
public static function getQuartersBetween($start_ts, $end_ts)
{
$quarters = [];
$months_per_year = [];
$years = self::getYearsBetween($start_ts, $end_ts);
$months = self::getMonthsBetween($start_ts, $end_ts);
foreach ($years as $year) {
foreach ($months as $month) {
if ($year->format('Y') == $month->format('Y')) {
$months_per_year[$year->format('Y')][] = $month;
}
}
}
foreach ($months_per_year as $year => $months) {
$january = new Date('01-01-' . $year);
$march = new Date('01-03-' . $year);
$april = new Date('01-04-' . $year);
$june = new Date('01-06-' . $year);
$july = new Date('01-07-' . $year);
$september = new Date('01-09-' . $year);
$october = new Date('01-10-' . $year);
$december = new Date('01-12-' . $year);
if (in_array($january, $months) && in_array($march, $months)) {
$quarter_per_year['label'] = 'T1 / ' . $year;
$quarter_per_year['start_day'] = $january->startOfMonth();
$quarter_per_year['end_day'] = $march->endOfMonth()->endOfDay();
array_push($quarters, $quarter_per_year);
}
if (in_array($april, $months) && in_array($june, $months)) {
$quarter_per_year['label'] = 'T2 / ' . $year;
$quarter_per_year['start_day'] = $april->startOfMonth();
$quarter_per_year['end_day'] = $june->endOfMonth()->endOfDay();
array_push($quarters, $quarter_per_year);
}
if (in_array($july, $months) && in_array($september, $months)) {
$quarter_per_year['label'] = 'T3 / ' . $year;
$quarter_per_year['start_day'] = $july->startOfMonth();
$quarter_per_year['end_day'] = $september->endOfMonth()->endOfDay();
array_push($quarters, $quarter_per_year);
}
if (in_array($october, $months) && in_array($december, $months)) {
$quarter_per_year['label'] = 'T4 / ' . $year;
$quarter_per_year['start_day'] = $october->startOfMonth();
$quarter_per_year['end_day'] = $december->endOfMonth()->endOfDay();
array_push($quarters, $quarter_per_year);
}
}
return $quarters;
}
and getting the years between:
public static function getYearsBetween($start_ts, $end_ts, $full_period = false)
{
$return_data = [];
$current = mktime(0, 0, 0, date('m', $start_ts), date('d', $start_ts), date('Y', $start_ts));
while ($current < $end_ts) {
$temp_date = $current;
$year = new Date($temp_date);
$return_data[] = $year;
$current = strtotime("+1 year", $current); // add a year
}
if ($full_period) {
$return_data[] = $end_ts;
}
return $return_data;
}
, also getting the months needed
public static function getMonthsBetween($start_ts, $end_ts, $full_period = false)
{
$return_data = $month_list = [];
$current = mktime(0, 0, 0, date('m', $start_ts), date('d', $start_ts), date('Y', $start_ts));
while ($current <= $end_ts) {
$temp_date = $current;
$date = new Date($temp_date);
$month_list[] = $date;
$current = strtotime("+1 month", $current); // add a month
}
$start_date_last_month = new Date(array_first($month_list));
$start_date_last_month = $start_date_last_month->startOfMonth()->format('m-d');
$temp_end_date = new Date($start_ts);
$temp_end_date = $temp_end_date->format('m-d');
if ($start_date_last_month < $temp_end_date) {
array_shift($month_list);
}
$end_date_last_month = new Date(end($month_list));
$current_day_month = $end_date_last_month->endOfMonth()->format('m-d');
$temp_end_date = new Date($end_ts);
$end_day_of_month = $temp_end_date->format('m-d');
if ($end_day_of_month < $current_day_month) {
array_pop($month_list);
}
if (count($month_list) == 0) {
$month_list[] = $end_date_last_month->subMonth();
}
$return_data = $month_list;
if ($full_period) {
$return_data[] = $end_ts;
}
return $return_data;
}
You can do something like in this example:
$February = 2;
$October = 10;
$completedQuarters = ceil($October/3) - ceil($February/3); // = 3
What about the quarter in which the date range starts, should it also count? If it should only count if it begins in the first month of a quarter you can check for it like this:
$completedQuarters = ceil($October/3) - ceil($February/3) -1; // = 2
if($February-1%3 == 0) $completedQuarters += 1;
You´re description is not very clear, let me know if that´s what you had in mind.
Not sure if the following is what you are meaning but might be useful
$date_start='2015/03/12';
$date_end='2017/11/14';
$timezone=new DateTimeZone('Europe/London');
$start=new DateTime( $date_start, $timezone );
$end=new DateTime( $date_end, $timezone );
$difference = $end->diff( $start );
$months = ( ( $difference->format('%y') * 12 ) + $difference->format('%m') );
$quarters = intval( $months / 3 );
printf( 'Quarters between %s and %s is %d covering %d months', $start->format('l, jS F Y'), $end->format('l, jS F Y'), $quarters, $months );
/*
This will output
----------------
Quarters between Thursday, 12th March 2015 and Tuesday, 14th November 2017 is 10 covering 32 months
*/
Something like this in the function and you should be set.
use Carbon\Carbon;
$first = Carbon::parse('2012-1-1'); //first param
$second = Carbon::parse('2014-9-15'); //second param
$fY = $first->year; //2012
$fQ = $first->quarter; //1
$sY = $second->year; //2014
$sQ = $second->quarter; //3
$n = 0; //the number of quarters we have counted
$i = 0; //an iterator we will use to determine if we are in the first year
for ($y=$fY; $y < $sY; $y++, $i++) { //for each year less than the second year (if any)
$s = ($i > 0) ? 1 : $fQ; //determine the starting quarter
for ($q=$s; $q <= 4; $q++) { //for each quarter
$n++; //count it
}
}
if ($sY > $fY) { //if both dates are not in the same year
$n = $n + $sQ; //total is the number of quarters we've counted plus the second quarter value
} else {
for ($q=$fQ; $q <= $sQ; $q++) { //for each quarter between the first quarter and second
$n++; //count it
}
}
print $n; //the value to return (11)

Wrong Data in Month Array

What I can do if would like start my January month since 2016-01-01 not 2016-01-02.
https://3v4l.org/uRRfU
function countDaysForChoosenYears(int $year):int
{
$att = [];
for ($i = 1; $i <= 12; $i++) {
$att[] = cal_days_in_month(CAL_GREGORIAN, $i, $year);
}
$att = array_sum($att);
return $att;
}
$YearMonthDayStructure = [];
$Date = new DateTime('2016-01-01');
for ($i = 1; $i <= countDaysForChoosenYears(2016); $i++) {
$monthName = $Date->format('F');
$yearNumber = $Date->format('Y');
$YearMonthDayStructure[$yearNumber][$monthName][$Date->format('d')] = $Date->add(new DateInterval('P1D'))->format('Y-m-d');
}
print_r($YearMonthDayStructure);
The very first time you enter the loop you are adding P1D to the date. So your first date ends up being 2 Jan because you added P1D to 1 Jan on that line with the $Date->add. It's not like $x++ where the '++' happens after you use the variable. It performs the add and then gives the result, with the added P1D, to the $YearMonthDayStructure value.

Average day of month from list of dates in PHP

I'm trying to find the average date and guess the next one.
The input is a list of dates that looks like this:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
I'm trying to scan a list of x amount of dates, and output an average of the dates overall.
So in the above example I think the output would be 2015-5-15 is expected average date.
How would I tackle this?
If you're looking for the average of those dates you can simply get the day of the year for each of those dates, average them out, and use that date:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
$first = null;
$last = null;
foreach($completeDate as $date) {
$dayOfYear = (new DateTime($date))->format('z');
if (is_null($first)) {
$first = $last = $dayOfYear;
}
else {
if ($dt < $first) {
$first = $dayOfYear;
}
if ($dt > $last) {
$last = $dayOfYear;
}
}
}
$avg = round(($first + $last) / 2);
$averageDate = DateTime::createFromFormat('z', $avg);
echo $averageDate->format('Y-m-d'); // 2015-02-26
Demo
If your looking for the average of the day of the month for the dates in that array and then use that day of the next month, you just need to average out the days of the month and then use that with the next month:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
$month = 0;
$days = 0;
foreach($completeDate as $date) {
$dt = new DateTime($date);
$month_num = $dt->format('n');
if ($month_num > $month) {
$month = $month_num;
}
$days += $dt->format('j');
}
$avg = round($days / count($completeDate));
$date = new DateTime(sprintf('%d-%01d-%01d', $dt->format('Y'), ++$month, $avg));
echo $date->format('Y-m-d'); // 2015-05-15
Demo

Get the number of weeks in a month and the corresponding days

I'm pretty sure I'm overcomplicating this but atm I have no clue how to do it in another way.
I want to create an array which contains the number of a week in a month as a key and the days of this week as the value.
I'm using this function to get all days of a week:
public function getDaysOfWeek($year, $month, $day)
{
$firstMondayThisWeek = new DateTime($year . '/' . $month . '/' . $day, new DateTimeZone("Europe/Berlin"));
$firstMondayThisWeek->modify('tomorrow');
$firstMondayThisWeek->modify('last Monday');
$nextSevenDays = new DatePeriod(
$firstMondayThisWeek,
DateInterval::createFromDateString('+1 day'),
6
);
return $nextSevenDays;
}
And this function to build the array:
public function getWeekAndDays($year, $month)
{
$weeksInMonth = Carbon::createFromDate($year, $month)->endOfMonth()->weekOfMonth;
$weekBegin = Carbon::createFromDate($year, $month)->startOfMonth();
$weeks = [];
for($i=1; $i<=$weeksInMonth; $i++)
{
$collection = new \stdClass();
$collection->week = $i;
$collection->days = $this->getDaysOfWeek($year, $month, $weekBegin->day);
$weekBegin->addWeek(0);
$weeks[] = $collection;
}
return $weeks;
}
For all months except february I'm getting 5 weeks. For February I'm getting 4 weeks and so I'm not able to save the month-overlapping days.
Am I completely wrong here? What is a possible way to solve this task?
My friend I feel your pain, working with calendar data is annoying. Here's a function I wrote a while back that builds an array of weeks and days, separated by months. It's not the cleanest code but it works.
It will start from the date you pass in $today as "Y-m-d" (or default to the current date), then work back to the first week of the current month, start there, and then go forward for $scheduleMonths months building an array indexed first by month and then by week.
It's a bit hard to explain here, but it's self-contained so you can just copy/paste it into your code and then dd() the output to see what it looks like and if it works for you, and modify it from there. There's some formatting you may need to adjust as it was done that way for my specific use case, but the business logic should be sound.
You should be able to extract the number of weeks in a given month from the output (so if that's all you need you can prob simplify this once you confirm it's working).
public function getWeeks($today = null, $scheduleMonths = 6) {
$today = !is_null($today) ? Carbon::createFromFormat('Y-m-d',$today) : Carbon::now();
$startDate = Carbon::instance($today)->startOfMonth()->startOfWeek()->subDay(); // start on Sunday
$endDate = Carbon::instance($startDate)->addMonths($scheduleMonths)->endOfMonth();
$endDate->addDays(6 - $endDate->dayOfWeek);
$epoch = Carbon::createFromTimestamp(0);
$firstDay = $epoch->diffInDays($startDate);
$lastDay = $epoch->diffInDays($endDate);
$week=0;
$monthNum = $today->month;
$yearNum = $today->year;
$prevDay = null;
$theDay = $startDate;
$prevMonth = $monthNum;
$data = array();
while ($firstDay < $lastDay) {
if (($theDay->dayOfWeek == Carbon::SUNDAY) && (($theDay->month > $monthNum) || ($theDay->month == 1))) $monthNum = $theDay->month;
if ($prevMonth > $monthNum) $yearNum++;
$theMonth = Carbon::createFromFormat("Y-m-d",$yearNum."-".$monthNum."-01")->format('F Y');
if (!array_key_exists($theMonth,$data)) $data[$theMonth] = array();
if (!array_key_exists($week,$data[$theMonth])) $data[$theMonth][$week] = array(
'day_range' => '',
);
if ($theDay->dayOfWeek == Carbon::SUNDAY) $data[$theMonth][$week]['day_range'] = sprintf("%d-",$theDay->day);
if ($theDay->dayOfWeek == Carbon::SATURDAY) $data[$theMonth][$week]['day_range'] .= sprintf("%d",$theDay->day);
$firstDay++;
if ($theDay->dayOfWeek == Carbon::SATURDAY) $week++;
$theDay = $theDay->copy()->addDay();
$prevMonth = $monthNum;
}
$totalWeeks = $week;
return array(
'startDate' => $startDate,
'endDate' => $endDate,
'totalWeeks' => $totalWeeks,
'schedule' => $data,
);
}
I hope this helps you at least get started!
Something like this?
function weekOfMonth($date) {
$firstOfMonth = strtotime(date("Y-m-01", $date));
$lastWeekNumber = (int)date("W", $date);
$firstWeekNumber = (int)date("W", $firstOfMonth);
if (12 === (int)date("m", $date)) {
if (1 == $lastWeekNumber) {
$lastWeekNumber = (int)date("W", ($date - (7*24*60*60))) + 1;
}
} elseif (1 === (int)date("m", $date) and 1 < $firstWeekNumber) {
$firstWeekNumber = 0;
}
return $lastWeekNumber - $firstWeekNumber + 1;
}
function weeks($month, $year){
$lastday = date("t", mktime(0, 0, 0, $month, 1, $year));
return weekOfMonth(strtotime($year.'-'.$month.'-'.$lastday));
}
$result = [];
for ($year = 2017; $year < 2020; $year++){
for ($month = 1; $month < 13; $month++) {
$numOfWeeks = weeks($month, $year);
$result[$year][$month]['numOfWeeks'] = $numOfWeeks;
$daysInFirstWeek = 8 - date('N', strtotime($year.'-'.$month.'-01'));
$result[$year][$month]['daysPerWeek']['week_1'] = $daysInFirstWeek;
$startDay = date('d', strtotime('next Monday', strtotime($year.'-'.$month.'-01')));
for ($i = 1; $i < ($numOfWeeks - 1); $i++) {
$startDay += 7;
$result[$year][$month]['daysPerWeek']['week_'.($i+1)] = 7;
}
//last week
$result[$year][$month]['daysPerWeek']['week_'.($i+1)] = date('t', strtotime($year.'-'.$month.'-01')) - $startDay + 1;
}
}
echo json_encode($result)."\n";

php holidays in calendar error undefined variable

i found some random code on the net and tried using it for my calendar but i keep getting this error:
Notice: Undefined variable: nextHoliday
this error is referring to the code at the end "RETURN $nextHoliday; "
I believe nextHoliday is defined tho so i tried a few things but nothing makes it work.. can someone please help?
Here's the code:
FUNCTION GetTimeStamp($MySqlDate)
{
$date_array = EXPLODE("-",$MySqlDate); // split the array
$var_year = $date_array[0];
$var_month = $date_array[1];
$var_day = $date_array[2];
$var_timestamp = MKTIME(0,0,0,$var_month,$var_day,$var_year);
RETURN($var_timestamp); // return it to the user
} // End function GetTimeStamp()
FUNCTION ordinalDay($ord, $day, $month, $year)
// ordinalDay returns date of the $ord $day of $month.
// For example ordinalDay(3, 'Sun', 5, 2001) returns the
// date of the 3rd Sunday of May (ie. Mother's Day).
//
// Note: $day must be the 3 char abbr. for the day, as
// given by date("D");
//
{
$firstOfMonth = GetTimeStamp("$year-$month-01");
$lastOfMonth = $firstOfMonth + DATE("t", $firstOfMonth) * 86400;
$dayOccurs = 0;
FOR ($i = $firstOfMonth; $i < $lastOfMonth ; $i += 86400)
{
IF (DATE("D", $i) == $day)
{
$dayOccurs++;
IF ($dayOccurs == $ord)
{ $ordDay = $i; }
}
}
RETURN $ordDay;
} // End function ordinalDay()
FUNCTION getNextHoliday()
// Looks through a lists of defined holidays and tells you which
// one is coming up next.
//
{
$year = DATE("Y");
CLASS holiday
{
VAR $name;
VAR $date;
VAR $catNum;
FUNCTION holiday($name, $date, $catNum)
// Contructor to define the details of each holiday as it is created.
{
$this->name = $name; // Official name of holiday
$this->date = $date; // UNIX timestamp of date
$this->catNum = $catNum; // category, we used for databases access
}
} // end class holiday
$holidays[] = NEW holiday("Groundhog Day", GetTimeStamp("$year-2-2"), "20");
$holidays[] = NEW holiday("Valentine's Day", GetTimeStamp("$year-2-14"), "14");
$holidays[] = NEW holiday("St. Patrick's Day", GetTimeStamp("$year-3-17"), "15");
$holidays[] = NEW holiday("Easter", EASTER_DATE($year), "16");
$holidays[] = NEW holiday("Mother's Day", ordinalDay(2, 'Sun', 5, $year), "3");
$holidays[] = NEW holiday("Father's Day", ordinalDay(3, 'Sun', 6, $year), "4");
$holidays[] = NEW holiday("Independence Day", GetTimeStamp("$year-7-4"), "17");
$holidays[] = NEW holiday("Christmas", GetTimeStamp("$year-12-25"), "13");
$numHolidays = COUNT($holidays);
FOR ($i = 0; $i < $numHolidays; $i++)
{
IF ( DATE("z") > DATE("z", $holidays[$i]->date) && DATE("z") <= DATE("z",
$holidays[$i+1]->date) )
{
$nextHoliday["name"] = $holidays[$i+1]->name;
$nextHoliday["dateStamp"] = $holidays[$i+1]->date;
$nextHoliday["dateText"] = DATE("F j, Y", $nextHoliday["dateStamp"]);
$nextHoliday["num"] = $holidays[$i+1]->catNum;
}
}
RETURN $nextHoliday;
} // end function GetNextHoliday
$nextHoliday = getNextHoliday();
ECHO $nextHoliday["name"]." (".$nextHoliday["dateText"].")";
You have one of two options. Either instantiate empty keys for your array values:
$nextHoliday = array();
$nextHoliday['name'] = '';
$nextHoliday['dateStamp'] = '';
$nextHoliday['dateText'] = '';
$nextHoliday['num'] = '';
$numHolidays = COUNT($holidays);
for ($i = 0; $i < $numHolidays; $i++) {
// ... Blah
}
Or use isset before each array lookup:
echo (isset($nextHoliday['name'] ? $nextHoliday['name'] : '') .
" (" .
(isset($nextHoliday) ? $nextHoliday["dateText"] : '' ) .
")";
Nothing better than the ternary operator for inline conditionals.
It's actually good that we're testing this in January, because otherwise this bug would have bitten you later on. The problem is that you are using less than/greater than to determine what the next holiday is. This fails to take into account the last holiday of the last year.
To fix this, the variable $lastHoliday needs to be a negative representation of the last holiday:
$numHolidays = COUNT($holidays);
$nextHoliday = array('name' => '', 'dateStamp' => '', 'dateText' => '', 'num' => '');
for ($i = 0; $i < $numHolidays - 1; $i++) {
$today = DATE("z");
if ($i == 0) {
$lastHoliday = (365 - DATE("z", $holidays[$numHolidays - 1]->date)) * -1;
} else {
$lastHoliday = DATE("z", $holidays[$i]->date);
}
$futureHoliday = DATE("z", $holidays[$i+1]->date);
//print_r($today); echo "<br />";
//print_r($lastHoliday); echo "<br />";
//print_r($futureHoliday); echo "<br />";
if ($today > $lastHoliday && $today <= $futureHoliday ) {
$nextHoliday["name"] = $holidays[$i+1]->name;
$nextHoliday["dateStamp"] = $holidays[$i+1]->date;
$nextHoliday["dateText"] = DATE("F j, Y", $nextHoliday["dateStamp"]);
$nextHoliday["num"] = $holidays[$i+1]->catNum;
}
}
Also consider typing PHP in lowercase, not uppercase, as it is an almost universal standard in PHP. One true brace style wouldn't hurt either.
$nextHoliday is used in a if but is not declared
Declare the variable before the for:
[...]
$nextHoliday = array();
FOR ($i = 0; $i < $numHolidays; $i++)
[...]

Categories