Filtering String Dates
A question came up about how to verify dates from a string without throwing a casting error because of a non-conforming date. You can throw a number of exceptions, and I wrote a function to filter bad string formats like the DD-MON-RR
or DD-MON-YYYY
.
The first one is for a day between 1 and the last day of month, which is:
ORA-01847: day of month must be between 1 and last day of month |
An incorrect string for a month, raises the following error:
ORA-01843: not a valid month |
A date format mask longer than a DD-MON-RR
or DD-MON-YYYY
raises the following exception:
ORA-01830: date format picture ends before converting entire input string |
The verify_date
function checks for non-conforming DD-MON-RR
and DD-MON-YYYY
date masks, and substitutes a SYSDATE
value for a bad date entry:
CREATE OR REPLACE FUNCTION verify_date ( pv_date_in VARCHAR2) RETURN DATE IS /* Local return variable. */ lv_date DATE; BEGIN /* Check for a DD-MON-RR or DD-MON-YYYY string. */ IF REGEXP_LIKE(pv_date_in,'^[0-9]{2,2}-[ADFJMNOS][ACEOPU][BCGLNPRTVY]-([0-9]{2,2}|[0-9]{4,4})$') THEN /* Case statement checks for 28 or 29, 30, or 31 day month. */ CASE /* Valid 31 day month date value. */ WHEN SUBSTR(pv_date_in,4,3) IN ('JAN','MAR','MAY','JUL','AUG','OCT','DEC') AND TO_NUMBER(SUBSTR(pv_date_in,1,2)) BETWEEN 1 AND 31 THEN lv_date := pv_date_in; /* Valid 30 day month date value. */ WHEN SUBSTR(pv_date_in,4,3) IN ('APR','JUN','SEP','NOV') AND TO_NUMBER(SUBSTR(pv_date_in,1,2)) BETWEEN 1 AND 30 THEN lv_date := pv_date_in; /* Valid 28 or 29 day month date value. */ WHEN SUBSTR(pv_date_in,4,3) = 'FEB' THEN /* Verify 2-digit or 4-digit year. */ IF (LENGTH(pv_date_in) = 9 AND MOD(TO_NUMBER(SUBSTR(pv_date_in,8,2)) + 2000,4) = 0 OR LENGTH(pv_date_in) = 11 AND MOD(TO_NUMBER(SUBSTR(pv_date_in,8,4)),4) = 0) AND TO_NUMBER(SUBSTR(pv_date_in,1,2)) BETWEEN 1 AND 29 THEN lv_date := pv_date_in; ELSE /* Not a leap year. */ IF TO_NUMBER(SUBSTR(pv_date_in,1,2)) BETWEEN 1 AND 28 THEN lv_date := pv_date_in; ELSE lv_date := SYSDATE; END IF; END IF; ELSE /* Assign a default date. */ lv_date := SYSDATE; END CASE; ELSE /* Assign a default date. */ lv_date := SYSDATE; END IF; /* Return date. */ RETURN lv_date; END; / |
You can check valid dates with a DD-MON-RR
format:
SELECT verify_date('28-FEB-10') AS "Non-Leap Year" , verify_date('29-FEB-12') AS "Leap Year" , verify_date('31-MAR-14') AS "31-Day Year" , verify_date('30-APR-14') AS "30-Day Year" FROM dual; |
You can check valid dates with a DD-MON-YYYY
format:
SELECT verify_date('28-FEB-2010') AS "Non-Leap Year" , verify_date('29-FEB-2012') AS "Leap Year" , verify_date('31-MAR-2014') AS "31-Day Year" , verify_date('30-APR-2014') AS "30-Day Year" FROM dual; |
They both return:
Non-Leap Leap YEAR 31-DAY YEAR 30-DAY YEAR ----------- --------- ----------- ----------- 28-FEB-10 29-FEB-12 31-MAR-14 30-APR-14 |
You can check badly formatted dates with the following query:
SELECT verify_date('28-FEB-2010') AS "Non-Leap Year" , verify_date('29-FEB-2012') AS "Leap Year" , verify_date('31-MAR-2014') AS "31-Day Year" , verify_date('30-APR-2014') AS "30-Day Year" FROM dual; |
You can screen for an alphanumeric string with the following expression:
SELECT 'Valid alphanumeric string literal' AS "Statement" FROM dual WHERE REGEXP_LIKE('Some Mythical String $200','([:alnum:]|[:punct:]|[:space:])*'); |
You can screen for a numeric literal as a string with the following expression:
SELECT 'Valid numeric literal' AS "Statement" FROM dual WHERE REGEXP_LIKE('123.00','([:digit:]|[:punct:])'); |
As always, I hope this helps those who need this type of solution.