I'm creating a database that I want to use to schedule tasks for random 2-hour windows each day within a given date and time range. For instance, Task1 may run from 1 Jan to 12 Jan anytime between the hours of 5am and 5pm. Therefore, the database will schedule the task for a random 2-hour window (to start no earlier than 5am and stop no later than 5pm) on each of those days. It may throw out something like this for Task1:
Date Start_Time Stop_Time
01 Jan 06:32 08:32
02 Jan 14:24 16:24
03 Jan 08:05 10:05
04 Jan 12:17 14:17
05 Jan 11:23 13:23
06 Jan 12:53 14:53
07 Jan 09:11 11:11
08 Jan 05:27 05:27
09 Jan 12:46 14:46
In addition to the conditions for each Task (must be scheduled each day within a date range and within the given time range), no more than 2 tasks can overlap at any given point on any day, and no task can run into the next day (therefore they cannot start after 10pm).
So far, my database does this, albeit slowly, so I'm wanting to know if the method I'm using is the most efficient.
For tables, I have one (tblWindows) which basically just consists of a column called WindowStart populated with each minute of the day, starting at 00:00 and ending at 23:59. Literally, 1440 records -- one record for each minute of the day.
I have another table (tblTaskConfigs) where I have the configs for each task to be scheduled against. This is where I specify the start/stop dates and start/stop times for each Task to be scheduled.
Finally, my tblSchdTasks table keeps track of when tasks are scheduled.
Regarding the operation, it goes something like this:
Open tblTaskConfigs recordset. For each TaskConfig record:
1) Save the following into variables:
- StartDate
- StopDate
- StartTime
- StopTime
2) For each date the task is to be scheduled on:
A) Using DCount on tblSchdTasks, check if that task has already been scheduled for that date:
- Yes: Skip to the next date
- No:
I) Open a query recordset (qryAvailWin) that contains available windows for that date that fall within the TaskConfig's start/stop times (times from tblWindows in which there are no more than 1 task that overlaps those times).
II) Choose a random record from qryAvailWin to determine the start time of the Task to be scheduled.
III) Open a tblSchdTasks recordset and create a new record for the task and it's randomly-selected time for that day.
So, I'm opening up the tblTaskConfigs recordset, and looping through each record. For each of those records, for each day the Task is to be scheduled for, I'm opening up 2 more recordsets (qryAvailWin & tblSchdTasks) to check available times and to actually schedule the task.
For 1 task that lasts 56 days, this operation takes about 108-113 seconds. I suspect it's because it's opening and closing a total of 113 recordsets (1 + (56 x 2)). Additionally, qryAvailWin has three parameters (CurrDate, StartTime, and StopTime) that I need to set before each time it's opened so that it only shows available windows that are relevant to that date and that TaskConfig.
Can you think of a more efficient way of doing something like this?
Had some time to spare, so I coded my approach to the problem.
Since you're not giving me much to work with as to specifics, you will have to fill in a lot of blanks, such as variable names and WHERE criteria.
First, 2 helper functions for picking random numbers and dates:
Public Function RandomRange(Lower As Double, Upper As Double, Optional IncludeDecimals As Boolean = True) As Double
'Returns a random number between Lower and Upper, either with or without decimals
RandomRange = Lower + (Rnd() * (Upper - Lower + IIf(IncludeDecimals, 0, 1)))
If IncludeDecimals = True Then Exit Function
RandomRange = Int(RandomRange)
End Function
Public Function RandomTime(Lower As Date, Upper As Date) As Date
'Returns a random time between Lower and Upper
Dim randomTimeDbl As Double
randomTimeDbl = CDbl(Lower) + (Rnd() * CDbl(Upper - Lower))
RandomTime = CDate(randomTimeDbl)
End Function
Then, the actual logic to schedule tasks for an interval of days:
Public Function ScheduleTasksBetweenDays(StartDate As Date, EndDate As Date, TaskIdentifier As Variant)
'Open the scheduled tasks table
Dim rsSchdTasks As Recordset
Set rsSchdTasks = CurrentDb.OpenRecordset("SELECT * FROM tblSchdTasks SORT BY Date ASC, Start_Time ASC")
'Second recordset for tasks only on a specific date
Dim rsFiltered As Recordset
'Create a collection to save valid start and end dates
Dim startEndDates As Collection
'And create a collection for overlapping times
Dim overlappingTimes As Collection
'Create two arrays to save start-end date pairs
Dim startEndDatePair(2) As Date
Dim previousTaskStartEndTime(2) As Date
'Create an int to hold random values
Dim randomInt As Integer
'Make an iterator for a while loop, and one for the for loop
Dim dateIterator As Date
Dim i As Integer
dateIterator = StartDate
'Iterate through all the dates
While dateIterator < EndDate
'Set rsFiltered to all records of that date
rsSchdTasks.Filter = "Date = #" & Format(dateIterator, "yyyy\/mm\/dd") & "#"
'Open the filtered recordset
Set rsFiltered = rsSchdTasks.OpenRecordset()
'Use the already instantiated recordset instead of a DCount, if the task is already set, next iteration
rsFiltered.FindFirst "TaskIdentifier = " & TaskIdentifier
If Not rsFiltered.NoMatch Then GoTo NextDate
'Start at the first already set taks, iterate through them
rsFiltered.MoveFirst
'Clean up the overlappingTimes collection
Set overlappingTimes = New Collection
'Initialize the first task as the previous one
If Not rsFiltered.EOF Then
previousTaskStartEndTime(1) = rsFiltered.Fields("Start_Time")
previousTaskStartEndTime(2) = rsFiltered.Fields("End_Time")
rsFiltered.MoveNext
End If
Do While Not rsFiltered.EOF
'If the start time of the next task is less than the end time
If previousTaskStartEndTime(2) > rsFiltered.Fields("Start_Time") Then
'The overlapping segment is from end of previous to start of new
startEndDatePair(1) = rsFiltered.Fields("Start_Time")
startEndDatePair(2) = previousTaskStartEndTime(2)
'Add those that interval to the collection of invalid intervals
overlappingTimes.Add startEndDatePair
End If
previousTaskStartEndTime(1) = rsFiltered.Fields("Start_Time")
previousTaskStartEndTime(2) = rsFiltered.Fields("End_Time")
rsFiltered.MoveNext
Loop
'Now, we have a collection of all times that can't overlap with the task
'Lets set the next collection of possible start times
'First possible start time is 5 AM
startEndDatePair(1) = #5:00:00 AM#
'Open up a blank collection of possible valid intervals
Set startEndDates = New Collection
'Next step seems familliar, loop through the invalid times to calculate the valid ones
For i = 1 To overlappingTimes.Count
'If the start of the next task is more than 2 hours away than the possible start time, we can plan it before this task
If startEndDatePair(1) + #2:00:00 AM# < overlappingTimes(i)(1) Then
'Set the maximum start time to 2 hours before the next task
startEndDatePair(2) = overlappingTimes(i)(1) - #2:00:00 AM#
'Add it to the collection of possible times
startEndDates.Add startEndDatePair
End If
'Set the next possible valid interval to be behind this task
startEndDatePair(1) = overlappingTimes(i)(2)
Next i
'All unchanged since last edit here
'Check if we can put our task behind the last one, if so, last possible start time is 10 PM
If startEndDatePair(1) < #10:00:00 PM# Then
'Set it's maximum end time
startEndDatePair(2) = #10:00:00 PM#
startEndDates.Add startEndDatePair
End If
'Now, we have an array with all possible intervals where our task can start at
If startEndDates.Count = 0 Then
'Apparently, this day is already full and we can't plan the task. Do something adequate for that scenario
GoTo NextDate
End If
'pick a random interval from our intervals array
randomInt = RandomRange(1, startEndDates.Count, False)
'Assign our array to that interval
startEndDatePair(1) = startEndDates(randomInt)(1)
startEndDatePair(2) = startEndDates(randomInt)(1)
'update the schdTasks table
rsSchdTasks.Edit
rsSchdTasks.AddNew
rsSchdTasks.Fields("Date") = dateIterator
'Pick a random start time within our interval
rsSchdTasks.Fields("Start_Time") = RandomTime(startEndDatePair(1), startEndDatePair(2))
'End = 2 hours later
rsSchdTasks.Fields("End_Time") = rsSchdTasks.Fields("Start_Time") + #2:00:00 AM#
rsSchdTasks.Fields("TaskIdentifier") = TaskIdentifier
rsSchdTasks.Update
NextDate:
dateIterator = dateIterator + 1
Loop
End Function
As you may note, the improvements include:
You only open up tblSchdTasks once. Per day you filter the open table and put that into a new recordset
You no longer use DCount, but instead check the already opened table
Instead of just picking random times and checking if they are valid, you make a collection of all possible start and end intervals, then pick one of those intervals, then pick a timepoint within that interval
There is no longer any need for the (imho weird) tblWindows
You now include seconds in your start and end dates
You no longer need qryAvailWin, which was searching a giant table and probably taking up a lot of your time
You've gone from opening 113 recordsets and executing 56 DCounts (which are just as heavy as recordsets) to opening 1 recordset, and filtering and searching it 56 times
And a possible disadvantage:
If the intervals are really unequal in size (one is long, another is short) they both have an equal chance to be picked for the tasks. This isn't too hard to fix, but well, I've put in enough effort I think
Related
I am trying to create an attendance system that calculates tardies based on certain criteria. First off, each student member has a row dedicated to them in the spreadsheet. (Ie, student1's attendance is found on row 3, student2's attendance is found on row 4, etc.). Each student has personal information in the first few columns. Then each student has an attendance record for the meeting. All attendance information comes in pairs with a sign in time and a sign out time.
A student receives a tardy if they show up after 6:30 PM. They also receive a tardy if they are present for less than 2 hours. So I wrote a code to calculate the number of tardies.
/**
each student has one row dedicated to their attendance. Sign in and sign out are always in pairs. I will ensure that the range
begins with range[0][0] being the first sign in time.
#param range the row for the student's attendance
#return the total tardies for each student
**/
function calculateTardies(range){
var search = range[0].length;
var tardies = 0;//initially set to 0
for(k = 0; k<search; k+2){ //go through the entire row of the student's sign in and sign out times (k+2 because they always come in pairs)
var timeIn = new Date(range[0][k]); //sign in time will always be at a K value
var date = Utilities.formatDate(timeIn, "EST", "EEE"); //get the day of week that time stamp is
var timeOut = new Date(range[0][k+1]); //sign out time is to the right of the sign in time
if(date == "Tue" || date == "Wed" || date == "Thu"){ //they only need to be there for 2 hours Tues-Thurs
var checkHour = Number(Utilities.formatDate(timeIn, "EST", "HH")); //The hour student arrived
var checkMin = Number(Utilities.formatDate(timeIn, "EST", "mm")); //The minute student arrived
var outHour = Number(Utilities.formatDate(timeOut, "EST", "HH")); //hour student left
var outHour = Number(Utilities.formatDate(timeOut, "EST", "HH")); // min student left
//If the student wasn't present for at least 2 hours, add a tardy
if((checkHour+2)*60+checkMin < (outHour*60)+outMin){
tardies = tardies+1;
}
//if the student left before 6:30 PM, add a tardy
if((checkHour*60)+checkMin > 1830){ //11100 is 6:30 PM in minutes 6PM=> 18 hours * 60 min = 1080 minutes + 30 additional minutes
tardies = tardies+1;
}
}
}
return tardies;
}
To check if they are there for two hours I had to change the time stamps to minutes. I found that I couldn't simply compare the times as is. The same applies for why I changed the arrival time to minutes before comparing it to 6:30 PM.
I am running into an error that states exceeded maximum execution time (line 0). Any advice on how I can change the code or fix this error would be greatly appreciated.
I am running into an error that states exceeded maximum execution time
You have an endless loop that makes your code run until it exceeds maximum execution time
This endless loop is created by the definition
for(k = 0; k<search; k+2)
depending on what you want to do, the correct syntax is either
for(k = 0; k<search; k=k+2)
(if you want to increase k by 2 after each iteration) or
for(k = 0; k<search; k=k++)
if you want to increase k by 1 after each iteration.
I found that I couldn't simply compare the times as is.
Dates are proceeded in Apps Script the same way like in Javascript.
The documentation provides many useful methods and examples of how to wokr with dates.
In a nutshell:
If you want to calculate the difference between two timestamps - the easiest way might be to convert both dates to ms with getTime(), substract them and convert the difference back to hours (1 h = 1000*60*60 ms)
If you want to know the weekday of a date, you need to use getDay()
For working with dates it is recommendable that they are formatted as such in your spreadsheet, rather than manually converting them from numbers inside your script.
I want to fetch next week data. My date is 2/10/2015 and today is friday. I want to fetch next week data that starts from Monday. I refered this link.
I am using codeigniter so what is the process to write the sql query in codeigniter?
First of you'll need to get the date of the next monday with
$next_monday = new DateTime('next monday');
Then get the end of the week (if you need it):
$date = $next_monday->format('Y-m-d').' 23:59:59'; //Get monday at 23:59
$end_week = strtotime("+7 day", $date); //Get sunday at 23:59
And then in your Codeigniter model, making use of Active Records:
public function nextWeekData($monday, $sunday){
$this->db->where("your_date_field >= '". $monday->format('Y-m-d H:i:s')."' AND your_date_field <= '". $sunday->format('Y-m-d H:i:s')."'");
$this->db->get('your_table');
return $this->db->result_array(); // If you want an array
// return $this->db->result(); // If you want StdClass
}
So the call from your controller should be:
$this->my_model->nextWeekData($next_monday, $end_week);
Just remove $end_week and the second clausule at the WHERE statement in the model if you dont need upper limit.
This should do it, i think. Code not tested. Important: Mind the quotes in the where.
I'm having some trouble trying to make a function that acts like vlookup but returns a range rather than a cell
The data to search through looks like this
Sometimes there is a space separating sometimes not
What I would like to do is to look up 16 from my main page and return all the values in that range.
the code I currently am using will only return the first line in a messagebox
Public Function findrulepos(target) As Range
Dim ruleStart, ruleEnd, ruleEnd2 As String
Dim RuleRange As Range
'Dim ruleEnd As Range
MaxRule = 100000
MaxRow = 100000
Set target = Sheets("main").Range("E2")
Sheets("ResRules").Select
For i = 3 To MaxRow
If CStr(ThisWorkbook.Sheets("ResRules").Range("A" & i).Value) = _
CStr(target.Value) Then
ruleStart = _
ThisWorkbook.Sheets("ResRules").Range("A" & i).Offset(0, 1).Text
Exit For
Else
End If
Next i
End Function
If we can assume that the numeric group labels in col A are sequential, then I think this will achieve what you need:
Enter the numeric label you are wanting to extract (16 in your example) into cell E1
Note that =MATCH(E1,A:A,0) gives us the row number where group 16
starts, which is the first row we want to copy. Similarly,
=MATCH(E1+1,A:A,0) gives us the row number where group 17 starts,
which is one row below the last row we want to copy. (Unfortunately
that's not true for the very last group of code, but to rectify that
you just need to add a dummy number at the very bottom of the data
in col A.)
Enter the formula =IF(ROW()+MATCH(E$1,A:A,0)-1<MATCH(E$1+1,A:A,0),INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1),"") in F1. That should copy the first value of the selected code block -- [DO] ADD TO QUEUE P in your example.
Copy F1 down as many rows as the largest code block is likely to be.
The one problem with that is that it will put 0 whenever it copies a blank row. So you have to explicitly check for that case, e.g. by changing the formula in F1 to =IF(ROW()+MATCH(E$1,A:A,0)-1<MATCH(E$1+1,A:A,0),IF(ISBLANK(INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1)),"",INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1)),"")
I've searched for hours and thought it would be simple but still have no idea.
I'm using access 2010 and have a form to enter the start time of a task. The startTime field is text so that I can use a drop down to select full hours e.g. 11:00; 12:00; 13:00.
How can I set the default value to the next full hour?
If the time is now 12:32 then the default value should be 13:00; if the time is now 14:16 the default value should be 15:00
Edited:
Correct: =TimeSerial(Hour(time())-(Minute(time())>=1);0;0)
You can use CDate() to convert the startTime string to a Date/Time value. Then use DatePart() to get the hour. Give TimeSerial() the hour plus one, and zero for the minutes and seconds. Finally use Format() to convert the resulting Date/Time value back to a string.
Here is an Immediate window session which tests the function included below.
? NextHour("12:32")
13:00
? NextHour("07:00")
08:00
Public Function NextHour(ByVal pStartTime As String) As String
Dim dteStart As Date
Dim dteNext As Date
dteStart = CDate(pStartTime)
dteNext = TimeSerial(DatePart("h", dteStart) + 1, 0, 0)
NextHour = Format(dteNext, "hh:nn")
End Function
I have a column in my MS Access table which displays Time (hh:mm).
However, when I retrieve this data using the below query, I seem to be getting the entire year along with the time.
I want to display just the time.
resultString = resultString +""Time Is : " + rs("Time");
rs.MoveNext();
Result:
Time Is : Sat Dec 30 14:00:00 UTC+0530 1899
I tried using Hour([Time]) but it doesn't seem to fit in the above statement.
Try below vb code
resultString = resultString + "Time Is : " + Format(CDate(rs("Time")), "hh:MM")
rs.MoveNext