How to understand given sysdate is Date or Timestamp - oracle

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.

Related

ORA-01861: literal does not match format string not working with format in oracle

I am uploading the data in table like this 2016-10-17T00:00:00+05:30 and while fetching the data into variable I am using it like this V_FINALSRDATE := cur_r.FINAL_SR_DATE
So while executing it, I am getting the error as ORA-01861: literal does not match format string.
So I tried like this
V_FINALSRDATE := TO_DATE(cur_r.FINAL_SR_DATE,'dd-mm-yyyy');
Please suggest what needs to be changed and updated.
2016-10-17T00:00:00+05:30 does not match the format dd-mm-yyyy and it has a time zone so it will not work converting it to a date.
You should be storing it in a TIMESTAMP WITH TIME ZONE data type:
DECLARE
V_FINALSRDATE TIMESTAMP WITH TIME ZONE;
-- create a cur_r cursor with FINAL_SR_DATE column.
BEGIN
V_FINALSRDATE := TO_TIMESTAMP_TZ(cur_r.FINAL_SR_DATE,'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM');
END;
/
If you want to convert it to a DATE then you can use something like:
DECLARE
V_FINALSRDATE DATE;
-- create a cur_r cursor with FINAL_SR_DATE column.
BEGIN
V_FINALSRDATE := CAST(
TO_TIMESTAMP_TZ(cur_r.FINAL_SR_DATE,'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM')
-- AT TIME ZONE 'UTC' -- Optionally, convert the date to a common time zone.
AS DATE
);
END;
/
db<>fiddle here

"ORA-01861: literal does not match format string" Error in PL/SQL

Can someone help me understand what the following code line is doing wrong?
res_start_time_ := to_date(to_char(account_date_, 'YYYYMMDD ') || sched_ftime_, 'YYYYMMDD HH24:MI');
res_start_time_ and account_date_ are of DATE type.
sched_ftime_ is VARCHAR2 type and it can be NULL.
In a test scenario, I get the ORA-01861: literal does not match format string error when there is a value for account_date_ and NULL for sched_ftime_.
Can someone explain to me what I am doing wrong and how I can get rid of this error?
The source code is attempting to form a string that can be converted to a date having both a day value and a time-of-day value.
This to_char(account_date_, 'YYYYMMDD ') converts a date value into a 9 character string ending with a space which permits use of string concatenation of what should be value containing hours and minutes. Once concatenated it then attempts to convert that into a date value accurate to a minute.
However the error encountered will occur if a non-null value of sched_ftime_ isn't in a form that can be transformed to HH24:MI e.g. '123456' is to long to be interpreted as only hours and minutes.
This can be replicated:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '123456'; /* this value fails */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ') || sched_ftime_,
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
ORA-01861: literal does not match format string
ORA-06512: at line 6
However a shorter value e.g. '1234' can be interpreted as hours and minutes:
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '1234'; /* this value works */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ') || sched_ftime_,
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
1 rows affected
dbms_output:
2021-08-27 12:34:00
Hence I suggest you protect the conversion into a date by limiting the length of your second parameter e.g. to 4 characters perhaps using substr()
DECLARE
res_start_time_ DATE;
account_date_ DATE := TRUNC(SYSDATE);
sched_ftime_ VARCHAR2(20) := '123456'; /* this value gets truncated */
BEGIN
res_start_time_ := to_date(
to_char(account_date_, 'YYYYMMDD ')
|| substr(sched_ftime_,1,4),
'YYYYMMDD HH24:MI'
);
DBMS_OUTPUT.PUT_LINE(res_start_time_);
END;
/
You may need other validations on that varchar2 value such that they are all digits as well. Or, if you are including the colon into the hour & minutes value then the overall length needs to be 5 chars. In short you need to vet the hours and minutes so that they are logical and valid.
nb: Kudos to MT0 for the source db<>fiddle which I extended here
The source of the issue is you are allowing the time component (sched_ftime_) to assume values that you do not know the exact format before hand. You can do this, but it comes with a coding requirement. You need to establish the permissible and then derive the FORMAT SPECIFICATION at run time, and throw an error if the value does not match a permissible pattern. What is permissible and the validation is as simple or complicated as you want. As a demonstration the following function looks for 5 patterns:hh24mi, hh24miss, hh24:mi, hh24:mi:ss, and null. For those it returns the appropriate date, for anything else it returns a application defined exception.
create or replace
function get_actual_date_time( account_date_ date
, sched_ftime_ varchar2
)
return date
is
k_format_base constant varchar2(8) := 'yyyymmdd';
k_bad_time_format_msg constant varchar2(32) := ' invalid time specification.';
-- declare regexp for valid time formats
k_regx_time_hh24mm constant varchar2(7) := '^\d{4}$';
k_regx_time_hh24mmss constant varchar2(7) := '^\d{6}$';
k_regx_time_hh24mm_s constant varchar2(11) := '^\d\d:\d\d$';
k_regx_time_hh24mmss_s constant varchar2(16) := '^\d\d:\d\d:\d\d$';
-- declare actual format specification corresponding to valid format
k_fmt_time_hh24mm constant varchar2(6) := 'hh24mi';
k_fmt_time_hh24mmss constant varchar2(8) := 'hh24miss';
k_fmt_time_hh24mm_s constant varchar2(7) := 'hh24:mi';
k_fmt_time_hh24mmss_s constant varchar2(10) := 'hh24:mi:ss';
l_time_format varchar2(16);
begin
case when sched_ftime_ is null then
l_time_format := null;
when regexp_like ( sched_ftime_,k_regx_time_hh24mm) then
l_time_format := k_fmt_time_hh24mm;
when regexp_like ( sched_ftime_,k_regx_time_hh24mmss) then
l_time_format := k_fmt_time_hh24mmss;
when regexp_like ( sched_ftime_,k_regx_time_hh24mm_s) then
l_time_format := k_fmt_time_hh24mm_s;
when regexp_like ( sched_ftime_,k_regx_time_hh24mmss_s) then
l_time_format := k_fmt_time_hh24mmss_s;
else
raise_application_error( -20109,'''' || sched_ftime_ || '''' || k_bad_time_format_msg);
end case;
return to_date(to_char(account_date_,k_format_base) || sched_ftime_
, k_format_base || l_time_format);
end get_actual_date_time;
Keep in mind the above is an example only and there are many enhancement to be made. For example is will accept time specification of 99:99:99 even though it is obviously a invalid time specification. But it fits the validation for '^\d\d:\d\d:\d\d$'. Neither does it attempt to validate the valid time specification 06:45 PM. See fiddle here.

Oracle Datetime difference issue with AM/ PM

I have the following stored procedure which calculates the time taken for a merge statement
create or replace procedure ModAuditData(
O_UpdatedCount out int
,O_EndTime out timestamp
,O_Duration out int)
as
P_StartTime timestamp(3) WITH LOCAL TIME ZONE;
-- EndTime timestamp;
begin
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
begin
select extract( second from (O_EndTime-P_StartTime) )
into O_Duration
from dual;
Exception When others then
O_Duration:=0;
end;
end ModAuditData;
The Issue is
O_EndTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS')
gives exact opposite of
P_StartTime:=to_timestamp(to_char(current_timestamp,'DD/MM/YYYY HH:MI:SS'),'DD/MM/YYYY HH:MI:SS');
in terms of AM/PM
What is the correct way to calculate the start and end time
CURRENT_TIMESTAMP is already a TIMESTAMP WITH TIME ZONE, there is no reason to convert it first to VARCHAR2 and then back again into a TIMESTAMP.
Also timestamp(3) (which provides precision up to millisecond) does not make much sense when you return duration as INTEGER, i.e. full seconds.
Try it like this:
P_StartTime timestamp(3) WITH TIME ZONE;
begin
P_StartTime := current_timestamp;
-- merge Statment that does UPSERT
O_UpdatedCount :=SQL%ROWCOUNT;
commit;
O_Duration := EXTRACT(SECOND FROM (current_timestamp - P_StartTime));
end;
In case of SQL*Plus consider to use TIMING command.

oracle function to return list of dates as object

To whom it may respond to ,
I am trying to return list of dates and weekdays to be used in other functions. Code below is compiled without error. But it should give output of 15 days (via V_MAX_DAYS variable) and number of the day in that week.
I have tried to implement like this, but cannot get output using DBMS_OUTPUT. I want to test it but got ORA-06532 error at when running .
My aim is to return values to asp.net application as we have done using SYS_REFCURSOR.
How can I achieve that?
Thank you for your concern,
The script is as below :
CREATE OR REPLACE TYPE DATE_ROW AS OBJECT
(
WEEKDAY_VALUE DATE,
DATE_IN_LIST VARCHAR2(5)
)
/
CREATE OR REPLACE TYPE DATE_TABLE as table of DATE_ROW
/
CREATE OR REPLACE FUNCTION FN_LISTDATES
RETURN DATE_TABLE
IS
V_DATE_TABLE DATE_TABLE := DATE_TABLE ();
V_MAX_DAYS NUMBER := 15;
V_CALCULATED_DATE DATE;
V_WEEKDAY VARCHAR2 (5);
BEGIN
FOR X IN -2 .. V_MAX_DAYS
LOOP
SELECT TO_DATE (TO_CHAR (SYSDATE + X, 'DD.MM.YYYY'))
INTO V_CALCULATED_DATE
FROM DUAL;
V_DATE_TABLE.EXTEND;
V_DATE_TABLE(X) := DATE_ROW(V_CALCULATED_DATE, 'Test');
END LOOP;
RETURN V_DATE_TABLE;
END;
/
A few points.
If you want a DATE (V_CALCULATED_DATE) that is X days from SYSDATE with the time component set to midnight, which appears to be your intent here, you would want something like v_calculated_date := TRUNC(sysdate) + x;. A TO_DATE without an explicit format mask is going to create issues if a future session's NLS_DATE_FORMAT happens not to be DD.MM.YYYY
If you really want to return a collection like this, your collection indexes would need to start with 1, not -2. You could accomplish that by doing v_date_table(x+3) := DATE_ROW(v_calculated_date, 'Test');.
However, I would tend to suspect that you would be better served here with a pipelined table function.
The pipelined table function would look something like
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE FUNCTION FN_LISTDATES
2 RETURN DATE_TABLE
3 PIPELINED
4 IS
5 V_MAX_DAYS NUMBER := 15;
6 V_CALCULATED_DATE DATE;
7 V_WEEKDAY VARCHAR2 (5);
8 BEGIN
9 FOR X IN -2 .. V_MAX_DAYS
10 LOOP
11 v_calculated_date := trunc(sysdate) + x;
12 PIPE ROW( DATE_ROW(v_calculated_date,'Test') );
13 END LOOP;
14 RETURN;
15* END;
SQL> /
Function created.
SQL> select * from table( fn_listDates );
WEEKDAY_V DATE_
--------- -----
30-NOV-10 Test
01-DEC-10 Test
02-DEC-10 Test
03-DEC-10 Test
04-DEC-10 Test
05-DEC-10 Test
06-DEC-10 Test
07-DEC-10 Test
08-DEC-10 Test
09-DEC-10 Test
10-DEC-10 Test
WEEKDAY_V DATE_
--------- -----
11-DEC-10 Test
12-DEC-10 Test
13-DEC-10 Test
14-DEC-10 Test
15-DEC-10 Test
16-DEC-10 Test
17-DEC-10 Test
18 rows selected.

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