I writing a script where it will populate a series of dates based on the criteria set between a start and finish time.
I basically using the relative formats in php dates function (http://php.net/manual/en/datetime.formats.relative.php).
My script first get the intervals based on user's selection:
//first get the interval
$event_repeatmonth = $_POST['event_repeatmonth'];
if ($event_repeatmonth == 1){ $interval = 'first';}
if ($event_repeatmonth == 2){ $interval = 'second';}
if ($event_repeatmonth == 3){ $interval = 'third';}
if ($event_repeatmonth == 4){ $interval = 'fourth';}
if ($event_repeatmonth == 5){ $interval = 'fifth';}
if ($event_repeatmonth == 6){ $interval = 'last';}
$onthe_monday = $_POST['onthe_monday'];
$onthe_tuesday = $_POST['onthe_tuesday'];
$onthe_wednesday = $_POST['onthe_wednesday'];
$onthe_thursday = $_POST['onthe_thursday'];
$onthe_friday = $_POST['onthe_friday'];
$onthe_saturday = $_POST['onthe_saturday'];
$onthe_sunday = $_POST['onthe_sunday'];
if ($onthe_monday == 1){ $interval_period[] = $interval." Monday";}
if ($onthe_tuesday == 1){ $interval_period[] = $interval." Tuesday";}
if ($onthe_wednesday == 1){ $interval_period[] = $interval." Wednesday";}
if ($onthe_thursday == 1){ $interval_period[] = $interval. " Thursday";}
if ($onthe_friday == 1){ $interval_period[] = $interval. " Friday";}
if ($onthe_saturday == 1){ $interval_period[] = $interval. " Saturday";}
if ($onthe_sunday == 1){ $interval_period[] = $interval. " Sunday";}
Then it will loop through the dates within the start and finish date. It will grab the first date of the month as the reference date and find the corresponding 1st/2nd/3rd Monday/Tuesday... of the month:
//loop through to insert the array
while ($tmp <= $event_udate){
$tmpmonth = date("Y-m", strtotime($tmp))."-01";
//get proper date based on the intervals we are looking for
foreach ($interval_period AS $period){
$tmp = date("Y-m-d", strtotime($period, strtotime($tmpmonth)));
echo $period."--".$tmp."--".$tmpmonth."<br/>";
//sometimes the first interval will be before the start date, skip that
if ($tmp >= $event_sdate){
$event_datearray[] = $tmp;
}
}
//reset if reach end of the month
if ($repeat_every > 1){
//go to next month
$tmp = date('Y-m-d', strtotime('+'. $repeat_every.' month', strtotime($tmp)));
}else{
//go to next month
$tmp = date('Y-m-d', strtotime('+1 month', strtotime($tmp)));
}
}
For example, here I asked the program to get the second Wednesday and the second Sunday of the month for every 3 months between the time frame of 4/1/2015 - 8/30/2015.
The result should have returned: 4/8, 4/12, 7/8, and 7/12. However, it appears that since 4/1 and 7/1 are on Wednesday, it skipped the first week and grabbed the 3rd Wednesday instead.
My echo result looks like below, as you can see, it grabbed 4/15 as the 2nd Wednesday of 2015-04-01.... and grabbed 7/15 as the 2nd Wednesday of 2015-07-01...
second Wednesday--2015-04-15--2015-04-01
second Sunday--2015-04-12--2015-04-01
second Wednesday--2015-07-15--2015-07-01
second Sunday--2015-07-12--2015-07-01
array(4) { [0]=> string(10) "2015-04-15" [1]=> string(10) "2015-04-12" [2]=> string(10) "2015-07-15" [3]=> string(10) "2015-07-12" }
I am pretty lost here. This seems to be issue only when the first of the month matches one of the criteria. If I change it to 2nd Thursday and 2nd Sunday, then it will return properly:
second Thursday--2015-04-09--2015-04-01
second Sunday--2015-04-12--2015-04-01
second Thursday--2015-07-09--2015-07-01
second Sunday--2015-07-12--2015-07-01
array(4) { [0]=> string(10) "2015-04-09" [1]=> string(10) "2015-04-12" [2]=> string(10) "2015-07-09" [3]=> string(10) "2015-07-12" }
Does anyone have the same issue before???
I figured this out after I posted it....
It turns out the relative format in PHP date DOES NOT include the date it refers to. For example, if you are looking for the 2nd Monday of 2015-04-01, it will look for the first instance of the 2nd Monday after 2015-04-01...
So to fix this, I just check to see if the first of the month matches the criteria I am looking for, if it does, then change my reference day to the day before so it will include the original date into consideration.
Related
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'm having issues with my if statement in that both conditions give the expected result individually but when combined so that when both are true the statement breaks.
The idea is to calculate the start date before Christmas so I can show a message about this. My logic was that by ensuring the start date is after October 1st and the end date before December 24th this message will always appear at the correct time give our start and end date schedule.
My code so far is below, there is also a dump below each variable .
foreach ( $events as $event ) {
$start = date("d-m-Y", strtotime($event->EventStartDate));
// string(10) "26-10-2018"
$end = date("d-m-Y", strtotime($event->EventEndDate));
// string(10) "20-12-2018"
echo '<div id="prod-dates">';
echo '<p>Order before ' . $start . '</p>';
echo '<p>Estimated Delivery ' . $end . '</p>';
echo '</div>';
//
//EVERYTHING IS FINE UNTIL HERE...
//
$this_christmas = date('Y-12-24');
// string(10) "2018-12-24"
$startx = date("Y-m-d", strtotime($event->EventStartDate));
// string(10) "2018-10-26"
$endx = date("Y-m-d", strtotime($event->EventEndDate));
// string(10) "2018-12-20"
$note_start = date('Y-11-01');
// string(10) "2018-11-01
if ( $startx >= $note_start || $endx <= $this_christmas ) {
echo 'This is your last deadline for <span>Christmas Delivery</span>';
}
}
Any help appreciated.
Thanks
You say "October 1st" in your description but in your code $note_start is set to November (2018-11-01).
You want to ensure "the start date is after October 1st and the end date before December 24th" but you check whether either condition is true with if ( $startx >= $note_start || $endx <= $this_christmas ). Replace the or (||) with and (&&).
date function returns a string value, so your dates are compared as strings.
Better use values that return strtotime to compare your dates
Sample:
$start = strtotime($event->EventStartDate);
$end = strtotime($event->EventEndDate);
//$note_start in time format
if ( $startx >= $note_start || $endx <= $this_christmas ) {
//echo
}
Example :
00:00 22-03-2017, John Wilson
08:00 22-03-2017, Gemma Arterton
16:00 22-03-2017, Arnold Plank
00:00 22-03-2017, Timmy Brouwer
08:00 22-03-2017, John Wilson <- names repeating
16:00 22-03-2017, Gemma Arterton
I am building a shift system that generated a time and date for the next 30 days. I am trying to get different related names to getting echo'd behing the date but so far no luck.
The names are interactive meaning there could be only 1 aswell as 25 (so to speak).
This is my current call to function code:
$month = $shift->date_range(date("Y-m-d"), date('Y-m-d', strtotime("+30 days")), "+" . $shift_facility['facility_shift_duration'] . " hours", "Y/m/d H:i:s", $user_per_facility);
And this is my function itself:
public function date_range($first, $last, $step = '+1 day', $output_format = 'd/m/Y', $users)
{
$dates = array();
$current = strtotime($first);
$last = strtotime($last);
while ($current <= $last) {
// Add date and time to array $dates
$dates[] = date($output_format, $current) . ';' . $users[0]["user_id"];
//check if we are still in range between the two dates
$current = strtotime($step, $current);
}
return $dates;
}
$users contains all the user data e.g:
array(2) {
[0]=>
array(5) {
["user_id"]=>
string(1) "3"
["user_alias"]=>
string(7) "ND12345"
["user_facility"]=>
string(2) "29"
["user_phone"]=>
string(5) "12345"
["user_name"]=>
string(9) "Jan steen"
}
[1]=>
array(5) {
["user_id"]=>
string(1) "7"
["user_alias"]=>
string(7) "ND68596"
["user_facility"]=>
string(2) "29"
["user_phone"]=>
string(11) "31115648597"
["user_name"]=>
string(8) "John Doe"
}
}
$users[0]["user_id"];
Only outputs the first name but I need them to alternate (look at the first example).
Does anyone have any idea that could point me in the right direction or help me out a bit?
Thanks in advance ^^
EDIT: More details
My function call is in a foreach:
foreach ($facility->getfacilities() as $shift_facility) {
$user_per_facility = $user->getusersbyFacilityId($shift_facility['facility_id']);
if (isset($user_per_facility[0]["user_id"])) {
//if shift count > dan db results generate new day
$month = $shift->date_range(date("Y-m-d"), date('Y-m-d', strtotime("+30 days")),
"+" . $shift_facility['facility_shift_duration'] . " hours", "Y/m/d H:i:s", $user_per_facility);
}
}
You're sending an entire array of $users to the date_range() method, where you should only be sending one entity of that array (e.g. a single user) to that method.
You're calling $users[0] from within that method, which will always reference the first (zeroth) element from that array, not the specific one you're looking for.
The issue starts from this block:
if (isset($user_per_facility[0]["user_id"])) {
//if shift count > dan db results generate new day
$month = $shift->date_range(date("Y-m-d"), date('Y-m-d', strtotime("+30 days")),
"+" . $shift_facility['facility_shift_duration'] . " hours", "Y/m/d H:i:s", $user_per_facility);
}
That only checks if the user_id is set in the first array element. You need to loop through the users array as well:
foreach ($facility->getfacilities() as $shift_facility) {
$user_per_facility = $user->getusersbyFacilityId($shift_facility['facility_id']);
// Make sure $users_per_facility can be iterated over
if (count($users_per_facility) > 0 && is_array($users_per_facility))
foreach ($users_per_facility as $u) {
if (isset($u["user_id"])) {
//if shift count > dan db results generate new day
$month = $shift->date_range(date("Y-m-d"), date('Y-m-d', strtotime("+30 days")),
"+" . $shift_facility['facility_shift_duration'] . " hours", "Y/m/d H:i:s", $u);
}
}
}
}
Then modify your date_range() method to only take a single user element, not the entire array. For clarification, I changed $users to $user because there is only one "user" being sent to that method:
public function date_range($first, $last, $step = '+1 day', $output_format = 'd/m/Y', $user) {
$dates = array();
$current = strtotime($first);
$last = strtotime($last);
while ($current <= $last) {
// Add date and time to array $dates
$dates[] = date($output_format, $current) . ';' . $user["user_id"];
//check if we are still in range between the two dates
$current = strtotime($step, $current);
}
return $dates;
}
I would also suggest that you re-order the argument list for the date_range method. I'd suggest putting $user first:
public function date_range($user, $first, $last, $step = '+1 day', $output_format = 'd/m/Y')
Also, it's good convention to give your methods (functions) names with a verb, since they "do" something. In this case, make_date_range. Since I can see this is in a class (having the public keyword), it's a good habit to make your method names camelCase, e.g. makeDateRange(), while keeping your variables with underscores. Not required by any means, but it's good practice.
Hope this helps.
In case people stumble on the same issue:
I fixed this issue by creating an variable $i and if it exceeded the size of var $users had it set back to value 0.
(aswell as counting up for each succesfull row)
$i = 0;
if ($i > count($users)-1) {
$i = 0;
}
// Add date and time to array $dates
$dates[] = date($output_format, $current) . ';' . $users[$i]["user_id"];
//check if we are still in range between the two dates
$current = strtotime($step, $current);
$i++;
I am building multi-calendar, I have a horizontal looking interface:
I am trying to run the days of the week S,M,T,W,T,F,S
throughout the whole month instead of just the first 7 as in the picture.
the function which draw the calendar:
//our case "SUN"
if(AC_START_DAY=="sun"){
for($k=0; $k<7; $k++){
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li class="cal_weekday"> '.$weekday.'</li>';
}
}
//If we chose Monday as start week.
else{
if ($first_week_day == 0) $first_week_day =7;
for($k=1; $k<=7; $k++){
if($k==7) $weekday = mb_substr($lang["day_0"][0],0,1,'UTF-8');
else $weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li title="'.$lang["day_".$k.""].'"> '.$weekday.'</li>';
}
}
The lang file:
$lang["day_0"] = "Sunday";
$lang["day_1"] = "Monday";
$lang["day_2"] = "Tuesday";
$lang["day_3"] = "Wednesday";
$lang["day_4"] = "Thursday";
$lang["day_5"] = "Friday";
$lang["day_6"] = "Saturday";
Already defined
$month=sprintf("%02s",$month);
// define vars
$today_timestamp = mktime(0,0,0,date('m'),date('d'),date('Y')); # current timestamp - used to check if date is in past
$this_month = getDate(mktime(0, 0, 0, $month, 1, $year)); # convert month to timestamp
$first_week_day = $this_month["wday"]; # define first weekday (0-6)
$days_in_this_month = cal_days_in_month(CAL_GREGORIAN,$month,$year); # define number of days in week
$day_counter_tot = 0; # count total number of days showin INCLUDING previous and next months - use to get 6th row of dates
Looks like the $lang["day_".$k.""] is just counting the days from 0 to 6.. how can i make is loop untill the end of the month?
NOTE: I tried increasing the $k<7 just more empty blue boxes appear.
Use the loop to the 30/31 day.
And then change this line
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
to
$weekday = mb_substr($lang["day_".$k%7.""],0,1,'UTF-8');
This should give you the day 0 for every sunday.
0 % 7 = 0 (sunday)
1 % 7 = 1 (monday)
...
7 % 7 = 0 (sunday again)
8 % 7 = 1 (monday again)
You can use this code to generate all the days of the current month.
for ($date = strtotime(date('Y-m-01')); $date < strtotime(date('Y-m-t')); $date = strtotime("+1 day", $date)) {
echo date("l-d", $date)."<br>";
}
Will print all the days of the current month as follows.
Thursday-01
Friday-02
Saturday-03
Sunday-04
Monday-05
Tuesday-06
Wednesday-07
Thursday-08
Friday-09
Saturday-10
Sunday-11
Monday-12
Tuesday-13
Wednesday-14
Thursday-15
Friday-16
Saturday-17
Sunday-18
Monday-19
Tuesday-20
Wednesday-21
Thursday-22
Friday-23
Saturday-24
Sunday-25
Monday-26
Tuesday-27
Wednesday-28
Thursday-29
Friday-30
Looks like you almost got it right.
You only need to slightly modify your code to make it work the way you want it to.
You should just change your code to:
$number_of_days_in_the_future = 42; // Here you can put in the number of days for which you want to display the corresponding letter, and based on your screenshot that is 42
//our case "SUN"
if(AC_START_DAY=="sun"){
for($i=0; $i<$number_of_days_in_the_future; $i++){
$k = $i % 7;
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li class="cal_weekday"> '.$weekday.'</li>';
}
}
//If we chose Monday as start week.
else{
if ($first_week_day == 0) $first_week_day =7;
for($i=1; $i<=$number_of_days_in_the_future; $i++){
$k = $i % 7;
if($k==7) $weekday = mb_substr($lang["day_0"][0],0,1,'UTF-8');
else $weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li title="'.$lang["day_".$k.""].'"> '.$weekday.'</li>';
}
}
Please note that I only tried to fix your code so it works as you expect it to.
There's probably a more elegant solution but I don't really know your full code so I would just be guessing if I tried to offer you another approach.
I hope this will help you.
Cheers
This question already has answers here:
How to check if a date is in a given range?
(10 answers)
Finding whether time is in a defined range [duplicate]
(5 answers)
Closed 8 years ago.
I've been working at this code off and on for the past few days and can't figure it out.
What I need to do is return from a function either a 0 or 1 depending on if the current time is within the times set by a user. If the time and date is within a 4 value array set by the user, then return 1, if not, return 0. The user can set multiple arrays for multiple periods of times.
I've been trying to work with this code for a while:
functions.php:
function determineWoE($woe) {
$curDayWeek = date('N');
$curTime = date('H:i');
$amountWoE = count($woe['WoEDayTimes']); // Determine how many WoE times we have.
if ( $amountWoE == 0 ) {
return 0; // There are no WoE's set! WoE can't be on!
}
for ( $i=0; $i < $amountWoE; $i++ ) {
if ( $woe['WoEDayTimes'][$i][0] == $curDayWeek && $woe['WoEDayTimes'][$i][2] == $curDayWeek ) { // Check the day of the week.
if ( $woe['WoEDayTimes'][$i][1] >= $curTime && $woe['WoEDayTimes'][$i][3] <= $curTime ) { // Check current time of day.
// WoE is active
return 1;
}
else {
// WoE is not active
return 0;
}
}
else {
// WoE is not active
return 0;
}
}
}
And...where the user sets as many periods of time for this feature that they want:
$woe = array( // Configuration options for WoE and times.
// -- WoE days and times --
// First parameter: Starding day 1=Monday / 2=Tuesday / 3=Wednesday / 4=Thursday / 5=Friday / 6=Saturday / 7=Sunday
// Second parameter: Starting hour in 24-hr format.
// Third paramter: Ending day (possible value is same or different as starting day).
// Fourth (final) parameter: Ending hour in 24-hr format.
'WoEDayTimes' => array(
array(6, '18:00', 6, '19:00'), // Example: Starts Saturday 6:00 PM and ends Saturday 7:00 PM
array(3, '14:00', 3, '15:00') // Example: Starts Wednesday 2:00 PM and ends Wednesday 3:00 PM
),
);
But, no matter what I do...the function determineWoE always returns 0.
Am I needing a foreach in the function instead of a for? How do I get determineWoE to return 1 if the time is within the user settable times?
Tried changing the for to a foreach:
foreach ( $woe['WoEDayTimes'] as $i ) {
And now I get error:
Warning: Illegal offset type in /var/www/jemstuff.com/htdocs/ero/functions.php on line 76
...which I have no idea why I would be getting that error. Line 76 is:
if ( $woe['WoEDayTimes'][$i][0] == $curDayWeek && $woe['WoEDayTimes'][$i][2] == $curDayWeek ) { // Check the day of the week.
In functions.php
var_dump($woe)
array(2) { ["WhoOnline"]=> string(2) "no" ["WoEDayTimes"]=> array(2) { [0]=> array(4) { [0]=> int(6) [1]=> string(5) "18:00" [2]=> int(6) [3]=> string(5) "19:00" } [1]=> array(4) { [0]=> int(3) [1]=> string(5) "14:00" [2]=> int(3) [3]=> string(5) "15:00" } } }
Thanks for any help you can provide to me. :)
A couple minor points:
A foreach loop and a for loop would both work fine, but you might find the foreach more covenient, since you wouldn't have to count() the days/times to check for.
You should return boolean true or false instead of 1 or 0.
I'm not sure why you're getting that error, but the bigger problem I see is how you compare the times. You cast the string times to numeric types, and that won't convert entirely like you think it will. For example...
"14:00" < "14:59"
...Would be false, because it casts both strings to 14. Thus, the first string actually equals the second.
You might be better off converting the strings to Unix Timestamps (which are the seconds since 1/1/1970), and comparing those.
Here's a rough idea of how I would do it:
// Function to help get a timestamp, when only given a day and a time
// $today is the current integer day
// $str should be 'last <day>', 'next <day>', or 'today'
// $time should be a time in the form of hh:mm
function specialStrtotime($today, $day, $time) {
// An array to turn integer days into textual days
static $days = array(
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
);
// Determine if the day (this week) is in the past, future, or today
if ($day < $today) {
$str = 'last ' . $days[$day];
} else if ($day > $today) {
$str = 'next ' . $days[$day];
} else {
$str = 'today';
}
// Get the day, at 00:00
$r = strtotime($str);
// Add the amount of seconds the time represents
$time = explode(':', $time);
$r += ($time[0] * 3600) + ($time[1] * 60);
// Return the timestamp
return $;
}
// Your function, modified
function determineWoE($timeNow, $woe) {
$dayNow = (int) date('N', $timeNow);
foreach ($woe as $a) {
// Determine current day
// Determine the first timestamp
$timeFirst = specialStrtotime($dayNow, $a[0], $a[1]);
// Determine the second timestamp
$timeSecond = specialStrtotime($dayNow, $a[2], $a[3]);
// See if current time is within the two timestamps
if ($timeNow > $timeFirst && $timeNow < $timeSecond) {
return true;
}
}
return false;
}
// Example of usage
$timeNow = time();
if (determineWoE($timeNow, $woe['WoEDayTimes'])) {
echo 'Yes!';
} else {
echo 'No!';
}
Good luck!