Algorithm to calculate a date for complex occupation management - algorithm

Hello fellow Stack Overflowers,
I have a situation, where I need some help choosing the best way to make an algorithm work, the objective is to manage the occupation of a resource (Lets consider the resource A) to have multiple tasks, and where each task takes a specified amount of time to complete. At this first stage I don't want to involve multiple variables, so lets keep it the simple way, lets consider he only has a schedule of the working days.
For example:
1 - We have 1 resource, resource A
2 - Resource A works from 8 am to 4 pm, monday to friday, to keep it simple by now, he doesn't have lunch for now, so, 8 hours of work a day.
3 - Resource A has 5 tasks to complete, to avoid complexity at this level, lets supose each one will take exactly 10 hours to complete.
4 - Resource A will start working on this tasks at 2018-05-16 exactly at 2 pm.
Problem:
Now, all I need to know is the correct finish date for all the 5 tasks, but considering all the previous limitations.
In this case, he has 6 working days and additionaly 2 hours of the 7th day.
The expected result that I want would be: 2018-05-24 (at 4 pm).
Implementation:
I thought about 2 options, and would like to have feedback on this options, or other options that I might not be considering.
Algorithm 1
1 - Create a list of "slots", where each "slot" would represent 1 hour, for x days.
2 - Cross this list of slots with the hour schedule of the resource, to remove all the slots where the resource isn't here. This would return a list with the slots that he can actually work.
3 - Occupy the remaining slots with the tasks that I have for him.
4 - Finnaly, check the date/hour of the last occupied slot.
Disadvantage: I think this might be an overkill solution, considering that I don't want to consider his occupation for the future, all I want is to know when will the tasks be completed.
Algorithm 2
1 - Add the task hours (50 hours) to the starting date, getting the expectedFinishDate. (Would get expectedFinishDate = 2018-05-18 (at 4 pm))
2 - Cross the hours, between starting date and expectedFinishDate with the schedule, to get the quantity of hours that he won't work. (would basically get the unavailable hours, 16 hours a day, would result in remainingHoursForCalc = 32 hours).
3 - calculate new expectedFinishDate with the unavailable hours, would add this 32 hours to the previous 2018-05-18 (at 4 pm).
4 - Repeat point 2 and 3 with new expectedFinishDate untill remainingHoursForCalc = 0.
Disadvantage: This would result in a recursive method or in a very weird while loop, again, I think this might be overkill for calculation of a simple date.
What would you suggest? Is there any other option that I might not be considering that would make this simpler? Or you think there is a way to improve any of this 2 algorithms to make it work?

Improved version:
import java.util.Calendar;
import java.util.Date;
public class Main {
public static void main(String args[]) throws Exception
{
Date d=new Date();
System.out.println(d);
d.setMinutes(0);
d.setSeconds(0);
d.setHours(13);
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(Calendar.YEAR, 2018);
c.set(Calendar.MONTH, Calendar.MAY);
c.set(Calendar.DAY_OF_MONTH, 17);
//c.add(Calendar.HOUR, -24-5);
d=c.getTime();
//int workHours=11;
int hoursArray[] = {1,2,3,4,5, 10,11,12, 19,20, 40};
for(int workHours : hoursArray)
{
try
{
Date end=getEndOfTask(d, workHours);
System.out.println("a task starting at "+d+" and lasting "+workHours
+ " hours will end at " +end);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
public static Date getEndOfTask(Date startOfTask, int workingHours) throws Exception
{
int totalHours=0;//including non-working hours
//startOfTask +totalHours =endOfTask
int startHour=startOfTask.getHours();
if(startHour<8 || startHour>16)
throw new Exception("a task cannot start outside the working hours interval");
System.out.println("startHour="+startHour);
int startDayOfWeek=startOfTask.getDay();//start date's day of week; Wednesday=3
System.out.println("startDayOfWeek="+startDayOfWeek);
if(startDayOfWeek==6 || startDayOfWeek==0)
throw new Exception("a task cannot start on Saturdays on Sundays");
int remainingHoursUntilDayEnd=16-startHour;
System.out.println("remainingHoursUntilDayEnd="+remainingHoursUntilDayEnd);
/*some discussion here: if task starts at 12:30, we have 3h30min
* until the end of the program; however, getHours() will return 12, which
* substracted from 16 will give 4h. It will work fine if task starts at 12:00,
* or, generally, at the begining of the hour; let's assume a task will start at HH:00*/
int remainingDaysUntilWeekEnd=5-startDayOfWeek;
System.out.println("remainingDaysUntilWeekEnd="+remainingDaysUntilWeekEnd);
int completeWorkDays = (workingHours-remainingHoursUntilDayEnd)/8;
System.out.println("completeWorkDays="+completeWorkDays);
//excluding both the start day, and the end day, if they are not fully occupied by the task
int workingHoursLastDay=(workingHours-remainingHoursUntilDayEnd)%8;
System.out.println("workingHoursLastDay="+workingHoursLastDay);
/* workingHours=remainingHoursUntilDayEnd+(8*completeWorkDays)+workingHoursLastDay */
int numberOfWeekends=(int)Math.ceil( (completeWorkDays-remainingDaysUntilWeekEnd)/5.0 );
if((completeWorkDays-remainingDaysUntilWeekEnd)%5==0)
{
if(workingHoursLastDay>0)
{
numberOfWeekends++;
}
}
System.out.println("numberOfWeekends="+numberOfWeekends);
totalHours+=(int)Math.min(remainingHoursUntilDayEnd, workingHours);//covers the case
//when task lasts 1 or 2 hours, and we have maybe 4h until end of day; that's why i use Math.min
if(completeWorkDays>0 || workingHoursLastDay>0)
{
totalHours+=8;//the hours of the current day between 16:00 and 24:00
//it might be the case that completeWorkDays is 0, yet the task spans up to tommorrow
//so we still have to add these 8h
}
if(completeWorkDays>0)//redundant if, because 24*0=0
{
totalHours+=24*completeWorkDays;//for every 8 working h, we have a total of 24 h that have
//to be added to the date
}
if(workingHoursLastDay>0)
{
totalHours+=8;//the hours between 00.00 AM and 8 AM
totalHours+=workingHoursLastDay;
}
if(numberOfWeekends>0)
{
totalHours+=48*numberOfWeekends;//every weekend between start and end dates means two days
}
System.out.println("totalHours="+totalHours);
Calendar calendar=Calendar.getInstance();
calendar.setTime(startOfTask);
calendar.add(Calendar.HOUR, totalHours);
return calendar.getTime();
}
}
You may adjust the hoursArray[], or d.setHours along with c.set(Calendar.DAY_OF_MONTH, to test various start dates along with various task lengths.
There is still a bug , due to the addition of the 8 hours between 16:00 and 24:00:
a task starting at Thu May 17 13:00:00 EEST 2018 and lasting 11 hours will end at Sat May 19 00:00:00 EEST 2018.
I've kept a lot of print statements, they are useful for debugging purposes.
Here is the terminology explained:

I agree that algorithm 1 is overkill.
I think I would make sure I had the conditions right: hours per day (8), working days (Mon, Tue, Wed, Thu, Fri). Would then divide the hours required (5 * 10 = 50) by the hours per day so I know a minimum of how many working days are needed (50 / 8 = 6). Slightly more advanced, divide by hours per week first (50 / 40 = 1 week). Count working days from the start date to get a first shot at the end date. There was probably a remainder from the division, so use this to determine whether the tasks can end on this day or run into the next working day.

Related

time difference between two date removing closing time

my company has numbers of shops around all the locations. They raised a request for delivering the item to their shop which they can sell . We wanted to understand how much time the company takes to deliver the item in minutes.However, we don't want to add the time in our elapsed time when the shop is closed i.e.
lets consider shop opening and closing time are
now elapsed time
When I deduct complain time and resolution time then I get calculatable elasped time in minutes but I need Required elapsed time in minutes so in the first case out of 2090 minutes those minutes are deducated when shop was closed. I need to write an oracle query to calcualted the required elapsed time in minutes which is in green.
help what query we can write.
One formula to get the net time is as follows:
For every day involved add up the opening times. For your first example this is two days 2021-01-11 and 2021-01-12 with 13 daily opening hours (09:00 - 22:00). That makes 26 hours.
If the first day starts after the store opens, subtract the difference. 10:12 - 09:00 = 1:12 = 72 minutes.
If the last day ends before the store closes, subtract the difference. 22:00 - 21:02 = 0:58 = 58 minutes.
Oracle doesn't have a TIME datatype, so I assume you are using Oracle's datetime data type they call DATE to store the opening and closing time and we must ignore the date part. And you are probably using the DATE type for the complain_time and the resolution_time, too.
In below query I convert the time parts to minutes right away, so the calculations get a tad more readable later.
with s as
(
select
shop,
extract(hour from opening_time) * 60 + extract(minute from opening_time) as opening_minute,
extract(hour from closing_time) * 60 + extract(minute from closing_time) as closing_minute
from shops
)
, r as
(
select
request, shop, complain_time, resolution_time,
trunc(complain_time) as complain_day,
trunc(resolution_time) as resolution_day,
extract(hour from complain_time) * 60 + extract(minute from complain_time) as complain_minute,
extract(hour from resolution_time) * 60 + extract(minute from resolution_time) as resolution_minute
from requests
)
select
r.request, r.shop, r.complain_time, r.resolution_time,
(r.resolution_day - r.complain_day + 1) * 60
- case when r.complain_minute > s.opening_minute) then r.complain_minute - s.opening_minute else 0 end
- case when r.resolution_minute < s.opening_minute) then s.closing_minute - r.resolution_minute else 0 end
as net_duration_in_minutes
from r
join s on s.shop = r.shop
order by r.request;

Get the most recent "Monday 05:30" Unix time in Go?

Is there a Time library function or another good way to get the most recent weekday, hour, minute combination in Unix time? For example, given:
day: Monday
hour: 11
minute: 30
And it's currently Tuesday, I want the Unix time for yesterday at 11:30am. Some languages / libraries have support for thing but I cannot find something in Go that would make this easy. Any advice?
If you look at the definition of Weekday, you'll see that Sunday is 0, Monday is 1, etc. So to get to the last Monday, you have to do go back today-Monday, and move back to 11:30 that day. However, this doesn't work if it is already Monday and before 11:30, so you need to check for that:
now:=time.Now()
dayOffset:=now.Weekday()-time.Monday
targetDate:=now.AddDate(0,0,-int(dayOffset))
targetDate=time.Date(targetDate.Year(),targetDate.Month(),targetDate.Day(),11,30,0,0,targetDate.Location())
if targetDate.After(now) {
targetDate=targetDate.AddDate(0,0,-7)
}
My attempt at a simple and intuitive solution. See code comments for explanation
package main
import (
"fmt"
"time"
)
func main() {
// The target day / hour / minute
const (
day = time.Monday
hour = 21
minute = 0
)
// The starting time to work from
startTime := time.Now()
// Create the time on that day with the desired hour and minute
t := time.Date(startTime.Year(), startTime.Month(), startTime.Day(), hour, minute, 0, 0, startTime.Location())
// As long as we're on the wrong weekday, or not before the start time, back up one day
for t.Weekday() != day || !t.Before(startTime){
t = t.AddDate(0, 0, -1)
}
// Print the time, and it's Unix timestamp.
fmt.Println(t, t.Unix())
}

Determining if a bi-weekly schedule matches a given date

I'm creating multiple Schedule objects, which have a started_at datetime which begins on Mondays.
I have Location objects which have a visit_frequency. Some of these are set to :bi_weekly, in which case I only need to see them every other week.
However, things don't always go according to plan and sometimes Locations are visited more or less often than the need to.
Right now I'm doing
Location.all.each do |location|
...
elsif location.frequency.rate == 'biweekly'
if (#schedule.start_date - location.last_visit_date) > 7
schedule_for_week location
end
The problem is, if I make a Schedule more than 7 days from now, the Location's last_visit_date will ALWAYS be > 7 days. I need to calculate if it falls into a bi-weekly rate.
Example:
Location 1 visit_frequency set to :bi_weekly
Location 1 is visited on Week 1
Week 2 Schedule Generated -- Location 1 is left out because it is within 7 days
Week 3 Schedule Generated -- Location 1 is included because it is within 7 days
Week 4 Schedule Generated -- Location 1 is included because it is within 7 days
The last line should not have happened. Location 1 should not be included because it was visited on Week 1 and scheduled for Week 3.
How can I calculate if a week is within a bi-weekly frequency succintly? I"m guessing I need to use beginning_of_week?
As I understand your question, I believe this would do it:
require 'date'
def schedule?(sched_start_date, last_visit_date)
(sched_start_date - last_visit_date) % 14 > 7
end
sched_start_date = Date.parse("2014-12-29")
#=> #<Date: 2014-12-29 ((2457021j,0s,0n),+0s,2299161j)> a Monday
schedule?(sched_start_date, Date.parse("2014-12-04")) #=> true
schedule?(sched_start_date, Date.parse("2014-12-14")) #=> false
schedule?(sched_start_date, Date.parse("2014-12-20")) #=> true
schedule?(sched_start_date, Date.parse("2014-12-23")) #=> false

Compute the number of seconds to a specific time in a specific Time Zone

I want to trigger a notification for all my users at a specific time in their time zone. I want to compute the delay the server should wait before firing the notification. I can compute the time at the users Time Zone using Time.now.in_time_zone(person.time_zone)
I can strip out the hours, minutes and seconds from that time and find out the seconds remaining to the specific time. However, I was wondering if there's a more elegant method where I could set 9:00 AM on today and tomorrow in a timezone and compare it with Time.now.in_time_zone(person.time_zone) and just find out the number of seconds using arithmetic operations in the ruby Time Class.
Or in short my question is: (was: before the downvote!)
How do I compute the number of seconds to the next 9:00 AM in New York?
What about this
next9am = Time.now.in_time_zone(person.time_zone).seconds_until_end_of_day + 3600 * 9
next9am -= 24 * 60 * 60 if Time.now.in_time_zone(person.time_zone).hour < 9
NOTIFICATION_HOUR = 9
local_time = Time.now.in_time_zone(person.time_zone)
desired_time = local_time.hour >= NOTIFICATION_HOUR ? local_time + 1.day : local_time
desired_time = Time.new(desired_time.year, desired_time.month, desired_time.day, NOTIFICATION_HOUR, 0, 0, desired_time.utc_offset)
return desired_time - local_time

NEXT_DAY in Crystal Reports

Is there anything like the Oracle "NEXT_DAY" function available in the syntax that Crystal Reports uses?
I'm trying to write a formula to output the following Monday # 9:00am if the datetime tested falls between Friday # 9:00pm and Monday # 9:00am.
So far I have
IF DAYOFWEEK ({DATETIMEFROMMYDB}) IN [7,1]
OR (DAYOFWEEK({DATETIMEFROMMYDB}) = 6 AND TIME({DATETIMEFROMMYDB}) in time(21,00,00) to time(23,59,59))
OR (DAYOFWEEK({DATETIMEFROMMYDB}) = 2 AND TIME({DATETIMEFROMMYDB}) in time(00,00,00) to time(08,59,59))
THEN ...
I know I can write seperate IF statements to do a different amount of DateAdd for each of Fri, Sat, Sun, Mon, but if I can keep it concise by lumping all of these into one I would much prefer it. I'm already going to be adding additional rules for if the datetime falls outside of business hours on the other weekdays so I want to do as much as possible to prevent this from becoming a very overgrown and ugly formula.
Since there is no CR equivalent that I know of, you can just cheat and borrow the NEXT_DAY() function from the Oracle database. You can do this by creating a SQL Expression and then entering something like:
-- SQL Expression {%NextDay}
(SELECT NEXT_DAY("MYTABLE"."MYDATETIME", 'MONDAY')
FROM DUAL)
then you could either use that directly in your formula:
IF DAYOFWEEK ({MYTABLE.MYDATETIME}) IN [7,1]
OR (DAYOFWEEK({MYTABLE.MYDATETIME}) = 6 AND TIME({MYTABLE.MYDATETIME}) in time(21,00,00) to time(23,59,59))
OR (DAYOFWEEK({MYTABLE.MYDATETIME}) = 2 AND TIME({MYTABLE.MYDATETIME) in time(00,00,00) to time(08,59,59))
THEN DateTime(date({%NextDay}),time(09,00,00))
Or, the even better way would be to just stuff ALL of the logic into the SQL Expression and do away with the formula altogether.
Considering Sunday is 1
And the first 7 is the week we want to back
7 = 1 week
14 = 2 weeks
The last Number (1) is 1 for Sunday, 2 for Monday, 3 for Tuestday
Last Sunday 1 week ago
Today - 7 + ( 7 - WEEKDAY(TODAY) )+1
Last Monday 2 weeks ago
Today - 14 + ( 7 - WEEKDAY(TODAY) )+2
So this 2 formulas give me MONDAY LAST WEEK and SUNDAY LAST WEEK.
EvaluateAfter({DATETIMEFROMMYDB}) ;
If DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday,crSaturday,crSunday,crMonday]
then
IF DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday]
AND TIME({DATETIMEFROMMYDB}) >= time(21,00,00)
then //your code here
Else if Not(DayOfWeek ({DATETIMEFROMMYDB}) In [crFriday] )
AND (TIME({DATETIMEFROMMYDB}) >= time(00,00,00) AND TIME({DATETIMEFROMMYDB}) <= time(23,59,59))
then //your code here
Else if DayOfWeek ({DATETIMEFROMMYDB})In [crMonday]
AND TIME({DATETIMEFROMMYDB}) < time(09,00,00)
then //your code here

Resources