Oracle regexp to validate time - sql

I am trying to validate a regular expression in oracle SQL to check Time format:
The desired format I want to check is:
2/27/2020 3:53:02 PM
I have already created a regexp for a date format such as:
20200227 --> using REGEXP_LIKE('20190222', '^\d{4}(0[1-9]|(1[0-2]))(0[1-9]|[1-2][0-9]|3[0-1])$')
Could someone give me a hint for the time format?

To me, it looks as if you started it wrong. If you want to validate date (time) format, you're storing it as a string, which is a big mistake.
If you set that column (or whatever it is) as DATE, which - in Oracle - contains both date and time, then database will take care that you can enter only valid values.
Format you mentioned, or any other, is matter of display, not storage.
One option is to force TO_DATE conversion; something like this:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> set ver off
SQL> select to_date('&date_value', 'mm/dd/yyyy hh:mi:ss am') result from dual;
Enter value for date_value: 2/27/2020 3:53:02 pm
RESULT
-------------------
27.02.2020 15:53:02
SQL> /
Enter value for date_value: 13/54/2020 x:23:83 am
select to_date('13/54/2020 x:23:83 am', 'mm/dd/yyyy hh:mi:ss am') result from dual
*
ERROR at line 1:
ORA-01843: not a valid month
SQL>
So: if conversion works, then format is OK. Otherwise, you entered something stupid and conversion won't pass. Such a code can be used as a function which returns e.g. Boolean.

Don't use a regular expression as the edge cases will make the expression long and complicated (i.e. months can have 28-31 days, leap years every 4 years ... except for multiples of 100 ... except for multiples of 400).
For example, your date regular expression ^\d{4}(0[1-9]|(1[0-2]))(0[1-9]|[1-2][0-9]|3[0-1])$ would validate 20190229 or 20200230 or 20200931 neither of which are valid dates.
A time regular expression is easier as you only have hours 0-23, minutes 0-59, seconds 0-59 (assuming you are ignoring leap seconds) and possibly fractional seconds and would be:
([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?
However, a simpler way is just to try to perform a conversion to a DATE and if it fails then you know it isn't valid. If you are on Oracle 12.2 or later then you can use the built-in VALIDATE_CONVERSION function:
SELECT validate_conversion( '20200230000000' AS DATE, 'YYYYMMDDHH24MISS' )
FROM DUAL
If you are on an earlier version then you can create a custom function to try and perform the conversion and if an exception occurs then you know the input is invalid:
CREATE FUNCTION isValidDate(
date_string IN VARCHAR2,
format_model IN VARCHAR2 DEFAULT 'FXYYYYMMDDHH24MISS'
) RETURN NUMBER
IS
d DATE;
BEGIN
d := TO_DATE( date_string, format_model );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
/

While looking into string Regex, I found that this one does the job that I'm looking for:
REGEXP_LIKE('2/27/2020 3:53:02 PM','^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{1,2}:\d{1,2} [AP]M\z')

Related

Converting timestamp to date in Oracle 12c

I want to convert a given timestamp in such format: 2019-04-08 00:00:00.0 to a date in this format: 2019-04-08.
I have already tried using:
SELECT TO_DATE('2019-04-20 00:00:00.0','YYYY-MM-DD') from dual;
But I got prompted with:
ORA-01830: date format picture ends before converting entire input
string
I think you may have some conceptual misunderstanding about how the TO_DATE function works, and also about how dates are processed by the DBMS.
YYY-MM-DD does not match the format of the actual string you're importing (2019-04-20 00:00:00.0) That's what the error is telling you. You must tell the TO_DATE function what to expect in the date string you input into it. You do that by means of the format string. if you don't specify a format string which matches the format you're actually going to supply, then the function will fail to process the string.
Next, you say you want to convert it "to a date in this format"...but this does not entirely make sense. TO_DATE converts a string into a variable of type DATETIME - i.e. a date object. A date object does not not exist in any particular format, it exists as an object. Internally it will store the date information in a way which is independent of any human-readable date format. The format relates entirely to the presentation of the date when seen as a string. Once you have a date object, you can then output the date in a particular format if you want to a human to be able to read it in the style that their culture is familiar with.
So, firstly to import your date string correctly as a date object, you can use an accurate format string, an also use TO_TIMESTAMP instead of TO_DATE so that it captures the sub-seconds value:
SELECT TO_TIMESTAMP('2019-04-20 00:00:00.0','YYYY-MM-DD HH24:MI:SS.FF5') from dual;
If you run this in a console the SELECT will then automatically re-format that date object (the result of the TO_DATE function) into the default date format configured in your server / session.
However if you actually want to see it on screen in a particular format, you can explicitly say so - a sensible way is using the TO_CHAR function:
SELECT TO_CHAR(TO_TIMESTAMPT('2019-04-20 00:00:00.0','YYYY-MM-DD HH24:MI:SS.FF5'), 'YYYY-MM-DD') from dual;
The full list of format specifiers can be found here (and in other places online as well).
Live demo of the above here: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=619d918ea73953e11b3150c6b560112c
Assuming the input is actual text, and not a real timestamp, you could try just truncating the text before you call TO_DATE:
WITH cte AS (
SELECT '2019-04-20 00:00:00.0' AS ts FROM dual
)
SELECT TO_DATE(SUBSTR(ts, 1, 10), 'YYYY-MM-DD')
FROM cte;
If your input is an actual Oracle timestamp, and you want to convert it to a date, then you may use CAST:
SELECT CAST(ts AS DATE) dt
FROM cte;
Would CAST do any good?
I'm setting date format so that it displays time component, although it is 00:00:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select cast(timestamp '2019-04-20 00:00:00.0' as date) result from dual;
RESULT
-------------------
20.04.2019 00:00:00
Another format (without time component):
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select cast(timestamp '2019-04-20 00:00:00.0' as date) result from dual;
RESULT
----------
20.04.2019
SQL>
Or, using TO_CHAR function (so that session's date format doesn't matter):
SQL> select to_char(timestamp '2019-04-20 00:00:00.0', 'dd.mm.yyyy') result from dual;
RESULT
----------
20.04.2019
SQL>

how to get the date format in 'DD-MM-YY' format in ORACLE without using TO_CHAR?

How do we convert a date in the 'DD-MM-YY' format WITHOUT using to_char ?
If I use the following query i get it in DD-Mon-YY format ?
select TO_DATE(SYSDATE,'DD-MM-YY') from dual ;
Output : 29-Mar-18
I want it in 29-03-18 format , without using to_char.
Is it possible ?
How do we convert a date in the 'DD-MM-YY' format WITHOUT using to_char ?
This is a common misconception that dates in the database have a format.
A date does not have a format - it is stored internally to the database as 7-bytes (representing year, month, day, hour, minute and second) and it is not until whatever user interface you are using (i.e. SQL/Plus, SQL Developer, Java, etc) tries to display it to you, the user, and converts it into something you would find meaningful (usually a string) that the date is given a format so that you, the user, find it meaningful on the client software.
So the question you should be asking is:
How do we get <insert name of SQL client software here> to change the default format it uses for a DATE data type?
If you are using SQL/Plus or SQL Developer then it will use the NLS_DATE_FORMAT session parameter to format the date. You can change this using:
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MM-YY';
Note: This is a session parameter and will only change the format for the current session (not for any other users or any subsequent sessions).
If you want to set this as the session default then you could set a logon trigger (if users are relying on the previous default format then applying this may not be well received).
You can also change the preferences in the SQL Developer GUI as described here.
we dont have alter permission , its client db
Then use TO_CHAR( date, format_model ) - that is what it is there for.
Note: Please do not use 2-digit years as the expected format. It is a source of errors when dates are given an unexpected and wrong century.
If I use the following query i get it in DD-Mon-YY format ?
select TO_DATE(SYSDATE,'DD-MM-YY') from dual ;
TO_DATE( date_string, format_model ) takes two string arguments and Oracle will implicitly call TO_CHAR to convert your date to a string so it will match the expected data type and then when the client program formats it it will implicitly perform a similar transformation again. So your query is effectively:
SELECT TO_CHAR(
TO_DATE(
TO_CHAR(
SYSDATE,
( SELECT VALUE
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT'
)
),
'DD-MM-YY'
),
( SELECT VALUE
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT'
)
)
FROM DUAL;
If the NLS_DATE_FORMAT is MM-DD-YY then SYSDATE will be implicitly converted to the default MM-DD-YY format then explicitly converted to a date using your format DD-MM-YY, and the day/month values will be swapped, before being converted back to the default MM-DD-YY format for display. Relying on implicit conversions is prone to many errors - try to avoid it.
If you want a date for your SQL client to format using their default format then just use:
SELECT SYSDATE FROM DUAL;
If you want to get a formatted date then use TO_CHAR( date, format_model ):
SELECT TO_CHAR( SYSDATE, 'DD-MM-YY' ) FROM DUAL;
ALTER SESSION is one option:
SQL> alter session set nls_date_format = 'dd-mm-yy';
Session altered.
SQL> select sysdate from dual;
SYSDATE
--------
29-03-18
Note that what you did - applied TO_DATE function to SYSDATE - is wrong. SYSDATE already returns DATE, so you could have applied TO_CHAR to it (with appropriate format mask), but not TO_DATE.
I would suggest you to use TO_CHAR or nls_date_format , though EXTRACT is an alternative if you want to answer an interview question.
SELECT EXTRACT (DAY FROM SYSDATE)
||'-'
|| LPAD(EXTRACT (MONTH FROM SYSDATE), 2, 0)
|| '-'
|| SUBSTR( EXTRACT(YEAR FROM SYSDATE),-2) as dt
FROM DUAL;

ORA-01843: not a valid month error when using to_date function

SELECT sysdate as today,
to_date(CONCAT (to_char(sysdate,'MM-DD-YYYY'),
to_char(sysdate,'hh24:mi:ss'))) as time
FROM DUAL
Every time I execute this query it gives me ORA-01843: not a valid month
Could someone please help me?
As I understand, you want to see time from sysdate, if so you need this select:
SELECT sysdate as today,
to_char(sysdate,'MM-DD-YYYY hh24:mi:ss'))) as time
FROM DUAL
In your example wrong part is to_date, you should use mask for to_date:
to_date(CONCAT (to_char(sysdate,'MM-DD-YYYY'),
to_char(sysdate,'hh24:mi:ss')),'MM-DD-YYYY hh24:mi:ss')
But this will return exactly the same as sysdate because both of this columns are date and the format of dates depends on variable NLS_DATE_FORMAT, check here
to_date(CONCAT (to_char(sysdate,'MM-DD-YYYY'),
to_char(sysdate,'hh24:mi:ss'))) as time
It is simply useless what you are doing. You are extracting date and time portions separately and then converting it back to DATE. It is nothing but SYSDATE itself.
It would make sense if you are extracting and displaying the date and time elements. Or, you have date and time as string literals separately, and now you want to convert it into a DATE.
For example,
SQL> alter session set nls_date_format='mm/dd/yyyy hh24:mi:ss';
Session altered.
SQL> SELECT to_date(CONCAT ('01/11/2016', '14:45:20'), 'MM/DD/YYYYHH24:MI:SS') my_date
2 FROM DUAL;
MY_DATE
-------------------
01/11/2016 14:45:20
ORA-01843: not a valid month
Reason
This mostly happens due to NLS dependency. Since you are not using an explicit format mask, Oracle is trying to implicitly convert it based on your locale-specific NLS settings.
Example
Let's change the NLS format of the session:
SQL> alter session set nls_date_format='dd/mon/yyyyhh24:mi:ss';
Session altered.
Let's execute the same query:
SQL> SELECT to_date(CONCAT ('01/11/2016', '14:45:20')) my_date
2 FROM DUAL;
SELECT to_date(CONCAT ('01/11/2016', '14:45:20')) my_date
*
ERROR at line 1:
ORA-01843: not a valid month
As expected, it throws ORA-01843: not a valid month.

Questions on To_Date function

I have two questions,
1.
Why can't I get HH24:MI:SS when using To_date function?
select To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08
select To_char(fn_adjusted_date(submit_date),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08-06:01:10
2.
Why am I getting an error when using:
To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
but changing it works fine when I change it to:
To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MM-YY-HH24:MI:SS')
To demonstrate:
select sysdate from dual;
03-MAR-15
alter session set nls_date_format = 'dd-mm-yyyy hh24:mi:ss';
select sysdate from dual;
03-03-2015 11:29:22
select To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
ORA-01843: not a valid month 01843. 00000 - "not a valid month"
select To_char(fn_adjusted_date(submit_date),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08-06:01:10
1.
Because to_date() gives you a date object, and you're leaving it up to your client to decide how to display that as a string; it's likely to be using your NLS_DATE_FORMAT settings.
Since your fn_adjusted_date() function returns a date not a string, do not then call to_date() on that; you're doing an implicit conversion to a string and then back to a date, both using NLS_DATE_FORMAT, and from how your first query is displayed - as DD-MON-YY? - that is losing the time portion anyway. So you're really doing:
select to_date(to_char(fn_adjusted_date(SUBMIT_DATE), 'DD-MON-YY'),
'DD-MON-YY-HH24:MI:SS') from HPD_Help_Desk;
2.
Because MON is the abbreviated month name in your date language. This follows on from the first point; now in the first of those you're doing an implicit to_char() of your value using the new NLS_DATE_FORMAT, which specifies the month number with MM, but then you try to convert that back to a date with MON. So this time you're really doing:
select to_date(to_char(fn_adjusted_date(SUBMIT_DATE), 'dd-mm-yyyy hh24:mi:ss'),
'DD-MON-YY-HH24:MI:SS') from HPD_Help_Desk;
And 11 is not a valid month name. Oracle is quite flexible with date formats when it can be; it can interpret 'NOV' using the MM model even though that doesn't make sense really since it isn't a number, but the meaning is pretty obvious; from your example in a comment:
select to_date('16-Nov-2008', 'DD-MM-YY') from dual;
TO_DATE('16-NOV-2008','DD-MM-YY')
---------------------------------
16-NOV-2008 00:00:00
It doesn't work the other way though; it can't interpret 11 using MON. That flexibility can appear inconsistent, and it sometimes seems to be too forgiving.
In the second query you're doing an explicit to_char() with a format model specified, which is the correct way to display a date as a string.
The underlying messages are the same for both: don't call to_date() when you already have a date object, don't ever rely on implicit conversion, and don't convert a date to a string while you're still processing it - only if you want it as a string in a specific format in your final result set.

Date format: Invalid Month

I have below data format
10/29/2003
10/21/2003 7:26:00 AM in a table
and I want to compare dates in between '07-14-2013' and '09-15-2013'. I have written code as
to_char(to_date(a.TEXT_VALUE, 'DD-MM-YYYY HH:MI:SS AM'),'dd-mm-YYYY') between '07-14-2013 00:00:00 AM' and '09-15-2013 00:00:00 AM'
this is not working. Can anyone suggest what should I do to get dates between these 2 dates?
You have your days and months reversed.
Americans (and possibly other countries too) use a notation of MM-DD-YYYY:
to_char(to_date(a.TEXT_VALUE, 'MM-DD-YYYY HH:MI:SS AM'),'mm-dd-YYYY')
between '07-14-2013 00:00:00 AM' and '09-15-2013 00:00:00 AM'
As others have also said, you really don't know whats in that varchar field, and dates should be stored as dates (so you can do all the wonderful things with dates, like compare them, subtract them, get date ranges, etc...).
So, if you have even 1 record that has an invalid date, the to_date will break. But, you say that you only want to grab records within a date range, you might ignore the time portion of the date using substr (and still hope the days are valid):
with date_strings as
(
select 1 as id, '01/31/2013' as dte_str from dual
union
select 2 as id, '02/01/2013 13:55:01' as dte_str from dual
union
select 3 as id, '02/28/2013 10:30:01 AM' as dte_str from dual
union
select 4 as id, '03/01/2013 11:15:01 AM' as dte_str from dual
)
select
id, dte_str, to_date(substr(dte_str, 1, 10), 'MM/DD/YYYY') as dte
from date_strings
where to_date(substr(dte_str, 1, 10), 'MM/DD/YYYY') between
to_date('02/01/2013', 'MM/DD/YYYY') and to_date('03/01/2013', 'MM/DD/YYYY')-1;
This example grabs rows that have a date that falls somewhere in Feb of 2013 (or fails if you have even 1 row where the MM/DD/YYYY part of the string is invalid, like 02/29/2013 for example). But at least you can probably ignore the variations in the time formats.
You've said that you have dates with format MM/DD/YYYY and MM/DD/YYYY HH:MI:SS AM, and none with MM/DD/YYYY HH24:MI:SS. The error messages you're getting indicate that you are mistaken; the 'hour must be between 1 and 12' means that you have at least one row with the time in 24-hour format. Or, potentially, with something that isn't a recognisable time at all.
The problem with storing structured data - a date in this case - in a free-text field as a varchar2 instead of as a proper data type is that you can get any old rubbish in there, and you are relying on your application validate data as it is entered - which is doesn't seem to be doing based on what you're seeing now. Well, one of the major problems, there are others, including performance implications.
The only way to try to salvage your data is to write a function that tries multiple conversions and only returns when it has something valid - or runs out of options. Something like this perhaps:
create or replace function clean_date(text_value varchar2) return date is
begin
begin
return to_date(text_value, 'MM/DD/YYYY HH24:MI:SS');
exception
when others then
null;
end;
begin
return to_date(text_value, 'MM/DD/YYYY HH:MI:SS AM');
exception
when others then
null;
end;
return null;
end clean_date;
/
This is only trying two formats but you can add more as your data needs - any row that gets a null back couldn't be converted by any of the formats it tried. You need to be a bit careful about the order you test them though, to avoid the potential for incorrect matches. Each begin/exception/end sub-block is testing one format; catching other isn't ideal but the alternative is to declare all possible date format exceptions which would be painful and error-prone. If there is no exception then that date value is returned; if there is any exception then it does nothing but moves on to the next block to try the next format.
This also won't help you if you have something really unexpected, and won't always error if you have a date in UK format as DD/MM/YYYY for example - if both the day and month are less than 13 it's impossible to tell which is which.
Anyway, with this your filter could become:
where trunc(clean_date(a.text_value))
between date '2013-07-14' and date '2013-09-15'
You could convert it back to a string if you prefer, but still use a sensible date format for the comparison:
where to_char(clean_date(a.text_value), 'YYYY-MM-DD')
between '2013-07-14' and '2013-09-15'