Oracle APEX Automation Schedule - oracle

Is it possible to exceute oracle apex automation on a specific day of the month(example every 28th of the month),,,once a month,,monthly? so far the settings only offer - weekly, daily,etc,
Is it possible to tweak through the Schedule Expression? (Example: FREQ=DAILY;INTERVAL=1;BYHOUR=23;BYMINUTE=0)
Thanks in advance.

You want it to fire every month, so FREQ=MONTHLY;INTERVAL=1. Then it should be on the 28th day of the month, so BYMONTHDAY=28. Hours and minutes don't really matter so just put BYHOUR=8;BYMINUTE=0 or whatever you prefer.
There is a very simple way to test this. APEX automation use the oracle scheduling calendar syntax, which can be evaluated using dbms_scheduler.evaluate_calendar_string
DECLARE
start_date TIMESTAMP;
return_date_after TIMESTAMP;
next_run_date TIMESTAMP;
l_interval VARCHAR2(500) := 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=28;BYHOUR=8;BYMINUTE=0';
BEGIN
start_date := current_timestamp;
return_date_after := start_date;
FOR i IN 1..5
LOOP
dbms_scheduler.evaluate_calendar_string
(
l_interval,
start_date,
return_date_after,
next_run_date
);
dbms_output.put_line('next_run_date: ' || TO_CHAR(next_run_date,'fmDay, DD-MON-YYYY HH24:MI SS'));
return_date_after := next_run_date;
END LOOP;
END;
/
next_run_date: Sunday, 28-NOV-2021 8:0 59
next_run_date: Tuesday, 28-DEC-2021 8:0 59
next_run_date: Friday, 28-JAN-2022 8:0 59
next_run_date: Monday, 28-FEB-2022 8:0 59
next_run_date: Monday, 28-MAR-2022 8:0 59

Related

How to understand given sysdate is Date or Timestamp

I have a procedure.It takes date parameter with type Date.What i want to know is the other users send sysdate to my procedure.Which format they send sysdate to my procedure?
For example:
01/02/2021 or 01/02/2020 00:00:00(timestamp)
Does my procedure accepts all sending formats?Maybe Date type converts sending formats this style 01/02/2020.I am not sure.
I mean that does my Date parameter accepts all date formats because I want to use date in my procedure without seconds or minutes.
My procedure is
DECLARE
var_P_DATE DATE; (for example : 01/01/2021)
BEGIN
SELECT last_day(var_P_DATE) INTO v_last_day FROM DUAL; (31/01/2021)
if v_last_day = var_P_DATE (it returns false because no second or minutes)
.....
END;
I used DBMS.OUTPUT. I think Date type converts just like this 01/02/2021 and i do not get any error but i am not sure.
Your procedure will only ever receive a date, because that is the data type of the formal parameter. When the procedure is called the caller can supply a date, or something that can be implicitly converted to a date (though they shouldn't; implicit conversions are generally a bad thing, particularly from strings).
The date data type includes time components. If you are being passed a date with a non-midnight time that you want to ignore, such as sysdate, you can use the trunc() function, with it's default 'DD' format; and you don't need to select from dual:
v_last_day := last_day(trunc(var_P_DATE));
If the caller passes in systimestamp then that will still be implcitly converted to a date by the time you see it - which means it loses any fractional seconds and time zone information, but retains hours, minutes and seconds.
Dates and timestamps do not have have any inherent human-readable format. A date can be displayed using various formats - see the documentation - either explicitly with to_char() and a format model, or implicitly using your session settings.
When you do
dbms_output.put_line(var_P_DATE);
you are doing an implicit conversion of the date value to a string, using the session's NLS_DATE_FORMAT setting. So, different users might see that in different formats. You have no control over that. If you want to see a specific format then specify that, e.g.:
dbms_output.put_line(to_char(var_P_DATE, 'YYYY-MM-DD'));
You also have no control over whether the caller sees that output - it's down to the application/client and its settings. It looks like you are probably only using it for debugging the comparison issue though, so that probably doesn't matter here.
So as a demonstration:
declare
var_P_DATE date := sysdate;
v_last_day date;
begin
v_last_day := last_day(var_P_DATE);
dbms_output.put_line(to_char(v_last_day, 'YYYY-MM-DD HH24:MI:SS'));
v_last_day := last_day(trunc(var_P_DATE));
dbms_output.put_line(to_char(v_last_day, 'YYYY-MM-DD HH24:MI:SS'));
end;
/
2021-02-28 09:59:02
2021-02-28 00:00:00
db<>fiddle demo
Date type in Oracle has hours, minutes and seconds, timestamp has fractions:
SQL> declare
2 vDate date := sysdate;
3 vTimeStamp timestamp := systimestamp;
4 begin
5 dbms_output.put_line('Date: ' || vDate);
6 dbms_output.put_line('Timestamp: ' || vTimestamp);
7 end;
8 /
Date: 2021-02-18 09:49:32
Timestamp: 18-FEB-21 09.49.32.015953 AM
If you want to use just the date part, with no time, of a date variable, use something like trunc(vDate):
SQL> declare
2 vDate date := sysdate;
3 vTimeStamp timestamp := systimestamp;
4 begin
5 dbms_output.put_line('Date: ' || vDate);
6 dbms_output.put_line('Date truncated: ' || trunc(vDate));
7 dbms_output.put_line('Timestamp: ' || vTimestamp);
8 end;
9 /
Date: 2021-02-18 09:51:51
Date truncated: 2021-02-18 00:00:00
Timestamp: 18-FEB-21 09.51.51.024384 AM
An example of how comparison works on date variables:
SQL> declare
2 vDate1 date;
3 vDate2 date;
4 begin
5 vDate1 := sysdate;
6 dbms_lock.sleep(5); /* wait 5 seconds */
7 vDate2 := sysdate;
8 --
9 if vDate1 = vDate2 then
10 dbms_output.put_line('Equal');
11 else
12 dbms_output.put_line('NOT equal');
13 end if;
14 --
15 if trunc(vDate1) = trunc(vDate2) then
16 dbms_output.put_line('Equal, truncated');
17 else
18 dbms_output.put_line('NOT equal, truncated');
19 end if;
20 end;
21 /
NOT equal
Equal, truncated
Just apply TRUNC() function and use that variable of type DATE within the procedure after defining the data type of var_P_DATE as TIMESTAMP
CREATE OR REPLACE PROCEDURE myproc( var_P_DATE TIMESTAMP) AS
dt DATE := TRUNC(var_P_DATE) ;
BEGIN
...
...
END;
/
If you mean DBMS_OUTPUT.PUT_LINE by DBMS.OUTPUT, then that's completely irrelevant with your current conversion, that's just used to display result to the console as a string.

cannot get year-month interval

I am learning PL-SQL and the exercise is to find the year-month interval difference between two dates. I wrote the following code:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
-- ym := '10-2';
ym := t3-t1;
DBMS_OUTPUT.PUT_LINE(ym);
END;
I would expect the ym variable to give '01-0' (1 year difference), but I get an error:
Error report -
ORA-06550: line 9, column 9:
PLS-00382: expression is of wrong type
ORA-06550: line 9, column 3:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I am really confused why this is happening, I tried changing the precision of the YEAR(_), but that doesn't help.
If ym is of datatype INTERVAL DAY(2) to SECOND(2), I get correct result. If I replace ym to ym := '10-2'; it also works. But with ym INTERVAL YEAR(2) to MONTH it is not working :(
I found in the psoug.org examples that you could use this syntax:
ym := (t3-t1) year to month;
dbfiddle demo
t3-t1 does not work as the resulting value is of the INTERVAL DAY TO SECOND data type and there is no implicit cast to INTERVAL YEAR TO MONTH.
Instead, you can use NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' ); to dynamically create the correct data type:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
ym := NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' );
DBMS_OUTPUT.PUT_LINE(ym);
END;
/
outputs:
+01-00
db<>fiddle here

Calculating the difference between two timestamps

I am trying to calculate the difference between to timestamps, but the output only returns the difference in days, but not time:
DECLARE
a INTERVAL DAY(2) TO SECOND(00);
TT DATE := TO_DATE('15-Nov-2015 10:00 am', 'dd-mon-yyyy hh:mi am');
TS DATE := TO_DATE('17-Nov-2015 12:12 am', 'dd-mon-yyyy hh:mi am');
BEGIN
--Compute interval and assign to an INTERVAL DAY TO SECOND variable
a := TO_TIMESTAMP(TS,'dd-Mon-yyyy hh:mi am')
- TO_TIMESTAMP(TT,'dd-Mon-yyyy hh:mi am');
DBMS_OUTPUT.PUT_LINE(a);
END;
This returns
+02 00:00:00
Use the following code
$start_date = new DateTime('2015-11-15 10:00:00');
$since_start = $start_date->diff(new DateTime('2015-11-17 12:12:25'));
echo $since_start->days.' days total<br>';
echo $since_start->y.' years<br>';
echo $since_start->m.' months<br>';
echo $since_start->d.' days<br>';
echo $since_start->h.' hours<br>';
echo $since_start->i.' minutes<br>';
echo $since_start->s.' seconds<br>';
This code will output
The above code will output:
2 days total
0 years
0 months
2 days
2 hours
12 minutes
25 seconds
You can further calculate the time difference in minutes and seconds
DECLARE
a INTERVAL DAY(2) TO SECOND(0);
BEGIN
--Compute interval and assign to an INTERVAL DAY TO SECOND variable
a := TO_TIMESTAMP('17:00',' hh24:mi ')
- TO_TIMESTAMP('08:00',' hh24:mi ');
-- - TO_TIMESTAMP('20-SEP-2015 10:59 pm','dd-Mon-yyyy hh:mi am');
DBMS_OUTPUT.PUT_LINE(SUBSTR(a, 5, 6));
END;
This is the correct solution! Thanks for the attempted help!

date + 7 working days

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

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