calculate interest by day in real time - php

i am a creating a system where users can do investments and gain interest. Now i want to calculate interest per day in real time, so the interest accumulate by day until the end date . I want this to be in real time, meaning i want to calculate the interest and up until the end date but my code only displays the results all at once. For instance if the start day is monday and end date is is friday the code should accumulate the interest everyday until friday in real time.
$interest = 89;
$startdate = '2022-06-06';
$enddate = '2023-06-04'
$days = (($enddate - $startdate) / 60 / 60 / 24);
$previousday = date('Y/m/d',strtotime("-1 days"));
$currentdate = date('Y/m/d');
$totalEarning = 0;
$Earningsbyday = [];
if ($startdate <= $enddate && $previousday < $currentdate ) {
for ($i = 0; $i < $days; $i++) {
$totalEarning += $interest;
$Earningsbyday[$i] = $totalEarning;
}
echo var_dump($Earningsbyday)."\n"."\n";
}

You can do this quite simply. First, use create_date to create a DateTime in PHP. With these two converted dates, we can pass it to date_diff and format the amount of days between the two dates.
From here, we can use array_fill to fill an array of nth days with the interest. The total is simply just the array_sum.
We can now array_walk and calculate the previous day + the current interest to build the array you're expecting.
class InterestCalculator
{
private array $dailyInterest = [];
private static int $interest = 89;
public function calculateDailyInterest(string $start, string $end): array
{
$this->dailyInterest = array_fill(0, date_diff(date_create($start), date_create($end))->format('%a'), self::$interest);
$total = array_sum($this->dailyInterest);
array_walk($this->dailyInterest, function (int &$value, int $key): void {
$value = ($this->dailyInterest[$key -1] ?? 0) + $value;
});
return compact('total') + ['dailyInterest' => $this->dailyInterest];
}
}
// total => 32307, dailyInterest => [0 => 89, 1 => 178, 2 => 267, ....]
print_r((new InterestCalculator)->calculateDailyInterest('2022-06-06', '2023-06-04'));
References:
https://www.php.net/manual/en/function.date-create.php
https://www.php.net/manual/en/datetime.diff.php
https://www.php.net/manual/en/function.array-fill.php
https://www.php.net/manual/en/function.array-walk.php
https://www.php.net/manual/en/language.references.pass.php
https://www.php.net/manual/es/function.compact.php
Hopefully, this completes what you need. Feel free to comment if I miss understood your question.
See it working over at 3v4l.org

Related

Counting working hours (different for weekdays and Saturdays)

I tried #ebelendez's code for Calculating working hours between two dates, however I'm confused on how to set the value of Saturdays by 3 hours (08:00-11:00). For example, the working hours per day during weekdays is 8 hours (excluding 1 hour break), let's say I want to get the total working hours from Thursday to Saturday, the expected result would be 19 hours.
Here is what I've done. Can someone help me with this?
$from = '2022-04-21 07:00:00';
$to = '2022-04-23 16:00:00';
echo abs(get_working_hours($from, $to));
function get_working_hours($from,$to){
//config
$ini_time = [7,0]; //hr, min
$end_time = [16,0]; //hr, min
//date objects
$ini = date_create($from);
$ini_wk = date_time_set(date_create($from),$ini_time[0],$ini_time[1]);
$end = date_create($to);
$end_wk = date_time_set(date_create($to),$end_time[0],$end_time[1]);
//days
$workdays_arr = get_workdays($ini,$end);
$workdays_count = count($workdays_arr);
$workday_seconds = (($end_time[0] * 60 + $end_time[1]) - ($ini_time[0] * 60 + $ini_time[1])) * 60 - 3600;
//get time difference
$ini_seconds = 0;
$end_seconds = 0;
if(in_array($ini->format('Y-m-d'),$workdays_arr)) $ini_seconds = $ini->format('U') - $ini_wk->format('U');
if(in_array($end->format('Y-m-d'),$workdays_arr)) $end_seconds = $end_wk->format('U') - $end->format('U');
$seconds_dif = $ini_seconds > 0 ? $ini_seconds : 0;
if($end_seconds > 0) $seconds_dif += $end_seconds;
//final calculations
$working_seconds = ($workdays_count * $workday_seconds) - $seconds_dif;
return $working_seconds / 3600; //return hrs
}
function get_workdays($ini,$end){
//config
$skipdays = [0]; //sunday:0
$skipdates = [];
//vars
$current = clone $ini;
$current_disp = $current->format('Y-m-d');
$end_disp = $end->format('Y-m-d');
$days_arr = [];
//days range
while($current_disp <= $end_disp){
if(!in_array($current->format('w'),$skipdays) && !in_array($current_disp,$skipdates)){
$days_arr[] = $current_disp;
}
$current->add(new DateInterval('P1D')); //adds one day
$current_disp = $current->format('Y-m-d');
}
return $days_arr;
}
Your code and linked answers seem unnecessarily complicated. All we really need is to:
Configure how many hours should be counted for for each day;
Create an iterable DatePeriod (with DateTime objects for each date in the period);
Iterate dates, look up how many hours should be counted for each day, sum it up.
class CountWorkingHours
{
// Define hours counted for each day:
public array $hours = [
'Mon' => 8,
'Tue' => 8,
'Wed' => 8,
'Thu' => 8,
'Fri' => 8,
'Sat' => 3,
'Sun' => 0
];
// Method for counting the hours:
public function get_hours_for_period(string $from, string $to): int
{
// Create DatePeriod with requested Start/End dates:
$period = new DatePeriod(
new DateTime($from),
new DateInterval('P1D'),
new DateTime($to)
);
$hours = [];
// Loop over DateTime objects in the DatePeriod:
foreach($period as $date) {
// Get name of day and add matching hours:
$day = $date->format('D');
$hours[] = $this->hours[$day];
}
// Return sum of hours:
return array_sum($hours);
}
}
Source # BitBucket
Usage (returns an integer with working hours in a given period):
$cwh = new CountWorkingHours();
$hours = $cwh->get_hours_for_period('2022-04-21 07:00:00', '2022-04-30 16:00:00');
// = 62
If you need to account for public holidays etc. exceptions to the standard weekly hour counts, you can add a check inside the period loop for "skip dates". For example, have a $skip_dates property with an array of non-work-days, then check for !in_array($date->format('Y-m-d'), $this->skip_dates) before incrementing the work hours for a given day.
P.S. This code assumes that you are calculating whole working days. If your start or end hours were defined in the middle of a working day, that wouldn't be accounted for. (If you wanted to factor that in, you'd have to configure daily work times and the code would have to account for that in evaluating start and end dates. Seemed an unnecessary exercise for current purposes.)

Generate random dates with random times between two dates for selected period and frequency

I have to create a scheduling component that will plan e-mails that need to be sent out. Users can select a start time, end time, and frequency. Code should produce a random moment for every frequency, between start and end time. Outside of office hours.
Paramaters:
User can select a period between 01/01/2020 (the start) and 01/01/2021 (the end). In this case user selects a timespan of one exactly year.
User can select a frequency. In this case user selects '2 months'.
Function:
Code produces a list of datetimes. The total time (one year) is divided by frequency (2 months). We expect a list of 6 datetimes.
Every datetime is a random moment in said frequency (2 months). Within office hours.
Result:
An example result for these paramaters might as follows, with the calculated frequency bounds for clarity:
[jan/feb] 21-02-2020 11.36
[mrt/apr] 04-03-2020 16.11
[mei/jun] 13-05-2020 09.49
[jul-aug] 14-07-2020 15.25
[sep-okt] 02-09-2020 14.09
[nov-dec] 25-12-2020 13.55
--
I've been thinking about how to implement this best, but I can't figure out an elegant solution.
How could one do this using PHP?
Any insights, references, or code spikes would be greatly appreciated. I'm really stuck on this one.
I think you're just asking for suggestions on how to generate a list of repeating (2 weekly) dates with a random time between say 9am and 5pm? Is that right?
If so - something like this (untested, pseudo code) might be a starting point:
$start = new Datetime('1st January 2021');
$end = new Datetime('1st July 2021');
$day_start = 9;
$day_end = 17;
$date = $start;
$dates = [$date]; // Start date into array
while($date < $end) {
$new_date = clone($date->modify("+ 2 weeks"));
$new_date->setTime(mt_rand($day_start, $day_end), mt_rand(0, 59));
$dates[] = $new_date;
}
var_dump($dates);
Steve's anwser seems good, but you should consider 2 additional things
holiday check, in the while after first $new_date line, like:
$holiday = array('2021-01-01', '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
also a check if date is a office day or a weekend in a similar way as above with working days as an array.
It's kind of crappy code but I think it will work as you wish.
function getDiffInSeconds(\DateTime $start, \DateTime $end) : int
{
$startTimestamp = $start->getTimestamp();
$endTimestamp = $end->getTimestamp();
return $endTimestamp - $startTimestamp;
}
function getShiftData(\DateTime $start, \DateTime $end) : array
{
$shiftStartHour = \DateTime::createFromFormat('H:i:s', $start->format('H:i:s'));
$shiftEndHour = \DateTime::createFromFormat('H:i:s', $end->format('H:i:s'));
$shiftInSeconds = intval($shiftEndHour->getTimestamp() - $shiftStartHour->getTimestamp());
return [
$shiftStartHour,
$shiftEndHour,
$shiftInSeconds,
];
}
function dayIsWeekendOrHoliday(\DateTime $date, array $holidays = []) : bool
{
$weekendDayIndexes = [
0 => 'Sunday',
6 => 'Saturday',
];
$dayOfWeek = $date->format('w');
if (empty($holidays)) {
$dayIsWeekendOrHoliday = isset($weekendDayIndexes[$dayOfWeek]);
} else {
$dayMonthDate = $date->format('d/m');
$dayMonthYearDate = $date->format('d/m/Y');
$dayIsWeekendOrHoliday = (isset($weekendDayIndexes[$dayOfWeek]) || isset($holidays[$dayMonthDate]) || isset($holidays[$dayMonthYearDate]));
}
return $dayIsWeekendOrHoliday;
}
function getScheduleDates(\DateTime $start, \DateTime $end, int $frequencyInSeconds) : array
{
if ($frequencyInSeconds < (24 * 60 * 60)) {
throw new \InvalidArgumentException('Frequency must be bigger than one day');
}
$diffInSeconds = getDiffInSeconds($start, $end);
// If difference between $start and $end is bigger than two days
if ($diffInSeconds > (2 * 24 * 60 * 60)) {
// If difference is bigger than 2 days we add 1 day to start and subtract 1 day from end
$start->modify('+1 day');
$end->modify('-1 day');
// Getting new $diffInSeconds after $start and $end changes
$diffInSeconds = getDiffInSeconds($start, $end);
}
if ($frequencyInSeconds > $diffInSeconds) {
throw new \InvalidArgumentException('Frequency is bigger than difference between dates');
}
$holidays = [
'01/01' => 'New Year',
'18/04/2020' => 'Easter 1st official holiday because 19/04/2020',
'20/04/2020' => 'Easter',
'21/04/2020' => 'Easter 2nd day',
'27/04' => 'Konings',
'04/05' => '4mei',
'05/05' => '4mei',
'24/12' => 'Christmas 1st day',
'25/12' => 'Christmas 2nd day',
'26/12' => 'Christmas 3nd day',
'27/12' => 'Christmas 3rd day',
'31/12' => 'Old Year'
];
[$shiftStartHour, $shiftEndHour, $shiftInSeconds] = getShiftData($start, $end);
$amountOfNotifications = floor($diffInSeconds / $frequencyInSeconds);
$periodInSeconds = intval($diffInSeconds / $amountOfNotifications);
$maxDaysBetweenNotifications = intval($periodInSeconds / (24 * 60 * 60));
// If $maxDaysBetweenNotifications is equals to 1 then we have to change $periodInSeconds to amount of seconds for one day
if ($maxDaysBetweenNotifications === 1) {
$periodInSeconds = (24 * 60 * 60);
}
$dates = [];
for ($i = 0; $i < $amountOfNotifications; $i++) {
$periodStart = clone $start;
$periodStart->setTimestamp($start->getTimestamp() + ($i * $periodInSeconds));
$seconds = mt_rand(0, $shiftInSeconds);
// If $maxDaysBetweenNotifications is equals to 1 then we have to check only one day without loop through the dates
if ($maxDaysBetweenNotifications === 1) {
$interval = new \DateInterval('P' . $maxDaysBetweenNotifications . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
} else {
// When $maxDaysBetweenNotifications we have to loop through the dates to pick them
$loopsCount = 0;
$maxLoops = 3; // Max loops before breaking and skipping the period
do {
$day = mt_rand(0, $maxDaysBetweenNotifications);
$periodStart->modify($shiftStartHour);
$interval = new \DateInterval('P' . $day . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
// If the day is weekend or holiday then we have to increment $loopsCount by 1 for each loop
if ($dayIsWeekendOrHoliday === true) {
$loopsCount++;
// If $loopsCount is equals to $maxLoops then we have to break the loop
if ($loopsCount === $maxLoops) {
break;
}
}
} while ($dayIsWeekendOrHoliday);
}
// Adds the date to $dates only if the day is not a weekend day and holiday
if ($dayIsWeekendOrHoliday === false) {
$dates[] = $date;
}
}
return $dates;
}
$start = new \DateTime('2020-12-30 08:00:00', new \DateTimeZone('Europe/Sofia'));
$end = new \DateTime('2021-01-18 17:00:00', new \DateTimeZone('Europe/Sofia'));
$frequencyInSeconds = 86400; // 1 day
$dates = getScheduleDates($start, $end, $frequencyInSeconds);
var_dump($dates);
You have to pass $start, $end and $frequencyInSeconds as I showed in example and then you will get your random dates. Notice that I $start and $end must have hours in them because they are used as start and end hours for shifts. Because the rule is to return a date within a shift time only in working days. Also you have to provide frequency in seconds - you can calculate them outside the function or you can change it to calculate them inside. I did it this way because I don't know what are your predefined periods.
This function returns an array of \DateTime() instances so you can do whatever you want with them.
UPDATE 08/01/2020:
Holidays now are part of calculation and they will be excluded from returned dates if they are passed when you are calling the function. You can pass them in d/m and d/m/Y formats because of holidays like Easter and in case when the holiday is on weekend but people will get additional dayoff during the working week.
UPDATE 13/01/2020:
I've made updated code version to fix the issue with infinite loops when $frequencyInSeconds is shorter like 1 day. The new code used few functions getDiffInSeconds, getShiftData and dayIsWeekendOrHoliday as helper methods to reduce code duplication and cleaner and more readable code

count how many days within a date range are within another date range

From October 1st to March 31 the fee is $1 (season 1). From April 1st to September 30 the fee is $2 (season 2).
How can I calculate the total fee of a given date range (user input) depending on how many days of this date range fall into season 1 and season 2?
The following gives me the number of days of the userĀ“s date range, but I have no idea how to test against season 1 or season 2:
$user_input_start_date = getdate( $a );
$user_input_end_date = getdate( $b );
$start_date_new = mktime( 12, 0, 0, $user_input_start_date['mon'], $user_input_start_date['mday'], $user_input_start_date['year'] );
$end_date_new = mktime( 12, 0, 0, $user_input_end_date['mon'], $user_input_end_date['mday'], $user_input_end_date['year'] );
return round( abs( $start_date_new - $end_date_new ) / 86400 );
Given that a date range starts and ends in 2012 or starts in 2012 and ends in 2013 alone gives me 10 different possibilities of in which season a date range can start and where it can end.
There must be a better solution than iterating if/else and comparing dates over and over again for the following conditions:
Date range is completely within season 1
Date range starts in season 1 and ends in season 2
Date range starts in season 1, spans across season 2 and ends in the second part of season 1
... and so forth with "Starts in season 2", etc
This not a duplicate of How many days until X-Y-Z date? as that only deals with counting the number of days. It does not address the issue of comparing one date range with another.
The key to this problem is to simplify it as much as possible. I think using an array as a lookup table for the cost of each day of the year is the way to go. The first thing to do then, is to generate the array. The array just represents each day of the year and doesn't represent any particular year. I chose to use 2012 to generate the lookup array as it is a leap year and so has every possible day in it.
function getSeasonArray()
{
/**
* I have chosen 2012 as it was a leap year. All we want to do is
* generate an array which has avery day of the year in it.
*/
$startDate = new DateTime('1st January 2012');
//DatePeriod always drops the last day.
$endDate = new DateTime('1st January 2013');
$season2Start = new DateTime('1st April 2012');
$season2End = new DateTime('1st October 2012');
$allDays = new DatePeriod($startDate, new DateInterval('P1D'), $endDate);
$season2Days = new DatePeriod($season2Start, new DateInterval('P1D'), $season2End);
$seasonArray = array();
foreach($allDays as $day){
$seasonArray[] = $day->format('d-M');
$seasonArray[$day->format('d-M')]['season'] = 1;
}
foreach($season2Days as $day){
$seasonArray[$day->format('d-M')]['season'] = 2;
}
return $seasonArray;
}
Once that is done you just need the period over which to calculate:-
$bookingStartDate = new DateTime();//Or wherever you get this from
$bookingEndDate = new DateTime();
$bookingEndDate->setTimestamp(strtotime('+ 7 month'));//Or wherever you get this from
$bookingPeriod = new DatePeriod($bookingStartDate, new DateInterval('P1D'), $bookingEndDate);
Then we can do the calculation:-
$seasons = getSeasonArray();
$totalCost = 0;
foreach($bookingPeriod as $day){
$totalCost += $seasons[$day->format('d-M')]['season'];
var_dump($day->format('d-M') . ' = $' . $seasons[$day->format('d-M')]['season']);
}
var_dump($totalCost);
I have chosen a long booking period, so that you can scan through the var_dump() output and verify the correct price for each day of the year.
This is a quick stab done between distractions at work and I'm sure that with a bit of thought you can mould it into a more elegant solution. I'd like to get rid of the double iteration for example, unfortunately, work pressures prevent me from spending further time on this.
See the PHP DateTime man page for further information on these useful classes.
At first I suggested using the DateTime class that PHP provides, naively assuming that it has some kind of thought-out API that one could use. It turns out that it does not. While it features very basic DateTime functionality, it is mostly unusable because, for most operations, it relies on the DateInterval class. In combination, those classes represent another masterpiece of bad API design.
An interval should be defined like so:
An interval in Joda-Time represents an interval of time from one millisecond instant to another instant. Both instants are fully specified instants in the datetime continuum, complete with time zone.
In PHP, however, an Interval is just a duration:
A date interval stores either a fixed amount of time (in years, months, days, hours etc) or a relative time string [such as "2 days"].
Unfortunately, PHP's DateInterval definition does not allow for intersection/overlap calculation (which the OP needs) because PHP's Intervals have no specific position in the datetime continuum. Therefore, I've implemented a (very rudimentary) class that adheres to JodaTime's definition of an interval. It is not extensively tested, but it should get the work done:
class ProperDateInterval {
private $start = null;
private $end = null;
public function __construct(DateTime $start, DateTime $end) {
$this->start = $start;
$this->end = $end;
}
/**
* Does this time interval overlap the specified time interval.
*/
public function overlaps(ProperDateInterval $other) {
$start = $this->getStart()->getTimestamp();
$end = $this->getEnd()->getTimestamp();
$oStart = $other->getStart()->getTimestamp();
$oEnd = $other->getEnd()->getTimestamp();
return $start < $oEnd && $oStart < $end;
}
/**
* Gets the overlap between this interval and another interval.
*/
public function overlap(ProperDateInterval $other) {
if(!$this->overlaps($other)) {
// I haven't decided what should happen here yet.
// Returning "null" doesn't seem like a good solution.
// Maybe ProperDateInterval::EMPTY?
throw new Exception("No intersection.");
}
$start = $this->getStart()->getTimestamp();
$end = $this->getEnd()->getTimestamp();
$oStart = $other->getStart()->getTimestamp();
$oEnd = $other->getEnd()->getTimestamp();
$overlapStart = NULL;
$overlapEnd = NULL;
if($start === $oStart || $start > $oStart) {
$overlapStart = $this->getStart();
} else {
$overlapStart = $other->getStart();
}
if($end === $oEnd || $end < $oEnd) {
$overlapEnd = $this->getEnd();
} else {
$overlapEnd = $other->getEnd();
}
return new ProperDateInterval($overlapStart, $overlapEnd);
}
/**
* #return long The duration of this interval in seconds.
*/
public function getDuration() {
return $this->getEnd()->getTimestamp() - $this->getStart()->getTimestamp();
}
public function getStart() {
return $this->start;
}
public function getEnd() {
return $this->end;
}
}
It may be used like so:
$seasonStart = DateTime::createFromFormat('j-M-Y', '01-Apr-2012');
$seasonEnd = DateTime::createFromFormat('j-M-Y', '30-Sep-2012');
$userStart = DateTime::createFromFormat('j-M-Y', '01-Jan-2012');
$userEnd = DateTime::createFromFormat('j-M-Y', '02-Apr-2012');
$i1 = new ProperDateInterval($seasonStart, $seasonEnd);
$i2 = new ProperDateInterval($userStart, $userEnd);
$overlap = $i1->overlap($i2);
var_dump($overlap->getDuration());

PHP calculating number of days between 2 dates

I am developing a web application which revolves around dates.
I need to calculate numbers based around days elasped, for example - pseudo code
$count_only = array('monday', 'wednesday', 'friday'); //count only these days
$start_date = 1298572294; // a day in the past
$finish_date = 1314210695; //another day
$var = number_of_days_between($start_date, $finish_date, $count_only);
Is there a way determine how many full days have elapsed, while only counting certain days?
You can simplify this considerably by calculating how many complete weeks fall between the two specified dates, then do some math for the beginning/end partial weeks to account for dangling dates.
e.g.
$start_date = 1298572294; // Tuesday
$finish_date = 1314210695; // Wednesday
$diff = 1314210695-1298572294 = 15638401 -> ~181 days -> 25.8 weeks -> 25 full weeks.
Then it's just a simple matter of checking for the dangling dates:
Tuesday -> add 2 days for Wednesday+Friday to get to the end of the week
Wednesday -> add 1 day for Monday to get to the beginning on the week
Total countable days = (25 * 3) + 2 + 1 = 75 + 3 = 78 countable days
You could create a loop which goes to the next day in the $count_only array, from the $start_date and stopping (returning from the function) upon reaching the $end_date.
function number_of_days_between($start_date, $finish_date, $count_only) {
$count = 0;
$start = new DateTime("#$start_date");
$end = new DateTime("#$finish_date");
$days = new InfiniteIterator(new ArrayIterator($count_only));
foreach ($days as $day) {
$count++;
$start->modify("next $day");
if ($start > $end) {
return $count;
}
}
}
Of course there is a way :-)
The days that have been elapsed is simply
$elapsed_days = floor(($finish_date-$start_date) / 86400);
This will not get the result you need. What you could do is the following (pesudo)code:
$elapsed_days = floor(($finish_date-$start_date) / 86400);
for(int $i=0;$i<$elapsed_days;$i++){
$act_day_name = strtolower(date('l',$start_date+$i*86400));
if(in_array($act_day_name,$count_only){
// found matching day
}
}
What I do:
I iterate over every day which is between the both dates, get the day-name with date('l'); and check if it's within the array.
There may be some fine tuning need to be done, but this should get you going.
Just a bit faster approach than "iterating through all days":
$count_only = array(1, 3, 5); // days numbers from getdate() function
$start_date = 1298572294;
$finish_date = 1314210695;
function days($start_date, $finish_date, $count_only)
{
$cnt = 0;
// iterate over 7 days
for ($deltaDays = 0; $deltaDays < 7; $deltaDays++)
{
$rangeStart = $start_date + $deltaDays * 86400;
// check the weekday of rangeStart
$d = getDate($rangeStart);
if (in_array($d['wday'], $count_only))
{
$cnt += ceil(($finish_date - $rangeStart) / 604800);
}
}
return $cnt;
}
The idea is to count number of weeks using some additional offsets for mondays, tuesdays, wednesdays etc.

Finding all weekdays in a month

How do I go about getting all the work days (mon-fri) in a given time period (let's say, today till the end of the next month) ?
If you're using PHP 5.2+ you can use the library I wrote in order to handle date recursion in PHP called When.
With the library, the code would be something like:
$r = new When();
$r->recur(<start date here>, 'weekly')
->until(<end date here>)
->wkst('SU')
->byday(array('MO', 'TU', 'WE', 'TH', 'FR'));
while($result = $r->next())
{
echo $result->format('c') . '<br />';
}
This sample does exactly what you need, in an quick and efficient way.
It doesn't do nested loops and uses the totally awesome DateTime object.
$oDateTime = new DateTime();
$oDayIncrease = new DateInterval("P1D");
$aWeekDays = array();
$sStart = $oDateTime->format("m-Y");
while($oDateTime->format("m-Y") == $sStart) {
$iDayInWeek = $oDateTime->format("w");
if ($iDayInWeek > 0 && $iDayInWeek < 6) {
$aWeekDays[] = clone $oDateTime;
}
$oDateTime->add($oDayIncrease);
}
Try it here: http://codepad.org/wuAyAqnF
To use it, simply pass a timestamp to get_weekdays. You'll get back an array of all the weekdays, as timestamps, for the rest of the current month. Optionally, you can pass a $to argument - you will get all weekdays between $from and $to.
function get_weekdays ($from, $to=false) {
if ($to == false)
$to = last_day_of_month($from);
$days = array();
for ($x = $from; $x < $to; $x+=86400 ) {
if (date('w', $x) > 0 && date('w', $x) < 6)
$days[] = $x;
}
return $days;
}
function last_day_of_month($ts=false) {
$m = date('m', $ts);
$y = date('y', $ts);
return mktime(23, 59, 59, ($m+1), 0, $y);
}
I suppose you could loop through the dates and check the day for each one, and increment a counter.
Can't think of anything else off the top of my head.
Pseudocode coming your way:
Calculate the number of days between now and the last day of the month
Get the current day of the week (i.e. Wednesday)
Based on the current day of the week, and the number of days left in the month, it's simple calculation to figure out how many weekend days are left in the month - it's going to be the number of days remaining in the month, minus the number of Sundays/Saturdays left in the month.
I would write a function, something like:
daysLeftInMonth(daysLeftInMonth, startingDayOfWeek, dayOfWeekToCalculate)
where:
daysLeftInMonth is last day of the month (30), minus the current date (15)
startingDayOfWeek is the day of the week you want to start on (for today it would be Wednesday)
dayOfWeekToCalculate is the day of the week you want to count, e.g. Saturday or Sunday. June 2011 currently has 2 Sunday, and 2 Saturdays left 'til the end of the month
So, your algorithm becomes something like:
getWeekdaysLeft(todaysDate)
...getWeekdaysLeft is something like:
sundaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Sunday");
saturdaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Saturday");
return ((lastDayOfMonth - todaysDate) - (sundaysLeft + saturdaysLeft));
This code does at least one part you ask for. Instead of "end of next month" it simply works with a given number of days.
$dfrom = time();
$fourweeks = 7 * 4;
for ($i = 0; $i < $fourweeks; $i ++) {
$stamp = $dfrom + ($i * 24 * 60 * 60);
$weekday = date("D", $stamp);
if (in_array($weekday, array("Mon", "Tue", "Wed", "Thu", "Fri"))) {
print date(DATE_RSS, $stamp) . "\n";
}
}
// Find today's day of the month (i.e. 15)
$today = intval(date('d'));
// Define the array that will hold the work days.
$work_days = array()
// Find this month's last day. (i.e. 30)
$last = intval(date('d', strtotime('last day of this month')));
// Loop through all of the days between today and the last day of the month (i.e. 15 through 30)
for ( $i = $today; $i <= $last; $i++ )
{
// Create a timestamp.
$timestamp = mktime(null, null, null, null, $i);
// If the day of the week is greater than Sunday (0) but less than Saturday (6), add the timestamp to an array.
if ( intval(date('w', $timestamp)) > 0 && intval(date('w', $timestamp)) < 6 )
$work_days[] = mktime($timestamp);
}
The $work_days array will contain timestamps which you could use this way:
echo date('Y-m-d', $work_days[0]);
The code above with work in PHP 4 as well as PHP 5. It does not rely on the functionality of the DateTime class which was not available until PHP 5.2 and does not require the use of "libraries" created by other people.

Categories