date + 7 working days - oracle

I need to write a function that will give me a new due date for an invoice. This needs to be 12 working days after the current due date
Say the current due date is 01.Oct.2014. If I look at my calendar manually, I can see that the new date would be 17.Oct.2014 (need to exclude weekends).
However, I also have a table with Bank Holidays. This would have to be taken into consideration. So if I would have a Bank Holiday on 04.Oct.2014, the new due date should be 18.Oct.2014.
EDIT: My table with Bank Holidays would look something like this:
Year: Date: Description
2014 04.Oct.2014 Bank Holiday 1
Any help with this would be deeply appreciated, I'm stuck at this for almost a day now.
Thanks a lot in advance.
Kind regards
Gerben

Something like this should work:
DECLARE
l_date DATE := SYSDATE;
FUNCTION IS_WEEKEND(P_DATE IN DATE)
RETURN BOOLEAN
IS
l_daynum VARCHAR2(1) := to_char (P_DATE, 'D');
BEGIN
RETURN l_daynum = '6' OR l_daynum = '7';
END;
FUNCTION IS_HOLIDAY(P_DATE IN DATE)
RETURN BOOLEAN
IS
CURSOR c_exists IS
SELECT 1 FROM bank_holidays WHERE date = TRUNC(P_DATE)
;
l_count NUMBER;
BEGIN
OPEN c_exists;
l_count := c_exists%ROWCOUNT;
CLOSE c_exists;
RETURN l_count > 0;
END;
PROCEDURE ADD_WORKING_DAYS(P_DATE IN OUT DATE, P_DAYS IN NUMBER)
IS
l_workdays_added NUMBER := 0;
BEGIN
WHILE TRUE
LOOP
P_DATE := P_DATE + 1;
IF NOT IS_WEEKEND(P_DATE) AND NOT IS_HOLIDAY(P_DATE) THEN
l_workdays_added := l_workdays_added + 1;
END IF;
IF l_workdays_added = P_DAYS THEN
RETURN;
END IF;
END LOOP;
END;
BEGIN
ADD_WORKING_DAYS(l_date, 12);
END;

I ended up doing things slightly different. I have a table with all my bank holiday. I created a second table as a kind of calendar. In here, I loaded all dates in a year. I then flag it as weekend or bank holiday (2 separate columns).
I take my original due date, and add the 12 days. I then have a start and end date (v_due_date_old and v_due_date_new)
After that, I count how many days there are in my 'calendar' table, where either my flag for weekend or bank holiday is set to Yes. If v_due_date_new is on a Saturday, I add another day to my count.
I then add the new count to v_due_date_new.
As a last step, I check what day v_due_date_new is. If it is Saturday or Sunday, I add another 2 days

Related

Automatically calculate the date oracle APEX

how can the date be calculated depending on the input of a user?
That is, depending on what the user enters, 3 months will be added to the current date or 5, etc.
I tried to add a computation to the date pickers:
SUBMISSION_DATE displays only the current date (already implemented)
DATE_OF_ISSUE should be SUBMISSION_DATE +3months or +5months, depending on users input in another field.
I added as well a computation to this date with the following pl/sql function body, but it is not working.
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := :P75_SUBMISSION_DATE +6;
else
sub_date := :P75_SUBMISSION_DATE +3;
END IF;
return sub_date;
end;
The page items are treated as VARCHAR2 so you need to convert them to dates before performing arithmetic on them:
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := TO_DATE(:P75_SUBMISSION_DATE) +6;
else
sub_date := TO_DATE(:P75_SUBMISSION_DATE) +3;
END IF;
return sub_date;
end;
/
#TonyAndrews has the correct process, but as he followed your initial statement, it will not give the desired result. Adding an integer to a data add that number of days not months. What you need is the ADD_Months function; So:
DECLARE
sub_date DATE default null;
BEGIN
if :P75_THESISTYPE =1
then
sub_date := add_months(to_date(:P75_SUBMISSION_DATE),6);
else
sub_date := add_months(to_date(:P75_SUBMISSION_DATE),3);
end if
return sub_date;
end;
/
Of course the above assumes your variable's format matches your nls_data_format. Generally a dangerous assumption at best,

Checking Date in PL/SQL

I'm setting up a win auto job to accrue paid time off.
However, for personal time they only get a lump sum every year.
So I'm working on a pl/sql statement to check the date, but I can't get it to work.
I'm not sure what I'm doing wrong!!!
IF to_char(sysdate, 'MM/dd') = '01/01' THEN
PTO.personal_time := 8;
END IF;
update: to clarify. I want to check the date and if it is January first, to update the amount of personal time to 8 hours. I'm not getting any errors, but the amount of personal time isn't changing. There is no roll over and everyone gets one personal day, so i just set in on January 1st.
TABLE is a keyword and you cannot use it as a variable; however, if you replace table with the name of your variable then your code works perfectly (assuming that the variables of the appropriate names/types already exists):
DECLARE
-- declare a type which has a field names "field"
TYPE item_type IS RECORD(
field NUMBER
);
-- declare an "item" variable
item item_type;
BEGIN
-- start of your code
IF to_char(sysdate, 'MM/dd') = '04/25' THEN
item.field := 8;
END IF;
-- end of your code
DBMS_OUTPUT.PUT_LINE( item.field );
END;
/
which outputs:
8
db<>fiddle here

ORA-01847 day of month must be between 1 and last day of month error when taking the sum or count

In my database I'm having a date column which is in varchar2 format when I tried to run the below query I'm getting ORA - 01847 error.
SELECT NVL(SUM(TOTAL_QOH),0)
FROM V_STOCK_ENQUIRY
WHERE LOCATION_ID = 82
AND TO_DATE(EXPIRY_DATE, 'DD-MM-YYYY')>= TO_DATE('01-11-2017', 'DD-MM-YYYY')
AND TO_DATE(EXPIRY_DATE, 'DD-MM-YYYY')<= TO_DATE('01-11-2017', 'DD-MM-YYYY')
But when I try to run the same query but just take the TOTAL_QOH instead of the sum I'm getting it right. Could anyone help me resolve this please.
Fetching part of rows may not cause errors, but when you sum then every row is checked and error occurs. Check your data, there should be wrong values somewhere, you could use below block code for instance. But the best option is to change column type to date. Storing dates as varchars causes problems like this and it's harder to build queries etc.
declare
dt date;
begin
for r in (select rowid, expiry_date from v_stock_enquiry) loop
begin
dt := to_date(r.expiry_date, 'dd-mm-yyyy');
exception when others then
if sqlcode in (-1839, -1847) then
dbms_output.put_line('value: '||r.expiry_date||' is not correct');
dbms_output.put_line('rowid: '||r.rowid);
dbms_output.put_line(sqlerrm);
else
dbms_output.put_line(sqlcode);
dbms_output.put_line(sqlerrm);
end if;
end;
end loop;
end;

function/procedure to calculate total based on total for each month in plqsl

I need advice on how to write a stored procedure or function to calculate total based on total of each month. Lets say:
target income for Jan = 20K, then for a month: 20K * 31 (total days of January)
target income for Feb = 19K, then for a month 19K * 28 (total days of February)
I need to get total up to the given date. For instance:
date_param = 25 February 2018. Then grand total = (20K * 31) + (19K * 25)
How to write this in pl/sql?
Thank you.
Your question remains rather light on specifics, so here are my assumptions.
The daily targets are stored in a table with a structure of
(month_no number, daily_tgt number).
The targets are the same for each year.
The calculation starts from the first month of the current year.
There is no need to handle leap years.
The presented question rules out the need to handle non-working days.
This solution is a function to return the calculated total.
create or replace function get_target_sum
(p_cutoff in date)
return number
as
rv number;
begin
select sum(daily_tgt * no_of_days)
into rv
from (
select daily_tgt
, case when month_no < to_number(to_char(p_cutoff, 'MM'))
then to_number(to_char(last_day(to_date(month_no,'MM')), 'DD'))
else to_number(to_char(p_cutoff, 'DD'))
end as no_of_days
from targets
where month_no <= to_number(to_char(p_cutoff, 'MM'))
);
return rv;
end;
/
Notes
Storing the daily targets by numeric month make it easier to filter the months before the cut-off date in the WHERE clause.
Casting the month number to a date allows us to derive the last day of the month from which we get the number of days to multiply for whole months.
For the final month we just need the day from the cut-off date.
Your question is not very clear as in stating how to get the Monthly rates. I assumed few things and providing my solution as below:
1) Created a table mnthly_tgt which would store monthly rates.
2) The function will take an input date and then return the sum from past months of the year.
Table Definition:
Create table mnthly_tgt(mnth number, tgt_amt number);
insert into mnthly_tgt values(1 , 15);
insert into mnthly_tgt values(2, 20);
insert into mnthly_tgt values(3 , 12);
insert into mnthly_tgt values(4 , 10);
insert into mnthly_tgt values(5 , 11);
insert into mnthly_tgt values(6, 15);
insert into mnthly_tgt values(7, 20);
Function:
Create or replace function Ret_Tot(ipt_dt date)
return number
is
v_curr_mnth number;
v_curr_days number;
v_tot_amt number;
v_sql varchar2(400);
v_date date;
v_num_of_days number;
cntr number:=0;
begin
-- Calculating total for current month.
Select to_char(ipt_dt,'MM') col1 -- Current Month
,to_char(ipt_dt,'dd') col3 -- Days in Current Month
INTO
v_curr_mnth ,
v_curr_days
from DUAL;
-- Current Month Total
v_tot_amt: = v_curr_days * (Select tgt_amt from mnthly_tgt where mnth = v_curr_mnth);
--Calculation for previous months
For i in (Select to_char(ipt_dt,'MM') - LEVEL col1
from Dual
CONNECT BY LEVEL < to_char(col,'MM')
Loop
cntr := cntr + 1;
v_sql:='Select ipt_dt - INTERVAL '|| cntr ||' MONTH from DUAL' ;
Execute Immediate v_sql INTO V_DATE;
--calculating no. of days of previous months
v_sql:='SELECT EXTRACT(DAY FROM LAST_DAY('|| V_DATE ||')) FROM dual';
Execute Immediate v_sql INTO v_num_of_days ;
--Summing up total
v_tot_amt: = v_tot_amt + ( v_num_of_days * (Select tgt_amt from mnthly_tgt where mnth = i.col1));
END LOOP;
return v_tot_amt;
end;
PS: Nt tested.

In Oracle, how can I detect the date on which daylight savings time begins / ends?

Is there a way in Oracle to select the date on which daylight savings will switch over for my locale?
Something vaguely equivalent to this would be nice:
SELECT CHANGEOVER_DATE
FROM SOME_SYSTEM_TABLE
WHERE DATE_TYPE = 'DAYLIGHT_SAVINGS_CHANGEOVER'
AND TO_CHAR(CHANGEOVER_DATE,'YYYY') = TO_CHAR(SYSDATE,'YYYY'); -- in the current year
Edit: I was hoping for a solution that would not require changes when Congress adjusts DST laws, as they did in 2007. The posted solutions will work, though.
To improve on Leigh Riffel's answer, this is much simpler with the same logic:
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
Begin
Return NEXT_DAY(TO_DATE(to_char(p_Date,'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN');
End;
We use the following two functions to calculate the start and end dates for any given year (post 2007, US).
Function DaylightSavingTimeStart (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set the date to the 8th day of March which will effectively skip the first Sunday.
v_Date := to_date('03/08/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the second Sunday.
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
Function DaylightSavingTimeEnd (p_Date IN Date)
Return Date Is
v_Date Date;
v_LoopIndex Integer;
Begin
--Set Date to the first of November this year
v_Date := to_date('11/01/' || to_char(p_Date,'YYYY') || '02:00:00 AM','MM/DD/YYYY HH:MI:SS PM');
--Advance to the first Sunday
FOR v_LoopIndex IN 0..6 LOOP
If (RTRIM(to_char(v_Date + v_LoopIndex,'DAY')) = 'SUNDAY') Then
Return v_Date + v_LoopIndex;
End If;
END LOOP;
End;
There is probably a simpler way to do it, but these have worked for us. Of course this query doesn't know whether daylight saving time is observed for where you are. For that you will need location data.
Instead of looping to get the next sunday you can also use the next_day(date, 'SUN') function of oracle.
In the United States, Daylight Savings Time is defined as beginning on the second Sunday in March, and ending on the first Sunday in November, for the areas that observe DST, for years after 2007.
I don't think there's an easy way to get this information from Oracle, but based on the standard definition, you should be able to write a stored procedure that calculates the beginning and ending date using the Doomsday Algorithm.
Here is a way to use Oracles internal knowledge of whether a timezone observes daylight saving time or not to determine the start and end of it. Aside from the complexity and general strangeness of it, it requires two timezones to be know have identical times when daylight saving time is not in effect and different times when it is. As such it is resilient to congressional changes in when daylight saving time occurs (assuming your database is up to date with the patches), but is not resilient to regional changes effecting the timezones keyed off of. With those warnings, here is what I have.
ALTER SESSION SET time_zone='America/Phoenix';
DROP TABLE TimeDifferences;
CREATE TABLE TimeDifferences(LocalTimeZone TIMESTAMP(0) WITH LOCAL TIME ZONE);
INSERT INTO TimeDifferences
(
SELECT to_date('01/01/' || to_char(sysdate-365,'YYYY') || '12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1
FROM dual CONNECT BY rownum<=365
);
COMMIT;
ALTER SESSION SET time_zone='America/Edmonton';
SELECT LocalTimeZone-1 DaylightSavingTimeStartAndEnd
FROM
(
SELECT LocalTimeZone,
to_char(LocalTimeZone,'HH24') Hour1,
LEAD(to_char(LocalTimeZone,'HH24')) OVER (ORDER BY LocalTimeZone) Hour2
FROM TimeDifferences
)
WHERE Hour1 <> Hour2;
I told you it was strange. The code only figures out the day of the change, but could be enhanced to show the hour. Currently it returns 09-MAR-08 and 02-NOV-08. It is also sensitive to the time of year it is run, which is why I had to do the -365...+365. All in all I don't recommend this solution, but it was fun to investigate. Maybe someone else has something better.
Here's my version of the above. It's advantage is that it does not need a second 'alter session set time zone', and can be used more easily from an application.
You create the stored function, and then you simply use:
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,GetDSTDates(2012,2) DSTEnd,SessionTimeZone TZ from dual;
which will return the dst start date,dst end date, timezone for the specified year.
create or replace function GetDSTDates
(
year integer,
GetFrom integer
)
return Date
as
cursor c is
select 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24')) offset,
min(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) fromdate,
max(to_char(LocalTimeZone at time zone '+00:00','DD/MM/YYYY')) todate
from (
SELECT cast((to_date('01/01/'||to_char(year)||'12:00:00','MM/DD/YYYYHH24:MI:SS')+rownum-1) as timestamp with local time zone) LocalTimeZone
FROM dual CONNECT BY rownum<=365
)
group by 12-to_number(to_char(LocalTimeZone at time zone '+00:00','HH24'));
dstoffset integer;
offset integer;
dstfrom date;
dstto date;
begin
offset := 999;
dstoffset := -999;
for rec in c
loop
if rec.offset<offset
then
offset := rec.offset;
end if;
if rec.offset>dstoffset
then
dstoffset := rec.offset;
dstfrom := to_date(rec.fromdate,'DD/MM/YYYY');
dstto :=to_date(rec.todate,'DD/MM/YYYY');
end if;
end loop;
if (offset<999 and dstoffset>-999 and offset<>dstoffset)
then
if GetFrom=1
then
return dstfrom;
else
return dstto;
end if;
else
return null;
end if;
end;
/
ALTER SESSION SET time_zone='Asia/Jerusalem';
select GetDSTDates(2012,1) DSTStart,
GetDSTDates(2012,2) DSTEnd,
SessionTimeZone TZ from dual;
Old question but here's a new answer. Use 08-MAR for the first date since that skips the first week
--Start of DST
select next_day(to_date('08-MAR-' || to_char(sysdate, 'YYYY')), 'SUN') from dual
--End of DST
select next_day(to_date('01-NOV-' || to_char(sysdate, 'YYYY')), 'SUN') from dual

Resources