Recursive infinite daily dynamic loop array php - php

I am trying to display a number every day in a loop. After the last element is reached it needs to get to the first element again. This needs to happen daily. I have overworked my brains out but did not managed to solve it. Function needs to return current number by day/hour/minute, like . This is what I tried till now.
<?php
function recursive_daily_deals($i = 1) {
$current_date = strtotime(date('d-m-Y h:i:s'));
$dbs_date_1 = strtotime('29-06-2017 8:20:16');
$current_hour = date('h');
var_dump($current_hour);
$products = [
451,
455,
453
];
if ($i < count($products)) {
return recursive_daily_deals($i+1);
}
}
?>
EXPECTED output
> First day - June 29 2017
> It will appear 451
> Second day - June 30 2017
> It will appear 455
> 3rd day - July 1st 2017
> It will appear 453
> 4th Day - July 2nd 2017
> It will appear 453
> And start over

First you need to know how many days have been since the starting day. To do that you just need to sub the starting timestamp from the actual timestamp :
$dbs_date_1 = strtotime('29-06-2017 8:20:16');
$actualTimestamp = time();
$elapsedSec = $dbs_date_1 - $actualTimestamp;
// we need to get days from seconds
$elapsedDays = $elapsedSec / (3600*24);
$elapsedDays = floor($elapsedDays);
So when you have how many days have been since the starting day. We use floor() instead of round() because if the script runs after the half of the day it will return the number of days +1.
With this number of days we can have the number of cycles already done by dividing the number of elapsed days by the number of items in our array :
$nbItems = count($products);
$cycleCount = $elapsedDays / $nbItems;
$finishedCycles = floor($cycleCount);
We store the number of finished cycles by flooring the number of cycles. Then we just have to sub the days it took to complete those cycles from the elapsed days and we will get the position of the index.
$completeDays = $finishedCycles * $nbItems;
$actualPosition = $elapsedDays - $completeDays;
return $products[$actualPosition];

While this is a simplified version of the code originally posted, I believe it contains the kind of logic that the OP seeks, as follows:
<?php
$products = [
451,
455,
453
];
$modfactor = count($products);
$days = null;
$weekdays = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"];
for ($i=0, $max = 7; $i < $max; $i++) {
$days[$i] = $i % $modfactor;
}
foreach ($weekdays as $dex => $wday) {
echo "$wday, ";
echo $products[ $days[$dex] ], "\n";
}
See demo
Update: See demo here which makes use of array_map() and gets the current product ID, too.
While the loop is ever present, it is not infinite. If the number of products changes, then the modfactor changes, too. What stays constant are the days of the week. What makes the code work is taking advantage of a modulo operation.

Related

Calculating A Cycle number using dates for a 28 day repeating cycle

I have a job that runs every 28 days. and I want to assign it a cycle number based on a starting reference date.
e.g
1st cycle is 01/27/22. and that cycle number would be 2201.
subsequently I want to calculate the cycle number based on the current date. but for each year there could be either 13 or 14 cycles.
I've managed to figure out the number of cycles since the reference date to figure out the latest cycle date (see below)
const REF_ZERO_DATE = '01/27/2022';
const REF_ZERO_CYCLE_YEAR = "22";
const REF_ZERO_CYCLE_NUM = "01";
$today = new \DateTime("2023/12/29");
echo ("Today = ".$today->format("Y/m/d")."\n");
$ref_zero = new \DateTime(self::REF_ZERO_DATE);
echo ("ref_zero = ".$ref_zero->format("Y/m/d")."\n");
$number_of_days_since_ref_zero = $today->diff($ref_zero)->format("%a");
echo ("Number of days since ref zero = ".$number_of_days_since_ref_zero."\n");
$number_of_cycles_since_ref_zero = floor($number_of_days_since_ref_zero/28);
echo ("Number of cycles since ref zero = ".$number_of_cycles_since_ref_zero."\n");
$interval = 'P' . $number_of_cycles_since_ref_zero*28 . 'D';
echo ("Interval = ".$interval);
$date_of_lastest_cycle = date_add($ref_zero,new \DateInterval($interval));
echo ("last Cycle Date = ".$date_of_lastest_cycle->format("Y/m/d")."\n");
But my math for the cycle adjustment is missing coping with 12 or 13 cycle in a specific year.
It is not explicitly stated whether the cycle of the previous year continues into the next or not.
The scenario in which the cycles can overlap between years is more complicated, so this is assumed.
The interval count code was extracted to the following function:
function calculateIntervalCount($startDate, $endDate, $interval) {
$start = new \DateTime($startDate);
$end = new \DateTime($endDate);
$interval = new \DateInterval($interval);
$periodDays = intval($end->diff($start)->format('%a'));
$intervalDays = intval($interval->format('%d'));
return floor($periodDays / $intervalDays);
}
There are two cases when calculating the interval count of a particular year:
year of start and end are the same year
year of end is after year of start
In the first case the interval count is the same as the interval count of the whole period.
In the second case the interval count of a particular year can be calculated from the difference between the interval counts of the whole period and the period before the end year.
The following function returns the cycle number:
function calculateCycleNumber($startDate, $endDate, $interval) {
$totalCycles = calculateIntervalCount($startDate,$endDate,$interval);
$startYear = intval((new \DateTime($startDate))->format('Y'));
$endYear = intval((new \DateTime($endDate))->format('Y'));
if($startYear < $endYear) {
$endOfLastYearDate = (new \DateTime($endDate))->modify('last day of December last year')->format('Y-m-d');
$cyclesSinceEndOfLastYear = calculateIntervalCount($endOfLastYearDate, $endDate, $interval);
$yearCycle = $totalCycles - $cyclesSinceEndOfLastYear + 1;
} else {
$yearCycle = $totalCycles;
}
$yearCode = substr($endYear,-2);
$yearCycleCode = sprintf('%02d', $yearCycle);
return $yearCode . $yearCycleCode;
}
A cycle number of 2314 was obtained with the inputs provided.
echo calculateCycleNumber('01/27/2022','2023/12/29','P28D');
Note that 14 is possible in case of overlapping cycles.
You can use timestamp, where you add 28 days each time so you get the next date and so on.
Get the next timestamp
$next_date = strtotime('+28 day', $timestamp);
Convert to readable date
echo date('m/d/Y', $next_date);

Time attendance system how to mach a user with a spesific shift taking into consideration entrance time? Laravel // PHP // Carbon

A time attendance system can have many shifts.
For instance:
Shift a) 08:00 - 16:00,
Shift b) 16:00 - 00:00,
Shift c) 00:00 - 08:00,
A user starts working at 07:55 what is the best way to match this user with the correct shift which is shift a?
Keep in mind that the time attendance system may have many shifts much closer together, for instance a
shift that starts at 8:00 and a shift that starts at 9:00.
Important info:
What i have done is a foreach loop that compares all starting times of the shifts (in our example 09:00, 16:00, 00:00) with the time that user started working. In our example 7:55.
The one that is closer to the users start working time is the correct shift.
This looks like its ok but in reality its not. The reason is that when time is round 00:00:00 and since times of shifts do not have a date, when the comparison is 23:59:59 and 00:00:01 i get 86400 secs instead of just 2 secs.
Additional you never know which date is greater than the other, because a user may come earlier for work or late.
So any ideas must take these into consideration.
Thanks for efforts
I've updated the answer based on the comment, but there is not enough information in the question to show you how to query your database and convert your shifts into the array I'm using in this example.
This codeblock is reference, not code to use. This is the array of start times you need to convert your database table to.
$shift_starts = [
// 1 represents the ID of the shift in your database.
1 => [
// Shift ID 1 starts at midnight, hour 0, minute 0
[0, 0],
],
// 2 represents the ID of the shift in your database.
2 => [
// Shift ID 2 starts at 8am, hour 8, minute 0
[8, 0],
],
// 3 represents the ID of the shift in your database.
3 => [
// Shift ID 3 starts at 4pm, hour 16, minute 0
[16, 0],
],
];
Create a function, something like this. Again, I don't know how you are querying your database, nor the schema. I just know how you are storing the start times:
// Psudeo Code!!! Study and write your own function that returns
// the array as defined above.
function get_shift_start_array() {
$shift_starts = [];
$result = mysqli_query($db, "SELECT * FROM shifts ORDER BY start_time");
while ($row = mysqli_fetch_row($result)) {
// If the start_time is formatted h:m:s, then make it so you can get hours
// and minutes into their own variables:
$arr = explode(':', $row['start_time']);
$hour = $arr[0];
$minute = $arr[1];
$shift_starts[$row->id] = [$hour, $minute];
}
return $shift_starts;
}
Now that we have a way to get your shift data into a format we can code around, this function will take a unix timestamp and return the database ID of the shift. (Notice this function calls the function above)
Read the comments and study the PHP functions you might not understand.
/**
* Get the shift ID for a specific time.
*
* #param int $punchin_time Unix timestamp Default is the current time.
* #return int The shift id (the array key from $shift_starts)
*/
function findShift($punchin_time = null): int
{
if ($punchin_time === null) {
$punchin_time = time();
}
// Call the psudo code function to get an array of shift start times keyed by shift id.
$shift_starts = get_shift_start_array();
// Set $day to the unix timestamp of midnight yesterday.
$day = strtotime(date('Y-m-d', $punchin_time - 86400));
// We'll be checking the difference between punchin time and the shift time.
// $last_diff will be used to compare the diff of the current shift to the last shift.
// Initialize this with an arbitrarily high value beyond the 3 days we're looking at.
$last_diff = 86400 * 5; //
$last_index = null;
// Loop over 3 days starting with yesterday to accommodate punchin times before midnight.
// Return the shift ID when we find the smallest difference between punchin time and shift start.
for ($i = 0; $i <= 3; $i++) {
// Get the month, day, and year numbers for the day we are iterating on.
// We will use these in our calls to mktime()
$m = date('n', $day);
$d = date('j', $day);
$y = date('y', $day);
// Loop over each shift.
foreach ($shift_starts as $index => $start) {
// Create a unix timestamp of the shift start time.
// This is the date and time the shift starts based on the day iteration.
$time = mktime($start[0], $start[1], 0, $m, $d, $y);
// Get the difference in seconds between this shift start time and the punchin time.
$diff = abs($punchin_time - $time);
// $diff should be getting smaller as we get closer to the actual shift.
if ($diff > $last_diff) {
// If $diff got bigger than the last one, we've past the shift.
// Return the index of the last shift.
return $last_index;
}
$last_index = $index;
$last_diff = $diff;
}
$day = strtotime('+1 day', $day);
}
// Return null if no shift found.
return null;
}
Now that the functions are defined, you just need to call the last one to convert specific time, to a shift.
$punchin_time = mktime(15, 55, 0, 4, 15, 2020);
$shift_id = findShift($punchin_time);
Alternatively, don't pass a time in and the current time will be used.
$shift_id = findShift($punchin_time);
mktime
strtotime()
DateTime::getTimestamp()

Complex date format

I am trying to come up with the most efficient and best way to accomplish this somewhat of a complex situation. I know that I could build this solution using probably around 5 if else statements, maybe more - however there must be a better way to accomplish what I want to.
So here's what I am trying to do. I have an events page on my website, and what I want to do is display the dates in a minimalistic way when possible. What I mean is the following:
Say I have 3 dates: May 5, May 6, May 7. I want to display it as: May 5 - 7.
However, there will be situations where the dates may be: May 5, May 7. In this case I would like to display it as: May 5 & 7.
However, there may also be situations where the dates may be: May 25, June 2. In this case I would like to display it as: May 25 & June 2.
However! There also may be situations where the dates may be: May 25, May 26, June 2. In this case it should display as: May 25 - 26 & June 2
Of course, there could just be a single date as well. But one other thing, it could be possible that there could be more than 3 dates as well, so it would be nice if it could work regardless of how many dates there are (IE loop through an array).
I know that we are suppose to make an attempt and show some code to debug, however I don't even know where to start with this, if this is too much for someone to put together - just giving me an idea of how to do something like this efficiently would be a huge help.
Thanks
//input data: sorted list of dates
$dates = array('May 5','May 6','May 7','May 30','Jun 2','Jun 3','Dec 11','Dec 12','Dec 14');
array_push($dates,false); //add an extra value so the last range gets printed
//initialize min & previous date as first date
$min_date = array_shift($dates);
$prev_date = $min_date;
$counter = 0; //keep count of # of days between min and max
$formatted_dates = array();
foreach($dates as $date) {
//if the difference in number of days between current date and min date
//is greater than counted number of days then we capture the current range
//and start a new one by resetting $min_date to $date and $counter to 0
if(!$date || ($counter + 1) < diff_in_days($min_date,$date)) {
if($counter == 0) { //format for 1 date
$formatted_dates[] = $min_date;
}
elseif($counter == 1) { //format for 2 dates
$formatted_dates[] = "$min_date & $prev_date";
}
elseif($counter > 1) { //format for > 2 dates
$formatted_dates[] = "$min_date - $prev_date";
}
$counter = 0;
$min_date = $date;
}
else {
$counter++;
}
$prev_date = $date;
}
//may also want to verify that neither formatted date contains an '&'
//so you don't end up with "May 11 & May 12 & June 1 & June 2" which may be confusing
if(count($formatted_dates) == 2) {
print implode(' & ',$formatted_dates);
}
else {
print implode("\n",$formatted_dates);
}
function diff_in_days($day1,$day2) {
$datetime1 = new DateTime($day1);
$datetime2 = new DateTime($day2);
$interval = $datetime1->diff($datetime2);
$ret = (int) $interval->format('%a');
return $ret;
}
Output
May 5 - May 7
May 30
Jun 2 & Jun 3
Dec 11 & Dec 12
Dec 14

Calculating different bands of overtime in php

This is my first time posting here so I'm sorry if I get something wrong. I'm trying to calculate how many hours overtime a worker has worked based on when they signed in. The problem is that we have different bands of overtime:
If the worker works between 5 and 7 then it's 25% extra per hour
If they worked between 7pm and 10pm then its 50% extran for each hour
If the worker works between 10 and 12 then it's 75% extra
If the worker works between 12am and 7am is 100% more
I need to count how many hours they worked at each of the overtime bands
$number_of_25_percent_hours=0;
$number_of_50_percent_hours=0;
$number_of_75_percent_hours=0;
$number_of_100_percent_hours=0;
$clockInTime=$arr['4'];
$clockOutTime=$arr['5'];
$startingPieces=explode(':',$clockInTime);
$startingHour=$startingPieces[0];
$finishingPieces=explode(':',$clockInTime);
$finishingHour=$finishingPieces[0];
//Regular hours are between 7am and and 5pm
//If the worker works between 5 and 7 then it's 25% extra per hour
if(($startingHour<=5)&&($finishingHour>=6)){$number_of_25_percent_hours++;}
if(($startingHour<=6)&&($finishingHour>=7)){$number_of_25_percent_hours++;}
The problem with using the lines above is that it does not work if for example they worked an hour from 6:30 to 7:30.
I'm interested in finding other ways to do this.
you need to store the data more exactly. From your script it looks as if you were only saving the starting hour - which propably is a full number (1,2,3,4 whatsoever)
You script however needs a exact time representation. There are surely many ways to do this but for the sake of a better Script (and as you will propably be able to use some of these more exact values later on) I'd recommend you to store it as a UNIX Timestamp, then get the hour of the Timestamp :
$startingHour = date('H' $timeStampStored)
and check if it's in any of your "bonus" segments. If the user started working at 6:30, the value will hold 6.
This code is completely off the top of my head, untested etc. It's intended as a suggestion of one method you might use to solve the problem, not as a robust example of working code. It uses integers instead of dates, relies on array data being entered in order etc, and probably wouldn't even run.
The basic idea is to set up the scales for each level of overtime multiplier, as well as the hours for non-overtime pay in an array, then loop through that array checking how many hours of each level of overtime have been worked between the inputted times, meanwhile keeping track of a total billable hours value.
$PayMultipliers = array();
$PayMultipliers[0] = array(17,19,1.25);
$PayMultipliers[1] = array(19,22,1.5);
$PayMultipliers[2] = array(22,24,1.75);
$PayMultipliers[3] = array(0,7,1.5);
$PayMultipliers[4] = array(7, 17, 1);
$Start = 3;
$End = 11;
$TotalHours = 0;
for($i = 0; $i <= count($PayMultipliers); $i++)
{
if($Start > $PayMultipliers[$i][0] && $Start < $PayMultipliers[$i][1])
{
$TotalHours += ($PayMultipliers[$i][1] - $Start) * $PayMultipliers[$i][2];
$Start = $PayMultipliers[$i][1];
}
}
echo $TotalHours;
If you want to calculate from 6:30 to 7:30 you'll have to caclulate in minutes, not hours. You can convert the hours and minutes to timestamps, check each time period, and then convert the seconds back to hours.
<?php
$number_of_overtime_hours = array();
$clockInTime = "18:30:00";
$clockOutTime = "19:30:00";
$startingPieces = explode(':',$clockInTime);
$finishingPieces = explode(':',$clockOutTime);
//Timestamps
$startTimestamp = mktime($startingPieces[0],$startingPieces[1],$startingPieces[2]);
$finishTimestamp = mktime($finishingPieces[0],$finishingPieces[1],$finishingPieces[2]);
//finish after 0h
if ($finishTimestamp < $startTimestamp){
$finishTimestamp += 3600 * 24;
}
//set starting and ending points
$overtimePeriods = array(
25 => array (17,19),
50 => array (19,22),
75 => array (22,24),
100 => array (24,31)
);
$overtimeWork = array();
foreach ($overtimePeriods as $key => $val){
//create Timestamps for overtime periods
$beginTimestamp = mktime($val[0],0,0);
$endTimestamp = mktime($val[1],0,0);
//calculate hours inside the given period
$overtimeWork[$key] = (min($finishTimestamp,$endTimestamp) - max($startTimestamp,$beginTimestamp)) / 3600;
//negative values mean zero work in this period
if ($overtimeWork[$key] < 0) $overtimeWork[$key] = 0;
}
var_dump($overtimeWork);

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.

Categories