I have a requisite on which I need to convert a RFC 2445 Recurrence Pattern to Dates using PLSQL.
Example:
RRULE = FREQ=DAILY;INTERVAL=5;COUNT=10
From that rule, I need to write a table with the next 10 occurrences of that pattern. Something like the image bellow, considering start date as 1/1/2019 12:00:00 AM:
Does Oracle provides any PLSQL Package that allows me to do this? If doesn't, does anybody knows any PLSQL project initiative for this?
Ps: this is the same exactly pattern that Oracle uses on Job Schedules.
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING might be able to do this.
The syntax supported by the package seems similar to RFC 2445, but not identical. The below PL/SQL block prints out the dates based on a calendar string. There are some complications, such as parsing out the COUNT=10 to determine how many times to repeat the calculation.
declare
--Test different calendar strings and start dates.
--p_calendar_string varchar2(4000) := 'FREQ=DAILY;INTERVAL=5;';
p_calendar_string varchar2(4000) := 'FREQ=DAILY;INTERVAL=5;COUNT=10';
p_start_date date := timestamp '2019-01-01 00:00:00';
v_next_run_date date;
v_count number;
--Find the COUNT and remove it rom the calendar string, if it exists.
procedure get_and_remove_count(p_calendar_string in out varchar2, p_count out number) is
begin
if lower(p_calendar_string) like '%count%' then
p_count := to_number(regexp_substr(p_calendar_string, 'COUNT=([0-9]+)', 1, 1, null, 1));
p_calendar_string := regexp_replace(p_calendar_string, 'COUNT=[0-9]+;?');
else
p_count := 1;
end if;
end;
begin
get_and_remove_count(p_calendar_string, v_count);
--TEST
--dbms_output.put_line('String: '||p_calendar_string||', count: '||v_count);
--Start with the original date.
v_next_run_date := p_start_date-1/24/60/60;
--Loop through the COUNT and display all dates.
for i in 1 .. v_count loop
dbms_scheduler.evaluate_calendar_string
(
calendar_string => p_calendar_string,
start_date => p_start_date,
return_date_after => v_next_run_date,
next_run_date => v_next_run_date
);
dbms_output.put_line(to_char(v_next_run_date, 'mm/dd/yyyy hh:mi:ss am'));
end loop;
end;
/
Output:
01/01/2019 12:00:00 am
01/06/2019 12:00:00 am
01/11/2019 12:00:00 am
01/16/2019 12:00:00 am
01/21/2019 12:00:00 am
01/26/2019 12:00:00 am
01/31/2019 12:00:00 am
02/05/2019 12:00:00 am
02/10/2019 12:00:00 am
02/15/2019 12:00:00 am
You can write a PL/SQL function to parse the string and output a pipelined collection of dates:
Oracle Setup:
CREATE FUNCTION parseRRule(
rrule IN VARCHAR2,
start_date IN DATE
) RETURN SYS.ODCIDATELIST PIPELINED
IS
freq VARCHAR2(10) := UPPER( REGEXP_SUBSTR( rrule, '(^|;)FREQ=(MONTHLY|WEEKLY|DAILY|HOURLY)(;|$)', 1, 1, 'i', 2 ) );
inter NUMBER(4,0) := TO_NUMBER( REGEXP_SUBSTR( rrule, '(^|;)INTERVAL=(\d+)(;|$)', 1, 1, 'i', 2 ) );
cnt NUMBER(4,0) := TO_NUMBER( REGEXP_SUBSTR( rrule, '(^|;)COUNT=(\d+)(;|$)', 1, 1, 'i', 2 ) );
dt DATE := start_date;
step_ds INTERVAL DAY TO SECOND;
step_m NUMBER(3,0);
BEGIN
IF freq IS NULL OR inter IS NULL OR cnt IS NULL OR dt IS NULL THEN
RETURN;
END IF;
IF freq = 'MONTHLY' THEN
step_ds := INTERVAL '0' DAY;
step_m := inter;
ELSIF freq = 'WEEKLY' THEN
step_ds := inter * INTERVAL '7' DAY;
step_m := 0;
ELSIF freq = 'DAILY' THEN
step_ds := inter * INTERVAL '1' DAY;
step_m := 0;
ELSIF freq = 'HOURLY' THEN
step_ds := inter * INTERVAL '1' HOUR;
step_m := 0;
ELSE
NULL;
-- raise exception
END IF;
PIPE ROW ( dt );
FOR i IN 1 .. cnt - 1 LOOP
dt := ADD_MONTHS( dt + step_ds, step_m );
PIPE ROW ( dt );
END LOOP;
END;
/
Query:
SELECT *
FROM TABLE(
parseRRule(
rrule => 'FREQ=DAILY;INTERVAL=5;COUNT=10',
start_date => DATE '2019-01-01'
)
)
Output:
| COLUMN_VALUE |
| :----------- |
| 2019-01-01 |
| 2019-01-06 |
| 2019-01-11 |
| 2019-01-16 |
| 2019-01-21 |
| 2019-01-26 |
| 2019-01-31 |
| 2019-02-05 |
| 2019-02-10 |
| 2019-02-15 |
db<>fiddle here
You can achieve this using connect by query but you need to find out the way of getting frequency and count (use regexp) and use them in below query:
Select date '2019-01-01' + (level-1) * 5 as dates
From dual
Connect by level <= 10;
Cheers!!
Related
I'm trying to create a function, which returns a random TIMESTAMP between a range of timestamps.
It doesn't appear to be working ALL the time as sometimes I get a value back before the starting range and sometimes I get a value back after the ending range.
Below is my test CASE and example of a TIMESTAMP out of range.In this example the TIMESTAMP is after the ending range of TIMESTAMP '2023-01-25 12:00:00'
Can someone please explain what the problem is and show me how to fix it as I can't seem to figure this out.
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY HH24:MI:SS.FF';
CREATE OR REPLACE FUNCTION random_timestamp(
p_from IN TIMESTAMP,
p_to IN TIMESTAMP,
p_fraction IN VARCHAR2 DEFAULT 'Y'
) RETURN TIMESTAMP
IS
return_val_y TIMESTAMP := p_from + dbms_random.value () * (p_to - p_from + INTERVAL '1' DAY);
return_val_n TIMESTAMP (0) := return_val_y;
BEGIN
RETURN CASE
WHEN UPPER (SUBSTR (p_fraction, 1, 1)) = 'Y'
THEN return_val_y
ELSE return_val_N
END;
END random_timestamp;
/
SELECT random_timestamp(
TIMESTAMP '2023-01-25 09:00:00', TIMESTAMP '2023-01-25 12:00:00') as ts from dual
TS
26-JAN-2023 03:59:06.013730
You are adding 1 day:
p_from + dbms_random.value () * (p_to - p_from + INTERVAL '1' DAY);
It falls within the range of p_from to p_to plus 1 day and is doing exactly what you told it to do.
If you don't want the range to be 1 day extra then remove + INTERVAL '1' DAY
CREATE OR REPLACE FUNCTION random_timestamp(
p_from IN TIMESTAMP,
p_to IN TIMESTAMP,
p_fraction IN VARCHAR2 DEFAULT 'Y'
) RETURN TIMESTAMP
IS
return_val_y TIMESTAMP(9) := p_from + dbms_random.value() * (p_to - p_from);
return_val_n TIMESTAMP(0) := return_val_y;
BEGIN
RETURN CASE
WHEN p_fraction LIKE 'Y%' OR p_fraction LIKE 'y%'
THEN return_val_y
ELSE return_val_n
END;
END random_timestamp;
/
Then:
SELECT MIN(ts),
MAX(ts)
FROM (
SELECT random_timestamp(
TIMESTAMP '2023-01-25 09:00:00',
TIMESTAMP '2023-01-25 12:00:00'
) AS ts
FROM DUAL
CONNECT BY LEVEL <= 1e6
);
May randomly output:
MIN(TS)
MAX(TS)
25-JAN-23 09.00.00.017186000
25-JAN-23 11.59.59.999534000
And stays within the range.
fiddle
I have a date range, I am trying to take one date every week through a loop
DECLARE
start_date DATE := TO_DATE('06.01.2021', 'dd.MM.yyyy');
end_date DATE := TO_DATE('26.05.2021', 'dd.mm.yyyy');
active_date DATE;
start_number NUMBER;
end_number NUMBER;
BEGIN
start_number := TO_NUMBER(TO_CHAR(start_date, 'j'));
end_number := TO_NUMBER(TO_CHAR(end_date, 'j'));
active_date := start_date;
FOR cur_r IN start_number..end_number
LOOP
INSERT INTO test_tbl
SELECT snap_date FROM s_act
WHERE
snap_date = active_date;
active_date := TRUNC(active_date) + 7;
COMMIT;
END LOOP;
END;
When I execute this script, only one date 06.01.2021 is written to the table through all iterations.
Where am I making a mistake? How can this be fixed?
You do not need PL/SQL for this and can just use a recursive sub-query:
INSERT INTO test_tbl
WITH date_range ( start_date, end_date ) AS (
SELECT DATE '2021-01-06', DATE '2021-05-26' FROM DUAL
UNION ALL
SELECT start_date + INTERVAL '7' DAY,
end_date
FROM date_range
WHERE start_date + INTERVAL '7' DAY <= end_date
)
SELECT snap_date
FROM s_act s
WHERE EXISTS(
SELECT 1
FROM date_range r
WHERE r.start_date = s.snap_date
);
or a hierarchical query:
INSERT INTO test_tbl
SELECT snap_date
FROM s_act s
WHERE EXISTS(
WITH date_range ( start_date, end_date ) AS (
SELECT DATE '2021-01-06', DATE '2021-05-26' FROM DUAL
)
SELECT 1
FROM date_range r
WHERE r.start_date + ( LEVEL - 1 ) * INTERVAL '7' DAY = s.snap_date
CONNECT BY r.start_date + ( LEVEL - 1 ) * INTERVAL '7' DAY <= r.end_date
);
If you really want to use PL/SQL then you can make it much simpler and iterate by weeks rather than days (however, this will be much less efficient as you will have one INSERT per week and the associated context switch from PL/SQL to SQL compared to the SQL solution which is only a single INSERT for the entire operation and no context switches):
DECLARE
start_date DATE := DATE '2021-01-06';
end_date DATE := DATE '2021-05-26';
active_date DATE := start_date;
BEGIN
LOOP
EXIT WHEN active_date > end_date;
INSERT INTO test_tbl
SELECT snap_date FROM s_act
WHERE snap_date = active_date;
active_date := active_date + INTERVAL '7' DAY;
END LOOP;
END;
/
db<>fiddle here
To me, it looks as if everything is, actually, OK with code you wrote, because active_date gets its new value:
SQL> set serveroutput on;
SQL> declare
2 start_date date := to_date('06.01.2021', 'dd.MM.yyyy');
3 end_date date := to_date('26.05.2021', 'dd.mm.yyyy');
4 active_date date;
5 start_number number;
6 end_number number;
7 begin
8 start_number := to_number(to_char(start_date, 'j'));
9 end_number := to_number(to_char(end_date, 'j'));
10 active_date := start_date;
11
12 for cur_r in start_number..end_number
13 loop
14 dbms_output.put_line('Active_date = ' || to_char(active_date, 'dd.mm.yyyy'));
15 /* Commented, as I don't have your tables nor data
16 INSERT INTO test_tbl
17 SELECT snap_date
18 FROM s_act
19 WHERE snap_date = active_date;
20 */
21 active_date := trunc(active_date) + 7;
22 end loop;
23 -- move COMMIT out of the loop!
24 commit;
25 end;
26 /
Active_date = 06.01.2021
Active_date = 13.01.2021
Active_date = 20.01.2021
<snip>
Active_date = 06.09.2023
Active_date = 13.09.2023
PL/SQL procedure successfully completed.
SQL>
You said
When I execute this script, only one date 06.01.2021 is written to the table through all iterations.
This is piece of code responsible for that:
INSERT INTO test_tbl
SELECT snap_date
FROM s_act
WHERE snap_date = active_date;
I interpret it as:
s_act table contains rows only with snap_date equal to 06.01.2021, or
if it contains rows with other dates, maybe they contain a time component (hours, minutes, seconds) and where condition prevents them to be inserted. If that's so, try with
where trunc(snap_date) = active_date
and see what happens.
I have this procedure that goes PROCEDURE(monthday varchar2). It receives an varchar2 that represents the month and date concatenated always with the format of MMDD. I then want to create a DATE type variable that uses this month and day, and the year being the current year.
Like: desired_date DATE;
desired_date = ?
I'm using Oracle SQL Developer.
EXTRACT the year from SYSDATE and then combine using string concatenation with your input and use TO_DATE to convert to a date:
CREATE PROCEDURE test (
monthday IN VARCHAR2,
desired_date OUT DATE
)
IS
BEGIN
desired_date := TO_DATE( EXTRACT( YEAR FROM SYSDATE ) || monthday, 'YYYYMMDD' );
END;
/
then:
DECLARE
dt DATE;
BEGIN
test( '0101', dt );
DBMS_OUTPUT.PUT_LINE( '0101: ' || dt );
test( '1231', dt );
DBMS_OUTPUT.PUT_LINE( '1231: ' || dt );
BEGIN
test( '9876', dt );
DBMS_OUTPUT.PUT_LINE( '9876: ' || dt );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( '9876: ' || SQLERRM );
END;
END;
/
outputs:
0101: 2019-01-01 00:00:00
1231: 2019-12-31 00:00:00
9876: ORA-01843: not a valid month
db<>fiddle here
If you want to return NULL for invalid inputs then:
CREATE PROCEDURE test (
monthday IN VARCHAR2,
desired_date OUT DATE
)
IS
BEGIN
desired_date := TO_DATE( EXTRACT( YEAR FROM SYSDATE ) || monthday, 'YYYYMMDD' );
EXCEPTION
WHEN OTHERS THEN
-- In general don't catch OTHERS but in this case the only exceptions
-- are going to be from TO_DATE
desired_date := NULL;
END;
/
db<>fiddle here
Update:
You could simplify the code (as suggested by Aleksej and William Robertson) by not specifying the year value in TO_DATE and use the default which appears to be the current year; however this behaviour is not, obviously, documented in any Oracle documentation pages so I would also include inline documentation within the function so future developers reviewing your function know that you are deliberately using this behaviour:
CREATE PROCEDURE test (
monthday IN VARCHAR2,
desired_date OUT DATE
)
IS
BEGIN
-- Assumes that TO_DATE will, when not specified, default the year to the
-- current year and the time to midnight.
desired_date := TO_DATE( monthday, 'MMDD' );
END;
/
This could be a way
desired_date := to_date(monthday, 'MMDD');
According to this old post in AskTom, to_date should use the current year, if not given:
default year = current year
default month = current month
default day = 1
default hour = 0
default minute = 0
default second = 0
ops$tkyte%ORA10GR2> select sysdate, to_date( ' ', ' ' ) from dual;
SYSDATE TO_DATE('','')
-------------------- --------------------
17-aug-2012 13:41:06 01-aug-2012 00:00:00
Still unable to find this information in Oracle Docs
I am getting 'ORA-01027: bind variables not allowed for data definition'
procedure create_dates_testing (dummy_variable varchar2 default
to_char(sysdate,'YYYYMMDD')) is
begin
DECLARE
day_of_month varchar2(255) := extract(day from sysdate);
today varchar2(255) := to_char(sysdate, 'DAY');
start_date date;
next_start_date date;
BEGIN
IF today='SUNDAY' THEN
-- Select yesterday
start_date := trunc(sysdate) - interval '1' day;
next_start_date := trunc(sysdate);
ELSE IF day_of_month=3 then
-- Select the whole of last month
start_date := runc(sysdate, 'MM') - interval '1' month;
next_start_date := runc(sysdate, 'MM') - interval '1' month
END IF;
END;
execute immediate 'drop table new_customers';
execute immediate 'create table new_customers as
select id, client_name, invoice_date
from clients table
where transactiondate >= :start_date
and transactiondate < :next_start_date;';
end;
How can I resolve this error? Where am I going wrong? I need to put this procedure in a pl/sql package.
As the error says, you can't use bind variables here, so you have to concatenate:
create or replace procedure create_dates_testing
( dummy_variable varchar2 default to_char(sysdate,'YYYYMMDD') )
as
day_of_month varchar2(255) := extract(day from sysdate);
today varchar2(255) := to_char(sysdate +1, 'fmDAY', 'nls_date_language = English');
start_date date;
next_start_date date;
begin
if today = 'SUNDAY' then
-- select yesterday
start_date := trunc(sysdate) - interval '1' day;
next_start_date := trunc(sysdate);
elsif day_of_month = 3 then
-- select the whole of last month
start_date := trunc(sysdate, 'MM') - interval '1' month;
next_start_date := trunc(sysdate, 'MM') - interval '1' month;
else
return;
end if;
execute immediate 'drop table new_customers';
execute immediate 'create table new_customers as
select id, client_name, invoice_date
from clients table
where transactiondate >= date ''' || to_char(start_date,'YYYY-MM-DD') ||
''' and transactiondate < date ''' || to_char(next_start_date,'YYYY-MM-DD') ||'''';
end create_dates_testing;
Presumably there will be some more code to handle the case where it is neither Sunday nor the third of the month, or the new_customers table does not exist.
Edit: added else condition to end processing if neither of the date conditions are met.
I want to bring a string which contains a date to a single format date.
EX:
13-06-2012 to 13-JUN-12
13/06/2012 to 13-JUN-12
13-JUN-2012 to
13-JUN-12
13/jun-2012 to 13-JUN-12
...
I tried to delete all special characters and after that use a function to transform that string into a single format of date. My function return more exceptions, I don't know why...
The function:
CREATE OR REPLACE FUNCTION normalize_date (data_in IN VARCHAR2)
RETURN DATE
IS
tmp_month VARCHAR2 (3);
tmp_day VARCHAR2 (2);
tmp_year VARCHAR2 (4);
TMP_YEAR_NUMBER NUMBER;
result DATE;
BEGIN
tmp_day := SUBSTR (data_in, 1, 2);
tmp_year := SUBSTR (data_in, -4);
--if(REGEXP_LIKE(SUBSTR(data_in,3,2), '[:alpha:]')) then
if(SUBSTR(data_in,3,1) in ('a','j','i','f','m','s','o','n','d','A','J','I','F','M','S','O','N','D')) then
tmp_month := UPPER(SUBSTR (data_in, 3, 3));
else
tmp_month := SUBSTR (data_in, 3, 2);
end if;
DBMS_OUTPUT.put_line (tmp_year);
TMP_YEAR_NUMBER := TO_NUMBER (tmp_year);
IF (tmp_month = 'JAN')
THEN
tmp_month := '01';
END IF;
IF (tmp_month = 'FEB')
THEN
tmp_month := '02';
END IF;
IF (tmp_month = 'MAR')
THEN
tmp_month := '03';
END IF;
IF (tmp_month = 'APR')
THEN
tmp_month := '04';
END IF;
IF (tmp_month = 'MAY')
THEN
tmp_month := '05';
END IF;
IF (tmp_month = 'JUN')
THEN
tmp_month := '06';
END IF;
IF (tmp_month = 'JUL')
THEN
tmp_month := '07';
END IF;
IF (tmp_month = 'AUG')
THEN
tmp_month := '08';
END IF;
IF (tmp_month = 'SEP')
THEN
tmp_month := '09';
END IF;
IF (tmp_month = 'OCT')
THEN
tmp_month := '10';
END IF;
IF (tmp_month = 'NOV')
THEN
tmp_month := '11';
END IF;
IF (tmp_month = 'DEC')
THEN
tmp_month := '12';
END IF;
-- dbms_output.put_line(tmp_day || '~'||tmp_year || '~' ||tmp_month);
IF (LENGTH (tmp_day || tmp_year || tmp_month) <> 8)
THEN
result := TO_DATE ('31122999', 'DDMMYYYY');
RETURN result;
END IF;
-- dbms_output.put_line('before end');
result:=TO_DATE (tmp_day || tmp_month ||tmp_year , 'DDMMYYYY');
-- dbms_output.put_line('date result: '|| result);
RETURN result;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
result := TO_DATE ('3012299', 'DDMMYYYY');
RETURN result;
RAISE;
END normalize_date;
Usage
SELECT customer_no,
str_data_expirare,
normalize_date (str_data_expirare_trim) AS data_expirare_buletin
FROM (SELECT customer_no,
str_data_expirare,
REGEXP_REPLACE (str_data_expirare, '[^a-zA-Z0-9]+', '')
AS str_data_expirare_trim
FROM (SELECT Q1.set_act_id_1,
Q1.customer_no,
NVL (SUBSTR (set_act_id_1,
INSTR (set_act_id_1,
'+',
1,
5)
+ 1,
LENGTH (set_act_id_1)),
'NULL')
AS str_data_expirare
FROM STAGE_CORE.IFLEX_CUSTOMERS Q1
WHERE Q1.set_act_id_1 IS NOT NULL
)
);
If you have a sound idea of all the possible date formats it might be easier to use brute force:
create or replace function clean_date
( p_date_str in varchar2)
return date
is
l_dt_fmt_nt sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll
('DD-MON-YYYY', 'DD-MON-YY', 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD'
, 'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY/MM/DD', 'DD/MM/YY', 'MM/DD/YY');
return_value date;
begin
for idx in l_dt_fmt_nt.first()..l_dt_fmt_nt.last()
loop
begin
return_value := to_date(p_date_str, l_dt_fmt_nt(idx));
exit;
exception
when others then null;
end;
end loop;
if return_value is null then
raise no_data_found;
end if;
return return_value;
exception
when no_data_found then
raise_application_error(-20000, p_date_str|| ' is unknown date format');
end clean_date;
/
Be aware that modern versions of Oracle are quite forgiving with date conversion. This function handled dates in formats which aren't in the list, with some interesting consequences:
SQL> select clean_date('20160817') from dual;
CLEAN_DAT
---------
17-AUG-16
SQL> select clean_date('160817') from dual;
CLEAN_DAT
---------
16-AUG-17
SQL>
Which demonstrates the limits of automated data cleansing in the face of lax data integrity rules. The wages of sin is corrupted data.
#AlexPoole raises the matter of using the 'RR' format. This element of the date mask was introduced as a Y2K kludge. It's rather depressing that we're still discussing it almost two decades into the new Millennium.
Anyway, the issue is this. If we cast this string '161225' to a date what century does it have? Well, 'yymmdd' will give 2016-12-15. Fair enough, but what about '991225'? How likely is that the date we really want is 2099-12-15? This is where the 'RR' format comes into play. Basically it defaults the century: numbers 00-49 default to 20, 50-99 default to 19. This window was determined by the Y2K issue: in 2000 it was more likely that '98 referred to the recent past than the near future, and similar logic applied to '02. Hence the halfway point of 1950. Note this is a fixed point not a sliding window. As we move further from the year 2000 the less useful that pivot point becomes. Find out more.
Anyway, the key point is that 'RRRR' does not play nicely with other date formats: to_date('501212', 'rrrrmmdd') hurlsora-01843: not a valid month. So, use'RR'and test for it before using'YYYY'`. So my revised function (with some tidying up) looks like this:
create or replace function clean_date
( p_date_str in varchar2)
return date
is
l_dt_fmt_nt sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll
('DD-MM-RR', 'MM-DD-RR', 'RR-MM-DD', 'RR-DD-MM'
, 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD', 'YYYY-DD-MM');
return_value date;
begin
for idx in l_dt_fmt_nt.first()..l_dt_fmt_nt.last()
loop
begin
return_value := to_date(p_date_str, l_dt_fmt_nt(idx));
exit;
exception
when others then null;
end;
end loop;
if return_value is null then
raise no_data_found;
end if;
return return_value;
exception
when no_data_found then
raise_application_error(-20000, p_date_str|| ' is unknown date format');
end clean_date;
/
The key point remains: there's a limit to how smart we can make this function when it comes to interpreting dates, so make sure you lead with the best fit. If you think most of your date strings fit day-month-year put that first; you will still get some wrong casts but less that if you lead with year-month-day.
The String-to-Date Conversion Rules allow additional formatting rules (without any other modifiers being applied). (Also see this question) So:
MM also matches MON and MONTH;
MON also matches MONTH (and vice versa);
YY also matches YYYY;
RR also matches RRRR; and
The punctuation is optional.
Which means you can do:
CREATE OR REPLACE FUNCTION parse_Date_String(
in_string VARCHAR2
) RETURN DATE DETERMINISTIC
IS
BEGIN
BEGIN
RETURN TO_DATE( in_string, 'DD-MM-YY' );
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
RETURN TO_DATE( in_string, 'MM-DD-YY' );
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
BEGIN
RETURN TO_DATE( in_string, 'YY-MM-DD' );
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
RETURN NULL;
END;
/
Query:
WITH dates ( value ) AS (
SELECT '010101' FROM DUAL UNION ALL
SELECT '02JAN01' FROM DUAL UNION ALL
SELECT '03JANUARY01' FROM DUAL UNION ALL
SELECT '04012001' FROM DUAL UNION ALL
SELECT '05JAN2001' FROM DUAL UNION ALL
SELECT '06JANUARY2001' FROM DUAL UNION ALL
SELECT 'JAN0701' FROM DUAL UNION ALL
SELECT 'JANUARY0801' FROM DUAL UNION ALL
SELECT 'JAN0901' FROM DUAL UNION ALL
SELECT 'JANUARY1001' FROM DUAL UNION ALL
SELECT '990111' FROM DUAL UNION ALL
SELECT '99JAN12' FROM DUAL UNION ALL
SELECT '99JANUARY13' FROM DUAL UNION ALL
SELECT '19990114' FROM DUAL UNION ALL
SELECT '2001-01-15' FROM DUAL UNION ALL
SELECT '2001JAN16' FROM DUAL UNION ALL
SELECT '2001JANUARY17' FROM DUAL UNION ALL
SELECT '20010118' FROM DUAL
)
SELECT value, parse_Date_String( value ) AS dt
FROM dates;
Output:
VALUE DT
------------- -------------------
010101 2001-01-01 00:00:00
02JAN01 2001-01-02 00:00:00
03JANUARY01 2001-01-03 00:00:00
04012001 2001-01-04 00:00:00
05JAN2001 2001-01-05 00:00:00
06JANUARY2001 2001-01-06 00:00:00
JAN0701 2001-01-07 00:00:00
JANUARY0801 2001-01-08 00:00:00
JAN092001 2001-01-09 00:00:00
JANUARY102001 2001-01-10 00:00:00
990111 2099-01-11 00:00:00
99JAN12 2099-01-12 00:00:00
99JANUARY13 2099-01-13 00:00:00
19990114 1999-01-14 00:00:00
2001-01-15 2001-01-15 00:00:00
2001JAN16 2001-01-16 00:00:00
2001JANUARY17 2001-01-17 00:00:00
20010118 0118-01-20 00:00:00
(Note: the date formats you are using are ambiguous, as the last example demonstrates. You can swap the order the formats are parsed in the function to get different results but if you have 010203 is it 01-FEB-2003, 02-JAN-2003, 03-FEB-2001 or even 01-FEB-0003?)
If you want it in the format DD-MON-YY (but why YY and not YYYY?) then just use:
TO_CHAR( parse_Date_String( value ), 'DD-MON-YY' )