I'm working on a system that controls the finances of the company. One of the features is to schedule fixed income/expenses that happens every 15 days.
I already have a function that schedules incomes/expenses on a weekly basis, and I tried to copy that to make it work every 15 days. This is the code:
$registry_list = array();
foreach ($list as $item) {
/**
* Day of month 01-31 of the income/expense.
* This is the day the user created the first registered item.
*/
$firstDay = $item->day;
// How many days there is on the month
$daysOnMonth = cal_days_in_month(CAL_GREGORIAN, $params['month'], $params['year']);
// We check if the current month/year is greater than the first registry
// expire_date is always day 01. The day itself is on the property 'day'
if ($item->expire_date < $params['year'].$params['month'].'-01') {
$newDate = new \DateTime($item->expire_date);
$newYear = $newDate->format('Y');
$newMonth = $newDate->format('m');
$newDate = $newDate->setDate($newYear, $newMonth, $firstDay);
$newDate = $newDate->format('l');
$firstDay = strtotime("first ".$newDate." ".$params['year']."-".$params['month']);
$firstDay = date('d', $firstDay);
}
// First registry
$newItem = clone($item);
$newItem->expire_date = $params['year'].'-'.$params['month'].'-'.$firstDay;
$newItem = new Registry($newItem); // Just to format some data
array_push($registry_list, $newItem);
while ($firstDay < $daysOnMonth) {
$firstDay = $firstDay + 14; // 14 because the day itself count as +1
if ($firstDay <= $daysOnMonth) {
$newItem=clone($item);
$newItem->expire_date = $params['year'].'-'.$params['month'].'-'.$firstDay;
$newItem = new Registry($newItem);
array_push($registry_list, $newItem);
}
}
}
return $registry_list;
This code runs just fine when the month/year is the same as when the registry was created. But as it goes to the next month, sometimes it's not correct. For example, if I start it on 02/08/2022 (d/m/Y) the schedules for that month are:
02/08/2022
16/08/2022
30/08/2022
Which is correct. However, when September starts it messes with the list. Since I'm starting on the first weekday (based on the first registry) it's not always correct. So September list is as follow:
06/09/2022
20/09/2022
Which is incorrect. This should be the correct dates:
13/09/2022
27/09/2022
Because the last one from August was 30/08/2022.
I just don't know how to identify when I need to skip the first week of the month based on the last month. I also don't want to always start counting from the date the registry was created, otherwise I would go a long way to find the correct date when we pass a year or 2.
Since it's business financial control, they tend to look further on the calendar.
Is there a way to fix this issue?
This is the kind of problem that DateInterval can solve pretty easily:
/*
This function returns all dates between two dates at the given interval
Interval syntax: https://www.php.net/manual/en/dateinterval.construct.php
Date format syntax: https://www.php.net/manual/en/datetime.format.php
*/
function get_schedule_dates($start_date, $end_date, $interval, $format = 'Y-m-d')
{
$date = new DateTime($start_date);
$end = new DateTime($end_date);
$schedule = [$date->format($format)];
while ($date->add(new DateInterval($interval)) <= $end)
$schedule[] = $date->format($format);
return $schedule;
}
var_dump(get_schedule_dates('2022-08-02', '2022-10-03', 'P14D', 'd/m/Y'));
Yields
array(5) {
[0]=>
string(10) "02/08/2022"
[1]=>
string(10) "16/08/2022"
[2]=>
string(10) "30/08/2022"
[3]=>
string(10) "13/09/2022"
[4]=>
string(10) "27/09/2022"
}
What you could do is get the expiry_date of the first item as the start date, then calculate a date in advance, say 2 years, based on that, and use that as the end date.
You could then iterate through the scheduled dates and clone and create new Registry objects, pushing to $registry_list
I have this foreach loop:
foreach($courses as $course){ //$course is just course name
$day = $dowMap[$course_day[$course]-1]; //$day = Sun/Mon...
$date = date('Y-m-d', strtotime("next ".$day, strtotime(SEMESTER_START))); //first lectur in semester
while (strtotime($date) <= strtotime(SEMESTER_END)) {
if(!array_key_exists($course, $date_arr)){
$date_arr[$course] = '';
$date_arr[$course]++;
}else{
$date_arr[$course]++;
}
$date = date ("Y-m-d", strtotime("+7 day", strtotime($date)));
}
}
its actually not that complicated, im creating array - $date_arr in which the key is the course name and the value is the number of dates between the beginning and the end of semester were lecture takes place.
so if there 15 Sundays between 2016-01-01 (semester start) and 2016-04-12 (semester end), and course takes place once a week on Sunday then the array will have this part in it:
["some_course"] => 15
the all thing works fine except that the last course value always 1 less than it should be.
any idea what am i missing in this loop? or to be more precise why is the last execution over the last course in the $courses array is missing?? thx
I have an Array of Timestamps (as key) and Strings (as value).
I would like to get all the elements of the Array, where the Timestamp is in a specific Month.
E.g: 2015-April: from 0:00 1th of April 2015, until 23:59 30th of April 2015.
What is the best method for that? Shall i sort my array first with ksort, or that makes it slower?
EDITED: Dump of my array:
array(327) { [1428226200]=> string(95) "foo" [1428231600]=> string(95) "bar" ... }
I also can get an array like this easily, if it's better:
array(327) { ["2014/03/05 09:30"]=> string(95) "foo" ["2015/04/07 11:00"]=> string(95) "bar" ... }
Convert the timestamps to a date and compare against the formatted month:
$dates = /* your array */
$aprilDates = [];
foreach ($dates as $timestamp => $value) {
$date = new \DateTime("#$timestamp");
if ($date instanceof \DateTime && $date->format('m') == 4) {
$aprilDates[$timestamp] = $value;
}
}
Online Demo
Instead of \DateTime, you can also use the date function:
foreach ($dates as $timestamp => $value) {
if (date('m', $timestamp) == 2) {
$aprilDates[$timestamp] = $value;
}
}
Yet another option:
$startDate = strtotime('first day of April');
$endDate = strtotime('last day of April 23:59:59');
foreach ($dates as $timestamp => $value) {
if ($timestamp >= $startDate && $timestamp <= $endDate) {
echo $value, PHP_EOL;
}
}
Regarding your question
Shall i sort my array first with ksort, or that makes it slower?
Sorting the array first will do one additional iteration over the array. That means yes, it will be slower. With only a few hundreds items in there, it will likely make no noticeable difference though.
How to construct an array having elements current Quarter as first element and next four quarters as next elements.
Consider for the year 27-Nov-13,
array should contain elements as follows
$ac_quarters = array (AC4-13,AC1-14,AC2-14,AC3-14,AC4-14);
Please see me as novice to PHP programming.
Currently I have written code as shown below. Surely there gotta be better logic than this.
$date = "2013-1-13";
$d = date_parse_from_format("Y-m-d", $date);
if ($d["month"]==1 || $d["month"]==2 || $d["month"]==3) {
$ac_quarters = array (AC1-13,AC2-13,AC3-13,AC4-13,AC1-14);
}
To avoid special casing each month and looking it up, find out which quarter you're currently in by dividing the month number by 3, and then iterating through four quarters ahead.
$current = floor((date('n') - 1) / 3);
$year = date('y');
$quarters = array();
for ($i = 0; $i < 5; $i++)
{
$q = (($current+$i)%4) + 1;
$quarters[] = "AC" . $q . "-" . $year;
if (($current+$i+1)%4 == 0)
{
$year++;
}
}
var_dump($quarters);
We increment $year if the next quarter would be the start of a new year (which will give us 0 when using the modulo operator). You can use strtotime() if you want to change the startdate in the date() calls at the top.
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.