I need some PL/SQL help. I am trying to calculate a due date for a given task, given that the max time is 8 business hours and lunch from 12pm-1pm is not included in this time. For example, If a task comes in at 8am, it is due 5pm. If a task comes in at 11am, it is due 11am next day. I thought this would be fairly easy but I am new to PL/SQL and have just started. My thought process was to use sysdate but that is not going to work. Please help and apologies for not posting code, but I have not got anything that would help.
Asked
Active
Viewed 834 times
2
-
If a task comes in at 0730, is it due at 1530 or 0730 next day? – TommCatt Apr 11 '15 at 02:06
-
Sorry, I should have specified, but this is where it gets tricky and needlessly complicated in my opinion. 1: Tasks can come in at any time of a given day between 0-23 hours. 2: Tasks will only be process in working hours (8-5) - 1 hour for lunch(12-1) 4: Tasks must be processed in 8 business hours or they are late. 5: If a task comes in at 8am, it would be due at 5pm (8-12, 1-4) 6: If a task comes in at 10am, it would be due at 10am the following day (10-12, 1-5, 8-10) Thanks for your help and let me know if I need to provide more detail – Apr 11 '15 at 23:41
2 Answers
2
Messing around with this, the following should be a good starting point for you, pls tweak as you see fit.
It takes a starting date and number of work hours, and returns the due date. Note that it rounds to the nearest hour (so if start date/time is today at 3:45pm, then it uses 4pm as the actual start date/time):
create or replace function get_due_date(i_start_date in date default sysdate, i_hours in number default 8)
return date
as
l_dte date;
l_hours number;
begin
if (i_hours < 1 OR i_hours > 24) then
raise_application_error(-20001, 'Hours must be between 1 and 24');
end if;
l_hours := i_hours + 1;
select dte
into l_dte
from
(
select dte, to_char(dte, 'HH24') hr, levl, row_number() over(order by levl) rnum
from (
-- rounding to nearest hour
select round(i_start_date, 'HH24') + ((level-1)/24) as dte, level levl
from dual
connect by level <= 72
)
where to_char(dte, 'HH24') between '08' and '17'
-- skip lunch hour
and NOT(to_char(dte, 'HH24') = '12')
) x
where rnum = l_hours;
return l_dte;
end;
For example:
select sysdate, get_due_date(sysdate, 8) from dual;
Output:
4/10/2015 3:45:21 PM 4/11/2015 3:00:00 PM

tbone
- 15,107
- 3
- 33
- 40
-
Thanks a bunch, this looks like a great start that I can play around with. Just curious, why is it rounding? I will need to be specific to the minute and also, as the working hours will always be the same, I can get away without passing that. Thanks a lot. – Apr 11 '15 at 23:52
1
You can use the Oracle SCHEDULER SCHEDULE
. By default this is used for SCHEDULER JOBS
, however I don't see any reason not using it for other purpose.
It would be this one:
CREATE OR REPLACE FUNCTION GetDueDate(start_date IN TIMESTAMP DEFAULT SYSTIMESTAMP, duration IN INTEGER DEFAULT 8) RETURN TIMESTAMP AS
next_run_date TIMESTAMP := start_date;
BEGIN
FOR i IN 1..duration LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=HOURLY;INTERVAL=1;BYHOUR=7,8,9,10,11,13,14,15,16,17;BYDAY=MON,TUE,WED,THU,FRI', NULL, next_run_date, next_run_date);
END LOOP;
RETURN next_run_date;
END;
Some tests:
BEGIN
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-10 16:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-11 07:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-13 07:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-13 09:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-13 11:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-04-13 17:20:00'), 'Day yyyy-mm-dd hh24:mi') );
END;
Monday 2015-04-13 14:20
Monday 2015-04-13 15:20
Monday 2015-04-13 16:20
Tuesday 2015-04-14 07:20
Tuesday 2015-04-14 09:20
Tuesday 2015-04-14 15:20
You can make it even more sophisticated and consider holidays, e.g.
BEGIN
DBMS_SCHEDULER.CREATE_SCHEDULE('NEW_YEARS_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0101');
DBMS_SCHEDULER.CREATE_SCHEDULE('MARTIN_LUTHER_KING_DAY', repeat_interval => 'FREQ=YEARLY;BYMONTH=JAN;BYDAY=3 FRI', comments => 'Third Monday of January');
DBMS_SCHEDULER.CREATE_SCHEDULE('WASHINGTONS_BIRTHDAY', repeat_interval => 'FREQ=YEARLY;BYMONTH=FEB;BYDAY=3 MON', comments => 'Third Monday of February');
DBMS_SCHEDULER.CREATE_SCHEDULE('MEMORIAL_DAY', repeat_interval => 'FREQ=YEARLY;BYMONTH=MAY;BYDAY=-1 MON', comments => 'Last Monday of May');
DBMS_SCHEDULER.CREATE_SCHEDULE('INDEPENDENCE_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0704');
DBMS_SCHEDULER.CREATE_SCHEDULE('CHRISTMAS_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=1225');
DBMS_SCHEDULER.CREATE_SCHEDULE('SPRING_BREAK', repeat_interval => 'FREQ=YEARLY;BYDATE=0301+SPAN:7D');
END;
CREATE OR REPLACE FUNCTION GetDueDate(start_date IN TIMESTAMP DEFAULT SYSTIMESTAMP, duration IN INTEGER DEFAULT 8) RETURN TIMESTAMP AS
next_run_date TIMESTAMP := start_date;
BEGIN
FOR i IN 1..duration LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=HOURLY;INTERVAL=1;BYHOUR=7,8,9,10,11,13,14,15,16,17;BYDAY=MON,TUE,WED,THU,FRI; EXCLUDE=NEW_YEARS_DAY,MARTIN_LUTHER_KING_DAY,WASHINGTONS_BIRTHDAY,MEMORIAL_DAY,INDEPENDENCE_DAY,CHRISTMAS_DAY,SPRING_BREAK', NULL, next_run_date, next_run_date);
END LOOP;
RETURN next_run_date;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-12-24 07:20:00'), 'Day yyyy-mm-dd hh24:mi') );
DBMS_OUTPUT.PUT_LINE ( TO_CHAR(GetDueDate(TIMESTAMP '2015-12-24 16:20:00'), 'Day yyyy-mm-dd hh24:mi') );
END;
Thursday 2015-12-24 16:20
Monday 2015-12-28 14:20
See calendaring syntax here: Calendaring Syntax

Wernfried Domscheit
- 54,457
- 9
- 76
- 110
-
Wow, you actually helped me on another project I was thinking about, so thanks for this info. I cannot use the scheduler for this task though, anything not considered the norm does not go down well with these folks. Much appreciated though. – Apr 11 '15 at 23:54